mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Various improvements to the subevent creation form (#1670)
This commit is contained in:
@@ -2,6 +2,7 @@ from datetime import timedelta
|
||||
from urllib.parse import urlencode
|
||||
|
||||
from django import forms
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import formset_factory
|
||||
from django.urls import reverse
|
||||
from django.utils.dates import MONTHS, WEEKDAYS
|
||||
@@ -59,20 +60,6 @@ class SubEventForm(I18nModelForm):
|
||||
|
||||
|
||||
class SubEventBulkForm(SubEventForm):
|
||||
time_from = forms.TimeField(
|
||||
label=_('Event start time'),
|
||||
widget=forms.TimeInput(attrs={'class': 'timepickerfield'})
|
||||
)
|
||||
time_to = forms.TimeField(
|
||||
label=_('Event end time'),
|
||||
widget=forms.TimeInput(attrs={'class': 'timepickerfield'}),
|
||||
required=False
|
||||
)
|
||||
time_admission = forms.TimeField(
|
||||
label=_('Admission time'),
|
||||
widget=forms.TimeInput(attrs={'class': 'timepickerfield'}),
|
||||
required=False
|
||||
)
|
||||
rel_presale_start = RelativeDateTimeField(
|
||||
label=_('Start of presale'),
|
||||
help_text=_('Optional. No products will be sold before this date.'),
|
||||
@@ -376,3 +363,34 @@ RRuleFormSet = formset_factory(
|
||||
RRuleForm,
|
||||
can_order=False, can_delete=True, extra=1
|
||||
)
|
||||
|
||||
|
||||
class TimeForm(forms.Form):
|
||||
time_from = forms.TimeField(
|
||||
label=_('Event start time'),
|
||||
widget=forms.TimeInput(attrs={'class': 'timepickerfield'}),
|
||||
required=True
|
||||
)
|
||||
time_to = forms.TimeField(
|
||||
label=_('Event end time'),
|
||||
widget=forms.TimeInput(attrs={'class': 'timepickerfield'}),
|
||||
required=False
|
||||
)
|
||||
time_admission = forms.TimeField(
|
||||
label=_('Admission time'),
|
||||
widget=forms.TimeInput(attrs={'class': 'timepickerfield'}),
|
||||
required=False
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
d = super().clean()
|
||||
if d.get('time_from') and d.get('time_to') and d['time_from'] > d['time_to']:
|
||||
raise ValidationError({'time_to': _('The end of the event has to be later than its start.')})
|
||||
return d
|
||||
|
||||
|
||||
TimeFormSet = formset_factory(
|
||||
TimeForm,
|
||||
min_num=1,
|
||||
can_order=False, can_delete=True, extra=1, validate_min=True
|
||||
)
|
||||
|
||||
@@ -261,12 +261,87 @@
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>{% trans "Times" context "subevent" %}</legend>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-4"><strong>{% trans "Event start time" %}</strong></div>
|
||||
<div class="col-sm-4">
|
||||
<strong>{% trans "Event end time" %}</strong><br>
|
||||
<label><span class="optional">{% trans "Optional" %}</span></label>
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<strong>{% trans "Admission time" %}</strong><br>
|
||||
<label><span class="optional">{% trans "Optional" %}</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="formset" data-formset data-formset-prefix="{{ time_formset.prefix }}"
|
||||
id="time-formset">
|
||||
{{ time_formset.management_form }}
|
||||
{% bootstrap_formset_errors time_formset %}
|
||||
<div data-formset-body>
|
||||
{% for f in time_formset %}
|
||||
{% bootstrap_form_errors f %}
|
||||
<div data-formset-form>
|
||||
<div class="row form-inline">
|
||||
<div class="sr-only">
|
||||
{{ f.id }}
|
||||
{% bootstrap_field f.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field f.time_from layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field f.time_to layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
{% bootstrap_field f.time_admission layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-1 text-right flip">
|
||||
<button type="button" class="btn btn-danger btn-block"
|
||||
data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div data-formset-form>
|
||||
<div class="row form-inline">
|
||||
<div class="sr-only">
|
||||
{{ time_formset.empty_form.id }}
|
||||
{% bootstrap_field time_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field time_formset.empty_form.time_from layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% bootstrap_field time_formset.empty_form.time_to layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
{% bootstrap_field time_formset.empty_form.time_admission layout="inline" %}
|
||||
</div>
|
||||
<div class="col-sm-1 text-right flip">
|
||||
<button type="button" class="btn btn-danger btn-block"
|
||||
data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a new time slot" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% bootstrap_field form.active layout="control" %}
|
||||
{% bootstrap_field form.time_from layout="control" %}
|
||||
{% bootstrap_field form.time_to layout="control" %}
|
||||
<div class="geodata-section">
|
||||
{% bootstrap_field form.location layout="control" %}
|
||||
<div class="form-group geodata-group" data-tiles="{{ global_settings.leaflet_tiles|default_if_none:"" }}" data-attrib="{{ global_settings.leaflet_tiles_attribution }}" data-icon="{% static "leaflet/images/marker-icon.png" %}" data-shadow="{% static "leaflet/images/marker-shadow.png" %}">
|
||||
@@ -291,7 +366,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field form.time_admission layout="control" %}
|
||||
{% bootstrap_field form.frontpage_text layout="control" %}
|
||||
{% bootstrap_field form.is_public layout="control" %}
|
||||
{% if meta_forms %}
|
||||
|
||||
@@ -30,7 +30,7 @@ from pretix.control.forms.item import QuotaForm
|
||||
from pretix.control.forms.subevents import (
|
||||
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkForm,
|
||||
SubEventForm, SubEventItemForm, SubEventItemVariationForm,
|
||||
SubEventMetaValueForm,
|
||||
SubEventMetaValueForm, TimeFormSet,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.signals import subevent_forms
|
||||
@@ -215,7 +215,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
|
||||
formsetclass = inlineformset_factory(
|
||||
SubEvent, Quota,
|
||||
form=QuotaForm, formset=QuotaFormSet,
|
||||
form=QuotaForm, formset=QuotaFormSet, min_num=1, validate_min=True,
|
||||
can_order=False, can_delete=True, extra=extra,
|
||||
)
|
||||
if self.object:
|
||||
@@ -451,12 +451,17 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
kwargs['event'] = self.request.event
|
||||
initial = kwargs.get('initial', {})
|
||||
if self.copy_from:
|
||||
i = modelcopy(self.copy_from)
|
||||
i.pk = None
|
||||
kwargs['instance'] = i
|
||||
else:
|
||||
kwargs['instance'] = SubEvent(event=self.request.event)
|
||||
initial['location'] = self.request.event.location
|
||||
initial['geo_lat'] = self.request.event.geo_lat
|
||||
initial['geo_lon'] = self.request.event.geo_lon
|
||||
kwargs['initial'] = initial
|
||||
return kwargs
|
||||
|
||||
@transaction.atomic
|
||||
@@ -579,7 +584,7 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
form_class = SubEventBulkForm
|
||||
|
||||
def is_valid(self, form):
|
||||
return self.rrule_formset.is_valid() and super().is_valid(form)
|
||||
return self.rrule_formset.is_valid() and self.time_formset.is_valid() and super().is_valid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.subevents', kwargs={
|
||||
@@ -594,9 +599,17 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
prefix='rruleformset'
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def time_formset(self):
|
||||
return TimeFormSet(
|
||||
data=self.request.POST if self.request.method == "POST" else None,
|
||||
prefix='timeformset'
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['rrule_formset'] = self.rrule_formset
|
||||
ctx['time_formset'] = self.time_formset
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
@@ -621,7 +634,9 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
initial = {}
|
||||
initial = {
|
||||
'active': True,
|
||||
}
|
||||
kwargs['event'] = self.request.event
|
||||
tz = self.request.event.timezone
|
||||
if self.copy_from:
|
||||
@@ -643,9 +658,20 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
)) if i.presale_end else None
|
||||
else:
|
||||
kwargs['instance'] = SubEvent(event=self.request.event)
|
||||
initial['location'] = self.request.event.location
|
||||
initial['geo_lat'] = self.request.event.geo_lat
|
||||
initial['geo_lon'] = self.request.event.geo_lon
|
||||
kwargs['initial'] = initial
|
||||
return kwargs
|
||||
|
||||
def get_times(self):
|
||||
times = []
|
||||
for f in self.time_formset:
|
||||
if f in self.time_formset.deleted_forms:
|
||||
continue
|
||||
times.append(f.cleaned_data)
|
||||
return times
|
||||
|
||||
def get_rrule_set(self):
|
||||
s = rruleset()
|
||||
for f in self.rrule_formset:
|
||||
@@ -695,90 +721,91 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
||||
tz = self.request.event.timezone
|
||||
cnt = 0
|
||||
for rdate in self.get_rrule_set():
|
||||
se = copy.copy(form.instance)
|
||||
for t in self.get_times():
|
||||
se = copy.copy(form.instance)
|
||||
|
||||
se.date_from = make_aware(datetime.combine(rdate, form.cleaned_data['time_from']), tz)
|
||||
se.date_to = (
|
||||
make_aware(datetime.combine(rdate, form.cleaned_data['time_to']), tz)
|
||||
if form.cleaned_data.get('time_to')
|
||||
else None
|
||||
)
|
||||
se.date_admission = (
|
||||
make_aware(datetime.combine(rdate, form.cleaned_data['time_admission']), tz)
|
||||
if form.cleaned_data.get('time_admission')
|
||||
else None
|
||||
)
|
||||
se.presale_start = (
|
||||
form.cleaned_data['rel_presale_start'].datetime(se)
|
||||
if form.cleaned_data.get('rel_presale_start')
|
||||
else None
|
||||
)
|
||||
se.presale_end = (
|
||||
form.cleaned_data['rel_presale_end'].datetime(se)
|
||||
if form.cleaned_data.get('rel_presale_end')
|
||||
else None
|
||||
)
|
||||
se.save()
|
||||
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)
|
||||
se.date_from = make_aware(datetime.combine(rdate, t['time_from']), tz)
|
||||
se.date_to = (
|
||||
make_aware(datetime.combine(rdate, t['time_to']), tz)
|
||||
if t.get('time_to')
|
||||
else None
|
||||
)
|
||||
se.date_admission = (
|
||||
make_aware(datetime.combine(rdate, t['time_admission']), tz)
|
||||
if t.get('time_admission')
|
||||
else None
|
||||
)
|
||||
se.presale_start = (
|
||||
form.cleaned_data['rel_presale_start'].datetime(se)
|
||||
if form.cleaned_data.get('rel_presale_start')
|
||||
else None
|
||||
)
|
||||
se.presale_end = (
|
||||
form.cleaned_data['rel_presale_end'].datetime(se)
|
||||
if form.cleaned_data.get('rel_presale_end')
|
||||
else None
|
||||
)
|
||||
se.save()
|
||||
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'):
|
||||
for f in self.meta_forms:
|
||||
if f.cleaned_data.get('value'):
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.save()
|
||||
|
||||
for f in self.formset.forms:
|
||||
if self.formset._should_delete_form(f):
|
||||
continue
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.event = se.event
|
||||
i.save()
|
||||
selected_items = set(list(self.request.event.items.filter(id__in=[
|
||||
i.split('-')[0] for i in f.cleaned_data.get('itemvars', [])
|
||||
])))
|
||||
selected_variations = list(ItemVariation.objects.filter(item__event=self.request.event, id__in=[
|
||||
i.split('-')[1] for i in f.cleaned_data.get('itemvars', []) if '-' in i
|
||||
]))
|
||||
i.items.add(*[_i for _i in selected_items])
|
||||
i.variations.add(*[_i for _i in selected_variations])
|
||||
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
change_data['id'] = i.pk
|
||||
i.log_action(action='pretix.event.quota.added', user=self.request.user, data=change_data)
|
||||
se.log_action('pretix.subevent.quota.added', user=self.request.user, data=change_data)
|
||||
|
||||
for f in self.cl_formset.forms:
|
||||
if self.cl_formset._should_delete_form(f):
|
||||
continue
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.event = se.event
|
||||
i.save()
|
||||
i.limit_products.add(*f.cleaned_data.get('limit_products', []))
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
change_data['id'] = i.pk
|
||||
i.log_action(action='pretix.event.checkinlist.added', user=self.request.user, data=change_data)
|
||||
|
||||
for f in self.itemvar_forms:
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.save()
|
||||
|
||||
for f in self.formset.forms:
|
||||
if self.formset._should_delete_form(f):
|
||||
continue
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.event = se.event
|
||||
i.save()
|
||||
selected_items = set(list(self.request.event.items.filter(id__in=[
|
||||
i.split('-')[0] for i in f.cleaned_data.get('itemvars', [])
|
||||
])))
|
||||
selected_variations = list(ItemVariation.objects.filter(item__event=self.request.event, id__in=[
|
||||
i.split('-')[1] for i in f.cleaned_data.get('itemvars', []) if '-' in i
|
||||
]))
|
||||
i.items.add(*[_i for _i in selected_items])
|
||||
i.variations.add(*[_i for _i in selected_variations])
|
||||
for f in self.plugin_forms:
|
||||
f.is_valid()
|
||||
f.subevent = se
|
||||
f.save()
|
||||
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
change_data['id'] = i.pk
|
||||
i.log_action(action='pretix.event.quota.added', user=self.request.user, data=change_data)
|
||||
se.log_action('pretix.subevent.quota.added', user=self.request.user, data=change_data)
|
||||
|
||||
for f in self.cl_formset.forms:
|
||||
if self.cl_formset._should_delete_form(f):
|
||||
continue
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.event = se.event
|
||||
i.save()
|
||||
i.limit_products.add(*f.cleaned_data.get('limit_products', []))
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
change_data['id'] = i.pk
|
||||
i.log_action(action='pretix.event.checkinlist.added', user=self.request.user, data=change_data)
|
||||
|
||||
for f in self.itemvar_forms:
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.save()
|
||||
|
||||
for f in self.plugin_forms:
|
||||
f.is_valid()
|
||||
f.subevent = se
|
||||
f.save()
|
||||
|
||||
cnt += 1
|
||||
cnt += 1
|
||||
|
||||
messages.success(self.request, pgettext_lazy('subevent', '{} new dates have been created.').format(cnt))
|
||||
return redirect(reverse('control:event.subevents', kwargs={
|
||||
|
||||
Reference in New Issue
Block a user