Add sub-events and relative date settings (#503)

* Data model

* little crud

* SubEventItemForm etc

* Drop SubEventItem.active, quota editor

* Fix failing tests

* First frontend stuff

* Addons form stuff

* Quota calculation

* net price display on EventIndex

* Add tests, solve some bugs

* Correct quota selection in more places, consolidate pricing logic

* Fix failing quota tests

* Fix TypeError

* Add tests for checkout

* Fixed a bug in QuotaForm

* Prevent immutable cart if a quota was removed from an item

* Add tests for pricing

* Handle waiting list

* Filter in check-in list

* Fixed import lost in rebase

* Fix waiting list widget

* Voucher management

* Voucher redemption

* Fix broken tests

* Add subevents to OrderChangeManager

* Create a subevent during event creation

* Fix bulk voucher creation

* Introduce subevent.active

* Copy from for subevents

* Show active in list

* ICal download for subevents

* Check start and end of presale

* Failing tests / show cart logic

* Test

* Rebase migrations

* REST API integration of sub-events

* Integrate quota calculation into the traditional quota form

* Make subevent argument to add_position optional

* Log-display foo

* pretixdroid and subevents

* Filter by subevent

* Add more tests

* Some mor tests

* Rebase fixes

* More tests

* Relative dates

* Restrict selection in relative datetime widgets

* Filter subevent list

* Re-label has_subevents

* Rebase fixes, subevents in calendar view

* Performance and caching issues

* Refactor calendar templates

* Permission tests

* Calendar fixes and month selection

* subevent selection

* Rename subevents to dates

* Add tests for calendar views
This commit is contained in:
Raphael Michel
2017-07-11 13:56:00 +02:00
committed by GitHub
parent 554800c06f
commit 8123effa65
141 changed files with 5920 additions and 1012 deletions

View File

@@ -74,6 +74,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_time_format'] = get_javascript_format('TIME_INPUT_FORMATS')
ctx['js_locale'] = get_moment_locale()
if settings.DEBUG and 'runserver' not in sys.argv:

View File

@@ -10,6 +10,7 @@ from pytz import common_timezones, timezone
from pretix.base.forms import I18nModelForm, PlaceholderValidator, SettingsForm
from pretix.base.models import Event, Organizer
from pretix.base.reldate import RelativeDateField, RelativeDateTimeField
from pretix.control.forms import ExtFileField
@@ -20,6 +21,15 @@ class EventWizardFoundationForm(forms.Form):
widget=forms.CheckboxSelectMultiple,
help_text=_('Choose all languages that your event should be available in.')
)
has_subevents = forms.BooleanField(
label=_("This is an event series"),
help_text=_('Only recommended for advanced users. If this feature is enabled, this will not only be a '
'single event but a series of very similar events that are handled within a single shop. '
'The single events inside the series can only differ in date, time, location, prices and '
'quotas, but not in other settings, and buying tickets across multiple of these events at '
'the same time is possible. You cannot change this setting for this event later.'),
required=False,
)
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
@@ -72,10 +82,15 @@ class EventWizardBasicsForm(I18nModelForm):
def __init__(self, *args, **kwargs):
self.organizer = kwargs.pop('organizer')
self.locales = kwargs.get('locales')
self.has_subevents = kwargs.pop('has_subevents')
kwargs.pop('user')
super().__init__(*args, **kwargs)
self.initial['timezone'] = get_current_timezone_name()
self.fields['locale'].choices = [(a, b) for a, b in settings.LANGUAGES if a in self.locales]
self.fields['location'].widget.attrs['rows'] = '3'
if self.has_subevents:
del self.fields['presale_start']
del self.fields['presale_end']
def clean(self):
data = super().clean()
@@ -125,11 +140,12 @@ class EventWizardCopyForm(forms.Form):
def __init__(self, *args, **kwargs):
kwargs.pop('organizer')
kwargs.pop('locales')
has_subevents = kwargs.pop('has_subevents')
self.user = kwargs.pop('user')
super().__init__(*args, **kwargs)
self.fields['copy_from_event'] = forms.ModelChoiceField(
label=_("Copy configuration from"),
queryset=EventWizardCopyForm.copy_from_queryset(self.user),
queryset=EventWizardCopyForm.copy_from_queryset(self.user).filter(has_subevents=has_subevents),
widget=forms.RadioSelect,
empty_label=_('Do not copy'),
required=False
@@ -195,15 +211,15 @@ class EventSettingsForm(SettingsForm):
presale_start_show_date = forms.BooleanField(
label=_("Show start date"),
help_text=_("Show the presale start date before presale has started."),
widget=forms.CheckboxInput(attrs={'data-display-dependency': '#id_presale_start'}),
widget=forms.CheckboxInput,
required=False
)
last_order_modification_date = forms.DateTimeField(
last_order_modification_date = RelativeDateTimeField(
label=_('Last date of modifications'),
help_text=_("The last date users can modify details of their orders, such as attendee names or "
"answers to questions."),
"answers to questions. If you use the event series feature and an order contains tickest for "
"multiple event dates, the earliest date will be used."),
required=False,
widget=forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
)
timezone = forms.ChoiceField(
choices=((a, a) for a in common_timezones),
@@ -327,12 +343,12 @@ class PaymentSettingsForm(SettingsForm):
label=_('Payment term in days'),
help_text=_("The number of days after placing an order the user has to pay to preserve his reservation."),
)
payment_term_last = forms.DateField(
payment_term_last = RelativeDateField(
label=_('Last date of payments'),
help_text=_("The last date any payments are accepted. This has precedence over the number of "
"days configured above."),
"days configured above. If you use the event series feature and an order contains tickets for "
"multiple dates, the earliest date will be used."),
required=False,
widget=forms.DateInput(attrs={'class': 'datepickerfield'})
)
payment_term_weekdays = forms.BooleanField(
label=_('Only end payment terms on weekdays'),
@@ -364,8 +380,10 @@ class PaymentSettingsForm(SettingsForm):
def clean(self):
cleaned_data = super().clean()
payment_term_last = cleaned_data.get('payment_term_last')
print(payment_term_last)
if payment_term_last and self.obj.presale_end:
if payment_term_last < self.obj.presale_end.date():
print(payment_term_last, payment_term_last.datetime(self.obj), self.obj.presale_end.date())
if payment_term_last.datetime(self.obj) < self.obj.presale_end.date():
self.add_error(
'payment_term_last',
_('The last payment date cannot be before the end of presale.'),
@@ -392,6 +410,8 @@ class ProviderForm(SettingsForm):
v._required = v.one_required
v.one_required = False
v.widget.enabled_locales = self.locales
elif isinstance(v, (RelativeDateTimeField, RelativeDateField)):
v.set_event(self.obj)
def clean(self):
cleaned_data = super().clean()
@@ -658,12 +678,12 @@ class TicketSettingsForm(SettingsForm):
help_text=_("Use pretix to generate tickets for the user to download and print out."),
required=False
)
ticket_download_date = forms.DateTimeField(
ticket_download_date = RelativeDateTimeField(
label=_("Download date"),
help_text=_("Ticket download will be offered after this date."),
required=True,
widget=forms.DateTimeInput(attrs={'class': 'datetimepicker',
'data-display-dependency': '#id_ticket_download'}),
help_text=_("Ticket download will be offered after this date. If you use the event series feature and an order "
"contains tickets for multiple event dates, download of all tickets will be available if at least "
"one of the event dates allows it."),
required=False,
)
ticket_download_addons = forms.BooleanField(
label=_("Offer to download tickets separately for add-on products"),

View File

@@ -1,9 +1,9 @@
from django import forms
from django.db.models import Q
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.models import Item, Order, Organizer
from pretix.base.models import Item, Order, Organizer, SubEvent
from pretix.base.signals import register_payment_providers
from pretix.control.utils.i18n import i18ncomp
@@ -86,6 +86,12 @@ class EventOrderFilterForm(OrderFilterForm):
],
required=False,
)
subevent = forms.ModelChoiceField(
label=pgettext_lazy('subevent', 'Date'),
queryset=SubEvent.objects.none(),
required=False,
empty_label=pgettext_lazy('subevent', 'All dates')
)
def get_payment_providers(self):
providers = []
@@ -105,12 +111,20 @@ class EventOrderFilterForm(OrderFilterForm):
self.fields['provider'].choices += [(k, v.verbose_name) for k, v
in self.event.get_payment_providers().items()]
if self.event.has_subevents:
self.fields['subevent'].queryset = self.event.subevents.all()
elif 'subevent':
del self.fields['subevent']
def filter_qs(self, qs):
fdata = self.cleaned_data
qs = super().filter_qs(qs)
if fdata.get('item'):
qs = qs.filter(positions__item_id__in=(fdata.get('item'),))
qs = qs.filter(positions__item=fdata.get('item'))
if fdata.get('subevent'):
qs = qs.filter(positions__subevent=fdata.get('subevent'))
if fdata.get('provider'):
qs = qs.filter(payment_provider=fdata.get('provider'))
@@ -146,6 +160,57 @@ class OrderSearchFilterForm(OrderFilterForm):
return qs
class SubEventFilterForm(FilterForm):
status = forms.ChoiceField(
label=_('Status'),
choices=(
('', _('All')),
('active', _('Active')),
('running', _('Shop live and presale running')),
('inactive', _('Inactive')),
('future', _('Presale not started')),
('past', _('Presale over')),
),
required=False
)
query = forms.CharField(
label=_('Event name'),
widget=forms.TextInput(attrs={
'placeholder': _('Event name'),
'autofocus': 'autofocus'
}),
required=False
)
def filter_qs(self, qs):
fdata = self.cleaned_data
if fdata.get('status') == 'active':
qs = qs.filter(active=True)
elif fdata.get('status') == 'running':
qs = qs.filter(
active=True
).filter(
Q(presale_start__isnull=True) | Q(presale_start__lte=now())
).filter(
Q(presale_end__isnull=True) | Q(presale_end__gte=now())
)
elif fdata.get('status') == 'inactive':
qs = qs.filter(active=False)
elif fdata.get('status') == 'future':
qs = qs.filter(presale_start__gte=now())
elif fdata.get('status') == 'past':
qs = qs.filter(presale_end__lte=now())
if fdata.get('query'):
query = fdata.get('query')
qs = qs.filter(
Q(name__icontains=i18ncomp(query)) | Q(location__icontains=query)
)
return qs
class EventFilterForm(FilterForm):
status = forms.ChoiceField(
label=_('Status'),

View File

@@ -3,7 +3,6 @@ import copy
from django import forms
from django.core.exceptions import ValidationError
from django.db.models import Max
from django.forms import BooleanField, ModelMultipleChoiceField
from django.forms.formsets import DELETION_FIELD_NAME
from django.utils.translation import ugettext as __, ugettext_lazy as _
from i18nfield.forms import I18nFormField, I18nTextarea
@@ -52,7 +51,6 @@ class QuestionForm(I18nModelForm):
class QuestionOptionForm(I18nModelForm):
class Meta:
model = QuestionOption
localized_fields = '__all__'
@@ -62,36 +60,38 @@ class QuestionOptionForm(I18nModelForm):
class QuotaForm(I18nModelForm):
def __init__(self, **kwargs):
items = kwargs['items']
del kwargs['items']
instance = kwargs.get('instance', None)
self.original_instance = copy.copy(instance) if instance else None
self.instance = kwargs.get('instance', None)
self.event = kwargs.get('event')
items = kwargs.pop('items', None) or self.event.items.prefetch_related('variations')
self.original_instance = copy.copy(self.instance) if self.instance else None
initial = kwargs.get('initial', {})
if self.instance and self.instance.pk:
initial['itemvars'] = [str(i.pk) for i in self.instance.items.all()] + [
'{}-{}'.format(v.item_id, v.pk) for v in self.instance.variations.all()
]
kwargs['initial'] = initial
super().__init__(**kwargs)
if hasattr(self, 'instance') and self.instance.pk:
active_items = set(self.instance.items.all())
active_variations = set(self.instance.variations.all())
else:
active_items = set()
active_variations = set()
choices = []
for item in items:
if len(item.variations.all()) > 0:
self.fields['item_%s' % item.id] = ModelMultipleChoiceField(
label=_("Activate for"),
required=False,
initial=active_variations,
queryset=item.variations.all(),
widget=forms.CheckboxSelectMultiple
)
for v in item.variations.all():
choices.append(('{}-{}'.format(item.pk, v.pk), '{} {}'.format(item.name, v.value)))
else:
self.fields['item_%s' % item.id] = BooleanField(
label=_("Activate"),
required=False,
initial=(item in active_items)
)
choices.append(('{}'.format(item.pk), item.name))
self.fields['itemvars'] = forms.MultipleChoiceField(
label=_('Products'),
required=False,
choices=choices,
widget=forms.CheckboxSelectMultiple
)
if self.event.has_subevents:
self.fields['subevent'].queryset = self.event.subevents.all()
else:
del self.fields['subevent']
class Meta:
model = Quota
@@ -99,8 +99,29 @@ class QuotaForm(I18nModelForm):
fields = [
'name',
'size',
'subevent'
]
def save(self, *args, **kwargs):
creating = not self.instance.pk
inst = super().save(*args, **kwargs)
selected_items = set(list(self.event.items.filter(id__in=[
i.split('-')[0] for i in self.cleaned_data['itemvars']
])))
selected_variations = list(ItemVariation.objects.filter(item__event=self.event, id__in=[
i.split('-')[1] for i in self.cleaned_data['itemvars'] if '-' in i
]))
current_items = [] if creating else self.instance.items.all()
current_variations = [] if creating else self.instance.variations.all()
self.instance.items.remove(*[i for i in current_items if i not in selected_items])
self.instance.items.add(*[i for i in selected_items if i not in current_items])
self.instance.variations.remove(*[i for i in current_variations if i not in selected_variations])
self.instance.variations.add(*[i for i in selected_variations if i not in current_variations])
return inst
class ItemCreateForm(I18nModelForm):
has_variations = forms.BooleanField(label=_('The product should exist in multiple variations'),
@@ -197,7 +218,6 @@ class ItemUpdateForm(I18nModelForm):
class ItemVariationsFormSet(I18nFormSet):
def clean(self):
super().clean()
for f in self.forms:

View File

@@ -4,10 +4,12 @@ from django.core.exceptions import ValidationError
from django.db import models
from django.utils.formats import localize
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.forms import I18nModelForm
from pretix.base.models import Item, ItemAddOn, Order, OrderPosition
from pretix.base.models.event import SubEvent
from pretix.base.services.pricing import get_price
class ExtendForm(I18nModelForm):
@@ -55,6 +57,15 @@ class CommentForm(I18nModelForm):
}
class SubEventChoiceField(forms.ModelChoiceField):
def label_from_instance(self, obj):
p = get_price(self.instance.item, self.instance.variation,
voucher=self.instance.voucher,
subevent=obj)
return '{} {} ({} {})'.format(obj.name, obj.get_date_range_display(),
p, self.instance.order.event.currency)
class OrderPositionAddForm(forms.Form):
do = forms.BooleanField(
label=_('Add a new product to the order'),
@@ -74,6 +85,12 @@ class OrderPositionAddForm(forms.Form):
label=_('Gross price'),
help_text=_("Keep empty for the product's default price")
)
subevent = forms.ModelChoiceField(
SubEvent.objects.none(),
label=pgettext_lazy('subevent', 'Date'),
required=True,
empty_label=None
)
def __init__(self, *args, **kwargs):
order = kwargs.pop('order')
@@ -100,9 +117,20 @@ class OrderPositionAddForm(forms.Form):
else:
del self.fields['addon_to']
if order.event.has_subevents:
self.fields['subevent'].queryset = order.event.subevents.all()
else:
del self.fields['subevent']
class OrderPositionChangeForm(forms.Form):
itemvar = forms.ChoiceField()
subevent = SubEventChoiceField(
SubEvent.objects.none(),
label=pgettext_lazy('subevent', 'New date'),
required=True,
empty_label=None
)
price = forms.DecimalField(
required=False,
max_digits=10, decimal_places=2,
@@ -114,6 +142,7 @@ class OrderPositionChangeForm(forms.Form):
choices=(
('product', 'Change product'),
('price', 'Change price'),
('subevent', 'Change event date'),
('cancel', 'Remove product')
)
)
@@ -131,9 +160,15 @@ class OrderPositionChangeForm(forms.Form):
pass
initial['price'] = instance.price
initial['subevent'] = instance.subevent
kwargs['initial'] = initial
super().__init__(*args, **kwargs)
if instance.order.event.has_subevents:
self.fields['subevent'].instance = instance
self.fields['subevent'].queryset = instance.order.event.subevents.all()
else:
del self.fields['subevent']
choices = []
for i in instance.order.event.items.prefetch_related('variations').all():
pname = str(i.name)
@@ -142,11 +177,13 @@ class OrderPositionChangeForm(forms.Form):
variations = list(i.variations.all())
if variations:
for v in variations:
p = get_price(i, v, voucher=instance.voucher, subevent=instance.subevent)
choices.append(('%d-%d' % (i.pk, v.pk),
'%s %s (%s %s)' % (pname, v.value, localize(v.price),
'%s %s (%s %s)' % (pname, v.value, localize(p),
instance.order.event.currency)))
else:
choices.append((str(i.pk), '%s (%s %s)' % (pname, localize(i.default_price),
p = get_price(i, None, voucher=instance.voucher, subevent=instance.subevent)
choices.append((str(i.pk), '%s (%s %s)' % (pname, localize(p),
instance.order.event.currency)))
self.fields['itemvar'].choices = choices

View File

@@ -0,0 +1,98 @@
from django import forms
from django.utils.functional import cached_property
from i18nfield.forms import I18nInlineFormSet
from pretix.base.forms import I18nModelForm
from pretix.base.models.event import SubEvent
from pretix.base.models.items import SubEventItem
class SubEventForm(I18nModelForm):
def __init__(self, *args, **kwargs):
self.event = kwargs['event']
super().__init__(*args, **kwargs)
self.fields['location'].widget.attrs['rows'] = '3'
class Meta:
model = SubEvent
localized_fields = '__all__'
fields = [
'name',
'active',
'date_from',
'date_to',
'date_admission',
'presale_start',
'presale_end',
'location',
]
widgets = {
'date_from': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
'date_to': forms.DateTimeInput(attrs={'class': 'datetimepicker', 'data-date-after': '#id_date_from'}),
'date_admission': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
'presale_start': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
'presale_end': forms.DateTimeInput(attrs={'class': 'datetimepicker',
'data-date-after': '#id_presale_start'}),
}
class SubEventItemOrVariationFormMixin:
def __init__(self, *args, **kwargs):
self.item = kwargs.pop('item')
self.variation = kwargs.pop('variation', None)
super().__init__(*args, **kwargs)
class SubEventItemForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['price'].widget.attrs['placeholder'] = '{} {}'.format(
self.item.default_price, self.item.event.currency
)
class Meta:
model = SubEventItem
fields = ['price']
class SubEventItemVariationForm(SubEventItemOrVariationFormMixin, forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['price'].widget.attrs['placeholder'] = '{} {}'.format(
self.variation.price, self.item.event.currency
)
class Meta:
model = SubEventItem
fields = ['price']
class QuotaFormSet(I18nInlineFormSet):
def __init__(self, *args, **kwargs):
self.event = kwargs.pop('event', None)
self.locales = self.event.settings.get('locales')
super().__init__(*args, **kwargs)
@cached_property
def items(self):
return self.event.items.prefetch_related('variations').all()
def _construct_form(self, i, **kwargs):
kwargs['locales'] = self.locales
kwargs['event'] = self.event
kwargs['items'] = self.items
return super()._construct_form(i, **kwargs)
@property
def empty_form(self):
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix('__prefix__'),
empty_permitted=True,
locales=self.locales,
event=self.event,
items=self.items
)
self.add_fields(form, None)
return form

View File

@@ -4,7 +4,7 @@ from django import forms
from django.core.exceptions import ValidationError
from django.db.models import Q
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from pretix.base.forms import I18nModelForm
from pretix.base.models import Item, ItemVariation, Quota, Voucher
@@ -24,7 +24,7 @@ class VoucherForm(I18nModelForm):
localized_fields = '__all__'
fields = [
'code', 'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag',
'comment', 'max_usages', 'price_mode'
'comment', 'max_usages', 'price_mode', 'subevent'
]
widgets = {
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),
@@ -47,6 +47,12 @@ class VoucherForm(I18nModelForm):
else:
self.initial_instance_data = None
super().__init__(*args, **kwargs)
if instance.event.has_subevents:
self.fields['subevent'].queryset = instance.event.subevents.all()
elif 'subevent':
del self.fields['subevent']
choices = []
for i in self.instance.event.items.prefetch_related('variations').all():
variations = list(i.variations.all())
@@ -103,6 +109,12 @@ class VoucherForm(I18nModelForm):
else:
cnt = data['max_usages']
if self.instance.event.has_subevents and data['block_quota'] and not data.get('subevent'):
raise ValidationError(pgettext_lazy(
'subevent',
'If you want this voucher to block quota, you need to select a specific date.'
))
if self._clean_quota_needs_checking(data):
self._clean_quota_check(data, cnt)
@@ -136,6 +148,10 @@ class VoucherForm(I18nModelForm):
# The voucher has been reassigned to a different item, variation or quota
return True
if data.get('subevent') != self.initial.get('subevent'):
# The voucher has been reassigned to a different subevent
return True
return False
def _clean_was_valid(self):
@@ -147,9 +163,11 @@ class VoucherForm(I18nModelForm):
if self.initial_instance_data.quota:
quotas.add(self.initial_instance_data.quota)
elif self.initial_instance_data.variation:
quotas |= set(self.initial_instance_data.variation.quotas.all())
quotas |= set(self.initial_instance_data.variation.quotas.filter(
subevent=self.initial_instance_data.subevent))
elif self.initial_instance_data.item:
quotas |= set(self.initial_instance_data.item.quotas.all())
quotas |= set(self.initial_instance_data.item.quotas.filter(
subevent=self.initial_instance_data.subevent))
return quotas
def _clean_quota_check(self, data, cnt):
@@ -164,9 +182,9 @@ class VoucherForm(I18nModelForm):
raise ValidationError(_('You can only block quota if you specify a specific product variation. '
'Otherwise it might be unclear which quotas to block.'))
elif self.instance.item and self.instance.variation:
avail = self.instance.variation.check_quotas(ignored_quotas=old_quotas)
avail = self.instance.variation.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent'))
elif self.instance.item and not self.instance.item.has_variations:
avail = self.instance.item.check_quotas(ignored_quotas=old_quotas)
avail = self.instance.item.check_quotas(ignored_quotas=old_quotas, subevent=data.get('subevent'))
else:
raise ValidationError(_('You need to specify either a quota or a product.'))
@@ -195,7 +213,7 @@ class VoucherBulkForm(VoucherForm):
localized_fields = '__all__'
fields = [
'valid_until', 'block_quota', 'allow_ignore_quota', 'value', 'tag', 'comment',
'max_usages', 'price_mode'
'max_usages', 'price_mode', 'subevent'
]
widgets = {
'valid_until': forms.DateTimeInput(attrs={'class': 'datetimepicker'}),

View File

@@ -3,7 +3,7 @@ from decimal import Decimal
from django.dispatch import receiver
from django.utils import formats
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from i18nfield.strings import LazyI18nString
from pretix.base.models import Event, ItemVariation, LogEntry, OrderPosition
@@ -33,6 +33,17 @@ def _display_order_changed(event: Event, logentry: LogEntry):
new_price=formats.localize(Decimal(data['new_price'])),
currency=event.currency
)
elif logentry.action_type == 'pretix.event.order.changed.subevent':
old_se = str(event.subevents.get(pk=data['old_subevent']))
new_se = str(event.subevents.get(pk=data['new_subevent']))
return text + ' ' + _('Position #{posid}: Event date "{old_event}" ({old_price} {currency}) changed '
'to "{new_event}" ({new_price} {currency}).').format(
posid=data.get('positionid', '?'),
old_event=old_se, new_event=new_se,
old_price=formats.localize(Decimal(data['old_price'])),
new_price=formats.localize(Decimal(data['new_price'])),
currency=event.currency
)
elif logentry.action_type == 'pretix.event.order.changed.price':
return text + ' ' + _('Price of position #{posid} changed from {old_price} {currency} '
'to {new_price} {currency}.').format(
@@ -146,6 +157,12 @@ def pretixcontrol_logentry_display(sender: Event, logentry: LogEntry, **kwargs):
'pretix.team.created': _('The team has been created.'),
'pretix.team.changed': _('The team settings have been modified.'),
'pretix.team.deleted': _('The team has been deleted.'),
'pretix.subevent.deleted': pgettext_lazy('subevent', 'The event date has been deleted.'),
'pretix.subevent.changed': pgettext_lazy('subevent', 'The event date has been modified.'),
'pretix.subevent.added': pgettext_lazy('subevent', 'The event date has been created.'),
'pretix.subevent.quota.added': pgettext_lazy('subevent', 'A quota has been added to the event date.'),
'pretix.subevent.quota.changed': pgettext_lazy('subevent', 'A quota has been modified on the event date.'),
'pretix.subevent.quota.deleted': pgettext_lazy('subevent', 'A quota has been removed from the event date.'),
}
data = json.loads(logentry.data)

View File

@@ -45,7 +45,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="{% static "pretixbase/img/favicon.ico" %}">
</head>
<body data-datetimeformat="{{ js_datetime_format }}" data-dateformat="{{ js_date_format }}" data-datetimelocale="{{ js_locale }}" data-payment-weekdays-disabled="{{ js_payment_weekdays_disabled }}">
<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 }}">
<div id="wrapper">
<nav class="navbar navbar-inverse navbar-static-top" role="navigation">
<div class="navbar-header">

View File

@@ -21,6 +21,17 @@
</option>
{% endfor %}
</select>
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
{% endif %}
<input type="text" name="user" class="form-control" placeholder="{% trans "Search user" %}" value="{{ request.GET.user }}">
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
</form>
@@ -45,6 +56,10 @@
<a href="?{% url_replace request 'ordering' 'code'%}"><i class="fa fa-caret-up"></i></a></th>
<th>{% trans "Item" %} <a href="?{% url_replace request 'ordering' '-item'%}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'item'%}"><i class="fa fa-caret-up"></i></a></th>
{% if request.event.has_subevents %}
<th>{% trans "Date" context "subevent" %} <a href="?{% url_replace request 'ordering' '-subevent'%}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'subevent'%}"><i class="fa fa-caret-up"></i></a></th>
{% endif %}
<th>{% trans "Email" %} <a href="?{% url_replace request 'ordering' '-email'%}"><i class="fa fa-caret-down"></i></a>
<a href="?{% url_replace request 'ordering' 'email'%}"><i class="fa fa-caret-up"></i></a></th>
<th>{% trans "Name" %} <a href="?{% url_replace request 'ordering' '-name'%}"><i class="fa fa-caret-down"></i></a>
@@ -60,10 +75,12 @@
{% with e.checkins.first as checkin %}
<tr>
<td>
<strong><a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=e.order.code %}"
>{{ e.order.code }}</a></strong>
<strong><a href="{% url "control:event.order" event=request.event.slug organizer=request.event.organizer.slug code=e.order.code %}">{{ e.order.code }}</a></strong>
</td>
<td>{{ e.item.name }}</td>
<td>{{ e.item.name }}{% if e.variation %} {{ e.variation }}{% endif %}</td>
{% if request.event.has_subevents %}
<td>{{ e.subevent.name }} {{ e.subevent.get_date_range_display }}</td>
{% endif %}
<td>{{ e.order.email }}</td>
<td>
{% if e.addon_to %}

View File

@@ -11,13 +11,23 @@
{% trans "Dashboard" %}
</a>
</li>
{% if 'can_change_event_settings' in request.eventpermset or 'can_change_permissions' in request.eventpermset %}
{% if 'can_change_event_settings' in request.eventpermset %}
<li>
<a href="{% url 'control:event.settings' organizer=request.event.organizer.slug event=request.event.slug %}">
<a href="{% url 'control:event.settings' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.settings" == url_name or "event.settings." in url_name %}class="active"{% endif %}>
<i class="fa fa-wrench fa-fw"></i>
{% trans "Settings" %}
</a>
</li>
{% if request.event.has_subevents %}
<li>
<a href="{% url 'control:event.subevents' organizer=request.event.organizer.slug event=request.event.slug %}"
{% if "event.subevent" in url_name %}class="active"{% endif %}>
<i class="fa fa-calendar fa-fw"></i>
{% trans "Dates" context "subevent" %}
</a>
</li>
{% endif %}
{% endif %}
{% if 'can_change_items' in request.eventpermset %}
<li>

View File

@@ -18,9 +18,11 @@
{% bootstrap_field form.locale layout="horizontal" %}
{% bootstrap_field form.timezone layout="horizontal" %}
</fieldset>
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="horizontal" %}
{% bootstrap_field form.presale_end layout="horizontal" %}
</fieldset>
{% if form.presale_start %}
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="horizontal" %}
{% bootstrap_field form.presale_end layout="horizontal" %}
</fieldset>
{% endif %}
{% endblock %}

View File

@@ -4,4 +4,5 @@
{% block form %}
{% bootstrap_field form.organizer layout="horizontal" %}
{% bootstrap_field form.locales layout="horizontal" %}
{% bootstrap_field form.has_subevents layout="horizontal" %}
{% endblock %}

View File

@@ -14,6 +14,11 @@
</a>
{% endif %}
</h1>
{% if quota.subevent %}
<p>
<span class="fa fa-calendar"></span> {{ quota.subevent.name }} {{ quota.subevent.get_date_range_display }}
</p>
{% endif %}
<div class="row" id="quota-stats">
<div class="col-md-5 col-xs-12">
<legend>{% trans "Usage overview" %}</legend>

View File

@@ -21,6 +21,9 @@
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.size layout="horizontal" %}
{% if form.subevent %}
{% bootstrap_field form.subevent layout="horizontal" %}
{% endif %}
<legend>{% trans "Items" %}</legend>
<p>
{% blocktrans trimmed %}
@@ -30,27 +33,7 @@
left.
{% endblocktrans %}
</p>
<div class="panel-group items-on-quota">
{% for item in items %}
<div class="panel panel-default" data-formset-form>
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion"
href="#collapse{{ item.id }}">
{{ item.name }}
</a>
</h4>
</div>
<div id="collapse{{ item.id }}" class="panel-collapse collapse in">
<div class="panel-body">
<div class="form-horizontal">
{% bootstrap_field item.field layout="horizontal" %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% bootstrap_field form.itemvars layout="horizontal" %}
</fieldset>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">

