mirror of
https://github.com/pretix/pretix.git
synced 2026-05-07 15:34:02 +00:00
Merge branch 'master' into bulk-select-with-drag-over
This commit is contained in:
@@ -11,7 +11,7 @@ from django.db.models import QuerySet
|
|||||||
from django.utils.formats import localize
|
from django.utils.formats import localize
|
||||||
from django.utils.translation import gettext, gettext_lazy as _
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
from openpyxl import Workbook
|
from openpyxl import Workbook
|
||||||
from openpyxl.cell.cell import KNOWN_TYPES, ILLEGAL_CHARACTERS_RE
|
from openpyxl.cell.cell import ILLEGAL_CHARACTERS_RE, KNOWN_TYPES
|
||||||
|
|
||||||
from pretix.base.models import Event
|
from pretix.base.models import Event
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from datetime import date, datetime, time
|
|||||||
from django.core.validators import MinLengthValidator, RegexValidator
|
from django.core.validators import MinLengthValidator, RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Exists, OuterRef, Q
|
from django.db.models import Exists, OuterRef, Q
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.crypto import get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.timezone import get_current_timezone, make_aware, now
|
from django.utils.timezone import get_current_timezone, make_aware, now
|
||||||
@@ -88,6 +89,15 @@ class Organizer(LoggedModel):
|
|||||||
|
|
||||||
return ObjectRelatedCache(self)
|
return ObjectRelatedCache(self)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def all_logentries_link(self):
|
||||||
|
return reverse(
|
||||||
|
'control:organizer.log',
|
||||||
|
kwargs={
|
||||||
|
'organizer': self.slug,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_gift_cards(self):
|
def has_gift_cards(self):
|
||||||
return self.cache.get_or_set(
|
return self.cache.get_or_set(
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import datetime, time
|
from datetime import datetime, time, timedelta
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
@@ -766,10 +766,15 @@ class SubEventFilterForm(FilterForm):
|
|||||||
),
|
),
|
||||||
required=False
|
required=False
|
||||||
)
|
)
|
||||||
date = forms.DateField(
|
date_from = forms.DateField(
|
||||||
label=_('Date'),
|
label=_('Date from'),
|
||||||
required=False,
|
required=False,
|
||||||
widget=DatePickerWidget
|
widget=DatePickerWidget,
|
||||||
|
)
|
||||||
|
date_until = forms.DateField(
|
||||||
|
label=_('Date until'),
|
||||||
|
required=False,
|
||||||
|
widget=DatePickerWidget,
|
||||||
)
|
)
|
||||||
weekday = forms.ChoiceField(
|
weekday = forms.ChoiceField(
|
||||||
label=_('Weekday'),
|
label=_('Weekday'),
|
||||||
@@ -796,7 +801,8 @@ class SubEventFilterForm(FilterForm):
|
|||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['date'].widget = DatePickerWidget()
|
self.fields['date_from'].widget = DatePickerWidget()
|
||||||
|
self.fields['date_until'].widget = DatePickerWidget()
|
||||||
|
|
||||||
def filter_qs(self, qs):
|
def filter_qs(self, qs):
|
||||||
fdata = self.cleaned_data
|
fdata = self.cleaned_data
|
||||||
@@ -838,19 +844,21 @@ class SubEventFilterForm(FilterForm):
|
|||||||
Q(name__icontains=i18ncomp(query)) | Q(location__icontains=query)
|
Q(name__icontains=i18ncomp(query)) | Q(location__icontains=query)
|
||||||
)
|
)
|
||||||
|
|
||||||
if fdata.get('date'):
|
if fdata.get('date_until'):
|
||||||
date_start = make_aware(datetime.combine(
|
date_end = make_aware(datetime.combine(
|
||||||
fdata.get('date'),
|
fdata.get('date_until') + timedelta(days=1),
|
||||||
time(hour=0, minute=0, second=0, microsecond=0)
|
time(hour=0, minute=0, second=0, microsecond=0)
|
||||||
), get_current_timezone())
|
), get_current_timezone())
|
||||||
date_end = make_aware(datetime.combine(
|
|
||||||
fdata.get('date'),
|
|
||||||
time(hour=23, minute=59, second=59, microsecond=999999)
|
|
||||||
), get_current_timezone())
|
|
||||||
qs = qs.filter(
|
qs = qs.filter(
|
||||||
Q(date_to__isnull=True, date_from__gte=date_start, date_from__lte=date_end) |
|
Q(date_to__isnull=True, date_from__lt=date_end) |
|
||||||
Q(date_to__isnull=False, date_from__lte=date_end, date_to__gte=date_start)
|
Q(date_to__isnull=False, date_to__lt=date_end)
|
||||||
)
|
)
|
||||||
|
if fdata.get('date_from'):
|
||||||
|
date_start = make_aware(datetime.combine(
|
||||||
|
fdata.get('date_from'),
|
||||||
|
time(hour=0, minute=0, second=0, microsecond=0)
|
||||||
|
), get_current_timezone())
|
||||||
|
qs = qs.filter(date_from__gte=date_start)
|
||||||
|
|
||||||
if fdata.get('ordering'):
|
if fdata.get('ordering'):
|
||||||
qs = qs.order_by(self.get_order_by())
|
qs = qs.order_by(self.get_order_by())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from bootstrap3.renderers import FieldRenderer
|
from bootstrap3.renderers import FieldRenderer, InlineFieldRenderer
|
||||||
from bootstrap3.text import text_value
|
from bootstrap3.text import text_value
|
||||||
from django.forms import CheckboxInput
|
from django.forms import CheckboxInput
|
||||||
from django.forms.utils import flatatt
|
from django.forms.utils import flatatt
|
||||||
@@ -58,3 +58,40 @@ class ControlFieldRenderer(FieldRenderer):
|
|||||||
optional=not required and not isinstance(self.widget, CheckboxInput)
|
optional=not required and not isinstance(self.widget, CheckboxInput)
|
||||||
) + html
|
) + html
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
class BulkEditMixin:
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs['layout'] = self.layout
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def wrap_field(self, html):
|
||||||
|
field_class = self.get_field_class()
|
||||||
|
name = '{}{}'.format(self.field.form.prefix, self.field.name)
|
||||||
|
checked = self.field.form.data and name in self.field.form.data.getlist('_bulk')
|
||||||
|
html = (
|
||||||
|
'<div class="{klass} bulk-edit-field-group">'
|
||||||
|
'<label class="field-toggle">'
|
||||||
|
'<input type="checkbox" name="_bulk" value="{name}" {checked}> {label}'
|
||||||
|
'</label>'
|
||||||
|
'<div class="field-content">'
|
||||||
|
'{html}'
|
||||||
|
'</div>'
|
||||||
|
'</div>'
|
||||||
|
).format(
|
||||||
|
klass=field_class or '',
|
||||||
|
name=name,
|
||||||
|
label=pgettext('form_bulk', 'change'),
|
||||||
|
checked='checked' if checked else '',
|
||||||
|
html=html
|
||||||
|
)
|
||||||
|
return html
|
||||||
|
|
||||||
|
|
||||||
|
class BulkEditFieldRenderer(BulkEditMixin, FieldRenderer):
|
||||||
|
layout = 'horizontal'
|
||||||
|
|
||||||
|
|
||||||
|
class InlineBulkEditFieldRenderer(BulkEditMixin, InlineFieldRenderer):
|
||||||
|
layout = 'inline'
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
from datetime import timedelta
|
from datetime import datetime, timedelta
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.forms import formset_factory
|
from django.forms import formset_factory
|
||||||
|
from django.forms.utils import ErrorDict
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.dates import MONTHS, WEEKDAYS
|
from django.utils.dates import MONTHS, WEEKDAYS
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@@ -11,6 +12,7 @@ from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
|||||||
from i18nfield.forms import I18nInlineFormSet
|
from i18nfield.forms import I18nInlineFormSet
|
||||||
|
|
||||||
from pretix.base.forms import I18nModelForm
|
from pretix.base.forms import I18nModelForm
|
||||||
|
from pretix.base.forms.widgets import DatePickerWidget, TimePickerWidget
|
||||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||||
from pretix.base.models.items import SubEventItem
|
from pretix.base.models.items import SubEventItem
|
||||||
from pretix.base.reldate import RelativeDateTimeField
|
from pretix.base.reldate import RelativeDateTimeField
|
||||||
@@ -88,6 +90,142 @@ class SubEventBulkForm(SubEventForm):
|
|||||||
del self.fields['date_admission']
|
del self.fields['date_admission']
|
||||||
|
|
||||||
|
|
||||||
|
class NullBooleanSelect(forms.NullBooleanSelect):
|
||||||
|
def __init__(self, attrs=None):
|
||||||
|
choices = (
|
||||||
|
('unknown', _('Keep the current values')),
|
||||||
|
('true', _('Yes')),
|
||||||
|
('false', _('No')),
|
||||||
|
)
|
||||||
|
super(forms.NullBooleanSelect, self).__init__(attrs, choices)
|
||||||
|
|
||||||
|
|
||||||
|
class SubEventBulkEditForm(I18nModelForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.mixed_values = kwargs.pop('mixed_values')
|
||||||
|
self.queryset = kwargs.pop('queryset')
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.fields['location'].widget.attrs['rows'] = '3'
|
||||||
|
|
||||||
|
for k in ('name', 'location', 'frontpage_text'):
|
||||||
|
# i18n fields
|
||||||
|
if k in self.mixed_values:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
|
||||||
|
else:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = ''
|
||||||
|
self.fields[k].one_required = False
|
||||||
|
|
||||||
|
for k in ('geo_lat', 'geo_lon'):
|
||||||
|
# scalar fields
|
||||||
|
if k in self.mixed_values:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = '[{}]'.format(_('Selection contains various values'))
|
||||||
|
else:
|
||||||
|
self.fields[k].widget.attrs['placeholder'] = ''
|
||||||
|
self.fields[k].widget.is_required = False
|
||||||
|
self.fields[k].required = False
|
||||||
|
|
||||||
|
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end'):
|
||||||
|
self.fields[k + '_day'] = forms.DateField(
|
||||||
|
label=self._meta.model._meta.get_field(k).verbose_name,
|
||||||
|
help_text=self._meta.model._meta.get_field(k).help_text,
|
||||||
|
widget=DatePickerWidget(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
self.fields[k + '_time'] = forms.TimeField(
|
||||||
|
label=self._meta.model._meta.get_field(k).verbose_name,
|
||||||
|
help_text=self._meta.model._meta.get_field(k).help_text,
|
||||||
|
widget=TimePickerWidget(),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = SubEvent
|
||||||
|
localized_fields = '__all__'
|
||||||
|
fields = [
|
||||||
|
'name',
|
||||||
|
'location',
|
||||||
|
'frontpage_text',
|
||||||
|
'geo_lat',
|
||||||
|
'geo_lon',
|
||||||
|
'is_public',
|
||||||
|
'active',
|
||||||
|
]
|
||||||
|
field_classes = {
|
||||||
|
}
|
||||||
|
widgets = {
|
||||||
|
}
|
||||||
|
|
||||||
|
def save(self, commit=True):
|
||||||
|
objs = list(self.queryset)
|
||||||
|
fields = set()
|
||||||
|
|
||||||
|
check_map = {
|
||||||
|
'geo_lat': '__geo',
|
||||||
|
'geo_lon': '__geo',
|
||||||
|
}
|
||||||
|
for k in self.fields:
|
||||||
|
cb_val = self.prefix + check_map.get(k, k)
|
||||||
|
if cb_val not in self.data.getlist('_bulk'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if k.endswith('_day'):
|
||||||
|
for obj in objs:
|
||||||
|
oldval = getattr(obj, k.replace('_day', ''))
|
||||||
|
cval = self.cleaned_data[k]
|
||||||
|
if cval is None:
|
||||||
|
newval = None
|
||||||
|
if not self._meta.model._meta.get_field(k.replace('_day', '')).null:
|
||||||
|
continue
|
||||||
|
elif oldval:
|
||||||
|
oldval = oldval.astimezone(self.event.timezone)
|
||||||
|
newval = oldval.replace(
|
||||||
|
year=cval.year,
|
||||||
|
month=cval.month,
|
||||||
|
day=cval.day,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# If there is no previous date/time set, we'll just set to midnight
|
||||||
|
# If the user also selected a time, this will be overridden anyways
|
||||||
|
newval = datetime(
|
||||||
|
year=cval.year,
|
||||||
|
month=cval.month,
|
||||||
|
day=cval.day,
|
||||||
|
tzinfo=self.event.timezone
|
||||||
|
)
|
||||||
|
setattr(obj, k.replace('_day', ''), newval)
|
||||||
|
fields.add(k.replace('_day', ''))
|
||||||
|
elif k.endswith('_time'):
|
||||||
|
for obj in objs:
|
||||||
|
# If there is no previous date/time set and only a time is changed not the
|
||||||
|
# date, we instead use the date of the event
|
||||||
|
oldval = getattr(obj, k.replace('_time', '')) or obj.date_from
|
||||||
|
cval = self.cleaned_data[k]
|
||||||
|
if cval is None:
|
||||||
|
continue
|
||||||
|
oldval = oldval.astimezone(self.event.timezone)
|
||||||
|
newval = oldval.replace(
|
||||||
|
hour=cval.hour,
|
||||||
|
minute=cval.minute,
|
||||||
|
second=cval.second,
|
||||||
|
)
|
||||||
|
setattr(obj, k.replace('_time', ''), newval)
|
||||||
|
fields.add(k.replace('_time', ''))
|
||||||
|
else:
|
||||||
|
fields.add(k)
|
||||||
|
for obj in objs:
|
||||||
|
setattr(obj, k, self.cleaned_data[k])
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
SubEvent.objects.bulk_update(objs, fields, 200)
|
||||||
|
|
||||||
|
def full_clean(self):
|
||||||
|
if len(self.data) == 0:
|
||||||
|
# form wasn't submitted
|
||||||
|
self._errors = ErrorDict()
|
||||||
|
return
|
||||||
|
super().full_clean()
|
||||||
|
|
||||||
|
|
||||||
class SubEventItemOrVariationFormMixin:
|
class SubEventItemOrVariationFormMixin:
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.item = kwargs.pop('item')
|
self.item = kwargs.pop('item')
|
||||||
@@ -162,7 +300,7 @@ class SubEventMetaValueForm(forms.ModelForm):
|
|||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.property = kwargs.pop('property')
|
self.property = kwargs.pop('property')
|
||||||
self.default = kwargs.pop('default', None)
|
self.default = kwargs.pop('default', None)
|
||||||
self.disabled = kwargs.pop('disabled')
|
self.disabled = kwargs.pop('disabled', False)
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
if self.property.allowed_values:
|
if self.property.allowed_values:
|
||||||
self.fields['value'] = forms.ChoiceField(
|
self.fields['value'] = forms.ChoiceField(
|
||||||
|
|||||||
@@ -273,8 +273,15 @@ def _display_checkin(event, logentry):
|
|||||||
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
|
||||||
plains = {
|
plains = {
|
||||||
'pretix.object.cloned': _('This object has been created by cloning.'),
|
'pretix.object.cloned': _('This object has been created by cloning.'),
|
||||||
|
'pretix.organizer.changed': _('The organizer has been changed.'),
|
||||||
|
'pretix.organizer.settings': _('The organizer settings have been changed.'),
|
||||||
|
'pretix.giftcards.acceptance.added': _('Gift card acceptance for another organizer has been added.'),
|
||||||
|
'pretix.giftcards.acceptance.removed': _('Gift card acceptance for another organizer has been removed.'),
|
||||||
|
'pretix.webhook.created': _('The webhook has been created.'),
|
||||||
|
'pretix.webhook.changed': _('The webhook has been changed.'),
|
||||||
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
'pretix.event.comment': _('The event\'s internal comment has been updated.'),
|
||||||
'pretix.event.canceled': _('The event has been canceled.'),
|
'pretix.event.canceled': _('The event has been canceled.'),
|
||||||
|
'pretix.event.deleted': _('An event has been deleted.'),
|
||||||
'pretix.event.order.modified': _('The order details have been changed.'),
|
'pretix.event.order.modified': _('The order details have been changed.'),
|
||||||
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
'pretix.event.order.unpaid': _('The order has been marked as unpaid.'),
|
||||||
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
'pretix.event.order.secret.changed': _('The order\'s secret has been changed.'),
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
<form method="post" href="">
|
<form method="post" href=""
|
||||||
|
>
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<fieldset class="form-inline form-refund-choose">
|
<fieldset class="form-inline form-refund-choose">
|
||||||
<legend>{% trans "How should the refund be sent?" %}</legend>
|
<legend>{% trans "How should the refund be sent?" %}</legend>
|
||||||
|
|||||||
@@ -22,54 +22,68 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% bootstrap_form_errors sform %}
|
{% bootstrap_form_errors sform %}
|
||||||
{% bootstrap_form_errors form %}
|
{% bootstrap_form_errors form %}
|
||||||
<div class="tabbed-form">
|
<div class="row">
|
||||||
<fieldset>
|
<div class="col-xs-12 col-lg-10">
|
||||||
<legend>{% trans "General" %}</legend>
|
<div class="tabbed-form">
|
||||||
{% bootstrap_field form.name layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field form.slug layout="control" %}
|
<legend>{% trans "General" %}</legend>
|
||||||
{% if form.domain %}
|
{% bootstrap_field form.name layout="control" %}
|
||||||
{% bootstrap_field form.domain layout="control" %}
|
{% bootstrap_field form.slug layout="control" %}
|
||||||
{% endif %}
|
{% if form.domain %}
|
||||||
{% bootstrap_field sform.imprint_url layout="control" %}
|
{% bootstrap_field form.domain layout="control" %}
|
||||||
{% bootstrap_field sform.contact_mail layout="control" %}
|
{% endif %}
|
||||||
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
{% bootstrap_field sform.imprint_url layout="control" %}
|
||||||
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
{% bootstrap_field sform.contact_mail layout="control" %}
|
||||||
</fieldset>
|
{% bootstrap_field sform.organizer_info_text layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.event_team_provisioning layout="control" %}
|
||||||
<legend>{% trans "Organizer page" %}</legend>
|
</fieldset>
|
||||||
{% bootstrap_field sform.organizer_logo_image layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field sform.organizer_logo_image_large layout="control" %}
|
<legend>{% trans "Organizer page" %}</legend>
|
||||||
{% bootstrap_field sform.organizer_homepage_text layout="control" %}
|
{% bootstrap_field sform.organizer_logo_image layout="control" %}
|
||||||
{% bootstrap_field sform.event_list_type layout="control" %}
|
{% bootstrap_field sform.organizer_logo_image_large layout="control" %}
|
||||||
{% bootstrap_field sform.event_list_availability layout="control" %}
|
{% bootstrap_field sform.organizer_homepage_text layout="control" %}
|
||||||
{% bootstrap_field sform.organizer_link_back layout="control" %}
|
{% bootstrap_field sform.event_list_type layout="control" %}
|
||||||
</fieldset>
|
{% bootstrap_field sform.event_list_availability layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.organizer_link_back layout="control" %}
|
||||||
<legend>{% trans "Localization" %}</legend>
|
</fieldset>
|
||||||
{% bootstrap_field sform.locales layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field sform.region layout="control" %}
|
<legend>{% trans "Localization" %}</legend>
|
||||||
</fieldset>
|
{% bootstrap_field sform.locales layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.region layout="control" %}
|
||||||
<legend>{% trans "Shop design" %}</legend>
|
</fieldset>
|
||||||
<p class="help-block">
|
<fieldset>
|
||||||
{% blocktrans trimmed %}
|
<legend>{% trans "Shop design" %}</legend>
|
||||||
These settings will be used for the organizer page as well as for the default settings
|
<p class="help-block">
|
||||||
for all events in this account that do not have their own design settings.
|
{% blocktrans trimmed %}
|
||||||
{% endblocktrans %}
|
These settings will be used for the organizer page as well as for the default settings
|
||||||
</p>
|
for all events in this account that do not have their own design settings.
|
||||||
{% bootstrap_field sform.primary_color layout="control" %}
|
{% endblocktrans %}
|
||||||
{% bootstrap_field sform.theme_color_success layout="control" %}
|
</p>
|
||||||
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
{% bootstrap_field sform.primary_color layout="control" %}
|
||||||
{% bootstrap_field sform.theme_color_background layout="control" %}
|
{% bootstrap_field sform.theme_color_success layout="control" %}
|
||||||
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
{% bootstrap_field sform.theme_color_danger layout="control" %}
|
||||||
{% bootstrap_field sform.primary_font layout="control" %}
|
{% bootstrap_field sform.theme_color_background layout="control" %}
|
||||||
{% bootstrap_field sform.favicon layout="control" %}
|
{% bootstrap_field sform.theme_round_borders layout="control" %}
|
||||||
</fieldset>
|
{% bootstrap_field sform.primary_font layout="control" %}
|
||||||
<fieldset>
|
{% bootstrap_field sform.favicon layout="control" %}
|
||||||
<legend>{% trans "Gift cards" %}</legend>
|
</fieldset>
|
||||||
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
|
<fieldset>
|
||||||
{% bootstrap_field sform.giftcard_length layout="control" %}
|
<legend>{% trans "Gift cards" %}</legend>
|
||||||
</fieldset>
|
{% bootstrap_field sform.giftcard_expiry_years layout="control" %}
|
||||||
|
{% bootstrap_field sform.giftcard_length layout="control" %}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-lg-2">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">
|
||||||
|
{% trans "Change history" %}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
{% include "pretixcontrol/includes/logs.html" with obj=organizer %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group submit-group">
|
<div class="form-group submit-group">
|
||||||
<button type="submit" class="btn btn-primary btn-save">
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{% extends "pretixcontrol/items/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
|
{% block title %}{% trans "Organizer logs" %}{% endblock %}
|
||||||
|
{% block inside %}
|
||||||
|
<h1>{% trans "Organizer logs" %}</h1>
|
||||||
|
<form class="form-inline helper-display-inline" action="" method="get">
|
||||||
|
<input type="hidden" name="content_type" value="{{ request.GET.content_type }}">
|
||||||
|
<input type="hidden" name="object" value="{{ request.GET.object }}">
|
||||||
|
<p>
|
||||||
|
<select name="user" class="form-control">
|
||||||
|
<option value="">{% trans "All actions" %}</option>
|
||||||
|
{% for up in userlist %}
|
||||||
|
{% if up.user__id %}
|
||||||
|
<option value="{{ up.user__id }}"
|
||||||
|
{% if request.GET.user == up.user__id %}selected="selected"{% endif %}>
|
||||||
|
{{ up.user__email }}
|
||||||
|
</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
|
||||||
|
</p>
|
||||||
|
</form>
|
||||||
|
<ul class="list-group">
|
||||||
|
{% for log in logs %}
|
||||||
|
<li class="list-group-item logentry">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-2 col-sm-6 col-xs-12">
|
||||||
|
<span class="fa fa-clock-o"></span>
|
||||||
|
{{ log.datetime|date:"SHORT_DATETIME_FORMAT" }}
|
||||||
|
{% if log.shredded %}
|
||||||
|
<span class="fa fa-eraser fa-danger fa-fw"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{% trans "Personal data was cleared from this log entry." %}">
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 col-sm-6 col-xs-12">
|
||||||
|
{% if log.user %}
|
||||||
|
{% if log.user.is_staff %}
|
||||||
|
<span class="fa fa-id-card fa-danger fa-fw"
|
||||||
|
data-toggle="tooltip"
|
||||||
|
title="{% trans "This change was performed by a pretix administrator." %}">
|
||||||
|
</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="fa fa-user fa-fw"></span>
|
||||||
|
{% endif %}
|
||||||
|
{{ log.user.get_full_name }}
|
||||||
|
{% if log.oauth_application %}
|
||||||
|
<br><span class="fa fa-plug fa-fw"></span>
|
||||||
|
{{ log.oauth_application.name }}
|
||||||
|
{% endif %}
|
||||||
|
{% elif log.device %}
|
||||||
|
<span class="fa fa-mobile fa-fw"></span>
|
||||||
|
{{ log.device.name }}
|
||||||
|
{% elif log.api_token %}
|
||||||
|
<span class="fa fa-key fa-fw"></span>
|
||||||
|
{{ log.api_token.name }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-2 col-sm-12 col-xs-12">
|
||||||
|
{% if log.display_object %}
|
||||||
|
<span class="fa fa-flag"></span> {{ log.display_object|safe }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6 col-sm-12 col-xs-12">
|
||||||
|
{{ log.display }}
|
||||||
|
{% if staff_session %}
|
||||||
|
<a href="" class="btn btn-default btn-xs" data-expandlogs data-id="{{ log.pk }}">
|
||||||
|
<span class="fa-eye fa fa-fw"></span>
|
||||||
|
{% trans "Inspect" %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% empty %}
|
||||||
|
<div class="list-group-item">
|
||||||
|
<em>{% trans "No results" %}</em>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% include "pretixcontrol/pagination.html" %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,351 @@
|
|||||||
|
{% extends "pretixcontrol/event/base.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load formset_tags %}
|
||||||
|
{% load captureas %}
|
||||||
|
{% load static %}
|
||||||
|
{% load eventsignal %}
|
||||||
|
{% block title %}{% trans "Change multiple dates" context "subevent" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>
|
||||||
|
{% trans "Change multiple dates" context "subevent" %}
|
||||||
|
<small>
|
||||||
|
{% blocktrans trimmed with number=subevents.count %}
|
||||||
|
{{ number }} selected
|
||||||
|
{% endblocktrans %}
|
||||||
|
</small>
|
||||||
|
</h1>
|
||||||
|
<form action="" method="post" class="form-horizontal" id="subevent-bulk-create-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
{% for f in itemvar_forms %}
|
||||||
|
{% bootstrap_form_errors f %}
|
||||||
|
{% endfor %}
|
||||||
|
<div class="hidden">
|
||||||
|
{% for se in subevents %}
|
||||||
|
<input type="hidden" name="subevent" value="{{ se.pk }}">
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "General information" %}</legend>
|
||||||
|
{% bootstrap_field form.name layout="bulkedit" %}
|
||||||
|
{% bootstrap_field form.active layout="bulkedit" %}
|
||||||
|
<div class="geodata-section">
|
||||||
|
{% bootstrap_field form.location layout="bulkedit" %}
|
||||||
|
<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" %}">
|
||||||
|
<label class="col-md-3 control-label">
|
||||||
|
{% trans "Geo coordinates" %}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<div class="bulk-edit-field-group">
|
||||||
|
<label class="field-toggle">
|
||||||
|
<input type="checkbox" name="_bulk" value="{{ form.prefix }}__geo" {% if form.prefix|add:"__geo" in bulk_selected %}checked{% endif %}>
|
||||||
|
{% trans "change" context "form_bulk" %}
|
||||||
|
</label>
|
||||||
|
<div class="field-content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_field form.geo_lat layout="inline" %}
|
||||||
|
{% if global_settings.opencagedata_apikey %}
|
||||||
|
<p class="attrib">
|
||||||
|
<a href="https://openstreetmap.org/" target="_blank" tabindex="-1">
|
||||||
|
{% trans "Geocoding data © OpenStreetMap" %}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_field form.geo_lon layout="inline" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% bootstrap_field form.frontpage_text layout="bulkedit" %}
|
||||||
|
{% bootstrap_field form.is_public layout="bulkedit" %}
|
||||||
|
{% if meta_forms %}
|
||||||
|
<div class="form-group metadata-group">
|
||||||
|
<label class="col-md-3 control-label">{% trans "Meta data" %}</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
{% for form in meta_forms %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label for="{{ form.value.id_for_label }}">
|
||||||
|
{{ form.property.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-8">
|
||||||
|
{% bootstrap_form form layout="bulkedit_inline" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Timeline" %}</legend>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.date_from_day.id_for_label }}">
|
||||||
|
{{ form.date_from_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.date_from_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.date_from_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.date_to_day.id_for_label }}">
|
||||||
|
{{ form.date_to_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.date_to_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.date_to_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.date_admission_day.id_for_label }}">
|
||||||
|
{{ form.date_admission_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.date_admission_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.date_admission_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.presale_start_day.id_for_label }}">
|
||||||
|
{{ form.presale_start_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.presale_start_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.presale_start_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="{{ form.presale_end_day.id_for_label }}">
|
||||||
|
{{ form.presale_end_day.label }}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-5">
|
||||||
|
{% bootstrap_field form.presale_end_day layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
{% bootstrap_field form.presale_end_time layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Item prices" %}</legend>
|
||||||
|
{% for f in itemvar_forms %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label" for="id_{{ f.prefix }}-price">
|
||||||
|
{% if f.variation %}{{ f.item }} – {{ f.variation }}{% else %}{{ f.item }}{% endif %}
|
||||||
|
</label>
|
||||||
|
<div class="col-md-6">
|
||||||
|
{% bootstrap_field f.price addon_after=request.event.currency form_group_class="" layout="bulkedit_inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
{% bootstrap_field f.disabled layout="bulkedit_inline" form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Quotas" %}</legend>
|
||||||
|
{% if sampled_quotas|default_if_none:"NONE" == "NONE" %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You selected a set of dates that currently have different quota setups. You can therefore
|
||||||
|
not change their quotas in bulk. If you want, you can set up a new set of quotas to
|
||||||
|
<strong>replace</strong> the quota setup of all selected dates.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
<div class="bulk-edit-field-group">
|
||||||
|
<label class="field-toggle">
|
||||||
|
<input type="checkbox" name="_bulk" value="__quotas" {% if "__quotas" in bulk_selected %}checked{% endif %}>
|
||||||
|
{% trans "change" context "form_bulk" %}
|
||||||
|
</label>
|
||||||
|
<div class="field-content">
|
||||||
|
<div class="formset" data-formset data-formset-prefix="{{ formset.prefix }}">
|
||||||
|
{{ formset.management_form }}
|
||||||
|
{% bootstrap_formset_errors formset %}
|
||||||
|
<div data-formset-body>
|
||||||
|
{% for form in formset %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ form.id }}
|
||||||
|
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
{% bootstrap_field form.size layout="control" %}
|
||||||
|
{% bootstrap_field form.itemvars layout="control" %}
|
||||||
|
{% bootstrap_field form.release_after_exit layout="control" %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script type="form-template" data-formset-empty-form>
|
||||||
|
{% escapescript %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ formset.empty_form.id }}
|
||||||
|
{% bootstrap_field formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field formset.empty_form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_field formset.empty_form.size layout="control" %}
|
||||||
|
{% bootstrap_field formset.empty_form.itemvars layout="control" %}
|
||||||
|
{% bootstrap_field formset.empty_form.release_after_exit layout="control" %}
|
||||||
|
</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 quota" %}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<p> </p>
|
||||||
|
<fieldset>
|
||||||
|
<legend>{% trans "Check-in lists" %}</legend>
|
||||||
|
{% if sampled_lists|default_if_none:"NONE" == "NONE" %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
{% blocktrans trimmed %}
|
||||||
|
You selected a set of dates that currently have different check-in list setups. You can
|
||||||
|
therefore not change their check-in lists in bulk.
|
||||||
|
{% endblocktrans %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="bulk-edit-field-group">
|
||||||
|
<label class="field-toggle">
|
||||||
|
<input type="checkbox" name="_bulk" value="__checkinlists" {% if "__checkinlists" in bulk_selected %}checked{% endif %}>
|
||||||
|
{% trans "change" context "form_bulk" %}
|
||||||
|
</label>
|
||||||
|
<div class="field-content">
|
||||||
|
<div class="formset" data-formset data-formset-prefix="{{ cl_formset.prefix }}">
|
||||||
|
{{ cl_formset.management_form }}
|
||||||
|
{% bootstrap_formset_errors cl_formset %}
|
||||||
|
<div data-formset-body>
|
||||||
|
{% for form in cl_formset %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ form.id }}
|
||||||
|
{% bootstrap_field form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_form_errors form %}
|
||||||
|
{% bootstrap_field form.include_pending layout="control" %}
|
||||||
|
{% bootstrap_field form.all_products layout="control" %}
|
||||||
|
{% bootstrap_field form.limit_products layout="control" %}
|
||||||
|
{% bootstrap_field form.allow_entry_after_exit layout="control" %}
|
||||||
|
{% if form.gates %}
|
||||||
|
{% bootstrap_field form.gates layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<script type="form-template" data-formset-empty-form>
|
||||||
|
{% escapescript %}
|
||||||
|
<div class="panel panel-default" data-formset-form>
|
||||||
|
<div class="sr-only">
|
||||||
|
{{ cl_formset.empty_form.id }}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||||
|
</div>
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h4 class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
{% bootstrap_field cl_formset.empty_form.name layout='inline' form_group_class="" %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 text-right flip">
|
||||||
|
<button type="button" class="btn btn-danger" data-formset-delete-button>
|
||||||
|
<i class="fa fa-trash"></i></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body form-horizontal">
|
||||||
|
{% bootstrap_field cl_formset.empty_form.include_pending layout="control" %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.all_products layout="control" %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.limit_products layout="control" %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.allow_entry_after_exit layout="control" %}
|
||||||
|
{% if cl_formset.empty_form.gates %}
|
||||||
|
{% bootstrap_field cl_formset.empty_form.gates layout="control" %}
|
||||||
|
{% endif %}
|
||||||
|
</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 check-in list" %}
|
||||||
|
</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</fieldset>
|
||||||
|
<div class="form-group submit-group">
|
||||||
|
<button type="submit" class="btn btn-primary btn-save">
|
||||||
|
{% trans "Save" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
@@ -22,14 +22,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form class="row filter-form" action="" method="get">
|
<form class="row filter-form" action="" method="get">
|
||||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.query layout='inline' %}
|
{% bootstrap_field filter_form.query layout='inline' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-3 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.status layout='inline' %}
|
{% bootstrap_field filter_form.status layout='inline' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.date layout='inline' %}
|
{% bootstrap_field filter_form.date_from layout='inline' %}
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
|
{% bootstrap_field filter_form.date_until layout='inline' %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 col-sm-6 col-xs-12">
|
<div class="col-md-2 col-sm-6 col-xs-12">
|
||||||
{% bootstrap_field filter_form.weekday layout='inline' %}
|
{% bootstrap_field filter_form.weekday layout='inline' %}
|
||||||
@@ -43,16 +46,21 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<p>
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
<p>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
{% trans "Create a new date" context "subevent" %}</a>
|
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||||
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
|
{% trans "Create a new date" context "subevent" %}</a>
|
||||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
{% trans "Create many new dates" context "subevent" %}</a>
|
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||||
</p>
|
{% trans "Create many new dates" context "subevent" %}</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
<form action="{% url "control:event.subevents.bulkaction" organizer=request.event.organizer.slug event=request.event.slug %}" method="post">
|
<form action="{% url "control:event.subevents.bulkaction" organizer=request.event.organizer.slug event=request.event.slug %}" method="post">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
<div class="hidden">
|
||||||
|
{{ filter_form.as_p }}
|
||||||
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-hover table-quotas">
|
<table class="table table-hover table-quotas">
|
||||||
<thead>
|
<thead>
|
||||||
@@ -67,21 +75,33 @@
|
|||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Begin" %}
|
{% trans "Begin" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' 'date_from' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Paid tickets per quota" %}
|
{% trans "Paid tickets per quota" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' '-sum_tickets_paid' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' 'sum_tickets_paid' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th>
|
||||||
{% trans "Status" %}
|
{% trans "Status" %}
|
||||||
<a href="?{% url_replace request 'ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' '-active' %}"><i class="fa fa-caret-down"></i></a>
|
||||||
<a href="?{% url_replace request 'ordering' 'active' %}"><i class="fa fa-caret-up"></i></a>
|
<a href="?{% url_replace request 'filter-ordering' 'active' %}"><i class="fa fa-caret-up"></i></a>
|
||||||
</th>
|
</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
{% if "can_change_event_settings" in request.eventpermset %}
|
||||||
|
<tr class="table-select-all warning hidden">
|
||||||
|
<td>
|
||||||
|
<input type="checkbox" name="__ALL" id="__all">
|
||||||
|
</td>
|
||||||
|
<td colspan="5">
|
||||||
|
<label for="__all">
|
||||||
|
{% trans "Select all results on other pages as well" %}
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for s in subevents %}
|
{% for s in subevents %}
|
||||||
@@ -150,6 +170,10 @@
|
|||||||
<button type="submit" class="btn btn-default btn-save" name="action" value="delete">
|
<button type="submit" class="btn btn-default btn-save" name="action" value="delete">
|
||||||
{% trans "Delete selected" %}
|
{% trans "Delete selected" %}
|
||||||
</button>
|
</button>
|
||||||
|
<button type="submit" class="btn btn-default btn-save" name="action" value="disable"
|
||||||
|
formaction="{% url "control:event.subevents.bulkedit" organizer=request.event.organizer.slug event=request.event.slug %}">
|
||||||
|
{% trans "Change selected" %}
|
||||||
|
</button>
|
||||||
<button type="submit" class="btn btn-default btn-save" name="action" value="enable">
|
<button type="submit" class="btn btn-default btn-save" name="action" value="enable">
|
||||||
{% trans "Enable selected" %}
|
{% trans "Enable selected" %}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ urlpatterns = [
|
|||||||
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
|
url(r'^organizer/(?P<organizer>[^/]+)/team/(?P<team>[^/]+)/delete$', organizer.TeamDeleteView.as_view(),
|
||||||
name='organizer.team.delete'),
|
name='organizer.team.delete'),
|
||||||
url(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
|
url(r'^organizer/(?P<organizer>[^/]+)/slugrng', main.SlugRNG.as_view(), name='events.add.slugrng'),
|
||||||
|
url(r'^organizer/(?P<organizer>[^/]+)/logs', organizer.LogView.as_view(), name='organizer.log'),
|
||||||
url(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
|
url(r'^organizer/(?P<organizer>[^/]+)/export/$', organizer.ExportView.as_view(), name='organizer.export'),
|
||||||
url(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'),
|
url(r'^organizer/(?P<organizer>[^/]+)/export/do$', organizer.ExportDoView.as_view(), name='organizer.export.do'),
|
||||||
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
|
url(r'^nav/typeahead/$', typeahead.nav_context_list, name='nav.typeahead'),
|
||||||
@@ -173,6 +174,7 @@ urlpatterns = [
|
|||||||
url(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
|
url(r'^subevents/add$', subevents.SubEventCreate.as_view(), name='event.subevents.add'),
|
||||||
url(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
|
url(r'^subevents/bulk_add$', subevents.SubEventBulkCreate.as_view(), name='event.subevents.bulk'),
|
||||||
url(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
|
url(r'^subevents/bulk_action$', subevents.SubEventBulkAction.as_view(), name='event.subevents.bulkaction'),
|
||||||
|
url(r'^subevents/bulk_edit$', subevents.SubEventBulkEdit.as_view(), name='event.subevents.bulkedit'),
|
||||||
url(r'^items/$', item.ItemList.as_view(), name='event.items'),
|
url(r'^items/$', item.ItemList.as_view(), name='event.items'),
|
||||||
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
url(r'^items/add$', item.ItemCreate.as_view(), name='event.items.add'),
|
||||||
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
url(r'^items/(?P<item>\d+)/$', item.ItemUpdateGeneral.as_view(), name='event.item'),
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from pretix.control.forms.organizer import (
|
|||||||
GiftCardUpdateForm, OrganizerDeleteForm, OrganizerForm,
|
GiftCardUpdateForm, OrganizerDeleteForm, OrganizerForm,
|
||||||
OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, WebHookForm,
|
OrganizerSettingsForm, OrganizerUpdateForm, TeamForm, WebHookForm,
|
||||||
)
|
)
|
||||||
|
from pretix.control.logdisplay import OVERVIEW_BANLIST
|
||||||
from pretix.control.permissions import (
|
from pretix.control.permissions import (
|
||||||
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
|
AdministratorPermissionRequiredMixin, OrganizerPermissionRequiredMixin,
|
||||||
)
|
)
|
||||||
@@ -1434,3 +1435,24 @@ class EventMetaPropertyDeleteView(OrganizerDetailViewMixin, OrganizerPermissionR
|
|||||||
self.object.delete()
|
self.object.delete()
|
||||||
messages.success(request, _('The selected property has been deleted.'))
|
messages.success(request, _('The selected property has been deleted.'))
|
||||||
return redirect(success_url)
|
return redirect(success_url)
|
||||||
|
|
||||||
|
|
||||||
|
class LogView(OrganizerPermissionRequiredMixin, ListView):
|
||||||
|
template_name = 'pretixcontrol/organizers/logs.html'
|
||||||
|
permission = 'can_change_organizer_settings'
|
||||||
|
model = LogEntry
|
||||||
|
context_object_name = 'logs'
|
||||||
|
paginate_by = 20
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
qs = self.request.organizer.all_logentries().select_related(
|
||||||
|
'user', 'content_type', 'api_token', 'oauth_application', 'device'
|
||||||
|
).order_by('-datetime')
|
||||||
|
qs = qs.exclude(action_type__in=OVERVIEW_BANLIST)
|
||||||
|
if self.request.GET.get('user'):
|
||||||
|
qs = qs.filter(user_id=self.request.GET.get('user'))
|
||||||
|
return qs
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data()
|
||||||
|
return ctx
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import copy
|
import copy
|
||||||
|
from collections import defaultdict
|
||||||
from datetime import datetime, time, timedelta
|
from datetime import datetime, time, timedelta
|
||||||
|
|
||||||
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
|
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.files import File
|
from django.core.files import File
|
||||||
from django.db import connections, transaction
|
from django.db import connections, transaction
|
||||||
from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
|
from django.db.models import (
|
||||||
from django.db.models.functions import Coalesce
|
Count, F, IntegerField, OuterRef, Prefetch, Subquery, Sum,
|
||||||
|
)
|
||||||
|
from django.db.models.functions import Coalesce, TruncDate, TruncTime
|
||||||
from django.forms import inlineformset_factory
|
from django.forms import inlineformset_factory
|
||||||
from django.http import Http404, HttpResponseRedirect
|
from django.http import Http404, HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.formats import get_format
|
from django.utils.formats import get_format
|
||||||
@@ -16,7 +19,9 @@ from django.utils.functional import cached_property
|
|||||||
from django.utils.timezone import make_aware
|
from django.utils.timezone import make_aware
|
||||||
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
from django.utils.translation import gettext_lazy as _, pgettext_lazy
|
||||||
from django.views import View
|
from django.views import View
|
||||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
from django.views.generic import (
|
||||||
|
CreateView, DeleteView, FormView, ListView, UpdateView,
|
||||||
|
)
|
||||||
|
|
||||||
from pretix.base.models import CartPosition, LogEntry
|
from pretix.base.models import CartPosition, LogEntry
|
||||||
from pretix.base.models.checkin import CheckinList
|
from pretix.base.models.checkin import CheckinList
|
||||||
@@ -31,24 +36,27 @@ from pretix.control.forms.checkin import SimpleCheckinListForm
|
|||||||
from pretix.control.forms.filter import SubEventFilterForm
|
from pretix.control.forms.filter import SubEventFilterForm
|
||||||
from pretix.control.forms.item import QuotaForm
|
from pretix.control.forms.item import QuotaForm
|
||||||
from pretix.control.forms.subevents import (
|
from pretix.control.forms.subevents import (
|
||||||
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkForm,
|
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkEditForm,
|
||||||
SubEventForm, SubEventItemForm, SubEventItemVariationForm,
|
SubEventBulkForm, SubEventForm, SubEventItemForm,
|
||||||
SubEventMetaValueForm, TimeFormSet,
|
SubEventItemVariationForm, SubEventMetaValueForm, TimeFormSet,
|
||||||
)
|
)
|
||||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
from pretix.control.signals import subevent_forms
|
from pretix.control.signals import subevent_forms
|
||||||
from pretix.control.views import PaginationMixin
|
from pretix.control.views import PaginationMixin
|
||||||
from pretix.control.views.event import MetaDataEditorMixin
|
from pretix.control.views.event import MetaDataEditorMixin
|
||||||
|
from pretix.helpers import GroupConcat
|
||||||
from pretix.helpers.models import modelcopy
|
from pretix.helpers.models import modelcopy
|
||||||
|
|
||||||
|
|
||||||
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
class SubEventQueryMixin:
|
||||||
model = SubEvent
|
|
||||||
context_object_name = 'subevents'
|
|
||||||
template_name = 'pretixcontrol/subevents/index.html'
|
|
||||||
permission = 'can_change_settings'
|
|
||||||
|
|
||||||
def get_queryset(self):
|
@cached_property
|
||||||
|
def request_data(self):
|
||||||
|
if self.request.method == "POST":
|
||||||
|
return self.request.POST
|
||||||
|
return self.request.GET
|
||||||
|
|
||||||
|
def get_queryset(self, list=False):
|
||||||
sum_tickets_paid = Quota.objects.filter(
|
sum_tickets_paid = Quota.objects.filter(
|
||||||
subevent=OuterRef('pk')
|
subevent=OuterRef('pk')
|
||||||
).order_by().values('subevent').annotate(
|
).order_by().values('subevent').annotate(
|
||||||
@@ -56,18 +64,39 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
|||||||
).values(
|
).values(
|
||||||
's'
|
's'
|
||||||
)
|
)
|
||||||
|
qs = self.request.event.subevents
|
||||||
qs = self.request.event.subevents.annotate(
|
if list:
|
||||||
sum_tickets_paid=Subquery(sum_tickets_paid, output_field=IntegerField())
|
qs = qs.annotate(
|
||||||
).prefetch_related(
|
sum_tickets_paid=Subquery(sum_tickets_paid, output_field=IntegerField())
|
||||||
Prefetch('quotas',
|
).prefetch_related(
|
||||||
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
Prefetch('quotas',
|
||||||
to_attr='first_quotas')
|
queryset=Quota.objects.annotate(s=Coalesce(F('size'), 0)).order_by('-s'),
|
||||||
)
|
to_attr='first_quotas')
|
||||||
|
)
|
||||||
if self.filter_form.is_valid():
|
if self.filter_form.is_valid():
|
||||||
qs = self.filter_form.filter_qs(qs)
|
qs = self.filter_form.filter_qs(qs)
|
||||||
|
|
||||||
|
if 'subevent' in self.request_data and '__ALL' not in self.request_data:
|
||||||
|
qs = qs.filter(
|
||||||
|
id__in=self.request_data.getlist('subevent')
|
||||||
|
)
|
||||||
|
|
||||||
return qs
|
return qs
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def filter_form(self):
|
||||||
|
return SubEventFilterForm(data=self.request_data, prefix='filter')
|
||||||
|
|
||||||
|
|
||||||
|
class SubEventList(EventPermissionRequiredMixin, PaginationMixin, SubEventQueryMixin, ListView):
|
||||||
|
model = SubEvent
|
||||||
|
context_object_name = 'subevents'
|
||||||
|
template_name = 'pretixcontrol/subevents/index.html'
|
||||||
|
permission = 'can_change_settings'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset(True)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data(**kwargs)
|
ctx = super().get_context_data(**kwargs)
|
||||||
ctx['filter_form'] = self.filter_form
|
ctx['filter_form'] = self.filter_form
|
||||||
@@ -95,10 +124,6 @@ class SubEventList(EventPermissionRequiredMixin, PaginationMixin, ListView):
|
|||||||
)
|
)
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def filter_form(self):
|
|
||||||
return SubEventFilterForm(data=self.request.GET)
|
|
||||||
|
|
||||||
|
|
||||||
class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
||||||
model = SubEvent
|
model = SubEvent
|
||||||
@@ -535,19 +560,13 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
|||||||
return formlist
|
return formlist
|
||||||
|
|
||||||
|
|
||||||
class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
class SubEventBulkAction(SubEventQueryMixin, EventPermissionRequiredMixin, View):
|
||||||
permission = 'can_change_settings'
|
permission = 'can_change_settings'
|
||||||
|
|
||||||
@cached_property
|
|
||||||
def objects(self):
|
|
||||||
return self.request.event.subevents.filter(
|
|
||||||
id__in=self.request.POST.getlist('subevent')
|
|
||||||
)
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
if request.POST.get('action') == 'disable':
|
if request.POST.get('action') == 'disable':
|
||||||
for obj in self.objects:
|
for obj in self.get_queryset():
|
||||||
obj.log_action(
|
obj.log_action(
|
||||||
'pretix.subevent.changed', user=self.request.user, data={
|
'pretix.subevent.changed', user=self.request.user, data={
|
||||||
'active': False
|
'active': False
|
||||||
@@ -557,7 +576,7 @@ class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
|||||||
obj.save(update_fields=['active'])
|
obj.save(update_fields=['active'])
|
||||||
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been disabled.'))
|
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been disabled.'))
|
||||||
elif request.POST.get('action') == 'enable':
|
elif request.POST.get('action') == 'enable':
|
||||||
for obj in self.objects:
|
for obj in self.get_queryset():
|
||||||
obj.log_action(
|
obj.log_action(
|
||||||
'pretix.subevent.changed', user=self.request.user, data={
|
'pretix.subevent.changed', user=self.request.user, data={
|
||||||
'active': True
|
'active': True
|
||||||
@@ -568,11 +587,11 @@ class SubEventBulkAction(EventPermissionRequiredMixin, View):
|
|||||||
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been enabled.'))
|
messages.success(request, pgettext_lazy('subevent', 'The selected dates have been enabled.'))
|
||||||
elif request.POST.get('action') == 'delete':
|
elif request.POST.get('action') == 'delete':
|
||||||
return render(request, 'pretixcontrol/subevents/delete_bulk.html', {
|
return render(request, 'pretixcontrol/subevents/delete_bulk.html', {
|
||||||
'allowed': self.objects.filter(orderposition__isnull=True),
|
'allowed': self.get_queryset().filter(orderposition__isnull=True),
|
||||||
'forbidden': self.objects.filter(orderposition__isnull=False),
|
'forbidden': self.get_queryset().filter(orderposition__isnull=False).distinct(),
|
||||||
})
|
})
|
||||||
elif request.POST.get('action') == 'delete_confirm':
|
elif request.POST.get('action') == 'delete_confirm':
|
||||||
for obj in self.objects:
|
for obj in self.get_queryset():
|
||||||
if obj.allow_delete():
|
if obj.allow_delete():
|
||||||
CartPosition.objects.filter(addon_to__subevent=obj).delete()
|
CartPosition.objects.filter(addon_to__subevent=obj).delete()
|
||||||
obj.cartposition_set.all().delete()
|
obj.cartposition_set.all().delete()
|
||||||
@@ -899,3 +918,537 @@ class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, Crea
|
|||||||
|
|
||||||
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
|
class SubEventBulkEdit(SubEventQueryMixin, EventPermissionRequiredMixin, FormView):
|
||||||
|
permission = 'can_change_settings'
|
||||||
|
form_class = SubEventBulkEditForm
|
||||||
|
template_name = 'pretixcontrol/subevents/bulk_edit.html'
|
||||||
|
context_object_name = 'subevent'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super().get_queryset().prefetch_related(None).order_by()
|
||||||
|
|
||||||
|
def get_success_url(self) -> str:
|
||||||
|
return reverse('control:event.subevents', kwargs={
|
||||||
|
'organizer': self.request.event.organizer.slug,
|
||||||
|
'event': self.request.event.slug,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return HttpResponse(status=405)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def cached_num(self):
|
||||||
|
return self.get_queryset().count()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def itemvar_forms(self):
|
||||||
|
matches = defaultdict(list)
|
||||||
|
for sei in SubEventItem.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).order_by().values('item', 'price', 'disabled').annotate(c=Count('*')):
|
||||||
|
matches['item', sei['item']].append(sei)
|
||||||
|
for sei in SubEventItemVariation.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).order_by().values('variation', 'price', 'disabled').annotate(c=Count('*')):
|
||||||
|
matches['variation', sei['variation']].append(sei)
|
||||||
|
total = self.cached_num
|
||||||
|
|
||||||
|
formlist = []
|
||||||
|
for i in self.request.event.items.filter(active=True).prefetch_related('variations'):
|
||||||
|
if i.has_variations:
|
||||||
|
for v in i.variations.all():
|
||||||
|
m = matches['variation', v.pk]
|
||||||
|
if m and len(m) == 1 and m[0]['c'] == total:
|
||||||
|
inst = SubEventItemVariation(variation=v, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||||
|
else:
|
||||||
|
inst = SubEventItemVariation(variation=v)
|
||||||
|
formlist.append(SubEventItemVariationForm(
|
||||||
|
prefix='itemvar-{}'.format(v.pk),
|
||||||
|
item=i, variation=v,
|
||||||
|
instance=inst,
|
||||||
|
data=(self.request.POST if self.is_submitted else None)
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
m = matches['item', i.pk]
|
||||||
|
if m and len(m) == 1 and m[0]['c'] == total:
|
||||||
|
inst = SubEventItem(item=i, disabled=m[0]['disabled'], price=m[0]['price'])
|
||||||
|
else:
|
||||||
|
inst = SubEventItem(item=i)
|
||||||
|
formlist.append(SubEventItemForm(
|
||||||
|
prefix='item-{}'.format(i.pk),
|
||||||
|
item=i,
|
||||||
|
instance=inst,
|
||||||
|
data=(self.request.POST if self.is_submitted else None)
|
||||||
|
))
|
||||||
|
return formlist
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def meta_forms(self):
|
||||||
|
matches = defaultdict(list)
|
||||||
|
for smv in SubEventMetaValue.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).order_by().values('property', 'value').annotate(c=Count('*')):
|
||||||
|
matches[smv['property']].append(smv)
|
||||||
|
total = self.cached_num
|
||||||
|
|
||||||
|
formlist = []
|
||||||
|
|
||||||
|
if not hasattr(self, '_default_meta'):
|
||||||
|
self._default_meta = self.request.event.meta_data
|
||||||
|
|
||||||
|
for p in self.request.organizer.meta_properties.all():
|
||||||
|
inst = SubEventMetaValue(property=p)
|
||||||
|
if len(matches[p.id]) == 1 and matches[p.id][0]['c'] == total:
|
||||||
|
inst.value = matches[p.id][0]['value']
|
||||||
|
formlist.append(SubEventMetaValueForm(
|
||||||
|
prefix='prop-{}'.format(p.pk),
|
||||||
|
property=p,
|
||||||
|
default=self._default_meta.get(p.name, ''),
|
||||||
|
instance=inst,
|
||||||
|
data=(self.request.POST if self.is_submitted else None)
|
||||||
|
))
|
||||||
|
return formlist
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def quota_formset(self):
|
||||||
|
extra = 0
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if self.sampled_quotas is not None:
|
||||||
|
kwargs['instance'] = self.get_queryset()[0]
|
||||||
|
|
||||||
|
formsetclass = inlineformset_factory(
|
||||||
|
SubEvent, Quota,
|
||||||
|
form=QuotaForm, formset=QuotaFormSet, min_num=0, validate_min=False,
|
||||||
|
can_order=False, can_delete=True, extra=extra,
|
||||||
|
)
|
||||||
|
return formsetclass(
|
||||||
|
self.request.POST if self.is_submitted else None,
|
||||||
|
event=self.request.event, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def list_formset(self):
|
||||||
|
extra = 0
|
||||||
|
kwargs = {}
|
||||||
|
|
||||||
|
if self.sampled_lists is not None:
|
||||||
|
kwargs['instance'] = self.get_queryset()[0]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
formsetclass = inlineformset_factory(
|
||||||
|
SubEvent, CheckinList,
|
||||||
|
form=SimpleCheckinListForm, formset=CheckinListFormSet, min_num=0, validate_min=False,
|
||||||
|
can_order=False, can_delete=True, extra=extra,
|
||||||
|
)
|
||||||
|
return formsetclass(
|
||||||
|
self.request.POST if self.is_submitted else None,
|
||||||
|
event=self.request.event, **kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def save_list_formset(self, log_entries):
|
||||||
|
if not self.list_formset.has_changed() or self.sampled_lists is None:
|
||||||
|
return
|
||||||
|
qidx = 0
|
||||||
|
subevents = list(self.get_queryset().prefetch_related('checkinlist_set'))
|
||||||
|
to_save_products = []
|
||||||
|
to_save_gates = []
|
||||||
|
|
||||||
|
for f in self.list_formset.forms:
|
||||||
|
if self.list_formset._should_delete_form(f) and f in self.list_formset.extra_forms:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.list_formset._should_delete_form(f):
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.checkinlist_set.all())[qidx]
|
||||||
|
log_entries += [
|
||||||
|
q.log_action(action='pretix.event.checkinlist.deleted', user=self.request.user, save=False),
|
||||||
|
]
|
||||||
|
q.delete()
|
||||||
|
elif f in self.list_formset.extra_forms:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = copy.copy(f.instance)
|
||||||
|
q.pk = None
|
||||||
|
q.subevent = se
|
||||||
|
q.event = self.request.event
|
||||||
|
q.save()
|
||||||
|
for _i in f.cleaned_data.get('limit_products', []):
|
||||||
|
to_save_products.append(CheckinList.limit_products.through(checkinlist_id=q.pk, item_id=_i.pk))
|
||||||
|
for _i in f.cleaned_data.get('gates', []):
|
||||||
|
to_save_gates.append(CheckinList.gates.through(checkinlist_id=q.pk, gate_id=_i.pk))
|
||||||
|
change_data['id'] = q.pk
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.checkinlist.added', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if f.changed_data:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.checkinlist_set.all())[qidx]
|
||||||
|
for fname in ('name', 'all_products', 'include_pending', 'allow_entry_after_exit'):
|
||||||
|
setattr(q, fname, f.cleaned_data.get(fname))
|
||||||
|
q.save()
|
||||||
|
if 'limit_products' in f.changed_data:
|
||||||
|
q.limit_products.set(f.cleaned_data.get('limit_products', []))
|
||||||
|
if 'gates' in f.changed_data:
|
||||||
|
q.gates.set(f.cleaned_data.get('limit_products', []))
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.checkinlist.changed', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
qidx += 1
|
||||||
|
if to_save_products:
|
||||||
|
CheckinList.limit_products.through.objects.bulk_create(to_save_products)
|
||||||
|
if to_save_gates:
|
||||||
|
CheckinList.gates.through.objects.bulk_create(to_save_gates)
|
||||||
|
|
||||||
|
def save_quota_formset(self, log_entries):
|
||||||
|
if not self.quota_formset.has_changed():
|
||||||
|
return
|
||||||
|
qidx = 0
|
||||||
|
subevents = list(self.get_queryset().prefetch_related('quotas'))
|
||||||
|
to_save_items = []
|
||||||
|
to_save_variations = []
|
||||||
|
to_delete_quota_ids = []
|
||||||
|
|
||||||
|
if self.sampled_quotas is None:
|
||||||
|
if len(self.quota_formset.forms) == 0:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
for se in subevents:
|
||||||
|
for q in se.quotas.all():
|
||||||
|
to_delete_quota_ids.append(q.pk)
|
||||||
|
log_entries += [
|
||||||
|
q.log_action(action='pretix.event.quota.deleted', user=self.request.user, save=False),
|
||||||
|
se.log_action('pretix.subevent.quota.deleted', user=self.request.user, data={
|
||||||
|
'id': q.pk
|
||||||
|
}, save=False)
|
||||||
|
]
|
||||||
|
|
||||||
|
if to_delete_quota_ids:
|
||||||
|
Quota.objects.filter(id__in=to_delete_quota_ids).delete()
|
||||||
|
|
||||||
|
for f in self.quota_formset.forms:
|
||||||
|
if self.quota_formset._should_delete_form(f) and f in self.quota_formset.extra_forms:
|
||||||
|
continue
|
||||||
|
|
||||||
|
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
|
||||||
|
]))
|
||||||
|
|
||||||
|
if self.quota_formset._should_delete_form(f):
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.quotas.all())[qidx]
|
||||||
|
log_entries += [
|
||||||
|
q.log_action(action='pretix.event.quota.deleted', user=self.request.user, save=False),
|
||||||
|
se.log_action('pretix.subevent.quota.deleted', user=self.request.user, data={
|
||||||
|
'id': q.pk
|
||||||
|
}, save=False)
|
||||||
|
]
|
||||||
|
q.delete()
|
||||||
|
elif f in self.quota_formset.extra_forms:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = copy.copy(f.instance)
|
||||||
|
q.pk = None
|
||||||
|
q.subevent = se
|
||||||
|
q.event = self.request.event
|
||||||
|
q.save(clear_cache=False)
|
||||||
|
for _i in selected_items:
|
||||||
|
to_save_items.append(Quota.items.through(quota_id=q.pk, item_id=_i.pk))
|
||||||
|
for _i in selected_variations:
|
||||||
|
to_save_variations.append(Quota.variations.through(quota_id=q.pk, itemvariation_id=_i.pk))
|
||||||
|
|
||||||
|
change_data['id'] = q.pk
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.quota.added', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
log_entries.append(
|
||||||
|
se.log_action('pretix.subevent.quota.added', user=self.request.user, data=change_data,
|
||||||
|
save=False)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if f.changed_data:
|
||||||
|
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||||
|
for se in subevents:
|
||||||
|
q = list(se.quotas.all())[qidx]
|
||||||
|
for fname in ('size', 'name', 'release_after_exit'):
|
||||||
|
setattr(q, fname, f.cleaned_data.get(fname))
|
||||||
|
q.save(clear_cache=False)
|
||||||
|
if 'itemvar' in f.changed_data:
|
||||||
|
q.items.set(selected_items)
|
||||||
|
q.variations.set(selected_variations)
|
||||||
|
log_entries.append(
|
||||||
|
q.log_action(action='pretix.event.quota.added', user=self.request.user,
|
||||||
|
data=change_data, save=False)
|
||||||
|
)
|
||||||
|
qidx += 1
|
||||||
|
if to_save_items:
|
||||||
|
Quota.items.through.objects.bulk_create(to_save_items)
|
||||||
|
if to_save_variations:
|
||||||
|
Quota.variations.through.objects.bulk_create(to_save_variations)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
ctx = super().get_context_data(**kwargs)
|
||||||
|
ctx['subevents'] = self.get_queryset()
|
||||||
|
ctx['filter_form'] = self.filter_form
|
||||||
|
ctx['sampled_quotas'] = self.sampled_quotas
|
||||||
|
ctx['sampled_lists'] = self.sampled_lists
|
||||||
|
ctx['formset'] = self.quota_formset
|
||||||
|
ctx['cl_formset'] = self.list_formset
|
||||||
|
ctx['itemvar_forms'] = self.itemvar_forms
|
||||||
|
ctx['bulk_selected'] = self.request.POST.getlist("_bulk")
|
||||||
|
ctx['meta_forms'] = self.meta_forms
|
||||||
|
return ctx
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def sampled_quotas(self):
|
||||||
|
all_quotas = Quota.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).annotate(
|
||||||
|
item_list=GroupConcat('items__id'),
|
||||||
|
var_list=GroupConcat('variations__id'),
|
||||||
|
).values(
|
||||||
|
'item_list', 'var_list',
|
||||||
|
*(f.name for f in Quota._meta.fields if f.name not in (
|
||||||
|
'id', 'event', 'items', 'variations', 'cached_availability_state', 'cached_availability_number',
|
||||||
|
'cached_availability_paid_orders', 'cached_availability_time', 'closed',
|
||||||
|
))
|
||||||
|
).order_by('subevent_id')
|
||||||
|
|
||||||
|
if not all_quotas:
|
||||||
|
return Quota.objects.none()
|
||||||
|
|
||||||
|
quotas_by_subevent = defaultdict(list)
|
||||||
|
for q in all_quotas:
|
||||||
|
quotas_by_subevent[q.pop('subevent')].append(q)
|
||||||
|
|
||||||
|
prev = None
|
||||||
|
for se in self.get_queryset():
|
||||||
|
if se.pk not in quotas_by_subevent:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if prev is None:
|
||||||
|
prev = quotas_by_subevent[se.pk]
|
||||||
|
|
||||||
|
if quotas_by_subevent[se.pk] != prev:
|
||||||
|
return None
|
||||||
|
return se.quotas.all()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def sampled_lists(self):
|
||||||
|
all_lists = CheckinList.objects.filter(
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).annotate(
|
||||||
|
item_list=GroupConcat('limit_products__id'),
|
||||||
|
gates_list=GroupConcat('gates__id'),
|
||||||
|
).values(
|
||||||
|
'item_list', 'gates_list',
|
||||||
|
*(f.name for f in CheckinList._meta.fields if f.name not in (
|
||||||
|
'id', 'event', 'limit_products', 'gates',
|
||||||
|
))
|
||||||
|
).order_by('subevent_id')
|
||||||
|
|
||||||
|
if not all_lists:
|
||||||
|
return SubEvent.objects.none()
|
||||||
|
|
||||||
|
lists_by_subevent = defaultdict(list)
|
||||||
|
for cl in all_lists:
|
||||||
|
lists_by_subevent[cl.pop('subevent')].append(cl)
|
||||||
|
|
||||||
|
prev = None
|
||||||
|
for se in self.get_queryset():
|
||||||
|
if se.pk not in lists_by_subevent:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if prev is None:
|
||||||
|
prev = lists_by_subevent[se.pk]
|
||||||
|
|
||||||
|
if lists_by_subevent[se.pk] != prev:
|
||||||
|
return None
|
||||||
|
return se.checkinlist_set.all()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def is_submitted(self):
|
||||||
|
# Usually, django considers a form "bound" / "submitted" on every POST request. However, this view is always
|
||||||
|
# called with POST method, even if just to pass the selection of objects to work on, so we want to modify
|
||||||
|
# that behaviour
|
||||||
|
return '_bulk' in self.request.POST
|
||||||
|
|
||||||
|
def get_form_kwargs(self):
|
||||||
|
initial = {}
|
||||||
|
mixed_values = set()
|
||||||
|
qs = self.get_queryset()
|
||||||
|
|
||||||
|
qs = qs.annotate(
|
||||||
|
**{
|
||||||
|
# TODO: Once we're on Django 3.2, pass a tzinfo parameter
|
||||||
|
# Before Django 3.2, it uses the current timezone, which is hopefully fine
|
||||||
|
# as well in all cases we are concerned about
|
||||||
|
# See also: https://code.djangoproject.com/ticket/31948
|
||||||
|
k + '_day': TruncDate(k)
|
||||||
|
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
|
||||||
|
},
|
||||||
|
**{
|
||||||
|
k + '_time': TruncTime(k)
|
||||||
|
for k in ('date_from', 'date_to', 'date_admission', 'presale_start', 'presale_end')
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fields = {
|
||||||
|
'name',
|
||||||
|
'location',
|
||||||
|
'frontpage_text',
|
||||||
|
'geo_lat',
|
||||||
|
'geo_lon',
|
||||||
|
'is_public',
|
||||||
|
'active',
|
||||||
|
'date_from_day',
|
||||||
|
'date_from_time',
|
||||||
|
'date_to_day',
|
||||||
|
'date_to_time',
|
||||||
|
'date_admission_day',
|
||||||
|
'date_admission_time',
|
||||||
|
'presale_start_day',
|
||||||
|
'presale_start_time',
|
||||||
|
'presale_end_day',
|
||||||
|
'presale_end_time',
|
||||||
|
}
|
||||||
|
for k in fields:
|
||||||
|
existing_values = list(qs.order_by(k).values(k).annotate(c=Count('*')))
|
||||||
|
if len(existing_values) == 1:
|
||||||
|
initial[k] = existing_values[0][k]
|
||||||
|
elif len(existing_values) > 1:
|
||||||
|
mixed_values.add(k)
|
||||||
|
initial[k] = None
|
||||||
|
|
||||||
|
kwargs = super().get_form_kwargs()
|
||||||
|
kwargs['event'] = self.request.event
|
||||||
|
kwargs['prefix'] = 'bulkedit'
|
||||||
|
kwargs['initial'] = initial
|
||||||
|
kwargs['queryset'] = self.get_queryset()
|
||||||
|
kwargs['mixed_values'] = mixed_values
|
||||||
|
if not self.is_submitted:
|
||||||
|
kwargs['data'] = None
|
||||||
|
kwargs['files'] = None
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
form = self.get_form()
|
||||||
|
is_valid = (
|
||||||
|
self.is_submitted and
|
||||||
|
form.is_valid() and
|
||||||
|
self.quota_formset.is_valid() and
|
||||||
|
(not self.list_formset or self.list_formset.is_valid()) and
|
||||||
|
all(f.is_valid() for f in self.itemvar_forms)and
|
||||||
|
all(f.is_valid() for f in self.meta_forms)
|
||||||
|
)
|
||||||
|
if is_valid:
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
if self.is_submitted:
|
||||||
|
messages.error(self.request, _('We could not save your changes. See below for details.'))
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def save_meta(self):
|
||||||
|
for f in self.meta_forms:
|
||||||
|
if f.prefix + 'value' not in self.request.POST.getlist('_bulk'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if f.cleaned_data.get('value'):
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
SubEventMetaValue.objects.update_or_create(
|
||||||
|
property=f.instance.property,
|
||||||
|
subevent=obj,
|
||||||
|
defaults={
|
||||||
|
'value': f.cleaned_data['value']
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
SubEventMetaValue.objects.filter(
|
||||||
|
property=f.instance.property,
|
||||||
|
subevent__in=self.get_queryset()
|
||||||
|
).delete()
|
||||||
|
|
||||||
|
def save_itemvars(self):
|
||||||
|
for f in self.itemvar_forms:
|
||||||
|
u = {}
|
||||||
|
if f.prefix + 'price' in self.request.POST.getlist('_bulk'):
|
||||||
|
u['price'] = f.cleaned_data.get('price')
|
||||||
|
if f.prefix + 'disabled' in self.request.POST.getlist('_bulk'):
|
||||||
|
u['disabled'] = f.cleaned_data.get('disabled')
|
||||||
|
|
||||||
|
if not u:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(f, SubEventItemForm):
|
||||||
|
if u.get('price') is None and not u.get('disabled'):
|
||||||
|
SubEventItem.objects.filter(
|
||||||
|
subevent__in=self.get_queryset(),
|
||||||
|
item=f.instance.item,
|
||||||
|
).delete()
|
||||||
|
else:
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
SubEventItem.objects.update_or_create(
|
||||||
|
subevent=obj,
|
||||||
|
item=f.instance.item,
|
||||||
|
defaults=u
|
||||||
|
)
|
||||||
|
elif isinstance(f, SubEventItemVariationForm):
|
||||||
|
if u.get('price') is None and not u.get('disabled'):
|
||||||
|
SubEventItemVariation.objects.filter(
|
||||||
|
subevent__in=self.get_queryset(),
|
||||||
|
variation=f.instance.variation,
|
||||||
|
).delete()
|
||||||
|
else:
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
SubEventItemVariation.objects.update_or_create(
|
||||||
|
subevent=obj,
|
||||||
|
variation=f.instance.variation,
|
||||||
|
defaults=u
|
||||||
|
)
|
||||||
|
|
||||||
|
@transaction.atomic()
|
||||||
|
def form_valid(self, form):
|
||||||
|
log_entries = []
|
||||||
|
|
||||||
|
# Main form
|
||||||
|
form.save()
|
||||||
|
data = {
|
||||||
|
k: v for k, v in form.cleaned_data.items() if k in form.changed_data
|
||||||
|
}
|
||||||
|
data['_raw_bulk_data'] = self.request.POST.dict()
|
||||||
|
for obj in self.get_queryset():
|
||||||
|
log_entries.append(
|
||||||
|
obj.log_action('pretix.subevent.changed', data=data, user=self.request.user, save=False)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Formsets
|
||||||
|
if '__quotas' in self.request.POST.getlist('_bulk'):
|
||||||
|
self.save_quota_formset(log_entries)
|
||||||
|
if '__checkinlists' in self.request.POST.getlist('_bulk'):
|
||||||
|
self.save_list_formset(log_entries)
|
||||||
|
|
||||||
|
self.save_itemvars()
|
||||||
|
self.save_meta()
|
||||||
|
|
||||||
|
if connections['default'].features.can_return_rows_from_bulk_insert:
|
||||||
|
LogEntry.objects.bulk_create(log_entries, batch_size=200)
|
||||||
|
LogEntry.bulk_postprocess(log_entries)
|
||||||
|
else:
|
||||||
|
for le in log_entries:
|
||||||
|
le.save()
|
||||||
|
LogEntry.bulk_postprocess(log_entries)
|
||||||
|
|
||||||
|
self.request.event.cache.clear()
|
||||||
|
messages.success(self.request, _('Your changes have been saved.'))
|
||||||
|
return super().form_valid(form)
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ msgstr ""
|
|||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2021-01-27 17:45+0000\n"
|
"POT-Creation-Date: 2021-01-27 17:45+0000\n"
|
||||||
"PO-Revision-Date: 2020-12-14 10:00+0000\n"
|
"PO-Revision-Date: 2021-02-16 06:00+0000\n"
|
||||||
"Last-Translator: Ondřej Sokol <osokol@treesoft.cz>\n"
|
"Last-Translator: Ondřej Sokol <osokol@treesoft.cz>\n"
|
||||||
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/"
|
"Language-Team: Czech <https://translate.pretix.eu/projects/pretix/pretix/cs/>"
|
||||||
">\n"
|
"\n"
|
||||||
"Language: cs\n"
|
"Language: cs\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
|
||||||
"X-Generator: Weblate 3.10.3\n"
|
"X-Generator: Weblate 4.4.2\n"
|
||||||
|
|
||||||
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
#: htmlcov/pretix_control_views_dashboards_py.html:898
|
||||||
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
#: pretix/control/templates/pretixcontrol/events/index.html:144
|
||||||
@@ -218,17 +218,17 @@ msgstr ""
|
|||||||
#: pretix/api/serializers/organizer.py:142
|
#: pretix/api/serializers/organizer.py:142
|
||||||
#: pretix/control/views/organizer.py:539
|
#: pretix/control/views/organizer.py:539
|
||||||
msgid "pretix account invitation"
|
msgid "pretix account invitation"
|
||||||
msgstr ""
|
msgstr "pozvánka k pretix účtu"
|
||||||
|
|
||||||
#: pretix/api/serializers/organizer.py:164
|
#: pretix/api/serializers/organizer.py:164
|
||||||
#: pretix/control/views/organizer.py:638
|
#: pretix/control/views/organizer.py:638
|
||||||
msgid "This user already has been invited for this team."
|
msgid "This user already has been invited for this team."
|
||||||
msgstr "Tento uživatel byl již pozván do této skupiny."
|
msgstr "Tento uživatel byl již pozván do tohoto týmu."
|
||||||
|
|
||||||
#: pretix/api/serializers/organizer.py:180
|
#: pretix/api/serializers/organizer.py:180
|
||||||
#: pretix/control/views/organizer.py:655
|
#: pretix/control/views/organizer.py:655
|
||||||
msgid "This user already has permissions for this team."
|
msgid "This user already has permissions for this team."
|
||||||
msgstr ""
|
msgstr "Tento uživatel již má nastavena práva pro tento tým."
|
||||||
|
|
||||||
#: pretix/api/views/oauth.py:85 pretix/control/logdisplay.py:356
|
#: pretix/api/views/oauth.py:85 pretix/control/logdisplay.py:356
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
@@ -240,7 +240,7 @@ msgstr ""
|
|||||||
#: pretix/api/views/order.py:460 pretix/control/views/orders.py:1186
|
#: pretix/api/views/order.py:460 pretix/control/views/orders.py:1186
|
||||||
#: pretix/presale/views/order.py:663 pretix/presale/views/order.py:728
|
#: pretix/presale/views/order.py:663 pretix/presale/views/order.py:728
|
||||||
msgid "You cannot generate an invoice for this order."
|
msgid "You cannot generate an invoice for this order."
|
||||||
msgstr ""
|
msgstr "Nemůžete vygenerovat fakturu pro tuto objednávku."
|
||||||
|
|
||||||
#: pretix/api/views/order.py:465 pretix/control/views/orders.py:1188
|
#: pretix/api/views/order.py:465 pretix/control/views/orders.py:1188
|
||||||
#: pretix/presale/views/order.py:665 pretix/presale/views/order.py:730
|
#: pretix/presale/views/order.py:665 pretix/presale/views/order.py:730
|
||||||
@@ -1868,7 +1868,7 @@ msgstr "Časové pásmo"
|
|||||||
|
|
||||||
#: pretix/base/models/auth.py:108
|
#: pretix/base/models/auth.py:108
|
||||||
msgid "Two-factor authentication is required to log in"
|
msgid "Two-factor authentication is required to log in"
|
||||||
msgstr ""
|
msgstr "Pro přihlášení je vyžadována dvoufaktorová autentizace"
|
||||||
|
|
||||||
#: pretix/base/models/auth.py:112
|
#: pretix/base/models/auth.py:112
|
||||||
msgid "Receive notifications according to my settings below"
|
msgid "Receive notifications according to my settings below"
|
||||||
@@ -1991,7 +1991,7 @@ msgstr ""
|
|||||||
#: pretix/base/models/devices.py:91
|
#: pretix/base/models/devices.py:91
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
|
#: pretix/control/templates/pretixcontrol/organizers/gates.html:16
|
||||||
msgid "Gate"
|
msgid "Gate"
|
||||||
msgstr ""
|
msgstr "Brána"
|
||||||
|
|
||||||
#: pretix/base/models/devices.py:109
|
#: pretix/base/models/devices.py:109
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/devices.html:38
|
#: pretix/control/templates/pretixcontrol/organizers/devices.html:38
|
||||||
@@ -2008,7 +2008,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:46
|
#: pretix/base/models/event.py:46
|
||||||
msgid "The end of the event has to be later than its start."
|
msgid "The end of the event has to be later than its start."
|
||||||
msgstr ""
|
msgstr "Konec události musí být pozdější než začátek."
|
||||||
|
|
||||||
#: pretix/base/models/event.py:353
|
#: pretix/base/models/event.py:353
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -2030,7 +2030,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:369
|
#: pretix/base/models/event.py:369
|
||||||
msgid "Shop is live"
|
msgid "Shop is live"
|
||||||
msgstr ""
|
msgstr "Obchod je spuštěný"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:371
|
#: pretix/base/models/event.py:371
|
||||||
msgid "Event currency"
|
msgid "Event currency"
|
||||||
@@ -2057,7 +2057,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:380 pretix/base/models/event.py:1106
|
#: pretix/base/models/event.py:380 pretix/base/models/event.py:1106
|
||||||
msgid "Show in lists"
|
msgid "Show in lists"
|
||||||
msgstr ""
|
msgstr "Zobrazit v seznamu"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:381
|
#: pretix/base/models/event.py:381
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -2068,7 +2068,7 @@ msgstr ""
|
|||||||
#: pretix/base/models/event.py:384 pretix/base/models/event.py:1120
|
#: pretix/base/models/event.py:384 pretix/base/models/event.py:1120
|
||||||
#: pretix/control/forms/subevents.py:75
|
#: pretix/control/forms/subevents.py:75
|
||||||
msgid "End of presale"
|
msgid "End of presale"
|
||||||
msgstr ""
|
msgstr "Konec předprodeje"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:385 pretix/base/models/event.py:1121
|
#: pretix/base/models/event.py:385 pretix/base/models/event.py:1121
|
||||||
#: pretix/control/forms/subevents.py:76
|
#: pretix/control/forms/subevents.py:76
|
||||||
@@ -2080,7 +2080,7 @@ msgstr ""
|
|||||||
#: pretix/base/models/event.py:390 pretix/base/models/event.py:1126
|
#: pretix/base/models/event.py:390 pretix/base/models/event.py:1126
|
||||||
#: pretix/control/forms/subevents.py:69
|
#: pretix/control/forms/subevents.py:69
|
||||||
msgid "Start of presale"
|
msgid "Start of presale"
|
||||||
msgstr ""
|
msgstr "Začátek předprodeje"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:391 pretix/base/models/event.py:1127
|
#: pretix/base/models/event.py:391 pretix/base/models/event.py:1127
|
||||||
#: pretix/control/forms/subevents.py:70
|
#: pretix/control/forms/subevents.py:70
|
||||||
@@ -2101,13 +2101,13 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/models/event.py:416 pretix/control/navigation.py:44
|
#: pretix/base/models/event.py:416 pretix/control/navigation.py:44
|
||||||
msgid "Plugins"
|
msgid "Plugins"
|
||||||
msgstr ""
|
msgstr "Zásuvné moduly"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:419
|
#: pretix/base/models/event.py:419
|
||||||
#: pretix/control/templates/pretixcontrol/event/index.html:143
|
#: pretix/control/templates/pretixcontrol/event/index.html:143
|
||||||
#: pretix/control/templates/pretixcontrol/order/index.html:865
|
#: pretix/control/templates/pretixcontrol/order/index.html:865
|
||||||
msgid "Internal comment"
|
msgid "Internal comment"
|
||||||
msgstr ""
|
msgstr "Interní poznámka"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:423 pretix/control/forms/event.py:211
|
#: pretix/base/models/event.py:423 pretix/control/forms/event.py:211
|
||||||
#: pretix/control/forms/filter.py:988
|
#: pretix/control/forms/filter.py:988
|
||||||
@@ -2133,7 +2133,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/search/orders.html:44
|
#: pretix/control/templates/pretixcontrol/search/orders.html:44
|
||||||
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:18
|
#: pretix/presale/templates/pretixpresale/event/waitinglist.html:18
|
||||||
msgid "Event"
|
msgid "Event"
|
||||||
msgstr ""
|
msgstr "Událost"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:437 pretix/control/navigation.py:305
|
#: pretix/base/models/event.py:437 pretix/control/navigation.py:305
|
||||||
#: pretix/control/navigation.py:407
|
#: pretix/control/navigation.py:407
|
||||||
@@ -2144,7 +2144,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/organizers/webhooks.html:37
|
#: pretix/control/templates/pretixcontrol/organizers/webhooks.html:37
|
||||||
#: pretix/control/views/organizer.py:1205
|
#: pretix/control/views/organizer.py:1205
|
||||||
msgid "Events"
|
msgid "Events"
|
||||||
msgstr ""
|
msgstr "Události"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:951
|
#: pretix/base/models/event.py:951
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -2182,7 +2182,7 @@ msgstr ""
|
|||||||
#: pretix/control/forms/filter.py:1250
|
#: pretix/control/forms/filter.py:1250
|
||||||
#: pretix/control/templates/pretixcontrol/users/index.html:46
|
#: pretix/control/templates/pretixcontrol/users/index.html:46
|
||||||
msgid "Active"
|
msgid "Active"
|
||||||
msgstr ""
|
msgstr "Aktivní"
|
||||||
|
|
||||||
#: pretix/base/models/event.py:1103
|
#: pretix/base/models/event.py:1103
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -6399,7 +6399,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/base/settings.py:1694
|
#: pretix/base/settings.py:1694
|
||||||
msgid "Primary color"
|
msgid "Primary color"
|
||||||
msgstr ""
|
msgstr "Hlavní barva"
|
||||||
|
|
||||||
#: pretix/base/settings.py:1714
|
#: pretix/base/settings.py:1714
|
||||||
msgid "Accent color for success"
|
msgid "Accent color for success"
|
||||||
@@ -9737,7 +9737,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:3
|
#: pretix/control/templates/pretixcontrol/dashboard.html:3
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:5
|
#: pretix/control/templates/pretixcontrol/dashboard.html:5
|
||||||
msgid "Dashboard"
|
msgid "Dashboard"
|
||||||
msgstr ""
|
msgstr "Nástěnka"
|
||||||
|
|
||||||
#: pretix/control/navigation.py:28 pretix/control/navigation.py:329
|
#: pretix/control/navigation.py:28 pretix/control/navigation.py:329
|
||||||
#: pretix/control/navigation.py:424
|
#: pretix/control/navigation.py:424
|
||||||
@@ -10328,7 +10328,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:123
|
#: pretix/control/templates/pretixcontrol/waitinglist/index.html:123
|
||||||
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:80
|
#: pretix/plugins/banktransfer/templates/pretixplugins/banktransfer/import_form.html:80
|
||||||
msgid "Filter"
|
msgid "Filter"
|
||||||
msgstr ""
|
msgstr "Filtrovat"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/checkin/index.html:52
|
#: pretix/control/templates/pretixcontrol/checkin/index.html:52
|
||||||
msgid "No attendee record was found."
|
msgid "No attendee record was found."
|
||||||
@@ -10558,11 +10558,11 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:9
|
#: pretix/control/templates/pretixcontrol/dashboard.html:9
|
||||||
msgid "Go to event"
|
msgid "Go to event"
|
||||||
msgstr ""
|
msgstr "Jít na událost"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:15
|
#: pretix/control/templates/pretixcontrol/dashboard.html:15
|
||||||
msgid "Your upcoming events"
|
msgid "Your upcoming events"
|
||||||
msgstr ""
|
msgstr "Vaše nadcházející události"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:20
|
#: pretix/control/templates/pretixcontrol/dashboard.html:20
|
||||||
#: pretix/control/templates/pretixcontrol/events/create_base.html:4
|
#: pretix/control/templates/pretixcontrol/events/create_base.html:4
|
||||||
@@ -10571,11 +10571,11 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/events/index.html:57
|
#: pretix/control/templates/pretixcontrol/events/index.html:57
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/detail.html:12
|
#: pretix/control/templates/pretixcontrol/organizers/detail.html:12
|
||||||
msgid "Create a new event"
|
msgid "Create a new event"
|
||||||
msgstr ""
|
msgstr "Vytvořit novou událost"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:39
|
#: pretix/control/templates/pretixcontrol/dashboard.html:39
|
||||||
msgid "View all upcoming events"
|
msgid "View all upcoming events"
|
||||||
msgstr ""
|
msgstr "Zobrazit všechny nadcházející události"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/dashboard.html:44
|
#: pretix/control/templates/pretixcontrol/dashboard.html:44
|
||||||
msgid "Your most recent events"
|
msgid "Your most recent events"
|
||||||
@@ -13546,7 +13546,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:98
|
#: pretix/control/templates/pretixcontrol/orders/index.html:98
|
||||||
msgid "Remove filter"
|
msgid "Remove filter"
|
||||||
msgstr ""
|
msgstr "Odstranit filtr"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:116
|
#: pretix/control/templates/pretixcontrol/orders/index.html:116
|
||||||
msgid "Order paid / total"
|
msgid "Order paid / total"
|
||||||
@@ -13618,7 +13618,7 @@ msgstr ""
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:69
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:69
|
||||||
#: pretix/plugins/reports/exporters.py:259
|
#: pretix/plugins/reports/exporters.py:259
|
||||||
msgid "Purchased"
|
msgid "Purchased"
|
||||||
msgstr ""
|
msgstr "Zakoupeno"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:178
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:178
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -13679,7 +13679,7 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:14
|
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:14
|
||||||
msgid "Download pretixSCAN"
|
msgid "Download pretixSCAN"
|
||||||
msgstr ""
|
msgstr "Stáhnout pretixSCAN"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:18
|
#: pretix/control/templates/pretixcontrol/organizers/device_connect.html:18
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -17735,12 +17735,12 @@ msgstr ""
|
|||||||
#: pretix/plugins/reports/exporters.py:120
|
#: pretix/plugins/reports/exporters.py:120
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Page %d"
|
msgid "Page %d"
|
||||||
msgstr ""
|
msgstr "Strana %d"
|
||||||
|
|
||||||
#: pretix/plugins/reports/exporters.py:122
|
#: pretix/plugins/reports/exporters.py:122
|
||||||
#, python-format
|
#, python-format
|
||||||
msgid "Created: %s"
|
msgid "Created: %s"
|
||||||
msgstr ""
|
msgstr "Vytvořeno: %s"
|
||||||
|
|
||||||
#: pretix/plugins/reports/exporters.py:162
|
#: pretix/plugins/reports/exporters.py:162
|
||||||
msgid "Order overview (PDF)"
|
msgid "Order overview (PDF)"
|
||||||
@@ -17810,10 +17810,8 @@ msgstr ""
|
|||||||
|
|
||||||
#: pretix/plugins/reports/exporters.py:668
|
#: pretix/plugins/reports/exporters.py:668
|
||||||
#: pretix/plugins/reports/exporters.py:713
|
#: pretix/plugins/reports/exporters.py:713
|
||||||
#, fuzzy
|
|
||||||
#| msgid "Country"
|
|
||||||
msgid "Country code"
|
msgid "Country code"
|
||||||
msgstr "Stát"
|
msgstr "Kód země"
|
||||||
|
|
||||||
#: pretix/plugins/returnurl/__init__.py:9
|
#: pretix/plugins/returnurl/__init__.py:9
|
||||||
#: pretix/plugins/returnurl/__init__.py:12
|
#: pretix/plugins/returnurl/__init__.py:12
|
||||||
|
|||||||
@@ -720,6 +720,8 @@ BOOTSTRAP3 = {
|
|||||||
'default': 'bootstrap3.renderers.FieldRenderer',
|
'default': 'bootstrap3.renderers.FieldRenderer',
|
||||||
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
'inline': 'bootstrap3.renderers.InlineFieldRenderer',
|
||||||
'control': 'pretix.control.forms.renderers.ControlFieldRenderer',
|
'control': 'pretix.control.forms.renderers.ControlFieldRenderer',
|
||||||
|
'bulkedit': 'pretix.control.forms.renderers.BulkEditFieldRenderer',
|
||||||
|
'bulkedit_inline': 'pretix.control.forms.renderers.InlineBulkEditFieldRenderer',
|
||||||
'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer',
|
'checkout': 'pretix.presale.forms.renderers.CheckoutFieldRenderer',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -758,3 +760,6 @@ OAUTH2_PROVIDER = {
|
|||||||
COUNTRIES_OVERRIDE = {
|
COUNTRIES_OVERRIDE = {
|
||||||
'XK': _('Kosovo'),
|
'XK': _('Kosovo'),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 25000
|
||||||
|
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||||
|
|||||||
@@ -362,6 +362,9 @@ $(document).ready(function () {
|
|||||||
} else {
|
} else {
|
||||||
this.$set(this.rule[this.operator], 1, time);
|
this.$set(this.rule[this.operator], 1, time);
|
||||||
}
|
}
|
||||||
|
if (event.target.value === "custom") {
|
||||||
|
this.$set(this.rule[this.operator], 2, 0);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setTimeValue: function (val) {
|
setTimeValue: function (val) {
|
||||||
console.log(val);
|
console.log(val);
|
||||||
|
|||||||
@@ -546,6 +546,25 @@ var form_handlers = function (el) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
el.find(".bulk-edit-field-group").each(function () {
|
||||||
|
var $checkbox = $(this).find("input[type=checkbox][name=_bulk]");
|
||||||
|
var $content = $(this).find(".field-content");
|
||||||
|
var $fields = $content.find("input, select, textarea, button");
|
||||||
|
|
||||||
|
var update = function () {
|
||||||
|
var isChecked = $checkbox.prop("checked");
|
||||||
|
$content.toggleClass("enabled", isChecked);
|
||||||
|
$fields.attr("tabIndex", isChecked ? 0 : -1);
|
||||||
|
}
|
||||||
|
$content.on("focusin change click", function () {
|
||||||
|
if ($checkbox.prop("checked")) return;
|
||||||
|
$checkbox.prop("checked", true);
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
$checkbox.on('change', update)
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent);
|
el.find("input[name*=question], select[name*=question]").change(questions_toggle_dependent);
|
||||||
questions_toggle_dependent();
|
questions_toggle_dependent();
|
||||||
};
|
};
|
||||||
@@ -563,7 +582,6 @@ $(function () {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
$("[data-formset]").on("formAdded", "div", function (event) {
|
$("[data-formset]").on("formAdded", "div", function (event) {
|
||||||
console.log("formAdded")
|
|
||||||
form_handlers($(event.target));
|
form_handlers($(event.target));
|
||||||
});
|
});
|
||||||
$(document).on("click", ".variations .variations-select-all", function (e) {
|
$(document).on("click", ".variations .variations-select-all", function (e) {
|
||||||
@@ -672,8 +690,8 @@ $(function () {
|
|||||||
// Tables with bulk selection, e.g. subevent list
|
// Tables with bulk selection, e.g. subevent list
|
||||||
$("input[data-toggle-table]").each(function (ev) {
|
$("input[data-toggle-table]").each(function (ev) {
|
||||||
var $toggle = $(this);
|
var $toggle = $(this);
|
||||||
|
|
||||||
var $table = $toggle.closest("table");
|
var $table = $toggle.closest("table");
|
||||||
|
var $selectAll = $table.find(".table-select-all");
|
||||||
var $rows = $table.find("tbody tr");
|
var $rows = $table.find("tbody tr");
|
||||||
var $checkboxes = $rows.find("td:first-child input[type=checkbox]");
|
var $checkboxes = $rows.find("td:first-child input[type=checkbox]");
|
||||||
var firstIndex, lastIndex, selectionChecked, onChangeSelectionHappened = false;
|
var firstIndex, lastIndex, selectionChecked, onChangeSelectionHappened = false;
|
||||||
@@ -739,6 +757,7 @@ $(function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var update = function () {
|
var update = function () {
|
||||||
|
console.log("update!");
|
||||||
var all_same;
|
var all_same;
|
||||||
var checkboxes = $checkboxes.toArray();
|
var checkboxes = $checkboxes.toArray();
|
||||||
var i = checkboxes.length;
|
var i = checkboxes.length;
|
||||||
@@ -748,19 +767,25 @@ $(function () {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (all_same != checkboxes[i].checked) {
|
if (all_same != checkboxes[i].checked) {
|
||||||
$toggle.prop("checked", false).prop("indeterminate", true);
|
$toggle.prop("checked", false).prop("indeterminate", true).trigger("change");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$toggle.prop("checked", all_same).prop("indeterminate", false);
|
$toggle.prop("checked", all_same).prop("indeterminate", false).trigger("change");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var debounceUpdate;
|
||||||
$checkboxes.change(function() {
|
$checkboxes.change(function() {
|
||||||
$(this).closest("tr").toggleClass("warning", this.checked);
|
$(this).closest("tr").toggleClass("warning", this.checked);
|
||||||
update();
|
// when changing the $toggle’s checked-property, lots of change events
|
||||||
|
// get triggered => debounce
|
||||||
|
if (debounceUpdate) window.clearTimeout(debounceUpdate);
|
||||||
|
debounceUpdate = window.setTimeout(update, 10);
|
||||||
});
|
});
|
||||||
$(this).change(function (ev) {
|
$toggle.change(function (ev) {
|
||||||
$checkboxes.prop("checked", this.checked).trigger("change");
|
if (!$toggle.prop("indeterminate")) $checkboxes.prop("checked", this.checked).trigger("change");
|
||||||
|
$selectAll.toggleClass("hidden", !this.checked).prop("hidden", !this.checked);
|
||||||
|
if (!this.checked) $selectAll.find("input").prop("checked", false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -614,9 +614,40 @@ table td > .checkbox input[type="checkbox"] {
|
|||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.batch-select-label {
|
.batch-select-label {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 1.5em;
|
height: 1.5em;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bulk-edit-field-group {
|
||||||
|
.field-toggle {
|
||||||
|
font-weight: normal;
|
||||||
|
display: inline-block;
|
||||||
|
background: $gray-lighter;
|
||||||
|
padding: 2px 8px 4px;
|
||||||
|
border-top-left-radius: $border-radius-base;
|
||||||
|
border-top-right-radius: $border-radius-base;
|
||||||
|
margin-bottom: 0;
|
||||||
|
input {
|
||||||
|
position: relative;
|
||||||
|
top: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.field-content {
|
||||||
|
border: 2px solid $gray-lighter;
|
||||||
|
padding: 15px;
|
||||||
|
opacity: 0.5;
|
||||||
|
.datepickerfield::placeholder, .timepickerfield::placeholder {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
&.enabled {
|
||||||
|
opacity: 1;
|
||||||
|
.datepickerfield::placeholder, .timepickerfield::placeholder {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,16 +34,32 @@ def extract_form_fields(soup):
|
|||||||
|
|
||||||
if field['type'] in ('checkbox', 'radio'):
|
if field['type'] in ('checkbox', 'radio'):
|
||||||
if field.has_attr('checked') and field.has_attr('name'):
|
if field.has_attr('checked') and field.has_attr('name'):
|
||||||
data[field['name']] = field.get('value', 'on')
|
if field['name'] in data:
|
||||||
|
if not isinstance(data[field['name']], list):
|
||||||
|
data[field['name']] = [data[field['name']]]
|
||||||
|
data[field['name']].append(field.get('value', 'on'))
|
||||||
|
else:
|
||||||
|
data[field['name']] = field.get('value', 'on')
|
||||||
continue
|
continue
|
||||||
elif field.has_attr('name'):
|
elif field.has_attr('name'):
|
||||||
# single element name/value fields
|
# single element name/value fields
|
||||||
data[field['name']] = field.get('value', '')
|
value = field.get('value', '')
|
||||||
|
if field['name'] in data:
|
||||||
|
if not isinstance(data[field['name']], list):
|
||||||
|
data[field['name']] = [data[field['name']]]
|
||||||
|
data[field['name']].append(value)
|
||||||
|
else:
|
||||||
|
data[field['name']] = value
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# textareas
|
# textareas
|
||||||
for textarea in soup.findAll('textarea'):
|
for textarea in soup.findAll('textarea'):
|
||||||
data[textarea['name']] = textarea.text or ''
|
if textarea['name'] in data:
|
||||||
|
if not isinstance(data[textarea['name']], list):
|
||||||
|
data[textarea['name']] = [data[textarea['name']]]
|
||||||
|
data[textarea['name']].append(textarea.text or '')
|
||||||
|
else:
|
||||||
|
data[textarea['name']] = textarea.text or ''
|
||||||
|
|
||||||
# select fields
|
# select fields
|
||||||
for select in soup.find_all('select'):
|
for select in soup.find_all('select'):
|
||||||
@@ -66,6 +82,11 @@ def extract_form_fields(soup):
|
|||||||
else:
|
else:
|
||||||
value = [option['value'] for option in selected_options]
|
value = [option['value'] for option in selected_options]
|
||||||
|
|
||||||
data[select['name']] = value
|
if select['name'] in data:
|
||||||
|
if not isinstance(data[select['name']], list):
|
||||||
|
data[select['name']] = [data[select['name']]]
|
||||||
|
data[select['name']].append(value)
|
||||||
|
else:
|
||||||
|
data[select['name']] = value
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -9,10 +9,7 @@ from i18nfield.strings import LazyI18nString
|
|||||||
from pytz import timezone
|
from pytz import timezone
|
||||||
from tests.base import SoupTest, extract_form_fields
|
from tests.base import SoupTest, extract_form_fields
|
||||||
|
|
||||||
from pretix.base.models import (
|
from pretix.base.models import Event, Order, Organizer, Team, User
|
||||||
Event, Order, OrderPosition, Organizer, SubEvent, Team, User,
|
|
||||||
)
|
|
||||||
from pretix.base.models.items import SubEventItem
|
|
||||||
from pretix.testutils.mock import mocker_context
|
from pretix.testutils.mock import mocker_context
|
||||||
|
|
||||||
|
|
||||||
@@ -996,646 +993,6 @@ class EventsTest(SoupTest):
|
|||||||
assert doc.select(".has-error")
|
assert doc.select(".has-error")
|
||||||
|
|
||||||
|
|
||||||
class SubEventsTest(SoupTest):
|
|
||||||
@scopes_disabled()
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
|
|
||||||
self.orga1 = Organizer.objects.create(name='CCC', slug='ccc')
|
|
||||||
self.event1 = Event.objects.create(
|
|
||||||
organizer=self.orga1, name='30C3', slug='30c3',
|
|
||||||
date_from=datetime.datetime(2013, 12, 26, tzinfo=datetime.timezone.utc),
|
|
||||||
plugins='pretix.plugins.banktransfer,tests.testdummy',
|
|
||||||
has_subevents=True
|
|
||||||
)
|
|
||||||
|
|
||||||
t = Team.objects.create(organizer=self.orga1, can_create_events=True, can_change_event_settings=True,
|
|
||||||
can_change_items=True)
|
|
||||||
t.members.add(self.user)
|
|
||||||
t.limit_events.add(self.event1)
|
|
||||||
self.ticket = self.event1.items.create(name='Early-bird ticket',
|
|
||||||
category=None, default_price=23,
|
|
||||||
admission=True)
|
|
||||||
|
|
||||||
self.client.login(email='dummy@dummy.dummy', password='dummy')
|
|
||||||
|
|
||||||
self.subevent1 = self.event1.subevents.create(name='SE1', date_from=now())
|
|
||||||
self.subevent2 = self.event1.subevents.create(name='SE2', date_from=now())
|
|
||||||
|
|
||||||
def test_list(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/')
|
|
||||||
tabletext = doc.select("#page-wrapper .table")[0].text
|
|
||||||
self.assertIn("SE1", tabletext)
|
|
||||||
|
|
||||||
def test_create(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/add')
|
|
||||||
assert doc.select("input[name=quotas-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/add', {
|
|
||||||
'name_0': 'SE2',
|
|
||||||
'active': 'on',
|
|
||||||
'date_from_0': '2017-07-01',
|
|
||||||
'date_from_1': '10:00:00',
|
|
||||||
'date_to_0': '2017-07-01',
|
|
||||||
'date_to_1': '12:00:00',
|
|
||||||
'location_0': 'Hamburg',
|
|
||||||
'presale_start_0': '2017-06-20',
|
|
||||||
'presale_start_1': '10:00:00',
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '1',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
'checkinlist_set-0-name': 'Default',
|
|
||||||
'checkinlist_set-0-all_products': 'on',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'item-%d-price' % self.ticket.pk: '12'
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
se = self.event1.subevents.first()
|
|
||||||
assert str(se.name) == "SE2"
|
|
||||||
assert se.active
|
|
||||||
assert se.date_from.isoformat() == "2017-07-01T10:00:00+00:00"
|
|
||||||
assert se.date_to.isoformat() == "2017-07-01T12:00:00+00:00"
|
|
||||||
assert str(se.location) == "Hamburg"
|
|
||||||
assert se.presale_start.isoformat() == "2017-06-20T10:00:00+00:00"
|
|
||||||
assert not se.presale_end
|
|
||||||
assert se.quotas.count() == 1
|
|
||||||
q = se.quotas.last()
|
|
||||||
assert q.name == "Q1"
|
|
||||||
assert q.size == 50
|
|
||||||
assert list(q.items.all()) == [self.ticket]
|
|
||||||
sei = SubEventItem.objects.get(subevent=se, item=self.ticket)
|
|
||||||
assert sei.price == 12
|
|
||||||
assert se.checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
def test_modify(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk)
|
|
||||||
assert doc.select("input[name=quotas-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/' % self.subevent1.pk, {
|
|
||||||
'name_0': 'SE2',
|
|
||||||
'active': 'on',
|
|
||||||
'date_from_0': '2017-07-01',
|
|
||||||
'date_from_1': '10:00:00',
|
|
||||||
'date_to_0': '2017-07-01',
|
|
||||||
'date_to_1': '12:00:00',
|
|
||||||
'location_0': 'Hamburg',
|
|
||||||
'presale_start_0': '2017-06-20',
|
|
||||||
'presale_start_1': '10:00:00',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '1',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
'checkinlist_set-0-name': 'Default',
|
|
||||||
'checkinlist_set-0-all_products': 'on',
|
|
||||||
'item-%d-price' % self.ticket.pk: '12'
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
self.subevent1.refresh_from_db()
|
|
||||||
se = self.subevent1
|
|
||||||
assert str(se.name) == "SE2"
|
|
||||||
assert se.active
|
|
||||||
assert se.date_from.isoformat() == "2017-07-01T10:00:00+00:00"
|
|
||||||
assert se.date_to.isoformat() == "2017-07-01T12:00:00+00:00"
|
|
||||||
assert str(se.location) == "Hamburg"
|
|
||||||
assert se.presale_start.isoformat() == "2017-06-20T10:00:00+00:00"
|
|
||||||
assert not se.presale_end
|
|
||||||
with scopes_disabled():
|
|
||||||
assert se.quotas.count() == 1
|
|
||||||
q = se.quotas.last()
|
|
||||||
assert q.name == "Q1"
|
|
||||||
assert q.size == 50
|
|
||||||
assert list(q.items.all()) == [self.ticket]
|
|
||||||
sei = SubEventItem.objects.get(subevent=se, item=self.ticket)
|
|
||||||
assert sei.price == 12
|
|
||||||
assert se.checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
def test_delete(self):
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk)
|
|
||||||
assert doc.select("button")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, {})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
# deleting the second event
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent2.pk, {})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert not SubEvent.objects.filter(pk=self.subevent2.pk).exists()
|
|
||||||
assert not SubEvent.objects.filter(pk=self.subevent1.pk).exists()
|
|
||||||
|
|
||||||
def test_delete_with_orders(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
o = Order.objects.create(
|
|
||||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
|
||||||
status=Order.STATUS_PENDING,
|
|
||||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
|
||||||
total=14, locale='en'
|
|
||||||
)
|
|
||||||
OrderPosition.objects.create(
|
|
||||||
order=o,
|
|
||||||
item=self.ticket,
|
|
||||||
subevent=self.subevent1,
|
|
||||||
price=Decimal("14"),
|
|
||||||
)
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, follow=True)
|
|
||||||
assert doc.select(".alert-danger")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/%d/delete' % self.subevent1.pk, {}, follow=True)
|
|
||||||
assert doc.select(".alert-danger")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert self.event1.subevents.filter(pk=self.subevent1.pk).exists()
|
|
||||||
|
|
||||||
def test_create_bulk(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'yearly',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'count',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'location_0': 'Loc',
|
|
||||||
'time_admission': '',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-id': '',
|
|
||||||
'quotas-0-name': 'Bar',
|
|
||||||
'quotas-0-size': '12',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'item-%d-price' % self.ticket.pk: '16',
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '1',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
'checkinlist_set-0-id': '',
|
|
||||||
'checkinlist_set-0-name': 'Foo',
|
|
||||||
'checkinlist_set-0-limit_products': str(self.ticket.pk),
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 10
|
|
||||||
|
|
||||||
assert str(ses[0].name) == "Foo"
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[0].date_to.isoformat() == "2018-04-03T13:29:31+00:00"
|
|
||||||
assert not ses[0].presale_start
|
|
||||||
assert ses[0].presale_end.isoformat() == "2018-04-02T11:29:31+00:00"
|
|
||||||
with scopes_disabled():
|
|
||||||
assert ses[0].quotas.count() == 1
|
|
||||||
assert list(ses[0].quotas.first().items.all()) == [self.ticket]
|
|
||||||
assert SubEventItem.objects.get(subevent=ses[0], item=self.ticket).price == 16
|
|
||||||
assert ses[0].checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
assert str(ses[1].name) == "Foo"
|
|
||||||
assert ses[1].date_from.isoformat() == "2019-04-03T11:29:31+00:00"
|
|
||||||
assert ses[1].date_to.isoformat() == "2019-04-03T13:29:31+00:00"
|
|
||||||
assert not ses[1].presale_start
|
|
||||||
assert ses[1].presale_end.isoformat() == "2019-04-02T11:29:31+00:00"
|
|
||||||
with scopes_disabled():
|
|
||||||
assert ses[1].quotas.count() == 1
|
|
||||||
assert list(ses[1].quotas.first().items.all()) == [self.ticket]
|
|
||||||
assert SubEventItem.objects.get(subevent=ses[0], item=self.ticket).price == 16
|
|
||||||
assert ses[1].checkinlist_set.count() == 1
|
|
||||||
|
|
||||||
assert ses[-1].date_from.isoformat() == "2027-04-03T11:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_daily_interval(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '2',
|
|
||||||
'rruleformset-0-freq': 'daily',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '1',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 183
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[110].date_from.isoformat() == "2018-11-09T12:29:31+00:00" # DST :)
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-04-02T11:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_daily_interval_multiple_times(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '2',
|
|
||||||
'rruleformset-0-freq': 'daily',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '2',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'timeformset-1-time_from': '15:29:31',
|
|
||||||
'timeformset-1-time_to': '17:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 183 * 2
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[1].date_from.isoformat() == "2018-04-03T13:29:31+00:00"
|
|
||||||
assert ses[220].date_from.isoformat() == "2018-11-09T12:29:31+00:00" # DST :)
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-04-02T13:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_exclude(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.get_doc('/control/event/ccc/30c3/subevents/bulk_add')
|
|
||||||
assert doc.select("input[name=rruleformset-TOTAL_FORMS]")
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '2',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'daily',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'rruleformset-1-interval': '1',
|
|
||||||
'rruleformset-1-freq': 'weekly',
|
|
||||||
'rruleformset-1-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-1-yearly_same': 'on',
|
|
||||||
'rruleformset-1-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-1-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-1-yearly_bymonth': '1',
|
|
||||||
'rruleformset-1-monthly_same': 'on',
|
|
||||||
'rruleformset-1-monthly_bysetpos': '1',
|
|
||||||
'rruleformset-1-monthly_byweekday': 'MO',
|
|
||||||
'rruleformset-1-weekly_byweekday': 'MO',
|
|
||||||
'rruleformset-1-end': 'until',
|
|
||||||
'rruleformset-1-count': '10',
|
|
||||||
'rruleformset-1-until': '2019-04-03',
|
|
||||||
'rruleformset-1-exclude': 'on',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_0': 'relative',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 314
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-03T11:29:31+00:00"
|
|
||||||
assert ses[5].date_from.isoformat() == "2018-04-08T11:29:31+00:00"
|
|
||||||
assert ses[6].date_from.isoformat() == "2018-04-10T11:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_monthly_interval(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'monthly',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'off',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '-1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO,TU,WE,TH,FR',
|
|
||||||
'rruleformset-0-weekly_byweekday': 'TH',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_0': 'unset',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 12
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-30T11:29:31+00:00"
|
|
||||||
assert ses[1].date_from.isoformat() == "2018-05-31T11:29:31+00:00"
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-03-29T12:29:31+00:00"
|
|
||||||
|
|
||||||
def test_create_bulk_weekly_interval(self):
|
|
||||||
with scopes_disabled():
|
|
||||||
self.event1.subevents.all().delete()
|
|
||||||
self.event1.settings.timezone = 'Europe/Berlin'
|
|
||||||
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_add', {
|
|
||||||
'rruleformset-TOTAL_FORMS': '1',
|
|
||||||
'rruleformset-INITIAL_FORMS': '0',
|
|
||||||
'rruleformset-MIN_NUM_FORMS': '0',
|
|
||||||
'rruleformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'rruleformset-0-interval': '1',
|
|
||||||
'rruleformset-0-freq': 'weekly',
|
|
||||||
'rruleformset-0-dtstart': '2018-04-03',
|
|
||||||
'rruleformset-0-yearly_same': 'on',
|
|
||||||
'rruleformset-0-yearly_bysetpos': '1',
|
|
||||||
'rruleformset-0-yearly_byweekday': 'MO',
|
|
||||||
'rruleformset-0-yearly_bymonth': '1',
|
|
||||||
'rruleformset-0-monthly_same': 'on',
|
|
||||||
'rruleformset-0-monthly_bysetpos': '-1',
|
|
||||||
'rruleformset-0-monthly_byweekday': 'MO,TU,WE,TH,FR',
|
|
||||||
'rruleformset-0-weekly_byweekday': 'TH',
|
|
||||||
'rruleformset-0-end': 'until',
|
|
||||||
'rruleformset-0-count': '10',
|
|
||||||
'rruleformset-0-until': '2019-04-03',
|
|
||||||
'timeformset-TOTAL_FORMS': '1',
|
|
||||||
'timeformset-INITIAL_FORMS': '0',
|
|
||||||
'timeformset-MIN_NUM_FORMS': '1',
|
|
||||||
'timeformset-MAX_NUM_FORMS': '1000',
|
|
||||||
'timeformset-0-time_from': '13:29:31',
|
|
||||||
'timeformset-0-time_to': '15:29:31',
|
|
||||||
'name_0': 'Foo',
|
|
||||||
'active': 'on',
|
|
||||||
'frontpage_text_0': '',
|
|
||||||
'rel_presale_start_0': 'unset',
|
|
||||||
'rel_presale_start_1': '',
|
|
||||||
'rel_presale_start_2': '1',
|
|
||||||
'rel_presale_start_3': 'date_from',
|
|
||||||
'rel_presale_start_4': '',
|
|
||||||
'rel_presale_end_0': 'unset',
|
|
||||||
'rel_presale_end_1': '',
|
|
||||||
'rel_presale_end_2': '1',
|
|
||||||
'rel_presale_end_3': 'date_from',
|
|
||||||
'rel_presale_end_4': '13:29:31',
|
|
||||||
'quotas-TOTAL_FORMS': '1',
|
|
||||||
'quotas-INITIAL_FORMS': '0',
|
|
||||||
'quotas-MIN_NUM_FORMS': '0',
|
|
||||||
'quotas-MAX_NUM_FORMS': '1000',
|
|
||||||
'quotas-0-name': 'Q1',
|
|
||||||
'quotas-0-size': '50',
|
|
||||||
'quotas-0-itemvars': str(self.ticket.pk),
|
|
||||||
'checkinlist_set-TOTAL_FORMS': '0',
|
|
||||||
'checkinlist_set-INITIAL_FORMS': '0',
|
|
||||||
'checkinlist_set-MIN_NUM_FORMS': '0',
|
|
||||||
'checkinlist_set-MAX_NUM_FORMS': '1000',
|
|
||||||
})
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
ses = list(self.event1.subevents.order_by('date_from'))
|
|
||||||
assert len(ses) == 52
|
|
||||||
|
|
||||||
assert ses[0].date_from.isoformat() == "2018-04-05T11:29:31+00:00"
|
|
||||||
assert ses[1].date_from.isoformat() == "2018-04-12T11:29:31+00:00"
|
|
||||||
assert ses[-1].date_from.isoformat() == "2019-03-28T12:29:31+00:00"
|
|
||||||
|
|
||||||
def test_delete_bulk(self):
|
|
||||||
self.subevent2.active = True
|
|
||||||
self.subevent2.save()
|
|
||||||
with scopes_disabled():
|
|
||||||
o = Order.objects.create(
|
|
||||||
code='FOO', event=self.event1, email='dummy@dummy.test',
|
|
||||||
status=Order.STATUS_PENDING,
|
|
||||||
datetime=now(), expires=now() + datetime.timedelta(days=10),
|
|
||||||
total=14, locale='en'
|
|
||||||
)
|
|
||||||
OrderPosition.objects.create(
|
|
||||||
order=o,
|
|
||||||
item=self.ticket,
|
|
||||||
subevent=self.subevent1,
|
|
||||||
price=Decimal("14"),
|
|
||||||
)
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_action', {
|
|
||||||
'subevent': [str(self.subevent1.pk), str(self.subevent2.pk)],
|
|
||||||
'action': 'delete_confirm'
|
|
||||||
}, follow=True)
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert not self.event1.subevents.filter(pk=self.subevent2.pk).exists()
|
|
||||||
assert self.event1.subevents.get(pk=self.subevent1.pk).active is False
|
|
||||||
|
|
||||||
def test_disable_bulk(self):
|
|
||||||
self.subevent2.active = True
|
|
||||||
self.subevent2.save()
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_action', {
|
|
||||||
'subevent': str(self.subevent2.pk),
|
|
||||||
'action': 'disable'
|
|
||||||
}, follow=True)
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert self.event1.subevents.get(pk=self.subevent2.pk).active is False
|
|
||||||
|
|
||||||
def test_enable_bulk(self):
|
|
||||||
self.subevent2.active = False
|
|
||||||
self.subevent2.save()
|
|
||||||
doc = self.post_doc('/control/event/ccc/30c3/subevents/bulk_action', {
|
|
||||||
'subevent': str(self.subevent2.pk),
|
|
||||||
'action': 'enable'
|
|
||||||
}, follow=True)
|
|
||||||
assert doc.select(".alert-success")
|
|
||||||
with scopes_disabled():
|
|
||||||
assert self.event1.subevents.get(pk=self.subevent2.pk).active is True
|
|
||||||
|
|
||||||
|
|
||||||
class EventDeletionTest(SoupTest):
|
class EventDeletionTest(SoupTest):
|
||||||
@scopes_disabled()
|
@scopes_disabled()
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|||||||
1045
src/tests/control/test_subevents.py
Normal file
1045
src/tests/control/test_subevents.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user