summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--postgresqleu/confreg/admin.py2
-rw-r--r--postgresqleu/confreg/backendforms.py28
-rw-r--r--postgresqleu/confreg/backendviews.py61
-rw-r--r--postgresqleu/confreg/migrations/0023_accesstokens.py29
-rw-r--r--postgresqleu/confreg/models.py26
-rw-r--r--postgresqleu/confsponsor/util.py7
-rw-r--r--postgresqleu/urls.py3
-rw-r--r--template/confreg/admin_dashboard_single.html1
8 files changed, 154 insertions, 3 deletions
diff --git a/postgresqleu/confreg/admin.py b/postgresqleu/confreg/admin.py
index 39a4e09e..ce725d3a 100644
--- a/postgresqleu/confreg/admin.py
+++ b/postgresqleu/confreg/admin.py
@@ -18,6 +18,7 @@ from models import ConferenceFeedbackQuestion, Speaker_Photo
from models import PrepaidVoucher, PrepaidBatch, BulkPayment, DiscountCode
from models import PendingAdditionalOrder
from models import VolunteerSlot
+from models import AccessToken
from selectable.forms.widgets import AutoCompleteSelectWidget, AutoCompleteSelectMultipleWidget
from postgresqleu.accountinfo.lookups import UserLookup
@@ -618,3 +619,4 @@ admin.site.register(BulkPayment, BulkPaymentAdmin)
admin.site.register(AttendeeMail, AttendeeMailAdmin)
admin.site.register(PendingAdditionalOrder, PendingAdditionalOrderAdmin)
admin.site.register(VolunteerSlot, VolunteerSlotAdmin)
+admin.site.register(AccessToken)
diff --git a/postgresqleu/confreg/backendforms.py b/postgresqleu/confreg/backendforms.py
index 87fc78f9..b893c4d6 100644
--- a/postgresqleu/confreg/backendforms.py
+++ b/postgresqleu/confreg/backendforms.py
@@ -4,6 +4,7 @@ from django.db.models import Q
import django.forms
import django.forms.widgets
from django.forms.widgets import TextInput
+from django.utils.safestring import mark_safe
import datetime
from psycopg2.extras import DateTimeTZRange
@@ -12,6 +13,7 @@ from selectable.forms.widgets import AutoCompleteSelectWidget, AutoCompleteSelec
from postgresqleu.util.admin import SelectableWidgetAdminFormMixin
from postgresqleu.util.forms import ConcurrentProtectedModelForm
+from postgresqleu.util.random import generate_random_token
from postgresqleu.accountinfo.lookups import UserLookup
from postgresqleu.confreg.lookups import RegistrationLookup
@@ -21,7 +23,7 @@ from postgresqleu.confreg.models import RegistrationClass, RegistrationType, Reg
from postgresqleu.confreg.models import ConferenceAdditionalOption, ConferenceFeedbackQuestion
from postgresqleu.confreg.models import ConferenceSession, Track, Room
from postgresqleu.confreg.models import ConferenceSessionScheduleSlot, VolunteerSlot
-from postgresqleu.confreg.models import DiscountCode
+from postgresqleu.confreg.models import DiscountCode, AccessToken, AccessTokenPermissions
from postgresqleu.confreg.models import valid_status_transitions, get_status_string
@@ -543,6 +545,30 @@ class BackendDiscountCodeForm(BackendForm):
self.update_protected_fields()
+class BackendAccessTokenForm(BackendForm):
+ list_fields = ['token', 'description', 'permissions', ]
+ readonly_fields = ['token', ]
+
+ class Meta:
+ model = AccessToken
+ fields = ['token', 'description', 'permissions', ]
+
+ def _transformed_accesstoken_permissions(self):
+ for k,v in AccessTokenPermissions:
+ baseurl = '/events/admin/{0}/tokendata/{1}/{2}'.format(self.conference.urlname, self.instance.token, k)
+ yield k, mark_safe('{0} (<a href="{1}.csv">csv</a>, <a href="{1}.tsv">tsv</a>)'.format(v, baseurl))
+
+ def fix_fields(self):
+ self.fields['permissions'].widget = django.forms.CheckboxSelectMultiple(
+ choices=self._transformed_accesstoken_permissions(),
+ )
+
+ @classmethod
+ def get_initial(self):
+ return {
+ 'token': generate_random_token()
+ }
+
#
# Form to pick a conference to copy from
#
diff --git a/postgresqleu/confreg/backendviews.py b/postgresqleu/confreg/backendviews.py
index a4219a8b..a0c1a511 100644
--- a/postgresqleu/confreg/backendviews.py
+++ b/postgresqleu/confreg/backendviews.py
@@ -2,7 +2,7 @@ from django.shortcuts import render, get_object_or_404
from django.db import transaction
from django import forms
from django.core import urlresolvers
-from django.http import HttpResponseRedirect, Http404
+from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.contrib.admin.utils import NestedObjects
from django.contrib import messages
from django.contrib.auth.decorators import login_required
@@ -10,14 +10,17 @@ from django.conf import settings
import urllib
import datetime
+import csv
from postgresqleu.util.middleware import RedirectException
-from postgresqleu.util.db import exec_to_dict, exec_no_result
+from postgresqleu.util.db import exec_to_list, exec_to_dict, exec_no_result
from models import Conference, ConferenceRegistration
from models import RegistrationType, RegistrationClass
+from models import AccessToken
from postgresqleu.invoices.models import Invoice
+from postgresqleu.confsponsor.util import get_sponsor_dashboard_data
from backendforms import BackendCopySelectConferenceForm
from backendforms import BackendConferenceForm, BackendRegistrationForm
@@ -26,6 +29,7 @@ from backendforms import BackendRegistrationDayForm, BackendAdditionalOptionForm
from backendforms import BackendTrackForm, BackendRoomForm, BackendConferenceSessionForm
from backendforms import BackendConferenceSessionSlotForm, BackendVolunteerSlotForm
from backendforms import BackendFeedbackQuestionForm, BackendDiscountCodeForm
+from backendforms import BackendAccessTokenForm
def get_authenticated_conference(request, urlname):
if not request.user.is_authenticated:
@@ -375,6 +379,12 @@ def edit_discountcodes(request, urlname, rest):
BackendDiscountCodeForm,
rest)
+def edit_accesstokens(request, urlname, rest):
+ return backend_list_editor(request,
+ urlname,
+ BackendAccessTokenForm,
+ rest)
+
###
# Non-simple-editor views
@@ -420,3 +430,50 @@ FROM confreg_conferenceregistration WHERE conference_id=%(confid)s""", {
'confid': conference.id,
})[0],
})
+
+
+
+def _reencode_row(r):
+ def _reencode_value(v):
+ if isinstance(v, unicode):
+ return v.encode('utf-8')
+ return v
+ return [_reencode_value(x) for x in r]
+
+def tokendata(request, urlname, token, datatype, dataformat):
+ conference = get_object_or_404(Conference, urlname=urlname)
+ if not AccessToken.objects.filter(conference=conference, token=token, permissions__contains=[datatype,]).exists():
+ raise Http404()
+
+ if dataformat.lower() == 'csv':
+ delimiter = ","
+ elif dataformat.lower() == 'tsv':
+ delimiter = "\t"
+ else:
+ raise Http404()
+
+ response = HttpResponse(content_type='text/plain; charset=utf-8')
+ writer = csv.writer(response, delimiter=delimiter)
+ writer.writerow(["File loaded", datetime.datetime.now()])
+
+ if datatype == 'regtypes':
+ writer.writerow(['Type', 'Confirmed', 'Unconfirmed'])
+ for r in exec_to_list("SELECT regtype, count(payconfirmedat) AS confirmed, count(r.id) FILTER (WHERE payconfirmedat IS NULL) AS unconfirmed FROM confreg_conferenceregistration r RIGHT JOIN confreg_registrationtype rt ON rt.id=r.regtype_id WHERE rt.conference_id=%(confid)s GROUP BY rt.id ORDER BY rt.sortkey", { 'confid': conference.id, }):
+ writer.writerow(_reencode_row(r))
+ elif datatype == 'discounts':
+ writer.writerow(['Code', 'Max uses', 'Confirmed', 'Unconfirmed'])
+ for r in exec_to_list("SELECT code, maxuses, count(payconfirmedat) AS confirmed, count(r.id) FILTER (WHERE payconfirmedat IS NULL) AS unconfirmed FROM confreg_conferenceregistration r RIGHT JOIN confreg_discountcode dc ON dc.code=r.vouchercode WHERE dc.conference_id=%(confid)s AND (r.conference_id=%(confid)s OR r.conference_id IS NULL) GROUP BY dc.id ORDER BY code", {'confid': conference.id, }):
+ writer.writerow(_reencode_row(r))
+ elif datatype == 'vouchers':
+ writer.writerow(["Code", "Buyer", "Used", "Unused"])
+ for r in exec_to_list("SELECT b.buyername, count(v.user_id) AS used, count(*) FILTER (WHERE v.user_id IS NULL) AS unused FROM confreg_prepaidbatch b INNER JOIN confreg_prepaidvoucher v ON v.batch_id=b.id WHERE b.conference_id=%(confid)s GROUP BY b.id ORDER BY buyername", {'confid': conference.id, }):
+ writer.writerow(_reencode_row(r))
+ elif datatype == 'sponsors':
+ (headers, data) = get_sponsor_dashboard_data(conference)
+ writer.writerow(headers)
+ for r in data:
+ writer.writerow(_reencode_row(r))
+ else:
+ raise Http404()
+
+ return response
diff --git a/postgresqleu/confreg/migrations/0023_accesstokens.py b/postgresqleu/confreg/migrations/0023_accesstokens.py
new file mode 100644
index 00000000..0167ffd0
--- /dev/null
+++ b/postgresqleu/confreg/migrations/0023_accesstokens.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import postgresqleu.util.forms
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('confreg', '0022_ask_more_fields'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AccessToken',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('token', models.CharField(max_length=200)),
+ ('description', models.TextField()),
+ ('permissions', postgresqleu.util.forms.ChoiceArrayField(base_field=models.CharField(max_length=32, choices=[(b'regtypes', b'Registration types and counters'), (b'discounts', b'Discount codes'), (b'vouchers', b'Voucher codes'), (b'sponsors', b'Sponsors and counts')]), size=None)),
+ ('conference', models.ForeignKey(to='confreg.Conference')),
+ ],
+ ),
+ migrations.AlterUniqueTogether(
+ name='accesstoken',
+ unique_together=set([('conference', 'token')]),
+ ),
+ ]
diff --git a/postgresqleu/confreg/models.py b/postgresqleu/confreg/models.py
index 98515084..2303aa11 100644
--- a/postgresqleu/confreg/models.py
+++ b/postgresqleu/confreg/models.py
@@ -11,6 +11,7 @@ from django.utils.dateformat import DateFormat
from django.contrib.postgres.fields import DateTimeRangeField
from postgresqleu.util.validators import validate_lowercase
+from postgresqleu.util.forms import ChoiceArrayField
from postgresqleu.confreg.dbimage import SpeakerImageStorage
@@ -903,3 +904,28 @@ class AggregatedDietary(models.Model):
class Meta:
unique_together = ( ('conference', 'dietary'), )
+
+
+AccessTokenPermissions = (
+ ('regtypes', 'Registration types and counters'),
+ ('discounts', 'Discount codes'),
+ ('vouchers', 'Voucher codes'),
+ ('sponsors', 'Sponsors and counts'),
+)
+
+class AccessToken(models.Model):
+ conference = models.ForeignKey(Conference, null=False, blank=False)
+ token = models.CharField(max_length=200, null=False, blank=False)
+ description = models.TextField(null=False, blank=False)
+ permissions = ChoiceArrayField(
+ models.CharField(max_length=32, blank=False, null=False, choices=AccessTokenPermissions)
+ )
+
+ class Meta:
+ unique_together = ( ('conference', 'token'), )
+
+ def __unicode__(self):
+ return self.token
+
+ def _display_permissions(self):
+ return ", ".join(self.permissions)
diff --git a/postgresqleu/confsponsor/util.py b/postgresqleu/confsponsor/util.py
new file mode 100644
index 00000000..5a81ef54
--- /dev/null
+++ b/postgresqleu/confsponsor/util.py
@@ -0,0 +1,7 @@
+from postgresqleu.util.db import exec_to_list
+
+def get_sponsor_dashboard_data(conference):
+ return (
+ ["Level", "Confirmed", "Unconfirmed"],
+ exec_to_list("SELECT l.levelname, count(s.id) FILTER (WHERE confirmed) AS confirmed, count(s.id) FILTER (WHERE NOT confirmed) AS unconfirmed FROM confsponsor_sponsorshiplevel l LEFT JOIN confsponsor_sponsor s ON s.level_id=l.id WHERE l.conference_id=%(confid)s GROUP BY l.id ORDER BY levelcost", {'confid': conference.id, })
+ )
diff --git a/postgresqleu/urls.py b/postgresqleu/urls.py
index 1a824426..ab3d44ac 100644
--- a/postgresqleu/urls.py
+++ b/postgresqleu/urls.py
@@ -150,9 +150,12 @@ urlpatterns = [
url(r'^events/admin/(\w+)/volunteerslots/(.*/)?$', postgresqleu.confreg.backendviews.edit_volunteerslots),
url(r'^events/admin/(\w+)/feedbackquestions/(.*/)?$', postgresqleu.confreg.backendviews.edit_feedbackquestions),
url(r'^events/admin/(\w+)/discountcodes/(.*/)?$', postgresqleu.confreg.backendviews.edit_discountcodes),
+ url(r'^events/admin/(\w+)/accesstokens/(.*/)?$', postgresqleu.confreg.backendviews.edit_accesstokens),
url(r'^events/admin/(\w+)/pendinginvoices/$', postgresqleu.confreg.backendviews.pendinginvoices),
url(r'^events/admin/(\w+)/purgedata/$', postgresqleu.confreg.backendviews.purge_personal_data),
+ url(r'^events/admin/(\w+)/tokendata/([a-z0-9]{64})/(\w+)\.(tsv|csv)$', postgresqleu.confreg.backendviews.tokendata),
+
url(r'^events/sponsor/', include('postgresqleu.confsponsor.urls')),
# "Homepage" for events
diff --git a/template/confreg/admin_dashboard_single.html b/template/confreg/admin_dashboard_single.html
index e712f585..df307c77 100644
--- a/template/confreg/admin_dashboard_single.html
+++ b/template/confreg/admin_dashboard_single.html
@@ -74,6 +74,7 @@
<h2>Metadata</h2>
<div class="row">
<div class="col-md-3 col-sm-6 col-xs-12 buttonrow"><a class="btn btn-default btn-block" href="/events/admin/{{c.urlname}}/edit/">Conference entry</a></div>
+ <div class="col-md-3 col-sm-6 col-xs-12 buttonrow"><a class="btn btn-default btn-block" href="/events/admin/{{c.urlname}}/accesstokens/">Access tokens</a></div>
<div class="col-md-3 col-sm-6 col-xs-12 buttonrow"><a class="btn btn-default btn-block" href="/events/admin/{{c.urlname}}/feedbackquestions/">Feedback questions</a></div>
</div>
<div class="row">