View File

@@ -12,12 +12,34 @@
number of a specific ticket type at the same time.
{% endblocktrans %}
</p>
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
<p>
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
{% endif %}
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
</p>
</form>
{% endif %}
{% if quotas|length == 0 %}
<div class="empty-collection">
<p>
{% blocktrans trimmed %}
You haven't created any quotas yet.
{% endblocktrans %}
{% if request.GET.subevent %}
{% trans "Your search did not match any quotas." %}
{% else %}
{% blocktrans trimmed %}
You haven't created any quotas yet.
{% endblocktrans %}
{% endif %}
</p>
<a href="{% url "control:event.items.quotas.add" organizer=request.event.organizer.slug event=request.event.slug %}"
@@ -34,6 +56,9 @@
<tr>
<th>{% trans "Quota name" %}</th>
<th>{% trans "Products" %}</th>
{% if request.event.has_subevents %}
<th>{% trans "Date" context "subevent" %}</th>
{% endif %}
<th>{% trans "Total capacity" %}</th>
<th>{% trans "Capacity left" %}</th>
<th class="action-col-2"></th>
@@ -52,6 +77,9 @@
{% endfor %}
</ul>
</td>
{% if request.event.has_subevents %}
<td>{{ q.subevent.name }} {{ q.subevent.get_date_range_display }}</td>
{% endif %}
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
<td>{% include "pretixcontrol/items/fragment_quota_availability.html" with availability=q.availability %}</td>
<td class="text-right">

