Various improvements to the subevent creation form (#1670)

This commit is contained in:
Raphael Michel
2020-05-06 15:50:43 +02:00
committed by GitHub
parent 094450564a
commit ea04c85486
5 changed files with 344 additions and 113 deletions

View File

@@ -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
)

View File

@@ -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 %}

View File

@@ -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={