mirror of
https://github.com/pretix/pretix.git
synced 2026-05-06 15:24:02 +00:00
Add support for reserved seating (#1228)
* Initial work on seating * Add seat guids * Add product_list_top * CartAdd: Ignore item when a seat is passed * Cart display * product_list_top → render_seating_plan * Render seating plan in voucher redemption * Fix failing tests * Add tests for extending cart positions with seats * Add subevent_forms to docs * Update schema, migrations * Dealing with expired orders * steps to order change * Change order positions * Allow to add seats * tests for ocm * Fix things after rebase * Seating plans API * Add more tests for cart behaviour * Widget support * Adjust widget tests * Re-enable CSP * Update schema * Api: position.seat * Add guid to word list * API: (sub)event.seating_plan * Vali fixes * Fix api * Fix reference in test * Fix test for real
This commit is contained in:
@@ -1236,7 +1236,8 @@ class OrderChange(OrderView):
|
||||
ocm.add_position(item, variation,
|
||||
self.add_form.cleaned_data['price'],
|
||||
self.add_form.cleaned_data.get('addon_to'),
|
||||
self.add_form.cleaned_data.get('subevent'))
|
||||
self.add_form.cleaned_data.get('subevent'),
|
||||
self.add_form.cleaned_data.get('seat'))
|
||||
except OrderError as e:
|
||||
self.add_form.custom_error = str(e)
|
||||
return False
|
||||
@@ -1266,6 +1267,9 @@ class OrderChange(OrderView):
|
||||
if item != p.item or variation != p.variation:
|
||||
ocm.change_item(p, item, variation)
|
||||
|
||||
if p.seat and p.form.cleaned_data['seat'] and p.form.cleaned_data['seat'] != p.seat:
|
||||
ocm.change_seat(p, p.form.cleaned_data['seat'])
|
||||
|
||||
if self.request.event.has_subevents and p.form.cleaned_data['subevent'] and p.form.cleaned_data['subevent'] != p.subevent:
|
||||
ocm.change_subevent(p, p.form.cleaned_data['subevent'])
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ from datetime import datetime
|
||||
|
||||
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
|
||||
from django.contrib import messages
|
||||
from django.core.files import File
|
||||
from django.db import transaction
|
||||
from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
@@ -32,6 +33,7 @@ from pretix.control.forms.subevents import (
|
||||
SubEventMetaValueForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import subevent_forms
|
||||
from pretix.control.views import PaginationMixin
|
||||
from pretix.control.views.event import MetaDataEditorMixin
|
||||
from pretix.helpers.models import modelcopy
|
||||
@@ -135,6 +137,16 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
meta_form = SubEventMetaValueForm
|
||||
meta_model = SubEventMetaValue
|
||||
|
||||
@cached_property
|
||||
def plugin_forms(self):
|
||||
forms = []
|
||||
for rec, resp in subevent_forms.send(sender=self.request.event, subevent=self.object, request=self.request):
|
||||
if isinstance(resp, (list, tuple)):
|
||||
forms.extend(resp)
|
||||
else:
|
||||
forms.append(resp)
|
||||
return forms
|
||||
|
||||
def _make_meta_form(self, p, val_instances):
|
||||
if not hasattr(self, '_default_meta'):
|
||||
self._default_meta = self.request.event.meta_data
|
||||
@@ -294,6 +306,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
ctx['cl_formset'] = self.cl_formset
|
||||
ctx['itemvar_forms'] = self.itemvar_forms
|
||||
ctx['meta_forms'] = self.meta_forms
|
||||
ctx['plugin_forms'] = self.plugin_forms
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
@@ -347,7 +360,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
def is_valid(self, form):
|
||||
return form.is_valid() and all([f.is_valid() for f in self.itemvar_forms]) and self.formset.is_valid() and (
|
||||
all([f.is_valid() for f in self.meta_forms])
|
||||
) and self.cl_formset.is_valid()
|
||||
) and self.cl_formset.is_valid() and all(f.is_valid() for f in self.plugin_forms)
|
||||
|
||||
|
||||
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
|
||||
@@ -361,9 +374,9 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
|
||||
self.object = self.get_object()
|
||||
form = self.get_form()
|
||||
if self.is_valid(form):
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
r = self.form_valid(form)
|
||||
return r
|
||||
return self.form_invalid(form)
|
||||
|
||||
def get_object(self, queryset=None) -> SubEvent:
|
||||
try:
|
||||
@@ -384,12 +397,23 @@ class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateVi
|
||||
# TODO: LogEntry?
|
||||
|
||||
messages.success(self.request, _('Your changes have been saved.'))
|
||||
if form.has_changed():
|
||||
if form.has_changed() or any(f.has_changed() for f in self.plugin_forms):
|
||||
data = {
|
||||
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||
}
|
||||
for f in self.plugin_forms:
|
||||
data.update({
|
||||
k: (f.cleaned_data.get(k).name
|
||||
if isinstance(f.cleaned_data.get(k), File)
|
||||
else f.cleaned_data.get(k))
|
||||
for k in f.changed_data
|
||||
})
|
||||
self.object.log_action(
|
||||
'pretix.subevent.changed', user=self.request.user, data={
|
||||
k: form.cleaned_data.get(k) for k in form.changed_data
|
||||
}
|
||||
'pretix.subevent.changed', user=self.request.user, data=data
|
||||
)
|
||||
for f in self.plugin_forms:
|
||||
f.subevent = self.object
|
||||
f.save()
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
@@ -416,8 +440,7 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
form = self.get_form()
|
||||
if self.is_valid(form):
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
return self.form_invalid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.subevents', kwargs={
|
||||
@@ -442,7 +465,16 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
messages.success(self.request, pgettext_lazy('subevent', 'The new date has been created.'))
|
||||
ret = super().form_valid(form)
|
||||
self.object = form.instance
|
||||
form.instance.log_action('pretix.subevent.added', data=dict(form.cleaned_data), user=self.request.user)
|
||||
|
||||
data = dict(form.cleaned_data)
|
||||
for f in self.plugin_forms:
|
||||
data.update({
|
||||
k: (f.cleaned_data.get(k).name
|
||||
if isinstance(f.cleaned_data.get(k), File)
|
||||
else f.cleaned_data.get(k))
|
||||
for k in f.cleaned_data
|
||||
})
|
||||
form.instance.log_action('pretix.subevent.added', data=dict(data), user=self.request.user)
|
||||
|
||||
self.save_formset(form.instance)
|
||||
self.save_cl_formset(form.instance)
|
||||
@@ -452,6 +484,9 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
for f in self.meta_forms:
|
||||
f.instance.subevent = form.instance
|
||||
self.save_meta()
|
||||
for f in self.plugin_forms:
|
||||
f.subevent = form.instance
|
||||
f.save()
|
||||
return ret
|
||||
|
||||
@cached_property
|
||||
@@ -657,7 +692,6 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
|
||||
tz = self.request.event.timezone
|
||||
cnt = 0
|
||||
for rdate in self.get_rrule_set():
|
||||
@@ -685,7 +719,15 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
else None
|
||||
)
|
||||
se.save()
|
||||
se.log_action('pretix.subevent.added', data=dict(form.cleaned_data), user=self.request.user)
|
||||
data = dict(form.cleaned_data)
|
||||
for f in self.plugin_forms:
|
||||
data.update({
|
||||
k: (f.cleaned_data.get(k).name
|
||||
if isinstance(f.cleaned_data.get(k), File)
|
||||
else f.cleaned_data.get(k))
|
||||
for k in f.cleaned_data
|
||||
})
|
||||
se.log_action('pretix.subevent.added', data=data, user=self.request.user)
|
||||
|
||||
for f in self.meta_forms:
|
||||
if f.cleaned_data.get('value'):
|
||||
@@ -731,6 +773,11 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
i.subevent = se
|
||||
i.save()
|
||||
|
||||
for f in self.plugin_forms:
|
||||
f.is_valid()
|
||||
f.subevent = se
|
||||
f.save()
|
||||
|
||||
cnt += 1
|
||||
|
||||
messages.success(self.request, pgettext_lazy('subevent', '{} new dates have been created.').format(cnt))
|
||||
@@ -742,7 +789,8 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
self.object = SubEvent(event=self.request.event)
|
||||
|
||||
if self.is_valid(form):
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
return self.form_invalid(form)
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.utils.formats import get_format
|
||||
from django.utils.timezone import make_aware
|
||||
from django.utils.translation import pgettext, ugettext as _
|
||||
|
||||
from pretix.base.models import Order, Organizer, User, Voucher
|
||||
from pretix.base.models import Order, Organizer, SubEvent, User, Voucher
|
||||
from pretix.control.forms.event import EventWizardCopyForm
|
||||
from pretix.control.permissions import event_permission_required
|
||||
from pretix.helpers.daterange import daterange
|
||||
@@ -221,6 +221,46 @@ def nav_context_list(request):
|
||||
return JsonResponse(doc)
|
||||
|
||||
|
||||
@event_permission_required("can_view_orders")
|
||||
def seat_select2(request, **kwargs):
|
||||
query = request.GET.get('query', '')
|
||||
try:
|
||||
page = int(request.GET.get('page', '1'))
|
||||
except ValueError:
|
||||
page = 1
|
||||
|
||||
if request.event.has_subevents:
|
||||
try:
|
||||
qs = request.event.subevents.get(active=True, pk=request.GET.get('subevent', 0)).free_seats
|
||||
except SubEvent.DoesNotExist:
|
||||
qs = request.event.seats.none()
|
||||
else:
|
||||
qs = request.event.free_seats
|
||||
qs = qs.filter(
|
||||
Q(name__icontains=query) | Q(seat_guid__icontains=query)
|
||||
).order_by('name').select_related('product', 'subevent')
|
||||
|
||||
total = qs.count()
|
||||
pagesize = 20
|
||||
offset = (page - 1) * pagesize
|
||||
doc = {
|
||||
'results': [
|
||||
{
|
||||
'id': e.pk,
|
||||
'text': '{} ({})'.format(e.name, str(e.product)),
|
||||
'product': e.product_id,
|
||||
'event': str(e.subevent) if e.subevent else ''
|
||||
|
||||
}
|
||||
for e in qs[offset:offset + pagesize]
|
||||
],
|
||||
'pagination': {
|
||||
"more": total >= (offset + pagesize)
|
||||
}
|
||||
}
|
||||
return JsonResponse(doc)
|
||||
|
||||
|
||||
@event_permission_required(None)
|
||||
def subevent_select2(request, **kwargs):
|
||||
query = request.GET.get('query', '')
|
||||
|
||||
Reference in New Issue
Block a user