View File

@@ -81,6 +81,16 @@
{% bootstrap_field position.form.itemvar layout='inline' %}
</label>
</div>
{% if request.event.has_subevents %}
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="subevent"
{% if position.form.operation.value == "subevent" %}checked="checked"{% endif %}>
{% trans "Change date to" context "subevent" %}
{% bootstrap_field position.form.subevent layout='inline' %}
</label>
</div>
{% endif %}
<div class="radio">
<label>
<input name="{{ position.form.prefix }}-operation" type="radio" value="price"
@@ -128,6 +138,9 @@
{% if add_form.addon_to %}
{% bootstrap_field add_form.addon_to layout='horizontal' %}
{% endif %}
{% if add_form.subevent %}
{% bootstrap_field add_form.subevent layout='horizontal' %}
{% endif %}
</div>
</div>
</div>

View File

@@ -184,6 +184,9 @@
{{ line.voucher.code }}
</a>
{% endif %}
{% if line.subevent %}
<br /><span class="fa fa-calendar"></span> {{ line.subevent.name }} &middot; {{ line.subevent.get_date_range_display }}
{% endif %}
{% if line.has_questions %}
<dl>
{% if line.item.admission and event.settings.attendee_names_asked %}

View File

@@ -32,17 +32,26 @@
<div class="input-group">
<input type="text" name="code" class="form-control" placeholder="{% trans "Order code" %}" autofocus>
<span class="input-group-btn">
<button class="btn btn-primary" type="submit">{% trans "Go!" %}</button>
</span>
<button class="btn btn-primary" type="submit">{% trans "Go!" %}</button>
</span>
</div>
</form>
<form class="" action="" method="get">
<div class="col-md-2 col-xs-6">
{% bootstrap_field filter_form.status layout='inline' %}
</div>
<div class="col-md-2 col-xs-6">
{% bootstrap_field filter_form.item layout='inline' %}
</div>
{% if request.event.has_subevents %}
<div class="col-md-1 col-xs-6">
{% bootstrap_field filter_form.item layout='inline' %}
</div>
<div class="col-md-1 col-xs-6">
{% bootstrap_field filter_form.subevent layout='inline' %}
</div>
{% else %}
<div class="col-md-2 col-xs-6">
{% bootstrap_field filter_form.item layout='inline' %}
</div>
{% endif %}
<div class="col-md-2 col-xs-6">
{% bootstrap_field filter_form.provider layout='inline' %}
</div>

