mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Bulk creation for event series dates (#848)
* copy-from things * Some frontend * rrule UI * . * Fixes * UI improvements * First test * Tests
This commit is contained in:
@@ -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__(
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
<script type="text/javascript" src="{% static "charts/raphael-min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "charts/morris.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "clipboard/clipboard.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "rrule/rrule.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/jquery.qrcode.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/clipboard.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "pretixcontrol/js/menu.js" %}"></script>
|
||||
@@ -52,7 +53,7 @@
|
||||
<link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}">
|
||||
{% block custom_header %}{% endblock %}
|
||||
</head>
|
||||
<body data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}" data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}" data-payment-weekdays-disabled="{{ js_payment_weekdays_disabled }}" data-select2-locale="{{ select2locale }}">
|
||||
<body data-datetimeformat="{{ js_datetime_format }}" data-timeformat="{{ js_time_format }}" data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}" data-payment-weekdays-disabled="{{ js_payment_weekdays_disabled }}" data-select2-locale="{{ select2locale }}" data-longdateformat="{{ js_long_date_format }}">
|
||||
<div id="wrapper">
|
||||
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">
|
||||
<div class="navbar-header">
|
||||
|
||||
442
src/pretix/control/templates/pretixcontrol/subevents/bulk.html
Normal file
442
src/pretix/control/templates/pretixcontrol/subevents/bulk.html
Normal file
@@ -0,0 +1,442 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% load formset_tags %}
|
||||
{% load captureas %}
|
||||
{% load eventsignal %}
|
||||
{% block title %}{% trans "Date" context "subevent" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Create multiple dates" context "subevent" %}</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="row">
|
||||
<div class="col-md-8">
|
||||
<fieldset>
|
||||
<legend>{% trans "Dates" context "subevent" %}</legend>
|
||||
|
||||
<div class="formset" data-formset data-formset-prefix="{{ rrule_formset.prefix }}"
|
||||
id="rrule-formset">
|
||||
{{ rrule_formset.management_form }}
|
||||
{% bootstrap_formset_errors rrule_formset %}
|
||||
<div data-formset-body>
|
||||
{% for f in rrule_formset %}
|
||||
{% bootstrap_form_errors f %}
|
||||
<div data-formset-form>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{% trans "Repetition rule" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<button type="button" class="btn btn-danger btn-xs"
|
||||
data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body form-inline rrule-form">
|
||||
<div class="sr-only">
|
||||
{{ f.id }}
|
||||
{% bootstrap_field f.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
{% captureas ffield_freq %}
|
||||
{% bootstrap_field f.freq layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_interval %}
|
||||
{% bootstrap_field f.interval layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_dtstart %}
|
||||
{% bootstrap_field f.dtstart layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_yearly_bysetpos %}
|
||||
{% bootstrap_field f.yearly_bysetpos layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_yearly_byweekday %}
|
||||
{% bootstrap_field f.yearly_byweekday layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_yearly_bymonth %}
|
||||
{% bootstrap_field f.yearly_bymonth layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_monthly_bysetpos %}
|
||||
{% bootstrap_field f.monthly_bysetpos layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_monthly_byweekday %}
|
||||
{% bootstrap_field f.monthly_byweekday layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_count %}
|
||||
{% bootstrap_field f.count layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_until %}
|
||||
{% bootstrap_field f.until layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
|
||||
{% blocktrans trimmed with freq=ffield_freq interval=ffield_interval start=ffield_dtstart %}
|
||||
Repeat every {{ interval }} {{ freq }}, starting at {{ start }}.
|
||||
{% endblocktrans %}<br>
|
||||
|
||||
<div class="repeat-yearly">
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ f.yearly_same.0 }}
|
||||
{% trans "At the same date every year" %}
|
||||
</label><br>
|
||||
<label>
|
||||
{{ f.yearly_same.1 }}
|
||||
{% blocktrans trimmed with setpos=ffield_yearly_bysetpos weekday=ffield_yearly_byweekday month=ffield_yearly_bymonth %}
|
||||
On the {{ setpos }} {{ weekday }} of {{ month }}
|
||||
{% endblocktrans %}<br>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repeat-monthly">
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ f.monthly_same.0 }}
|
||||
{% trans "At the same date every month" %}
|
||||
</label><br>
|
||||
<label>
|
||||
{{ f.monthly_same.1 }}
|
||||
{% blocktrans trimmed with setpos=ffield_monthly_bysetpos weekday=ffield_monthly_byweekday %}
|
||||
On the {{ setpos }} {{ weekday }}
|
||||
{% endblocktrans %}<br>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repeat-weekly">
|
||||
{% bootstrap_field f.weekly_byweekday layout="inline" %}
|
||||
</div>
|
||||
<div class="repeat-until">
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ f.end.0 }}
|
||||
{% blocktrans trimmed with count=ffield_count %}
|
||||
Repeat for {{ count }} times
|
||||
{% endblocktrans %}
|
||||
</label><br>
|
||||
<label>
|
||||
{{ f.end.1 }}
|
||||
{% blocktrans trimmed with until=ffield_until %}
|
||||
Repeat until {{ until }}
|
||||
{% endblocktrans %}<br>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field f.exclude layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<script type="form-template" data-formset-empty-form>
|
||||
{% escapescript %}
|
||||
<div data-formset-form>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h4 class="panel-title">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
{% trans "Repetition rule" %}
|
||||
</div>
|
||||
<div class="col-md-2 text-right">
|
||||
<button type="button" class="btn btn-danger btn-xs"
|
||||
data-formset-delete-button>
|
||||
<i class="fa fa-trash"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
<div class="panel-body form-inline rrule-form">
|
||||
<div class="sr-only">
|
||||
{{ rrule_formset.empty_form.id }}
|
||||
{% bootstrap_field rrule_formset.empty_form.DELETE form_group_class="" layout="inline" %}
|
||||
</div>
|
||||
{% captureas ffield_freq %}
|
||||
{% bootstrap_field rrule_formset.empty_form.freq layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_interval %}
|
||||
{% bootstrap_field rrule_formset.empty_form.interval layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_dtstart %}
|
||||
{% bootstrap_field rrule_formset.empty_form.dtstart layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_yearly_bysetpos %}
|
||||
{% bootstrap_field rrule_formset.empty_form.yearly_bysetpos layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_yearly_byweekday %}
|
||||
{% bootstrap_field rrule_formset.empty_form.yearly_byweekday layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_yearly_bymonth %}
|
||||
{% bootstrap_field rrule_formset.empty_form.yearly_bymonth layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_monthly_bysetpos %}
|
||||
{% bootstrap_field rrule_formset.empty_form.monthly_bysetpos layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_monthly_byweekday %}
|
||||
{% bootstrap_field rrule_formset.empty_form.monthly_byweekday layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_count %}
|
||||
{% bootstrap_field rrule_formset.empty_form.count layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
{% captureas ffield_until %}
|
||||
{% bootstrap_field rrule_formset.empty_form.until layout="inline" %}
|
||||
{% endcaptureas %}
|
||||
|
||||
{% blocktrans trimmed with freq=ffield_freq interval=ffield_interval start=ffield_dtstart %}
|
||||
Repeat every {{ interval }} {{ freq }}, starting at {{ start }}.
|
||||
{% endblocktrans %}<br>
|
||||
|
||||
<div class="repeat-yearly">
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ rrule_formset.empty_form.yearly_same.0 }}
|
||||
{% trans "At the same date every year" %}
|
||||
</label><br>
|
||||
<label>
|
||||
{{ rrule_formset.empty_form.yearly_same.1 }}
|
||||
{% blocktrans trimmed with setpos=ffield_yearly_bysetpos weekday=ffield_yearly_byweekday month=ffield_yearly_bymonth %}
|
||||
On the {{ setpos }} {{ weekday }} of {{ month }}
|
||||
{% endblocktrans %}<br>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repeat-monthly">
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ rrule_formset.empty_form.monthly_same.0 }}
|
||||
{% trans "At the same date every month" %}
|
||||
</label><br>
|
||||
<label>
|
||||
{{ rrule_formset.empty_form.monthly_same.1 }}
|
||||
{% blocktrans trimmed with setpos=ffield_monthly_bysetpos weekday=ffield_monthly_byweekday %}
|
||||
On the {{ setpos }} {{ weekday }}
|
||||
{% endblocktrans %}<br>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="repeat-weekly">
|
||||
{% bootstrap_field rrule_formset.empty_form.weekly_byweekday layout="inline" %}
|
||||
</div>
|
||||
<div class="repeat-until">
|
||||
<div class="radio">
|
||||
<label>
|
||||
{{ rrule_formset.empty_form.end.0 }}
|
||||
{% blocktrans trimmed with count=ffield_count %}
|
||||
Repeat for {{ count }} times
|
||||
{% endblocktrans %}
|
||||
</label><br>
|
||||
<label>
|
||||
{{ rrule_formset.empty_form.end.1 }}
|
||||
{% blocktrans trimmed with until=ffield_until %}
|
||||
Repeat until {{ until }}
|
||||
{% endblocktrans %}<br>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_field rrule_formset.empty_form.exclude layout="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endescapescript %}
|
||||
</script>
|
||||
<p>
|
||||
<button type="button" class="btn btn-default" data-formset-add>
|
||||
<i class="fa fa-plus"></i> {% trans "Add a new rule" %}</button>
|
||||
</p>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<fieldset>
|
||||
<legend>{% trans "Preview" context "subevent" %}</legend>
|
||||
<ul id="rrule-preview">
|
||||
</ul>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset>
|
||||
<legend>{% trans "General information" %}</legend>
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
{% bootstrap_field form.active layout="control" %}
|
||||
{% bootstrap_field form.time_from layout="control" %}
|
||||
{% bootstrap_field form.time_to layout="control" %}
|
||||
{% bootstrap_field form.location layout="control" %}
|
||||
{% bootstrap_field form.time_admission layout="control" %}
|
||||
{% bootstrap_field form.frontpage_text layout="control" %}
|
||||
{% 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="inline" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Timeline" %}</legend>
|
||||
{% bootstrap_field form.rel_presale_start layout="control" %}
|
||||
{% bootstrap_field form.rel_presale_end layout="control" %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Quotas" %}</legend>
|
||||
<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">
|
||||
<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" %}
|
||||
</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">
|
||||
<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" %}
|
||||
</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>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Item prices" %}</legend>
|
||||
{% for f in itemvar_forms %}
|
||||
{% bootstrap_field f.price addon_after=request.event.currency layout="control" %}
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>{% trans "Check-in lists" %}</legend>
|
||||
<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">
|
||||
<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" %}
|
||||
</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">
|
||||
<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" %}
|
||||
</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>
|
||||
</fieldset>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -38,6 +38,9 @@
|
||||
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||
{% trans "Create a new date" context "subevent" %}</a>
|
||||
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-default"><i class="fa fa-plus"></i>
|
||||
{% trans "Create many new dates" context "subevent" %}</a>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-quotas">
|
||||
@@ -97,7 +100,24 @@
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ s.id }}" class="btn btn-default btn-sm"><i class="fa fa-copy"></i></a>
|
||||
<div class="btn-group {% if forloop.revcounter0 < 2 %}dropup{% endif %}">
|
||||
<button type="button" class="btn btn-default btn-sm dropdown-toggle"
|
||||
data-toggle="dropdown">
|
||||
<span class="fa fa-copy"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ s.id }}">
|
||||
{% trans "Use as a template for a new date" context "subevent" %}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{% url "control:event.subevents.bulk" organizer=request.event.organizer.slug event=request.event.slug %}?copy_from={{ s.id }}">
|
||||
{% trans "Use as a template for many new dates" context "subevent" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<a href="{% url "control:event.subevent.delete" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -103,6 +103,7 @@ urlpatterns = [
|
||||
url(r'^subevents/(?P<subevent>\d+)/delete$', subevents.SubEventDelete.as_view(),
|
||||
name='event.subevent.delete'),
|
||||
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'^items/$', item.ItemList.as_view(), name='event.items'),
|
||||
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'),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import copy
|
||||
from datetime import datetime
|
||||
|
||||
from dateutil.rrule import DAILY, MONTHLY, WEEKLY, YEARLY, rrule, rruleset
|
||||
from django.contrib import messages
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import transaction
|
||||
@@ -7,19 +9,25 @@ from django.db.models import F, IntegerField, OuterRef, Prefetch, Subquery, Sum
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.forms import inlineformset_factory
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import make_aware
|
||||
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
|
||||
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
|
||||
|
||||
from pretix.base.models.checkin import CheckinList
|
||||
from pretix.base.models.event import SubEvent, SubEventMetaValue
|
||||
from pretix.base.models.items import Quota, SubEventItem, SubEventItemVariation
|
||||
from pretix.base.models.items import (
|
||||
ItemVariation, Quota, SubEventItem, SubEventItemVariation,
|
||||
)
|
||||
from pretix.base.reldate import RelativeDate, RelativeDateWrapper
|
||||
from pretix.control.forms.checkin import CheckinListForm
|
||||
from pretix.control.forms.filter import SubEventFilterForm
|
||||
from pretix.control.forms.item import QuotaForm
|
||||
from pretix.control.forms.subevents import (
|
||||
CheckinListFormSet, QuotaFormSet, SubEventForm, SubEventItemForm,
|
||||
SubEventItemVariationForm, SubEventMetaValueForm,
|
||||
CheckinListFormSet, QuotaFormSet, RRuleFormSet, SubEventBulkForm,
|
||||
SubEventForm, SubEventItemForm, SubEventItemVariationForm,
|
||||
SubEventMetaValueForm,
|
||||
)
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views import PaginationMixin
|
||||
@@ -92,7 +100,7 @@ class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
def get(self, request, *args, **kwargs):
|
||||
if self.get_object().orderposition_set.count() > 0:
|
||||
messages.error(request, pgettext_lazy('subevent', 'A date can not be deleted if orders already have been '
|
||||
'placed.'))
|
||||
'placed.'))
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
return super().get(request, *args, **kwargs)
|
||||
|
||||
@@ -103,7 +111,7 @@ class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
|
||||
if self.object.orderposition_set.count() > 0:
|
||||
messages.error(request, pgettext_lazy('subevent', 'A date can not be deleted if orders already have been '
|
||||
'placed.'))
|
||||
'placed.'))
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
elif not self.object.allow_delete(): # checking if this is the last date in the event series
|
||||
messages.error(request, pgettext_lazy('subevent', 'The last date of an event series can not be deleted.'))
|
||||
@@ -142,7 +150,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
extra = 0
|
||||
kwargs = {}
|
||||
|
||||
if self.copy_from:
|
||||
if self.copy_from and self.request.method != "POST":
|
||||
kwargs['initial'] = [
|
||||
{
|
||||
'name': cl.name,
|
||||
@@ -152,7 +160,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
} for cl in self.copy_from.checkinlist_set.prefetch_related('limit_products')
|
||||
]
|
||||
extra = len(kwargs['initial'])
|
||||
elif not self.object:
|
||||
elif not self.object and self.request.method != "POST":
|
||||
kwargs['initial'] = [
|
||||
{
|
||||
'name': '',
|
||||
@@ -179,7 +187,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
extra = 0
|
||||
kwargs = {}
|
||||
|
||||
if self.copy_from:
|
||||
if self.copy_from and self.request.method != "POST":
|
||||
kwargs['initial'] = [
|
||||
{
|
||||
'size': q.size,
|
||||
@@ -199,9 +207,11 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
if self.object:
|
||||
kwargs['queryset'] = self.object.quotas.prefetch_related('items', 'variations')
|
||||
|
||||
return formsetclass(self.request.POST if self.request.method == "POST" else None,
|
||||
instance=self.object,
|
||||
event=self.request.event, **kwargs)
|
||||
return formsetclass(
|
||||
self.request.POST if self.request.method == "POST" else None,
|
||||
instance=self.object,
|
||||
event=self.request.event, **kwargs
|
||||
)
|
||||
|
||||
def save_cl_formset(self, obj):
|
||||
for form in self.cl_formset.initial_forms:
|
||||
@@ -285,7 +295,7 @@ class SubEventEditorMixin(MetaDataEditorMixin):
|
||||
|
||||
@cached_property
|
||||
def copy_from(self):
|
||||
if self.request.GET.get("copy_from") and not getattr(self, 'object'):
|
||||
if self.request.GET.get("copy_from") and not getattr(self, 'object', None):
|
||||
try:
|
||||
return self.request.event.subevents.get(pk=self.request.GET.get("copy_from"))
|
||||
except SubEvent.DoesNotExist:
|
||||
@@ -428,6 +438,7 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
form.instance.event = self.request.event
|
||||
messages.success(self.request, pgettext_lazy('subevent', 'The new date has been created.'))
|
||||
ret = super().form_valid(form)
|
||||
self.object = form.instance
|
||||
form.instance.log_action('pretix.subevent.added', data=dict(form.cleaned_data), user=self.request.user)
|
||||
|
||||
self.save_formset(form.instance)
|
||||
@@ -435,6 +446,239 @@ class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateVi
|
||||
for f in self.itemvar_forms:
|
||||
f.instance.subevent = form.instance
|
||||
f.save()
|
||||
self.object = form.instance
|
||||
for f in self.meta_forms:
|
||||
f.instance.subevent = form.instance
|
||||
self.save_meta()
|
||||
return ret
|
||||
|
||||
@cached_property
|
||||
def meta_forms(self):
|
||||
def clone(o):
|
||||
o = copy.copy(o)
|
||||
o.pk = None
|
||||
return o
|
||||
|
||||
if self.copy_from:
|
||||
val_instances = {
|
||||
v.property_id: clone(v) for v in self.copy_from.meta_values.all()
|
||||
}
|
||||
else:
|
||||
val_instances = {}
|
||||
|
||||
formlist = []
|
||||
|
||||
for p in self.request.organizer.meta_properties.all():
|
||||
formlist.append(self._make_meta_form(p, val_instances))
|
||||
return formlist
|
||||
|
||||
|
||||
class SubEventBulkCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateView):
|
||||
model = SubEvent
|
||||
template_name = 'pretixcontrol/subevents/bulk.html'
|
||||
permission = 'can_change_settings'
|
||||
context_object_name = 'subevent'
|
||||
form_class = SubEventBulkForm
|
||||
|
||||
def is_valid(self, form):
|
||||
return self.rrule_formset.is_valid() and super().is_valid(form)
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('control:event.subevents', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
@cached_property
|
||||
def rrule_formset(self):
|
||||
return RRuleFormSet(
|
||||
data=self.request.POST if self.request.method == "POST" else None,
|
||||
prefix='rruleformset'
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
ctx = super().get_context_data(**kwargs)
|
||||
ctx['rrule_formset'] = self.rrule_formset
|
||||
return ctx
|
||||
|
||||
@cached_property
|
||||
def meta_forms(self):
|
||||
def clone(o):
|
||||
o = copy.copy(o)
|
||||
o.pk = None
|
||||
return o
|
||||
|
||||
if self.copy_from:
|
||||
val_instances = {
|
||||
v.property_id: clone(v) for v in self.copy_from.meta_values.all()
|
||||
}
|
||||
else:
|
||||
val_instances = {}
|
||||
|
||||
formlist = []
|
||||
|
||||
for p in self.request.organizer.meta_properties.all():
|
||||
formlist.append(self._make_meta_form(p, val_instances))
|
||||
return formlist
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
initial = {}
|
||||
kwargs['event'] = self.request.event
|
||||
tz = self.request.event.timezone
|
||||
if self.copy_from:
|
||||
i = copy.copy(self.copy_from)
|
||||
i.pk = None
|
||||
kwargs['instance'] = i
|
||||
initial['time_from'] = i.date_from.astimezone(tz).time()
|
||||
initial['time_to'] = i.date_to.astimezone(tz).time() if i.date_to else None
|
||||
initial['time_admission'] = i.date_admission.astimezone(tz).time() if i.date_admission else None
|
||||
initial['rel_presale_start'] = RelativeDateWrapper(RelativeDate(
|
||||
days_before=(i.date_from.astimezone(tz).date() - i.presale_start.astimezone(tz).date()).days,
|
||||
base_date_name='date_from',
|
||||
time=i.presale_start.astimezone(tz).time()
|
||||
)) if i.presale_start else None
|
||||
initial['rel_presale_end'] = RelativeDateWrapper(RelativeDate(
|
||||
days_before=(i.date_from.astimezone(tz).date() - i.presale_end.astimezone(tz).date()).days,
|
||||
base_date_name='date_from',
|
||||
time=i.presale_end.astimezone(tz).time()
|
||||
)) if i.presale_start else None
|
||||
else:
|
||||
kwargs['instance'] = SubEvent(event=self.request.event)
|
||||
kwargs['initial'] = initial
|
||||
return kwargs
|
||||
|
||||
def get_rrule_set(self):
|
||||
s = rruleset()
|
||||
for f in self.rrule_formset:
|
||||
if f in self.rrule_formset.deleted_forms:
|
||||
continue
|
||||
|
||||
rule_kwargs = {}
|
||||
rule_kwargs['dtstart'] = f.cleaned_data['dtstart']
|
||||
rule_kwargs['interval'] = f.cleaned_data['interval']
|
||||
|
||||
if f.cleaned_data['freq'] == 'yearly':
|
||||
freq = YEARLY
|
||||
if f.cleaned_data['yearly_same'] == "off":
|
||||
rule_kwargs['bysetpos'] = int(f.cleaned_data['yearly_bysetpos'])
|
||||
rule_kwargs['byweekday'] = f.parse_weekdays(f.cleaned_data['yearly_byweekday'])
|
||||
rule_kwargs['bymonth'] = int(f.cleaned_data['yearly_bymonth'])
|
||||
|
||||
elif f.cleaned_data['freq'] == 'monthly':
|
||||
freq = MONTHLY
|
||||
|
||||
if f.cleaned_data['monthly_same'] == "off":
|
||||
rule_kwargs['bysetpos'] = int(f.cleaned_data['monthly_bysetpos'])
|
||||
rule_kwargs['byweekday'] = f.parse_weekdays(f.cleaned_data['monthly_byweekday'])
|
||||
elif f.cleaned_data['freq'] == 'weekly':
|
||||
freq = WEEKLY
|
||||
|
||||
if f.cleaned_data['weekly_byweekday']:
|
||||
rule_kwargs['byweekday'] = [f.parse_weekdays(a) for a in f.cleaned_data['weekly_byweekday']]
|
||||
|
||||
elif f.cleaned_data['freq'] == 'daily':
|
||||
freq = DAILY
|
||||
|
||||
if f.cleaned_data['end'] == 'count':
|
||||
rule_kwargs['count'] = f.cleaned_data['count']
|
||||
else:
|
||||
rule_kwargs['until'] = f.cleaned_data['until']
|
||||
|
||||
if f.cleaned_data['exclude']:
|
||||
s.exrule(rrule(freq, **rule_kwargs))
|
||||
else:
|
||||
s.rrule(rrule(freq, **rule_kwargs))
|
||||
|
||||
return s
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
|
||||
tz = self.request.event.timezone
|
||||
cnt = 0
|
||||
for rdate in self.get_rrule_set():
|
||||
se = copy.copy(form.instance)
|
||||
|
||||
se.date_from = make_aware(datetime.combine(rdate, form.cleaned_data['time_from']), tz)
|
||||
se.date_to = (
|
||||
make_aware(datetime.combine(rdate, form.cleaned_data['time_to']), tz)
|
||||
if form.cleaned_data.get('time_to')
|
||||
else None
|
||||
)
|
||||
se.date_admission = (
|
||||
make_aware(datetime.combine(rdate, form.cleaned_data['time_admission']), tz)
|
||||
if form.cleaned_data.get('time_admission')
|
||||
else None
|
||||
)
|
||||
se.presale_start = (
|
||||
form.cleaned_data['rel_presale_start'].datetime(se)
|
||||
if form.cleaned_data.get('rel_presale_start')
|
||||
else None
|
||||
)
|
||||
se.presale_end = (
|
||||
form.cleaned_data['rel_presale_end'].datetime(se)
|
||||
if form.cleaned_data.get('rel_presale_end')
|
||||
else None
|
||||
)
|
||||
se.save()
|
||||
se.log_action('pretix.subevent.added', data=dict(form.cleaned_data), user=self.request.user)
|
||||
|
||||
for f in self.meta_forms:
|
||||
if f.cleaned_data.get('value'):
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.save()
|
||||
|
||||
for f in self.formset.forms:
|
||||
if self.formset._should_delete_form(f):
|
||||
continue
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.event = se.event
|
||||
i.save()
|
||||
selected_items = set(list(self.request.event.items.filter(id__in=[
|
||||
i.split('-')[0] for i in f.cleaned_data.get('itemvars', [])
|
||||
])))
|
||||
selected_variations = list(ItemVariation.objects.filter(item__event=self.request.event, id__in=[
|
||||
i.split('-')[1] for i in f.cleaned_data.get('itemvars', []) if '-' in i
|
||||
]))
|
||||
i.items.add(*[_i for _i in selected_items])
|
||||
i.variations.add(*[_i for _i in selected_variations])
|
||||
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
change_data['id'] = i.pk
|
||||
i.log_action(action='pretix.event.quota.added', user=self.request.user, data=change_data)
|
||||
se.log_action('pretix.subevent.quota.added', user=self.request.user, data=change_data)
|
||||
|
||||
for f in self.cl_formset.forms:
|
||||
if self.cl_formset._should_delete_form(f):
|
||||
continue
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.event = se.event
|
||||
i.save()
|
||||
i.limit_products.add(*f.cleaned_data.get('limit_products', []))
|
||||
change_data = {k: f.cleaned_data.get(k) for k in f.changed_data}
|
||||
change_data['id'] = i.pk
|
||||
i.log_action(action='pretix.event.checkinlist.added', user=self.request.user, data=change_data)
|
||||
|
||||
for f in self.itemvar_forms:
|
||||
i = copy.copy(f.instance)
|
||||
i.subevent = se
|
||||
i.save()
|
||||
|
||||
cnt += 1
|
||||
|
||||
messages.success(self.request, pgettext_lazy('subevent', '{} new dates have been created.').format(cnt))
|
||||
return redirect(reverse('control:event.subevents', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
}))
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
form = self.get_form()
|
||||
self.object = SubEvent(event=self.request.event)
|
||||
if self.is_valid(form):
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
@@ -34,6 +34,48 @@ date_conversion_to_moment = {
|
||||
'%X': ''
|
||||
}
|
||||
|
||||
out_date_conversion_to_moment = {
|
||||
'a': 'a',
|
||||
'A': 'A',
|
||||
'b': 'MMM',
|
||||
'c': 'YYYY-MM-DDTHH:mm:ss.SSSSSSZ',
|
||||
'd': 'DD',
|
||||
'e': 'zz',
|
||||
'E': 'MMMM',
|
||||
'f': 'h:mm',
|
||||
'F': 'MMMM',
|
||||
'g': 'h',
|
||||
'G': 'H',
|
||||
'h': 'hh',
|
||||
'H': 'HH',
|
||||
'i': 'mm',
|
||||
'I': '',
|
||||
'j': 'D',
|
||||
'l': 'dddd',
|
||||
'L': '',
|
||||
'm': 'MM',
|
||||
'M': 'MMM',
|
||||
'n': 'M',
|
||||
'N': 'MMM', # fuzzy
|
||||
'o': 'GGGG',
|
||||
'O': 'ZZ',
|
||||
'P': 'h:mm a',
|
||||
'r': 'ddd, D MMM YYYY HH:mm:ss Z',
|
||||
's': 'ss',
|
||||
'S': 'Do', # fuzzy
|
||||
't': '',
|
||||
'T': 'z',
|
||||
'u': 'SSSSSS',
|
||||
'U': 'X',
|
||||
'w': 'd',
|
||||
'W': 'W',
|
||||
'y': 'YY',
|
||||
'Y': 'YYYY',
|
||||
'z': 'DDD',
|
||||
'Z': ''
|
||||
|
||||
}
|
||||
|
||||
moment_locales = {
|
||||
'af', 'az', 'bs', 'de-at', 'en-gb', 'et', 'fr-ch', 'hi', 'it', 'ko', 'me', 'ms-my', 'pa-in', 'se', 'sr', 'th',
|
||||
'tzm-latn', 'zh-hk', 'ar', 'be', 'ca', 'de', 'en-ie', 'eu', 'fr', 'hr', 'ja', 'ky', 'mi', 'my', 'pl', 'si', 'ss',
|
||||
@@ -45,10 +87,23 @@ moment_locales = {
|
||||
}
|
||||
|
||||
toJavascript_re = re.compile(r'(?<!\w)(' + '|'.join(date_conversion_to_moment.keys()) + r')\b')
|
||||
toJavascriptOut_re = re.compile(r'(?<!\w)(' + '|'.join(out_date_conversion_to_moment.keys()) + r')\b')
|
||||
|
||||
|
||||
def get_javascript_output_format(format_name):
|
||||
f = get_format(format_name)
|
||||
if not isinstance(f, str):
|
||||
f = f[0]
|
||||
return toJavascriptOut_re.sub(
|
||||
lambda x: out_date_conversion_to_moment[x.group()],
|
||||
f
|
||||
)
|
||||
|
||||
|
||||
def get_javascript_format(format_name):
|
||||
f = get_format(format_name)[0]
|
||||
f = get_format(format_name)
|
||||
if not isinstance(f, str):
|
||||
f = f[0]
|
||||
return toJavascript_re.sub(
|
||||
lambda x: date_conversion_to_moment[x.group()],
|
||||
f
|
||||
|
||||
25
src/pretix/helpers/templatetags/captureas.py
Normal file
25
src/pretix/helpers/templatetags/captureas.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from django import template
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.tag(name='captureas')
|
||||
def do_captureas(parser, token):
|
||||
try:
|
||||
tag_name, args = token.contents.split(None, 1)
|
||||
except ValueError:
|
||||
raise template.TemplateSyntaxError("'captureas' node requires a variable name.")
|
||||
nodelist = parser.parse(('endcaptureas',))
|
||||
parser.delete_first_token()
|
||||
return CaptureasNode(nodelist, args)
|
||||
|
||||
|
||||
class CaptureasNode(template.Node):
|
||||
def __init__(self, nodelist, varname):
|
||||
self.nodelist = nodelist
|
||||
self.varname = varname
|
||||
|
||||
def render(self, context):
|
||||
output = self.nodelist.render(context)
|
||||
context[self.varname] = output
|
||||
return ''
|
||||
@@ -1,9 +1,146 @@
|
||||
/*globals $, Morris, gettext*/
|
||||
/*globals $, Morris, gettext, RRule, RRuleSet*/
|
||||
|
||||
$(function () {
|
||||
if (!$("div[data-formset-prefix=checkinlist_set]").length) {
|
||||
return;
|
||||
}
|
||||
|
||||
function parse_weekday(wd) {
|
||||
map = {
|
||||
'MO': 0,
|
||||
'TU': 1,
|
||||
'WE': 2,
|
||||
'TH': 3,
|
||||
'FR': 4,
|
||||
'SA': 5,
|
||||
'SU': 6
|
||||
}
|
||||
if (wd.indexOf(",") > 0) {
|
||||
var wds = [];
|
||||
$.each(wd.split(","), function (k, v) {
|
||||
wds.push(map[v]);
|
||||
});
|
||||
return wds;
|
||||
} else {
|
||||
return map[wd];
|
||||
}
|
||||
}
|
||||
|
||||
function rrule_preview() {
|
||||
var ruleset = new RRuleSet();
|
||||
|
||||
$(".rrule-form").each(function () {
|
||||
if ($(this).find("input[name$=DELETE]").prop("checked")) {
|
||||
return;
|
||||
}
|
||||
|
||||
var rule_args = {};
|
||||
var $form = $(this);
|
||||
var freq = $form.find("select[name*=freq]").val();
|
||||
if (!$form.find("input[name*=dtstart]").data("DateTimePicker")) {
|
||||
// uninitialized
|
||||
return;
|
||||
}
|
||||
var dtstart = $form.find("input[name*=dtstart]").data("DateTimePicker").date();
|
||||
dtstart = dtstart.add(dtstart.utcOffset(), 'm').add(12, 'h').utcOffset(0);
|
||||
rule_args.dtstart = dtstart.toDate();
|
||||
rule_args.interval = parseInt($form.find("input[name*=interval]").val()) || 1;
|
||||
|
||||
if (freq === 'yearly') {
|
||||
rule_args.freq = RRule.YEARLY;
|
||||
|
||||
var same = $form.find("input[name*=yearly_same]:checked").val();
|
||||
if (same === "off") {
|
||||
rule_args.bysetpos = parseInt($form.find("select[name*=yearly_bysetpos]").val());
|
||||
rule_args.byweekday = parse_weekday($form.find("select[name*=yearly_byweekday]").val());
|
||||
rule_args.bymonth = parseInt($form.find("select[name*=yearly_bymonth]").val());
|
||||
}
|
||||
} else if (freq === 'monthly') {
|
||||
rule_args.freq = RRule.MONTHLY;
|
||||
|
||||
var same = $form.find("input[name*=monthly_same]:checked").val();
|
||||
if (same === "off") {
|
||||
rule_args.bysetpos = parseInt($form.find("select[name*=monthly_bysetpos]").val());
|
||||
rule_args.byweekday = parse_weekday($form.find("select[name*=monthly_byweekday]").val());
|
||||
}
|
||||
} else if (freq === 'weekly') {
|
||||
rule_args.freq = RRule.WEEKLY;
|
||||
|
||||
var days = [];
|
||||
$form.find("input[name*=weekly_byweekday]:checked").each(function () {
|
||||
days.push(parse_weekday($(this).val()));
|
||||
});
|
||||
if (days.length !== 0) {
|
||||
rule_args.byweekday = days;
|
||||
}
|
||||
} else if (freq === 'daily') {
|
||||
rule_args.freq = RRule.DAILY;
|
||||
}
|
||||
|
||||
var end = $form.find("input[name*=end]:checked").val();
|
||||
if (end === "count") {
|
||||
rule_args.count = parseInt($form.find("input[name*=count]").val()) || 1;
|
||||
} else {
|
||||
var date = $form.find("input[name*=until]").data("DateTimePicker").date();
|
||||
if (date !== null) {
|
||||
rule_args.until = date.toDate();
|
||||
}
|
||||
}
|
||||
|
||||
if ($form.find("input[name*=exclude]").prop("checked")) {
|
||||
ruleset.exrule(new RRule(rule_args));
|
||||
$form.closest(".panel").addClass("panel-danger").removeClass("panel-default");
|
||||
} else {
|
||||
ruleset.rrule(new RRule(rule_args));
|
||||
$form.closest(".panel").addClass("panel-default").removeClass("panel-danger");
|
||||
}
|
||||
});
|
||||
|
||||
var all_dates = ruleset.all();
|
||||
var format = $("body").attr("data-longdateformat") + " (dddd)";
|
||||
$("#rrule-preview").html("");
|
||||
if (all_dates.length > 20) {
|
||||
$("#rrule-preview").html("");
|
||||
all_dates.slice(0, 10).forEach(function(element) {
|
||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
||||
});
|
||||
$("#rrule-preview").append($("<li>").text(ngettext(
|
||||
"(one more date)",
|
||||
"({num} more dates)",
|
||||
all_dates.length - 20
|
||||
).replace(/\{num\}/g, all_dates.length - 20)));
|
||||
all_dates.slice(-10).forEach(function(element) {
|
||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
||||
});
|
||||
} else {
|
||||
all_dates.forEach(function(element) {
|
||||
$("#rrule-preview").append($("<li>").text(moment(element).utc().format(format)));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function rrule_form_toggles($form) {
|
||||
var freq = $form.find("select[name*=freq]").val();
|
||||
$form.find(".repeat-yearly").toggle(freq === "yearly");
|
||||
$form.find(".repeat-monthly").toggle(freq === "monthly");
|
||||
$form.find(".repeat-weekly").toggle(freq === "weekly");
|
||||
}
|
||||
|
||||
function rrule_bind_form($form) {
|
||||
$form.find("select[name*=freq]").change(function () {
|
||||
rrule_form_toggles($form);
|
||||
});
|
||||
rrule_form_toggles($form);
|
||||
}
|
||||
|
||||
$("#rrule-formset").on("change keydown keyup keypress dp.change", "input, select", function () {
|
||||
rrule_preview();
|
||||
});
|
||||
rrule_preview();
|
||||
|
||||
$(".rrule-form").each(function () { rrule_bind_form($(this)); });
|
||||
$("#rrule-formset").on("formAdded", "div", function (event) { rrule_bind_form($(event.target)); });
|
||||
|
||||
var $namef = $("input[id^=id_name]").first();
|
||||
var lastValue = $namef.val();
|
||||
$namef.change(function () {
|
||||
|
||||
@@ -296,3 +296,44 @@ table td > .checkbox input[type="checkbox"] {
|
||||
width: 100px;
|
||||
display: inline;
|
||||
}
|
||||
.form-horizontal [data-formset] .rrule-form .form-group {
|
||||
margin: 0;
|
||||
width: auto;
|
||||
}
|
||||
.rrule-form {
|
||||
.form-control {
|
||||
display: inline;
|
||||
}
|
||||
input[type=number] {
|
||||
width: 100px;
|
||||
}
|
||||
.repeat-yearly, .repeat-monthly, .repeat-weekly {
|
||||
line-height: 35px;
|
||||
padding: 0 0 15px 0;
|
||||
margin: 10px 0;
|
||||
border-top: 1px solid $panel-default-border;
|
||||
border-bottom: 1px solid $panel-default-border;
|
||||
|
||||
input[type="radio"], input[type="checkbox"] {
|
||||
top: 2px;
|
||||
}
|
||||
}
|
||||
.repeat-weekly > div.form-group {
|
||||
display: block;
|
||||
line-height: 1;
|
||||
padding-top: 5px;
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
align-content: space-between;
|
||||
div.checkbox {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.repeat-until {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
2287
src/pretix/static/rrule/rrule.js
Normal file
2287
src/pretix/static/rrule/rrule.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -852,6 +852,327 @@ class SubEventsTest(SoupTest):
|
||||
assert doc.select(".alert-danger")
|
||||
assert self.event1.subevents.filter(pk=self.subevent1.pk).exists()
|
||||
|
||||
def test_create_bulk(self):
|
||||
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',
|
||||
'name_0': 'Foo',
|
||||
'active': 'on',
|
||||
'time_from': '13:29:31',
|
||||
'time_to': '15:29:31',
|
||||
'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")
|
||||
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"
|
||||
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"
|
||||
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):
|
||||
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',
|
||||
'name_0': 'Foo',
|
||||
'active': 'on',
|
||||
'time_from': '13:29:31',
|
||||
'time_to': '15:29:31',
|
||||
'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': '0',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'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")
|
||||
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_exclude(self):
|
||||
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',
|
||||
'name_0': 'Foo',
|
||||
'active': 'on',
|
||||
'time_from': '13:29:31',
|
||||
'time_to': '15:29:31',
|
||||
'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': '0',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'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")
|
||||
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):
|
||||
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',
|
||||
'name_0': 'Foo',
|
||||
'active': 'on',
|
||||
'time_from': '13:29:31',
|
||||
'time_to': '15:29:31',
|
||||
'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': '0',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'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")
|
||||
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):
|
||||
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',
|
||||
'name_0': 'Foo',
|
||||
'active': 'on',
|
||||
'time_from': '13:29:31',
|
||||
'time_to': '15:29:31',
|
||||
'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': '0',
|
||||
'quotas-INITIAL_FORMS': '0',
|
||||
'quotas-MIN_NUM_FORMS': '0',
|
||||
'quotas-MAX_NUM_FORMS': '1000',
|
||||
'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")
|
||||
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"
|
||||
|
||||
|
||||
class EventDeletionTest(SoupTest):
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user