diff --git a/.gitattributes b/.gitattributes index 79da8879f9..db2e5ec1b2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,6 +8,7 @@ src/static/fileupload/* linguist-vendored src/static/vuejs/* linguist-vendored src/static/select2/* linguist-vendored src/static/charts/* linguist-vendored +src/static/rrule/* linguist-vendored src/static/iframeresizer/* linguist-vendored src/pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/fabric.* linguist-vendored src/pretix/plugins/ticketoutputpdf/static/pretixplugins/ticketoutputpdf/pdf.* linguist-vendored diff --git a/src/pretix/base/reldate.py b/src/pretix/base/reldate.py index 4450e46165..ee6eb7378a 100644 --- a/src/pretix/base/reldate.py +++ b/src/pretix/base/reldate.py @@ -82,7 +82,6 @@ class RelativeDateWrapper: new_date = new_date.astimezone(tz) newoffset = new_date.utcoffset() new_date += oldoffset - newoffset - return new_date def to_string(self) -> str: @@ -154,6 +153,11 @@ class RelativeDateTimeField(forms.MultiValueField): ('absolute', _('Fixed date:')), ('relative', _('Relative date:')), ] + if kwargs.get('limit_choices'): + limit = kwargs.pop('limit_choices') + choices = [(k, v) for k, v in BASE_CHOICES if k in limit] + else: + choices = BASE_CHOICES if not kwargs.get('required', True): status_choices.insert(0, ('unset', _('Not set'))) fields = ( @@ -168,7 +172,7 @@ class RelativeDateTimeField(forms.MultiValueField): required=False ), forms.ChoiceField( - choices=BASE_CHOICES, + choices=choices, required=False ), forms.TimeField( @@ -176,7 +180,7 @@ class RelativeDateTimeField(forms.MultiValueField): ), ) if 'widget' not in kwargs: - kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=BASE_CHOICES) + kwargs['widget'] = RelativeDateTimeWidget(status_choices=status_choices, base_choices=choices) kwargs.pop('max_length', 0) kwargs.pop('empty_value', 0) super().__init__( diff --git a/src/pretix/control/context.py b/src/pretix/control/context.py index ddf44437cc..2a6c2e2990 100644 --- a/src/pretix/control/context.py +++ b/src/pretix/control/context.py @@ -9,7 +9,9 @@ from django.utils.translation import get_language from pretix.base.models.auth import StaffSession from pretix.base.settings import GlobalSettingsObject -from ..helpers.i18n import get_javascript_format, get_moment_locale +from ..helpers.i18n import ( + get_javascript_format, get_javascript_output_format, get_moment_locale, +) from .signals import html_head, nav_event, nav_global, nav_topbar SessionStore = import_module(settings.SESSION_ENGINE).SessionStore @@ -82,6 +84,7 @@ def contextprocessor(request): ctx['js_datetime_format'] = get_javascript_format('DATETIME_INPUT_FORMATS') ctx['js_date_format'] = get_javascript_format('DATE_INPUT_FORMATS') + ctx['js_long_date_format'] = get_javascript_output_format('DATE_FORMAT') ctx['js_time_format'] = get_javascript_format('TIME_INPUT_FORMATS') ctx['js_locale'] = get_moment_locale() ctx['select2locale'] = get_language()[:2] diff --git a/src/pretix/control/forms/subevents.py b/src/pretix/control/forms/subevents.py index a710a2bc0e..935d7fcd36 100644 --- a/src/pretix/control/forms/subevents.py +++ b/src/pretix/control/forms/subevents.py @@ -1,10 +1,16 @@ +from datetime import timedelta + from django import forms +from django.forms import formset_factory from django.utils.functional import cached_property +from django.utils.timezone import now +from django.utils.translation import pgettext_lazy, ugettext_lazy as _ from i18nfield.forms import I18nInlineFormSet from pretix.base.forms import I18nModelForm from pretix.base.models.event import SubEvent, SubEventMetaValue from pretix.base.models.items import SubEventItem +from pretix.base.reldate import RelativeDateTimeField from pretix.base.templatetags.money import money_filter from pretix.control.forms import SplitDateTimePickerWidget from pretix.helpers.money import change_decimal_field @@ -46,6 +52,44 @@ class SubEventForm(I18nModelForm): } +class SubEventBulkForm(SubEventForm): + time_from = forms.TimeField( + label=_('Event start time'), + widget=forms.TimeInput(attrs={'class': 'timepickerfield'}) + ) + time_to = forms.TimeField( + label=_('Event end time'), + widget=forms.TimeInput(attrs={'class': 'timepickerfield'}), + required=False + ) + time_admission = forms.TimeField( + label=_('Admission time'), + widget=forms.TimeInput(attrs={'class': 'timepickerfield'}), + required=False + ) + rel_presale_start = RelativeDateTimeField( + label=_('Start of presale'), + help_text=_('Optional. No products will be sold before this date.'), + required=False, + limit_choices=('date_from', 'date_to'), + ) + rel_presale_end = RelativeDateTimeField( + label=_('End of presale'), + help_text=_('Optional. No products will be sold after this date. If you do not set this value, the presale ' + 'will end after the end date of your event.'), + required=False, + limit_choices=('date_from', 'date_to'), + ) + + def __init__(self, *args, **kwargs): + self.event = kwargs['event'] + super().__init__(*args, **kwargs) + self.fields['location'].widget.attrs['rows'] = '3' + del self.fields['date_from'] + del self.fields['date_to'] + del self.fields['date_admission'] + + class SubEventItemOrVariationFormMixin: def __init__(self, *args, **kwargs): self.item = kwargs.pop('item') @@ -97,6 +141,7 @@ class QuotaFormSet(I18nInlineFormSet): kwargs['locales'] = self.locales kwargs['event'] = self.event kwargs['items'] = self.items + kwargs['items'] = self.items return super()._construct_form(i, **kwargs) @property @@ -155,3 +200,175 @@ class CheckinListFormSet(I18nInlineFormSet): ) self.add_fields(form, None) return form + + +class RRuleForm(forms.Form): + # TODO: calendar.setfirstweekday + exclude = forms.BooleanField( + label=_('Exclude these dates instead of adding them.'), + required=False + ) + freq = forms.ChoiceField( + choices=[ + ('yearly', _('year(s)')), + ('monthly', _('month(s)')), + ('weekly', _('week(s)')), + ('daily', _('day(s)')), + ] + ) + interval = forms.IntegerField( + label=_('Interval'), + initial=1 + ) + dtstart = forms.DateField( + label=_('Start date'), + widget=forms.DateInput( + attrs={ + 'class': 'datepickerfield', + 'required': 'required' + } + ), + initial=lambda: now().date() + ) + + end = forms.ChoiceField( + choices=[ + ('count', ''), + ('until', ''), + ], + initial='count', + widget=forms.RadioSelect + ) + count = forms.IntegerField( + label=_('Number of repititions'), + initial=10 + ) + until = forms.DateField( + widget=forms.DateInput( + attrs={ + 'class': 'datepickerfield', + 'required': 'required' + } + ), + label=_('Last date'), + required=True, + initial=lambda: now() + timedelta(days=365) + ) + + yearly_bysetpos = forms.ChoiceField( + choices=[ + ('1', pgettext_lazy('rrule', 'first')), + ('2', pgettext_lazy('rrule', 'second')), + ('3', pgettext_lazy('rrule', 'third')), + ('-1', pgettext_lazy('rrule', 'last')), + ], + required=False + ) + yearly_same = forms.ChoiceField( + choices=[ + ('on', ''), + ('off', ''), + ], + initial='on', + widget=forms.RadioSelect + ) + yearly_byweekday = forms.ChoiceField( + choices=[ + ('MO', _('Monday')), + ('TU', _('Tuesday')), + ('WE', _('Wednesday')), + ('TH', _('Thursday')), + ('FR', _('Friday')), + ('SA', _('Saturday')), + ('SU', _('Sunday')), + ('MO,TU,WE,TH,FR,SA,SU', _('Day')), + ('MO,TU,WE,TH,FR', _('Weekday')), + ('SA,SU', _('Weekend day')), + ], + required=False + ) + yearly_bymonth = forms.ChoiceField( + choices=[ + ('1', _('January')), + ('2', _('February')), + ('3', _('March')), + ('4', _('April')), + ('5', _('May')), + ('6', _('June')), + ('7', _('July')), + ('8', _('August')), + ('9', _('September')), + ('10', _('October')), + ('11', _('November')), + ('12', _('December')), + ], + required=False + ) + + monthly_same = forms.ChoiceField( + choices=[ + ('on', ''), + ('off', ''), + ], + initial='on', + widget=forms.RadioSelect + ) + monthly_bysetpos = forms.ChoiceField( + choices=[ + ('1', pgettext_lazy('rrule', 'first')), + ('2', pgettext_lazy('rrule', 'second')), + ('3', pgettext_lazy('rrule', 'third')), + ('-1', pgettext_lazy('rrule', 'last')), + ], + required=False + ) + monthly_byweekday = forms.ChoiceField( + choices=[ + ('MO', _('Monday')), + ('TU', _('Tuesday')), + ('WE', _('Wednesday')), + ('TH', _('Thursday')), + ('FR', _('Friday')), + ('SA', _('Saturday')), + ('SU', _('Sunday')), + ('MO,TU,WE,TH,FR,SA,SU', _('Day')), + ('MO,TU,WE,TH,FR', _('Weekday')), + ('SA,SU', _('Weekend day')), + ], + required=False + ) + + weekly_byweekday = forms.MultipleChoiceField( + choices=[ + ('MO', _('Monday')), + ('TU', _('Tuesday')), + ('WE', _('Wednesday')), + ('TH', _('Thursday')), + ('FR', _('Friday')), + ('SA', _('Saturday')), + ('SU', _('Sunday')), + ], + required=False, + widget=forms.CheckboxSelectMultiple + ) + + def parse_weekdays(self, value): + m = { + 'MO': 0, + 'TU': 1, + 'WE': 2, + 'TH': 3, + 'FR': 4, + 'SA': 5, + 'SU': 6 + } + if ',' in value: + return [m.get(a) for a in value.split(',')] + else: + return m.get(value) + + +RRuleFormSet = formset_factory( + RRuleForm, + can_order=False, can_delete=True, extra=1 +) diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html index 5fd55f3797..5885c2a10d 100644 --- a/src/pretix/control/templates/pretixcontrol/base.html +++ b/src/pretix/control/templates/pretixcontrol/base.html @@ -30,6 +30,7 @@ + @@ -52,7 +53,7 @@ {% block custom_header %}{% endblock %} -
+