View File

@@ -12,6 +12,32 @@
</div>
</div>
<h1>{% trans "Order overview" %}</h1>
{% if request.event.has_subevents %}
<form class="form-inline helper-display-inline" action="" method="get">
<p>
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
{% endif %}
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
</p>
</form>
{% endif %}
{% if subevent_warning %}
<div class="alert alert-info">
{% blocktrans trimmed context "subevent" %}
If you select a sub-event, payment method fees will not be listed here as it might not be clear which
sub-event they belong to.
{% endblocktrans %}
</div>
{% endif %}
<div class="table-responsive">
<table class="table table-condensed table-hover table-product-overview">
<thead>

View File

@@ -0,0 +1,19 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Delete date" context "subevent" %}{% endblock %}
{% block content %}
<h1>{% trans "Delete date" context "subevent" %}</h1>
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
<p>{% blocktrans %}Are you sure you want to delete the date <strong>{{ subevent }}</strong>?{% endblocktrans %}</p>
<div class="form-group submit-group">
<a href="{% url "control:event.vouchers" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default btn-cancel">
{% trans "Cancel" %}
</a>
<button type="submit" class="btn btn-danger btn-save">
{% trans "Delete" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,133 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load formset_tags %}
{% load eventsignal %}
{% block title %}{% trans "Date" context "subevent" %}{% endblock %}
{% block content %}
{% if not subevent.pk %}
<h1>{% trans "Create date" context "subevent" %}</h1>
{% else %}
<h1>{% trans "Date" context "subevent" %}</h1>
{% endif %}
<form action="" method="post" class="form-horizontal">
{% csrf_token %}
{% bootstrap_form_errors form %}
{% for f in itemvar_forms %}
{% bootstrap_form_errors f %}
{% endfor %}
<div class="row">
<div class="col-xs-12 {% if subevent.pk %}col-lg-10{% endif %}">
<fieldset>
<legend>{% trans "General information" %}</legend>
{% bootstrap_field form.name layout="horizontal" %}
{% bootstrap_field form.active layout="horizontal" %}
{% bootstrap_field form.date_from layout="horizontal" %}
{% bootstrap_field form.date_to layout="horizontal" %}
{% bootstrap_field form.location layout="horizontal" %}
{% bootstrap_field form.date_admission layout="horizontal" %}
</fieldset>
<fieldset>
<legend>{% trans "Timeline" %}</legend>
{% bootstrap_field form.presale_start layout="horizontal" %}
{% bootstrap_field form.presale_end layout="horizontal" %}
</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='horizontal' %}
{% bootstrap_field form.itemvars layout='horizontal' %}
</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='horizontal' %}
{% bootstrap_field formset.empty_form.itemvars layout='horizontal' %}
</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 %}
<div class="row">
<div class="col-xs-12 col-md-6">
{{ f.item }}{% if f.variation %} {{ f.variation }}{% endif %}
</div>
<div class="col-xs-12 col-md-6">
{% bootstrap_field f.price layout="inline" %}
</div>
</div>
{% endfor %}
</fieldset>
</div>
{% if subevent.pk %}
<div class="col-xs-12 col-lg-2">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
{% trans "Date history" context "subevent" %}
</h3>
</div>
{% include "pretixcontrol/includes/logs.html" with obj=subevent %}
</div>
</div>
{% endif %}
</div>
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,82 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Dates" context "subevent" %}{% endblock %}
{% block content %}
<h1>{% trans "Dates" context "subevent" %}</h1>
{% if subevents|length == 0 and not filter_form.filtered %}
<div class="empty-collection">
<p>
{% blocktrans trimmed %}
You haven't created any dates for this event series yet.
{% endblocktrans %}
</p>
<a href="{% url "control:event.subevents.add" organizer=request.event.organizer.slug event=request.event.slug %}"
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i>
{% trans "Create a new date" context "subevent" %}</a>
</div>
{% else %}
<form class="row filter-form" action="" method="get">
<div class="col-md-4 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.query layout='inline' %}
</div>
<div class="col-md-6 col-sm-6 col-xs-12">
{% bootstrap_field filter_form.status layout='inline' %}
</div>
<div class="col-md-2 col-sm-6 col-xs-12">
<button class="btn btn-primary btn-block" type="submit">
<span class="fa fa-filter"></span>
<span class="hidden-md">
{% trans "Filter" %}
</span>
</button>
</div>
</form>
<p>
<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>
</p>
<div class="table-responsive">
<table class="table table-hover table-quotas">
<thead>
<tr>
<th>{% trans "Name" %}</th>
<th>{% trans "Begin" %}</th>
<th>{% trans "Status" %}</th>
<th></th>
</tr>
</thead>
<tbody>
{% for s in subevents %}
<tr>
<td>
<strong><a href="{% url "control:event.subevent" organizer=request.event.organizer.slug event=request.event.slug subevent=s.id %}">
{{ s.name }}</a></strong>
</td>
<td>{{ s.get_date_from_display }}</td>
<td>
{% if not s.active %}
<span class="label label-danger">{% trans "Disabled" %}</span>
{% elif s.presale_has_ended %}
<span class="label label-warning">{% trans "Presale over" %}</span>
{% elif not s.presale_is_running %}
<span class="label label-warning">{% trans "Presale not started" %}</span>
{% else %}
<span class="label label-success">{% trans "On sale" %}</span>
{% endif %}
</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>
<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>
{% endfor %}
</tbody>
</table>
</div>
{% include "pretixcontrol/pagination.html" %}
{% endif %}
{% endblock %}

