forked from CGM_Public/pretix_original
Add sub-events and relative date settings (#503)
* Data model * little crud * SubEventItemForm etc * Drop SubEventItem.active, quota editor * Fix failing tests * First frontend stuff * Addons form stuff * Quota calculation * net price display on EventIndex * Add tests, solve some bugs * Correct quota selection in more places, consolidate pricing logic * Fix failing quota tests * Fix TypeError * Add tests for checkout * Fixed a bug in QuotaForm * Prevent immutable cart if a quota was removed from an item * Add tests for pricing * Handle waiting list * Filter in check-in list * Fixed import lost in rebase * Fix waiting list widget * Voucher management * Voucher redemption * Fix broken tests * Add subevents to OrderChangeManager * Create a subevent during event creation * Fix bulk voucher creation * Introduce subevent.active * Copy from for subevents * Show active in list * ICal download for subevents * Check start and end of presale * Failing tests / show cart logic * Test * Rebase migrations * REST API integration of sub-events * Integrate quota calculation into the traditional quota form * Make subevent argument to add_position optional * Log-display foo * pretixdroid and subevents * Filter by subevent * Add more tests * Some mor tests * Rebase fixes * More tests * Relative dates * Restrict selection in relative datetime widgets * Filter subevent list * Re-label has_subevents * Rebase fixes, subevents in calendar view * Performance and caching issues * Refactor calendar templates * Permission tests * Calendar fixes and month selection * subevent selection * Rename subevents to dates * Add tests for calendar views
This commit is contained in:
@@ -29,12 +29,32 @@
|
||||
The code tells the app all it needs about your event.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<div id="qrcodeCanvas"></div>
|
||||
<a href="?flush_key=1" class="btn btn-default">{% trans "Reset authentication token" %}</a>
|
||||
<script type="text/json" id="qrdata">
|
||||
{{ qrdata|safe }}
|
||||
{% if request.event.has_subevents %}
|
||||
<form class="form-inline helper-display-inline" action="" method="get">
|
||||
<p>
|
||||
{% if request.event.has_subevents %}
|
||||
<select name="subevent" class="form-control">
|
||||
<option value="">{% trans "Choose date" context "subevent" %}</option>
|
||||
{% for se in request.event.subevents.all %}
|
||||
<option value="{{ se.id }}"
|
||||
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
|
||||
{{ se.name }} – {{ se.get_date_range_display }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Show configuration" %}</button>
|
||||
</p>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if not request.event.has_subevents or subevent %}
|
||||
<div id="qrcodeCanvas"></div>
|
||||
<a href="?flush_key=1" class="btn btn-default">{% trans "Reset authentication token" %}</a>
|
||||
<script type="text/json" id="qrdata">
|
||||
{{ qrdata|safe }}
|
||||
|
||||
</script>
|
||||
</script>
|
||||
{% endif %}
|
||||
<script type="text/javascript" src="{% static "pretixplugins/pretixdroid/pretixdroid.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
from django.conf.urls import url
|
||||
from django.conf.urls import include, url
|
||||
|
||||
from . import views
|
||||
|
||||
pretixdroid_api_patterns = [
|
||||
url(r'^redeem/', views.ApiRedeemView.as_view(),
|
||||
name='api.redeem'),
|
||||
url(r'^search/', views.ApiSearchView.as_view(),
|
||||
name='api.search'),
|
||||
url(r'^download/', views.ApiDownloadView.as_view(),
|
||||
name='api.download'),
|
||||
url(r'^status/', views.ApiStatusView.as_view(),
|
||||
name='api.status'),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/pretixdroid/', views.ConfigView.as_view(),
|
||||
name='config'),
|
||||
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/redeem/', views.ApiRedeemView.as_view(),
|
||||
name='api.redeem'),
|
||||
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/search/', views.ApiSearchView.as_view(),
|
||||
name='api.search'),
|
||||
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/download/', views.ApiDownloadView.as_view(),
|
||||
name='api.download'),
|
||||
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/status/', views.ApiStatusView.as_view(),
|
||||
name='api.status'),
|
||||
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/(?P<subevent>\d+)/',
|
||||
include(pretixdroid_api_patterns)),
|
||||
url(r'^pretixdroid/api/(?P<organizer>[^/]+)/(?P<event>[^/]+)/', include(pretixdroid_api_patterns)),
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ from django.db.models import Count, Q
|
||||
from django.http import (
|
||||
HttpResponseForbidden, HttpResponseNotFound, JsonResponse,
|
||||
)
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.timezone import now
|
||||
@@ -15,6 +16,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.generic import TemplateView, View
|
||||
|
||||
from pretix.base.models import Checkin, Event, Order, OrderPosition
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.helpers.urls import build_absolute_uri
|
||||
from pretix.multidomain.urlreverse import (
|
||||
@@ -37,13 +39,26 @@ class ConfigView(EventPermissionRequiredMixin, TemplateView):
|
||||
allowed_chars=string.ascii_uppercase + string.ascii_lowercase + string.digits)
|
||||
self.request.event.settings.set('pretixdroid_key', key)
|
||||
|
||||
subevent = None
|
||||
url = build_absolute_uri('plugins:pretixdroid:api.redeem', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug
|
||||
})
|
||||
if self.request.event.has_subevents:
|
||||
if self.request.GET.get('subevent'):
|
||||
subevent = get_object_or_404(SubEvent, event=self.request.event, pk=self.request.GET['subevent'])
|
||||
url = build_absolute_uri('plugins:pretixdroid:api.redeem', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
'subevent': subevent.pk
|
||||
})
|
||||
|
||||
ctx['subevent'] = subevent
|
||||
|
||||
ctx['qrdata'] = json.dumps({
|
||||
'version': API_VERSION,
|
||||
'url': build_absolute_uri('plugins:pretixdroid:api.redeem', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug
|
||||
})[:-7], # the slice removes the redeem/ part at the end
|
||||
'key': key
|
||||
'url': url[:-7], # the slice removes the redeem/ part at the end
|
||||
'key': key,
|
||||
})
|
||||
return ctx
|
||||
|
||||
@@ -61,9 +76,19 @@ class ApiView(View):
|
||||
return HttpResponseNotFound('Unknown event')
|
||||
|
||||
if (not self.event.settings.get('pretixdroid_key')
|
||||
or self.event.settings.get('pretixdroid_key') != request.GET.get('key', '')):
|
||||
or self.event.settings.get('pretixdroid_key') != request.GET.get('key', '-unset-')):
|
||||
return HttpResponseForbidden('Invalid key')
|
||||
|
||||
self.subevent = None
|
||||
if self.event.has_subevents:
|
||||
if 'subevent' in kwargs:
|
||||
self.subevent = get_object_or_404(SubEvent, event=self.event, pk=kwargs['subevent'])
|
||||
else:
|
||||
return HttpResponseForbidden('No subevent selected.')
|
||||
else:
|
||||
if 'subevent' in kwargs:
|
||||
return HttpResponseForbidden('Subevents not enabled.')
|
||||
|
||||
return super().dispatch(request, **kwargs)
|
||||
|
||||
|
||||
@@ -85,7 +110,7 @@ class ApiRedeemView(ApiView):
|
||||
with transaction.atomic():
|
||||
created = False
|
||||
op = OrderPosition.objects.select_related('item', 'variation', 'order', 'addon_to').get(
|
||||
order__event=self.event, secret=secret
|
||||
order__event=self.event, secret=secret, subevent=self.subevent
|
||||
)
|
||||
if op.order.status == Order.STATUS_PAID or force:
|
||||
ci, created = Checkin.objects.get_or_create(position=op, defaults={
|
||||
@@ -161,6 +186,7 @@ class ApiSearchView(ApiView):
|
||||
& Q(
|
||||
Q(secret__istartswith=query) | Q(attendee_name__icontains=query) | Q(order__code__istartswith=query)
|
||||
)
|
||||
& Q(subevent=self.subevent)
|
||||
).annotate(checkin_cnt=Count('checkins'))[:25]
|
||||
|
||||
response['results'] = [serialize_op(op) for op in ops]
|
||||
@@ -177,7 +203,7 @@ class ApiDownloadView(ApiView):
|
||||
}
|
||||
|
||||
ops = OrderPosition.objects.select_related('item', 'variation', 'order', 'addon_to').filter(
|
||||
Q(order__event=self.event)
|
||||
Q(order__event=self.event) & Q(subevent=self.subevent)
|
||||
).annotate(checkin_cnt=Count('checkins'))
|
||||
response['results'] = [serialize_op(op) for op in ops]
|
||||
|
||||
@@ -186,25 +212,27 @@ class ApiDownloadView(ApiView):
|
||||
|
||||
class ApiStatusView(ApiView):
|
||||
def get(self, request, **kwargs):
|
||||
ev = self.subevent or self.event
|
||||
response = {
|
||||
'version': API_VERSION,
|
||||
'event': {
|
||||
'name': str(self.event),
|
||||
'name': str(ev.name),
|
||||
'slug': self.event.slug,
|
||||
'organizer': {
|
||||
'name': str(self.event.organizer),
|
||||
'slug': self.event.organizer.slug
|
||||
},
|
||||
'date_from': self.event.date_from,
|
||||
'date_to': self.event.date_to,
|
||||
'subevent': self.subevent.pk if self.subevent else str(self.event),
|
||||
'date_from': ev.date_from,
|
||||
'date_to': ev.date_to,
|
||||
'timezone': self.event.settings.timezone,
|
||||
'url': event_absolute_uri(self.event, 'presale:event.index')
|
||||
},
|
||||
'checkins': Checkin.objects.filter(
|
||||
position__order__event=self.event
|
||||
position__order__event=self.event, position__subevent=self.subevent
|
||||
).count(),
|
||||
'total': OrderPosition.objects.filter(
|
||||
order__event=self.event, order__status=Order.STATUS_PAID
|
||||
order__event=self.event, order__status=Order.STATUS_PAID, subevent=self.subevent
|
||||
).count()
|
||||
}
|
||||
|
||||
@@ -212,28 +240,32 @@ class ApiStatusView(ApiView):
|
||||
p['item']: p['cnt']
|
||||
for p in OrderPosition.objects.filter(
|
||||
order__event=self.event,
|
||||
order__status=Order.STATUS_PAID
|
||||
order__status=Order.STATUS_PAID,
|
||||
subevent=self.subevent
|
||||
).order_by().values('item').annotate(cnt=Count('id'))
|
||||
}
|
||||
op_by_variation = {
|
||||
p['variation']: p['cnt']
|
||||
for p in OrderPosition.objects.filter(
|
||||
order__event=self.event,
|
||||
order__status=Order.STATUS_PAID
|
||||
order__status=Order.STATUS_PAID,
|
||||
subevent=self.subevent
|
||||
).order_by().values('variation').annotate(cnt=Count('id'))
|
||||
}
|
||||
c_by_item = {
|
||||
p['position__item']: p['cnt']
|
||||
for p in Checkin.objects.filter(
|
||||
position__order__event=self.event,
|
||||
position__order__status=Order.STATUS_PAID
|
||||
position__order__status=Order.STATUS_PAID,
|
||||
position__subevent=self.subevent
|
||||
).order_by().values('position__item').annotate(cnt=Count('id'))
|
||||
}
|
||||
c_by_variation = {
|
||||
p['position__variation']: p['cnt']
|
||||
for p in Checkin.objects.filter(
|
||||
position__order__event=self.event,
|
||||
position__order__status=Order.STATUS_PAID
|
||||
position__order__status=Order.STATUS_PAID,
|
||||
position__subevent=self.subevent
|
||||
).order_by().values('position__variation').annotate(cnt=Count('id'))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user