forked from CGM_Public/pretix_original
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:
@@ -10,7 +10,9 @@ from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
|
||||
from pretix.base.forms import I18nModelForm, PlaceholderValidator
|
||||
from pretix.base.forms.widgets import DatePickerWidget
|
||||
from pretix.base.models import InvoiceAddress, ItemAddOn, Order, OrderPosition
|
||||
from pretix.base.models import (
|
||||
InvoiceAddress, ItemAddOn, Order, OrderPosition, Seat,
|
||||
)
|
||||
from pretix.base.models.event import SubEvent
|
||||
from pretix.base.services.pricing import get_price
|
||||
from pretix.control.forms.widgets import Select2
|
||||
@@ -196,6 +198,12 @@ class OrderPositionAddForm(forms.Form):
|
||||
required=False,
|
||||
label=_('Add-on to'),
|
||||
)
|
||||
seat = forms.ModelChoiceField(
|
||||
Seat.objects.none(),
|
||||
required=False,
|
||||
label=_('Seat'),
|
||||
empty_label=_('General admission')
|
||||
)
|
||||
price = forms.DecimalField(
|
||||
required=False,
|
||||
max_digits=10, decimal_places=2,
|
||||
@@ -241,6 +249,19 @@ class OrderPositionAddForm(forms.Form):
|
||||
else:
|
||||
del self.fields['addon_to']
|
||||
|
||||
self.fields['seat'].queryset = order.event.seats.all()
|
||||
self.fields['seat'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'seat',
|
||||
'data-select2-url': reverse('control:event.seats.select2', kwargs={
|
||||
'event': order.event.slug,
|
||||
'organizer': order.event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': _('General admission')
|
||||
}
|
||||
)
|
||||
self.fields['seat'].widget.choices = self.fields['seat'].choices
|
||||
|
||||
if order.event.has_subevents:
|
||||
self.fields['subevent'].queryset = order.event.subevents.all()
|
||||
self.fields['subevent'].widget = Select2(
|
||||
@@ -269,6 +290,11 @@ class OrderPositionChangeForm(forms.Form):
|
||||
required=False,
|
||||
empty_label=_('(Unchanged)')
|
||||
)
|
||||
seat = forms.ModelChoiceField(
|
||||
Seat.objects.none(),
|
||||
required=False,
|
||||
empty_label=_('(Unchanged)')
|
||||
)
|
||||
price = forms.DecimalField(
|
||||
required=False,
|
||||
max_digits=10, decimal_places=2,
|
||||
@@ -312,6 +338,22 @@ class OrderPositionChangeForm(forms.Form):
|
||||
else:
|
||||
del self.fields['subevent']
|
||||
|
||||
if instance.seat:
|
||||
self.fields['seat'].queryset = instance.order.event.seats.all()
|
||||
self.fields['seat'].widget = Select2(
|
||||
attrs={
|
||||
'data-model-select2': 'seat',
|
||||
'data-select2-url': reverse('control:event.seats.select2', kwargs={
|
||||
'event': instance.order.event.slug,
|
||||
'organizer': instance.order.event.organizer.slug,
|
||||
}),
|
||||
'data-placeholder': _('(Unchanged)')
|
||||
}
|
||||
)
|
||||
self.fields['seat'].widget.choices = self.fields['seat'].choices
|
||||
else:
|
||||
del self.fields['seat']
|
||||
|
||||
choices = [
|
||||
('', _('(Unchanged)'))
|
||||
]
|
||||
|
||||
@@ -36,7 +36,7 @@ class SubEventForm(I18nModelForm):
|
||||
'presale_start',
|
||||
'presale_end',
|
||||
'location',
|
||||
'frontpage_text'
|
||||
'frontpage_text',
|
||||
]
|
||||
field_classes = {
|
||||
'date_from': SplitDateTimeField,
|
||||
|
||||
@@ -42,6 +42,12 @@ def _display_order_changed(event: Event, logentry: LogEntry):
|
||||
old_price=money_filter(Decimal(data['old_price']), event.currency),
|
||||
new_price=money_filter(Decimal(data['new_price']), event.currency),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.seat':
|
||||
return text + ' ' + _('Position #{posid}: Seat "{old_seat}" changed '
|
||||
'to "{new_seat}".').format(
|
||||
posid=data.get('positionid', '?'),
|
||||
old_seat=data.get('old_seat'), new_seat=data.get('new_seat'),
|
||||
)
|
||||
elif logentry.action_type == 'pretix.event.order.changed.subevent':
|
||||
old_se = str(event.subevents.get(pk=data['old_subevent']))
|
||||
new_se = str(event.subevents.get(pk=data['new_subevent']))
|
||||
|
||||
@@ -279,6 +279,22 @@ styles. It is advisable to set a prefix for your form to avoid clashes with othe
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
subevent_forms = EventPluginSignal(
|
||||
providing_args=['request', 'subevent']
|
||||
)
|
||||
"""
|
||||
This signal allows you to return additional forms that should be rendered on the subevent creation
|
||||
or modification page. You are passed ``request`` and ``subevent`` arguments and are expected to return
|
||||
an instance of a form class that you bind yourself when appropriate. Your form will be executed
|
||||
as part of the standard validation and rendering cycle and rendered using default bootstrap
|
||||
styles. It is advisable to set a prefix for your form to avoid clashes with other plugins.
|
||||
|
||||
``subevent`` can be ``None`` during creation. Before ``save()`` is called, a ``subevent`` property of
|
||||
your form instance will automatically being set to the subevent that has just been created.
|
||||
|
||||
As with all plugin signals, the ``sender`` keyword argument will contain the event.
|
||||
"""
|
||||
|
||||
oauth_application_registered = Signal(
|
||||
providing_args=["user", "application"]
|
||||
)
|
||||
|
||||
@@ -145,7 +145,11 @@
|
||||
<a href="{{ nav.url }}" title="{{ nav.title }}" {% if nav.active %}class="active"{% endif %}
|
||||
{% if nav.children %}class="dropdown-toggle" data-toggle="dropdown"{% endif %}>
|
||||
{% if nav.icon %}
|
||||
<span class="fa fa-{{ nav.icon }}"></span>
|
||||
{% if "<svg" in nav.icon %}
|
||||
{{ nav.icon|safe }}
|
||||
{% else %}
|
||||
<span class="fa fa-{{ nav.icon }}"></span>
|
||||
{% endif %}
|
||||
<span class="visible-xs-inline">{{ nav.label }}</span>
|
||||
{% else %}
|
||||
{{ nav.label }}
|
||||
@@ -270,7 +274,11 @@
|
||||
<a href="{{ nav.url }}" {% if nav.active %}class="active"{% endif %}
|
||||
{% if nav.children %}class="has-children"{% endif %}>
|
||||
{% if nav.icon %}
|
||||
<i class="fa fa-{{ nav.icon }} fa-fw"></i>
|
||||
{% if "<svg" in nav.icon %}
|
||||
{{ nav.icon|safe }}
|
||||
{% else %}
|
||||
<span class="fa fa-fw fa-{{ nav.icon }}"></span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{{ nav.label }}
|
||||
</a>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-order-change" data-pricecalc-endpoint="{% url "api-v1:orderposition-price_calc" organizer=order.event.organizer.slug event=order.event.slug pk=position.pk %}">
|
||||
<div class="form-order-change" data-pricecalc-endpoint="{% url "api-v1:orderposition-price_calc" organizer=order.event.organizer.slug event=order.event.slug pk=position.pk %}" {% if position.subevent %}data-subevent="{{ position.subevent.id }}{% endif %}">
|
||||
{% bootstrap_form_errors position.form %}
|
||||
{% if position.custom_error %}
|
||||
<div class="alert alert-danger">
|
||||
@@ -87,20 +87,6 @@
|
||||
<strong>{% trans "Change to" %}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans "Product" %}</strong>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
{{ position.item }}
|
||||
{% if position.variation %}
|
||||
– {{ position.variation }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field position.form.itemvar layout='inline' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if request.event.has_subevents %}
|
||||
<div class="row">
|
||||
@@ -116,6 +102,34 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if position.seat %}
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans "Seat" %}</strong>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
{{ position.seat }}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field position.form.seat layout='inline' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans "Product" %}</strong>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
{{ position.item }}
|
||||
{% if position.variation %}
|
||||
– {{ position.variation }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field position.form.itemvar layout='inline' %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans "Price" %}</strong>
|
||||
@@ -182,6 +196,7 @@
|
||||
{% if add_form.subevent %}
|
||||
{% bootstrap_field add_form.subevent layout="control" %}
|
||||
{% endif %}
|
||||
{% bootstrap_field add_form.seat layout="control" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -258,6 +258,15 @@
|
||||
<span class="fa fa-fw fa-check" data-toggle="tooltip_html" title="{{ c.list.name }}<br>{% blocktrans trimmed with date=c.datetime|date:'SHORT_DATETIME_FORMAT' %}First scanned: {{ date }}{% endblocktrans %}"></span>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if line.seat %}
|
||||
<br />
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="14" viewBox="0 0 4.7624999 3.7041668" class="svg-icon">
|
||||
<path
|
||||
style="fill:black"
|
||||
d="m 1.9592032,1.8522629e-4 c -0.21468,0 -0.38861,0.17394000371 -0.38861,0.38861000371 0,0.21466 0.17393,0.38861 0.38861,0.38861 0.21468,0 0.3886001,-0.17395 0.3886001,-0.38861 0,-0.21467 -0.1739201,-0.38861000371 -0.3886001,-0.38861000371 z m 0.1049,0.84543000371 c -0.20823,-0.0326 -0.44367,0.12499 -0.39998,0.40462997 l 0.20361,1.01854 c 0.0306,0.15316 0.15301,0.28732 0.3483,0.28732 h 0.8376701 v 0.92708 c 0,0.29313 0.41187,0.29447 0.41187,0.005 v -1.19115 c 0,-0.14168 -0.0995,-0.29507 -0.29094,-0.29507 l -0.65578,-10e-4 -0.1757,-0.87644 C 2.3042533,0.95300523 2.1890432,0.86500523 2.0641032,0.84547523 Z m -0.58549,0.44906997 c -0.0946,-0.0134 -0.20202,0.0625 -0.17829,0.19172 l 0.18759,0.91054 c 0.0763,0.33956 0.36802,0.55914 0.66042,0.55914 h 0.6015201 c 0.21356,0 0.21448,-0.32143 -0.003,-0.32143 H 2.1954632 c -0.19911,0 -0.36364,-0.11898 -0.41341,-0.34107 l -0.17777,-0.87126 c -0.0165,-0.0794 -0.0688,-0.11963 -0.12557,-0.12764 z"/>
|
||||
</svg>
|
||||
{{ line.seat }}
|
||||
{% endif %}
|
||||
{% if line.voucher %}
|
||||
<br/><span class="fa fa-tags"></span> {% trans "Voucher code used:" %}
|
||||
<a href="{% url "control:event.voucher" event=request.event.slug organizer=request.event.organizer.slug voucher=line.voucher.pk %}">
|
||||
|
||||
@@ -434,6 +434,12 @@
|
||||
</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Additional settings" %}</legend>
|
||||
{% for f in plugin_forms %}
|
||||
{% bootstrap_form f layout="control" %}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
|
||||
@@ -192,6 +192,12 @@
|
||||
</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Additional settings" %}</legend>
|
||||
{% for f in plugin_forms %}
|
||||
{% bootstrap_form f layout="control" %}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
</div>
|
||||
{% if subevent.pk %}
|
||||
<div class="col-xs-12 col-lg-2">
|
||||
|
||||
@@ -140,6 +140,7 @@ urlpatterns = [
|
||||
url(r'^pdf/editor/(?P<filename>[^/]+).pdf$', pdf.PdfView.as_view(), name='pdf.background'),
|
||||
url(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
|
||||
url(r'^subevents/select2$', typeahead.subevent_select2, name='event.subevents.select2'),
|
||||
url(r'^seats/select2$', typeahead.seat_select2, name='event.seats.select2'),
|
||||
url(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
|
||||
url(r'^subevents/(?P<subevent>\d+)/delete$', subevents.SubEventDelete.as_view(),
|
||||
name='event.subevent.delete'),
|
||||
|
||||
@@ -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