View File

@@ -52,6 +52,9 @@
</div>
</div>
</div>
{% if form.subevent %}
{% bootstrap_field form.subevent layout="horizontal" %}
{% endif %}
{% bootstrap_field form.tag layout="horizontal" %}
{% bootstrap_field form.comment layout="horizontal" %}
</fieldset>

View File

@@ -20,6 +20,17 @@
<option value="r" {% if request.GET.status == "r" %}selected="selected"{% endif %}>{% trans "Redeemed" %}</option>
<option value="e" {% if request.GET.status == "e" %}selected="selected"{% endif %}>{% trans "Expired" %}</option>
</select>
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
{% endif %}
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
<button class="btn btn-default" type="submit" name="download" value="yes">{% trans "Download list" %}</button>
</p>
@@ -27,7 +38,7 @@
{% if vouchers|length == 0 %}
<div class="empty-collection">
<p>
{% if request.GET.search or request.GET.tag or request.GET.status %}
{% if request.GET.search or request.GET.tag or request.GET.status or request.GET.subevent %}
{% trans "Your search did not match any vouchers." %}
{% else %}
{% blocktrans trimmed %}
@@ -58,6 +69,9 @@
<th>{% trans "Expiry" %}</th>
<th>{% trans "Tag" %}</th>
<th>{% trans "Product" %}</th>
{% if request.event.has_subevents %}
<th>{% trans "Date" context "subevent" %}</th>
{% endif %}
<th></th>
</tr>
</thead>
@@ -85,6 +99,9 @@
{% endblocktrans %}
{% endif %}
</td>
{% if request.event.has_subevents %}
<td>{{ v.subevent.name }} {{ v.subevent.get_date_range_display }}</td>
{% endif %}
<td class="text-right">
<a href="{% url "control:event.voucher.delete" organizer=request.event.organizer.slug event=request.event.slug voucher=v.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
</td>

View File

@@ -19,7 +19,7 @@
<div class="panel-heading">
{% trans "Send vouchers" %}
</div>
<div class="panel-body">
<div class="panel-body form-inline">
{% csrf_token %}
{% if request.event.settings.waiting_list_auto %}
<p>
@@ -41,6 +41,17 @@
{% endblocktrans %}
</p>
{% endif %}
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
{% endif %}
<button class="btn btn-large btn-primary" type="submit">
{% trans "Send as many vouchers as possible" %}
</button>
@@ -82,6 +93,17 @@
</option>
{% endfor %}
</select>
{% if request.event.has_subevents %}
<select name="subevent" class="form-control">
<option value="">{% trans "All dates" context "subevent" %}</option>
{% for se in request.event.subevents.all %}
<option value="{{ se.id }}"
{% if request.GET.subevent|add:0 == se.id %}selected="selected"{% endif %}>
{{ se.name }} {{ se.get_date_range_display }}
</option>
{% endfor %}
</select>
{% endif %}
<button class="btn btn-primary" type="submit">{% trans "Filter" %}</button>
</form>
</p>
@@ -93,6 +115,9 @@
<tr>
<th>{% trans "User" %}</th>
<th>{% trans "Product" %}</th>
{% if request.event.has_subevents %}
<th>{% trans "Date" context "subevent" %}</th>
{% endif %}
<th>{% trans "On the list since" %}</th>
<th>{% trans "Status" %}</th>
<th>{% trans "Voucher" %}</th>
@@ -109,6 +134,9 @@
{{ e.variation }}
{% endif %}
</td>
{% if request.event.has_subevents %}
<td>{{ e.subevent.name }} {{ e.subevent.get_date_range_display }}</td>
{% endif %}
<td>{{ e.created|date:"SHORT_DATETIME_FORMAT" }}</td>
<td>
{% if e.voucher %}

View File

@@ -2,7 +2,7 @@ from django.conf.urls import include, url
from pretix.control.views import (
auth, checkin, dashboards, event, global_settings, item, main, orders,
organizer, search, typeahead, user, vouchers, waitinglist,
organizer, search, subevents, typeahead, user, vouchers, waitinglist,
)
urlpatterns = [
@@ -69,6 +69,11 @@ urlpatterns = [
url(r'^settings/invoice$', event.InvoiceSettings.as_view(), name='event.settings.invoice'),
url(r'^settings/invoice/preview$', event.InvoicePreview.as_view(), name='event.settings.invoice.preview'),
url(r'^settings/display', event.DisplaySettings.as_view(), name='event.settings.display'),
url(r'^subevents/$', subevents.SubEventList.as_view(), name='event.subevents'),
url(r'^subevents/(?P<subevent>\d+)/$', subevents.SubEventUpdate.as_view(), name='event.subevent'),
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'^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'),

View File

@@ -37,7 +37,11 @@ class CheckInView(EventPermissionRequiredMixin, ListView):
if self.request.GET.get("item", "") != "":
u = self.request.GET.get("item", "")
qs = qs.filter(item_id__in=(u,))
qs = qs.filter(item_id=u)
if self.request.GET.get("subevent", "") != "":
s = self.request.GET.get("subevent", "")
qs = qs.filter(subevent_id=s)
qs = qs.prefetch_related(
Prefetch('checkins', queryset=Checkin.objects.filter(position__order__event=self.request.event))
@@ -48,8 +52,11 @@ class CheckInView(EventPermissionRequiredMixin, ListView):
keys_allowed = self.get_ordering_keys_mappings()
if p in keys_allowed:
mapped_field = keys_allowed[p]
if type(mapped_field) is tuple:
qs = qs.annotate(**mapped_field[1]).order_by(mapped_field[0])
if isinstance(mapped_field, dict):
order = mapped_field.pop('_order')
qs = qs.annotate(**mapped_field).order_by(order)
elif isinstance(mapped_field, (list, tuple)):
qs = qs.order_by(*mapped_field)
else:
qs = qs.order_by(mapped_field)
@@ -58,7 +65,8 @@ class CheckInView(EventPermissionRequiredMixin, ListView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['items'] = Item.objects.filter(event=self.request.event)
ctx['filtered'] = ("status" in self.request.GET or "user" in self.request.GET or "item" in self.request.GET)
ctx['filtered'] = ("status" in self.request.GET or "user" in self.request.GET or "item" in self.request.GET
or "subevent" in self.request.GET)
return ctx
@staticmethod
@@ -73,10 +81,12 @@ class CheckInView(EventPermissionRequiredMixin, ListView):
'-status': F('checkins__id').desc(nulls_last=True),
'timestamp': F('checkins__datetime').asc(nulls_first=True),
'-timestamp': F('checkins__datetime').desc(nulls_last=True),
'item': 'item__name',
'-item': '-item__name',
'name': (F('display_name').asc(nulls_first=True),
{'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')}),
'-name': (F('display_name').desc(nulls_last=True),
{'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')}),
'item': ('item__name', 'variation__value'),
'-item': ('-item__name', 'variation__value'),
'subevent': ('subevent__date_from', 'subevent__name'),
'-subevent': ('-subevent__date_from', '-subevent__name'),
'name': {'_order': F('display_name').asc(nulls_first=True),
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')},
'-name': {'_order': F('display_name').desc(nulls_last=True),
'display_name': Coalesce('attendee_name', 'addon_to__attendee_name')},
}

View File

@@ -98,9 +98,9 @@ def waitinglist_widgets(sender, **kwargs):
for wle in wles:
if (wle.item, wle.variation) not in itemvar_cache:
itemvar_cache[(wle.item, wle.variation)] = (
wle.variation.check_quotas(count_waitinglist=False, _cache=quota_cache)
wle.variation.check_quotas(subevent=wle.subevent, count_waitinglist=False, _cache=quota_cache)
if wle.variation
else wle.item.check_quotas(count_waitinglist=False, _cache=quota_cache)
else wle.item.check_quotas(subevent=wle.subevent, count_waitinglist=False, _cache=quota_cache)
)
row = itemvar_cache.get((wle.item, wle.variation))
if row[1] > 0:

View File

@@ -5,7 +5,7 @@ from django.core.files import File
from django.core.urlresolvers import resolve, reverse
from django.db import transaction
from django.db.models import Count, F, Q
from django.forms.models import ModelMultipleChoiceField, inlineformset_factory
from django.forms.models import inlineformset_factory
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import redirect
from django.utils.functional import cached_property
@@ -21,6 +21,7 @@ from pretix.base.models import (
CachedTicket, Item, ItemCategory, ItemVariation, Order, Question,
QuestionAnswer, QuestionOption, Quota, Voucher,
)
from pretix.base.models.event import SubEvent
from pretix.base.models.items import ItemAddOn
from pretix.control.forms.item import (
CategoryForm, ItemAddOnForm, ItemAddOnsFormSet, ItemCreateForm,
@@ -549,54 +550,16 @@ class QuotaList(ListView):
template_name = 'pretixcontrol/items/quotas.html'
def get_queryset(self):
return Quota.objects.filter(
qs = Quota.objects.filter(
event=self.request.event
).prefetch_related("items")
if self.request.GET.get("subevent", "") != "":
s = self.request.GET.get("subevent", "")
qs = qs.filter(subevent_id=s)
return qs
class QuotaEditorMixin:
@cached_property
def items(self) -> "List[Item]":
return list(self.request.event.items.all().prefetch_related("variations"))
def get_form(self, form_class=QuotaForm):
if not hasattr(self, '_form'):
kwargs = self.get_form_kwargs()
kwargs['items'] = self.items
self._form = form_class(**kwargs)
return self._form
def get_context_data(self, *args, **kwargs) -> dict:
context = super().get_context_data(*args, **kwargs)
context['items'] = self.items
for item in context['items']:
item.field = self.get_form(QuotaForm)['item_%s' % item.id]
return context
@transaction.atomic
def form_valid(self, form):
res = super().form_valid(form)
items = self.object.items.all()
variations = self.object.variations.all()
selected_variations = []
self.object = form.instance
for item in self.items:
field = form.fields['item_%s' % item.id]
data = form.cleaned_data['item_%s' % item.id]
if isinstance(field, ModelMultipleChoiceField):
for v in data:
selected_variations.append(v)
if data and item not in items:
self.object.items.add(item)
elif not data and item in items:
self.object.items.remove(item)
self.object.variations.add(*[v for v in selected_variations if v not in variations])
self.object.variations.remove(*[v for v in variations if v not in selected_variations])
return res
class QuotaCreate(EventPermissionRequiredMixin, QuotaEditorMixin, CreateView):
class QuotaCreate(EventPermissionRequiredMixin, CreateView):
model = Quota
form_class = QuotaForm
template_name = 'pretixcontrol/items/quota_edit.html'
@@ -691,7 +654,7 @@ class QuotaView(ChartContainingView, DetailView):
raise Http404(_("The requested quota does not exist."))
class QuotaUpdate(EventPermissionRequiredMixin, QuotaEditorMixin, UpdateView):
class QuotaUpdate(EventPermissionRequiredMixin, UpdateView):
model = Quota
form_class = QuotaForm
template_name = 'pretixcontrol/items/quota_edit.html'
@@ -719,6 +682,22 @@ class QuotaUpdate(EventPermissionRequiredMixin, QuotaEditorMixin, UpdateView):
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
if ((form.initial.get('subevent') and not form.instance.subevent)
or (form.instance.subevent and form.initial.get('subevent') != form.instance.subevent.pk)):
if form.initial.get('subevent'):
se = SubEvent.objects.get(event=self.request.event, pk=form.initial.get('subevent'))
se.log_action(
'pretix.subevent.quota.deleted', user=self.request.user, data={
'id': form.instance.pk
}
)
if form.instance.subevent:
form.instance.subevent.log_action(
'pretix.subevent.quota.added', user=self.request.user, data={
'id': form.instance.pk
}
)
return super().form_valid(form)
def get_success_url(self) -> str:

View File

@@ -90,6 +90,7 @@ class EventWizard(SessionWizardView):
event = form_dict['basics'].instance
event.organizer = foundation_data['organizer']
event.plugins = settings.PRETIX_PLUGINS_DEFAULT
event.has_subevents = foundation_data['has_subevents']
form_dict['basics'].save()
has_control_rights = self.request.user.teams.filter(
@@ -106,6 +107,17 @@ class EventWizard(SessionWizardView):
t.members.add(self.request.user)
t.limit_events.add(event)
if event.has_subevents:
event.subevents.create(
name=event.name,
date_from=event.date_from,
date_to=event.date_to,
presale_start=event.presale_start,
presale_end=event.presale_end,
location=event.location,
active=True
)
logdata = {}
for f in form_list:
logdata.update({

View File

@@ -16,6 +16,7 @@ from pretix.base.models import (
CachedFile, CachedTicket, Invoice, InvoiceAddress, Item, ItemVariation,
Order, Quota, generate_position_secret, generate_secret,
)
from pretix.base.models.event import SubEvent
from pretix.base.services.export import export
from pretix.base.services.invoices import (
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
@@ -53,13 +54,6 @@ class OrderList(EventPermissionRequiredMixin, ListView):
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
if self.request.GET.get("ordering", "") != "":
p = self.request.GET.get("ordering", "")
p_admissable = ('-code', 'code', '-email', 'email', '-total', 'total', '-datetime', 'datetime',
'-status', 'status', 'pcnt', '-pcnt')
if p in p_admissable:
qs = qs.order_by(p)
return qs.distinct()
def get_context_data(self, **kwargs):
@@ -480,7 +474,8 @@ class OrderChange(OrderView):
try:
ocm.add_position(item, variation,
self.add_form.cleaned_data['price'],
self.add_form.cleaned_data.get('addon_to'))
self.add_form.cleaned_data.get('addon_to'),
self.add_form.cleaned_data.get('subevent'))
except OrderError as e:
self.add_form.custom_error = str(e)
return False
@@ -506,6 +501,8 @@ class OrderChange(OrderView):
ocm.change_item(p, item, variation)
elif p.form.cleaned_data['operation'] == 'price':
ocm.change_price(p, p.form.cleaned_data['price'])
elif p.form.cleaned_data['operation'] == 'subevent':
ocm.change_subevent(p, p.form.cleaned_data['subevent'])
elif p.form.cleaned_data['operation'] == 'cancel':
ocm.cancel(p)
@@ -613,7 +610,19 @@ class OverView(EventPermissionRequiredMixin, TemplateView):
def get_context_data(self, **kwargs):
ctx = super().get_context_data()
ctx['items_by_category'], ctx['total'] = order_overview(self.request.event)
subevent = None
if self.request.GET.get("subevent", "") != "" and self.request.event.has_subevents:
i = self.request.GET.get("subevent", "")
try:
subevent = self.request.event.subevents.get(pk=i)
except SubEvent.DoesNotExist:
pass
ctx['items_by_category'], ctx['total'] = order_overview(self.request.event, subevent=subevent)
ctx['subevent_warning'] = self.request.event.has_subevents and subevent and (
self.request.event.orders.filter(payment_fee__gt=0).exists()
)
return ctx

View File

@@ -0,0 +1,312 @@
import copy
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db import transaction
from django.forms import inlineformset_factory
from django.http import Http404, HttpResponseRedirect
from django.utils.functional import cached_property
from django.utils.translation import pgettext_lazy, ugettext_lazy as _
from django.views.generic import CreateView, DeleteView, ListView, UpdateView
from pretix.base.models.event import SubEvent
from pretix.base.models.items import Quota, SubEventItem, SubEventItemVariation
from pretix.control.forms.filter import SubEventFilterForm
from pretix.control.forms.item import QuotaForm
from pretix.control.forms.subevents import (
QuotaFormSet, SubEventForm, SubEventItemForm, SubEventItemVariationForm,
)
from pretix.control.permissions import EventPermissionRequiredMixin
class SubEventList(EventPermissionRequiredMixin, ListView):
model = SubEvent
context_object_name = 'subevents'
paginate_by = 30
template_name = 'pretixcontrol/subevents/index.html'
permission = 'can_change_settings'
def get_queryset(self):
qs = self.request.event.subevents.all()
if self.filter_form.is_valid():
qs = self.filter_form.filter_qs(qs)
return qs
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['filter_form'] = self.filter_form
return ctx
@cached_property
def filter_form(self):
return SubEventFilterForm(data=self.request.GET)
class SubEventDelete(EventPermissionRequiredMixin, DeleteView):
model = SubEvent
template_name = 'pretixcontrol/subevents/delete.html'
permission = 'can_change_settings'
context_object_name = 'subevents'
def get_object(self, queryset=None) -> SubEvent:
try:
return self.request.event.subevents.get(
id=self.kwargs['subevent']
)
except SubEvent.DoesNotExist:
raise Http404(pgettext_lazy("subevent", "The requested date does not exist."))
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.'))
return HttpResponseRedirect(self.get_success_url())
return super().get(request, *args, **kwargs)
@transaction.atomic
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
success_url = self.get_success_url()
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.'))
return HttpResponseRedirect(self.get_success_url())
else:
self.object.log_action('pretix.subevent.deleted', user=self.request.user)
self.object.delete()
messages.success(request, pgettext_lazy('subevent', 'The selected date has been deleted.'))
return HttpResponseRedirect(success_url)
def get_success_url(self) -> str:
return reverse('control:event.subevents', kwargs={
'organizer': self.request.event.organizer.slug,
'event': self.request.event.slug,
})
class SubEventEditorMixin:
@cached_property
def formset(self):
extra = 0
kwargs = {}
if self.copy_from:
kwargs['initial'] = [
{
'size': q.size,
'name': q.name,
'itemvars': [str(i.pk) for i in q.items.all()] + [
'{}-{}'.format(v.item_id, v.pk) for v in q.variations.all()
]
} for q in self.copy_from.quotas.prefetch_related('items', 'variations')
]
extra = len(kwargs['initial'])
formsetclass = inlineformset_factory(
SubEvent, Quota,
form=QuotaForm, formset=QuotaFormSet,
can_order=False, can_delete=True, extra=extra,
)
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)
def save_formset(self, obj):
for form in self.formset.initial_forms:
if form in self.formset.deleted_forms:
if not form.instance.pk:
continue
form.instance.log_action(action='pretix.event.quota.deleted', user=self.request.user)
obj.log_action('pretix.subevent.quota.deleted', user=self.request.user, data={
'id': form.instance.pk
})
form.instance.delete()
form.instance.pk = None
elif form.has_changed():
form.instance.question = obj
form.save()
change_data = {k: form.cleaned_data.get(k) for k in form.changed_data}
change_data['id'] = form.instance.pk
obj.log_action(
'pretix.subevent.quota.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
form.instance.log_action(
'pretix.event.quota.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
for form in self.formset.extra_forms:
if not form.has_changed():
continue
if self.formset._should_delete_form(form):
continue
form.instance.subevent = obj
form.instance.event = obj.event
form.save()
change_data = {k: form.cleaned_data.get(k) for k in form.changed_data}
change_data['id'] = form.instance.pk
form.instance.log_action(action='pretix.event.quota.added', user=self.request.user, data=change_data)
obj.log_action('pretix.subevent.quota.added', user=self.request.user, data=change_data)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['formset'] = self.formset
ctx['itemvar_forms'] = self.itemvar_forms
return ctx
@cached_property
def copy_from(self):
if self.request.GET.get("copy_from") and not getattr(self, 'object'):
try:
return self.request.event.subevents.get(pk=self.request.GET.get("copy_from"))
except SubEvent.DoesNotExist:
pass
@cached_property
def itemvar_forms(self):
se_item_instances = {
sei.item_id: sei for sei in SubEventItem.objects.filter(subevent=self.object)
}
se_var_instances = {
sei.variation_id: sei for sei in SubEventItemVariation.objects.filter(subevent=self.object)
}
if self.copy_from:
se_item_instances = {
sei.item_id: SubEventItem(item=sei.item, price=sei.price)
for sei in SubEventItem.objects.filter(subevent=self.copy_from).select_related('item')
}
se_var_instances = {
sei.variation_id: SubEventItemVariation(variation=sei.variation, price=sei.price)
for sei in SubEventItemVariation.objects.filter(subevent=self.copy_from).select_related('variation')
}
formlist = []
for i in self.request.event.items.filter(active=True).prefetch_related('variations'):
if i.has_variations:
for v in i.variations.all():
inst = se_var_instances.get(v.pk) or SubEventItemVariation(subevent=self.object, variation=v)
formlist.append(SubEventItemVariationForm(
prefix='itemvar-{}'.format(v.pk),
item=i, variation=v,
instance=inst,
data=(self.request.POST if self.request.method == "POST" else None)
))
else:
inst = se_item_instances.get(i.pk) or SubEventItem(subevent=self.object, item=i)
formlist.append(SubEventItemForm(
prefix='item-{}'.format(i.pk),
item=i,
instance=inst,
data=(self.request.POST if self.request.method == "POST" else None)
))
return formlist
def is_valid(self, form):
return form.is_valid() and all([f.is_valid() for f in self.itemvar_forms]) and self.formset.is_valid()
class SubEventUpdate(EventPermissionRequiredMixin, SubEventEditorMixin, UpdateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html'
permission = 'can_change_settings'
context_object_name = 'subevent'
form_class = SubEventForm
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.get_form()
if self.is_valid(form):
return self.form_valid(form)
else:
return self.form_invalid(form)
def get_object(self, queryset=None) -> SubEvent:
try:
return self.request.event.subevents.get(
id=self.kwargs['subevent']
)
except SubEvent.DoesNotExist:
raise Http404(pgettext_lazy("subevent", "The requested date does not exist."))
@transaction.atomic
def form_valid(self, form):
self.save_formset(self.object)
for f in self.itemvar_forms:
f.save()
# TODO: LogEntry?
messages.success(self.request, _('Your changes have been saved.'))
if form.has_changed():
self.object.log_action(
'pretix.subevent.changed', user=self.request.user, data={
k: form.cleaned_data.get(k) for k in form.changed_data
}
)
return super().form_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,
})
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['event'] = self.request.event
return kwargs
class SubEventCreate(SubEventEditorMixin, EventPermissionRequiredMixin, CreateView):
model = SubEvent
template_name = 'pretixcontrol/subevents/detail.html'
permission = 'can_change_settings'
context_object_name = 'subevent'
form_class = SubEventForm
def post(self, request, *args, **kwargs):
self.object = SubEvent(event=self.request.event)
form = self.get_form()
if self.is_valid(form):
return self.form_valid(form)
else:
return self.form_invalid(form)
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_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['event'] = self.request.event
if self.copy_from:
i = copy.copy(self.copy_from)
i.pk = None
kwargs['instance'] = i
else:
kwargs['instance'] = SubEvent(event=self.request.event)
return kwargs
@transaction.atomic
def form_valid(self, form):
form.instance.event = self.request.event
messages.success(self.request, pgettext_lazy('subevent', 'The new date has been created.'))
ret = super().form_valid(form)
form.instance.log_action('pretix.subevent.added', data=dict(form.cleaned_data), user=self.request.user)
self.save_formset(form.instance)
for f in self.itemvar_forms:
f.instance.subevent = form.instance
f.save()
return ret

View File

@@ -46,6 +46,9 @@ class VoucherList(EventPermissionRequiredMixin, ListView):
qs = qs.filter(redeemed__gt=0)
elif s == 'e':
qs = qs.filter(Q(valid_until__isnull=False) & Q(valid_until__lt=now())).filter(redeemed=0)
if self.request.GET.get("subevent", "") != "":
s = self.request.GET.get("subevent", "")
qs = qs.filter(subevent_id=s)
return qs
def get(self, request, *args, **kwargs):

View File

@@ -35,7 +35,8 @@ class AutoAssign(EventPermissionRequiredMixin, AsyncAction, View):
})
def post(self, request, *args, **kwargs):
return self.do(self.request.event.id, self.request.user.id)
return self.do(self.request.event.id, self.request.user.id,
self.request.POST.get('subevent'))
class WaitingListView(EventPermissionRequiredMixin, ListView):
@@ -78,7 +79,9 @@ class WaitingListView(EventPermissionRequiredMixin, ListView):
def get_queryset(self):
qs = WaitingListEntry.objects.filter(
event=self.request.event
).select_related('item', 'variation', 'voucher').prefetch_related('item__quotas', 'variation__quotas')
).select_related('item', 'variation', 'voucher').prefetch_related(
'item__quotas', 'variation__quotas'
)
s = self.request.GET.get("status", "")
if s == 's':
@@ -90,7 +93,11 @@ class WaitingListView(EventPermissionRequiredMixin, ListView):
if self.request.GET.get("item", "") != "":
i = self.request.GET.get("item", "")
qs = qs.filter(item_id__in=(i,))
qs = qs.filter(item_id=i)
if self.request.GET.get("subevent", "") != "":
s = self.request.GET.get("subevent", "")
qs = qs.filter(subevent_id=s)
return qs
@@ -107,9 +114,9 @@ class WaitingListView(EventPermissionRequiredMixin, ListView):
wle.availability = itemvar_cache.get((wle.item, wle.variation))
else:
wle.availability = (
wle.variation.check_quotas(count_waitinglist=False, _cache=quota_cache)
wle.variation.check_quotas(count_waitinglist=False, subevent=wle.subevent, _cache=quota_cache)
if wle.variation
else wle.item.check_quotas(count_waitinglist=False, _cache=quota_cache)
else wle.item.check_quotas(count_waitinglist=False, subevent=wle.subevent, _cache=quota_cache)
)
itemvar_cache[(wle.item, wle.variation)] = wle.availability
if wle.availability[0] == 100: