diff --git a/src/pretix/api/serializers/cart.py b/src/pretix/api/serializers/cart.py
index 74dc5242b4..4f9a6db272 100644
--- a/src/pretix/api/serializers/cart.py
+++ b/src/pretix/api/serializers/cart.py
@@ -77,11 +77,7 @@ class BaseCartPositionCreateSerializer(I18nAwareModelSerializer):
def validate_subevent(self, subevent):
if self.context['event'].has_subevents:
- if not subevent:
- raise ValidationError(
- 'You need to set a subevent.'
- )
- if subevent.event != self.context['event']:
+ if subevent and subevent.event != self.context['event']:
raise ValidationError(
'The specified subevent does not belong to this event.'
)
diff --git a/src/pretix/api/serializers/order.py b/src/pretix/api/serializers/order.py
index 418dbc00f9..e2cc04b225 100644
--- a/src/pretix/api/serializers/order.py
+++ b/src/pretix/api/serializers/order.py
@@ -1094,11 +1094,7 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
def validate_subevent(self, subevent):
if self.context['event'].has_subevents:
- if not subevent:
- raise ValidationError(
- 'You need to set a subevent.'
- )
- if subevent.event != self.context['event']:
+ if subevent and subevent.event != self.context['event']:
raise ValidationError(
'The specified subevent does not belong to this event.'
)
diff --git a/src/pretix/api/serializers/orderchange.py b/src/pretix/api/serializers/orderchange.py
index a92aa46b03..4f1c69bcd3 100644
--- a/src/pretix/api/serializers/orderchange.py
+++ b/src/pretix/api/serializers/orderchange.py
@@ -275,11 +275,15 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
def validate_subevent(self, subevent):
if self.context['event'].has_subevents:
- if not subevent:
+ if self.instance.subevent_id and not subevent:
raise ValidationError(
'You need to set a subevent.'
)
- if subevent.event != self.context['event']:
+ if not self.instance.subevent_id and subevent:
+ raise ValidationError(
+ 'You cannot set a subevent if none was set previously.'
+ )
+ if subevent and subevent.event != self.context['event']:
raise ValidationError(
'The specified subevent does not belong to this event.'
)
diff --git a/src/pretix/base/exporters/orderlist.py b/src/pretix/base/exporters/orderlist.py
index bf96145ec4..f33c46e04c 100644
--- a/src/pretix/base/exporters/orderlist.py
+++ b/src/pretix/base/exporters/orderlist.py
@@ -207,6 +207,7 @@ class OrderListExporter(MultiSheetListExporter):
if form_data.get('event_date_range'):
dt_start, dt_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['event_date_range'], self.timezone)
+ # Subevent-less positions in a series will never be found when this filter is set but that seems like a valid way to do this
if dt_start:
annotations['event_date_max'] = Case(
When(**{f'{rel}event__has_subevents': True}, then=Max(f'{rel}all_positions__subevent__date_from')),
diff --git a/src/pretix/base/forms/questions.py b/src/pretix/base/forms/questions.py
index bf5c758f88..448ec0af16 100644
--- a/src/pretix/base/forms/questions.py
+++ b/src/pretix/base/forms/questions.py
@@ -1202,8 +1202,9 @@ class TicketLevelQuestionsForm(BaseQuestionsForm):
return field
def clean(self):
- from pretix.base.addressvalidation import \
- validate_address # local import to prevent impact on startup time
+ from pretix.base.addressvalidation import ( # local import to prevent impact on startup time
+ validate_address,
+ )
d = super().clean()
@@ -1444,8 +1445,9 @@ class BaseInvoiceAddressForm(forms.ModelForm):
self.fields['transmission_type'].widget.attrs['data-trigger-address-info'] = 'on'
def clean(self):
- from pretix.base.addressvalidation import \
- validate_address # local import to prevent impact on startup time
+ from pretix.base.addressvalidation import ( # local import to prevent impact on startup time
+ validate_address,
+ )
data = self.cleaned_data
diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py
index 7aa035970a..e3fb587a26 100644
--- a/src/pretix/base/models/items.py
+++ b/src/pretix/base/models/items.py
@@ -896,7 +896,7 @@ class Item(LoggedModel):
return check_quotas
def check_quotas(self, ignored_quotas=None, count_waitinglist=True, subevent=None, _cache=None,
- include_bundled=False, trust_parameters=False, fail_on_no_quotas=False):
+ include_bundled=False, fail_on_no_quotas=False):
"""
This method is used to determine whether this Item is currently available
for sale.
@@ -906,8 +906,6 @@ class Item(LoggedModel):
to no quotas being checked at all, this method will return
unlimited availability.
:param include_bundled: Also take availability of bundled items into consideration.
- :param trust_parameters: Disable checking of the subevent parameter and disable checking if
- any variations exist (performance optimization).
:returns: any of the return codes of :py:meth:`Quota.availability()`.
:raises ValueError: if you call this on an item which has variations associated with it.
@@ -1307,7 +1305,7 @@ class ItemVariation(models.Model):
return check_quotas
def check_quotas(self, ignored_quotas=None, count_waitinglist=True, subevent=None, _cache=None,
- include_bundled=False, trust_parameters=False, fail_on_no_quotas=False) -> Tuple[int, int]:
+ include_bundled=False, fail_on_no_quotas=False) -> Tuple[int, int]:
"""
This method is used to determine whether this ItemVariation is currently
available for sale in terms of quotas.
@@ -1319,8 +1317,6 @@ class ItemVariation(models.Model):
:param count_waitinglist: If ``False``, waiting list entries will be ignored for quota calculation.
:returns: any of the return codes of :py:meth:`Quota.availability()`.
"""
- if not trust_parameters and not subevent and self.item.event.has_subevents: # NOQA
- raise TypeError('You need to supply a subevent.')
check_quotas = self._get_quotas(ignored_quotas=ignored_quotas, subevent=subevent)
quotacounter = Counter()
res = Quota.AVAILABILITY_OK, None
@@ -2201,9 +2197,7 @@ class Quota(LoggedModel):
@staticmethod
def clean_subevent(event, subevent):
if event.has_subevents:
- if not subevent:
- raise ValidationError(_('Subevent cannot be null for event series.'))
- if event != subevent.event:
+ if subevent and event != subevent.event:
raise ValidationError(_('The subevent does not belong to this event.'))
else:
if subevent:
diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py
index 15dba58a98..52feaaa3ea 100644
--- a/src/pretix/base/models/orders.py
+++ b/src/pretix/base/models/orders.py
@@ -656,8 +656,8 @@ class Order(LockModel, LoggedModel):
terms = [
until.datetime(se)
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
- ]
- return min(terms) if terms else None
+ ] or [until.datetime(self.event)] # use event settings only if there is no subevent in the cart
+ return min(terms)
else:
return until.datetime(self.event)
@@ -672,8 +672,8 @@ class Order(LockModel, LoggedModel):
terms = [
until.datetime(se)
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
- ]
- return min(terms) if terms else None
+ ] or [until.datetime(self.event)] # use event settings only if there is no subevent in the cart
+ return min(terms)
else:
return until.datetime(self.event)
@@ -890,8 +890,8 @@ class Order(LockModel, LoggedModel):
dates = [
modify_deadline.datetime(se)
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
- ]
- return min(dates) if dates else None
+ ] or [modify_deadline.datetime(self.event)] # use event settings only if there is no subevent in the cart
+ return min(dates)
elif modify_deadline:
return modify_deadline.datetime(self.event)
return None
@@ -953,8 +953,8 @@ class Order(LockModel, LoggedModel):
dates = [
dl_date.datetime(se)
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
- ]
- dl_date = min(dates) if dates else None
+ ] or [dl_date.datetime(self.event)] # use event settings only if there is no subevent in the cart
+ dl_date = min(dates)
else:
dl_date = dl_date.datetime(self.event)
return dl_date
@@ -983,7 +983,7 @@ class Order(LockModel, LoggedModel):
terms = [
term_last.datetime(se).date()
for se in self.event.subevents.filter(id__in=self.positions.values_list('subevent', flat=True))
- ]
+ ] or [term_last.datetime(self.event)] # use event settings only if there is no subevent in the cart
if terms:
term_last = min(terms)
else:
diff --git a/src/pretix/base/models/waitinglist.py b/src/pretix/base/models/waitinglist.py
index 2e90ec0694..c59b6f5454 100644
--- a/src/pretix/base/models/waitinglist.py
+++ b/src/pretix/base/models/waitinglist.py
@@ -300,9 +300,7 @@ class WaitingListEntry(LoggedModel):
@staticmethod
def clean_subevent(event, subevent):
if event.has_subevents:
- if not subevent:
- raise ValidationError(_('Subevent cannot be null for event series.'))
- if event != subevent.event:
+ if subevent and event != subevent.event:
raise ValidationError(_('The subevent does not belong to this event.'))
else:
if subevent:
diff --git a/src/pretix/base/payment.py b/src/pretix/base/payment.py
index 4dc51c0b42..aadd9537b2 100644
--- a/src/pretix/base/payment.py
+++ b/src/pretix/base/payment.py
@@ -637,6 +637,7 @@ class BasePaymentProvider:
def _absolute_availability_date(self, rel_date, cart_id=None, order=None, aggregate_fn=min):
if not rel_date:
return None
+
if self.event.has_subevents and cart_id:
dates = [
rel_date.datetime(se).date()
@@ -645,16 +646,16 @@ class BasePaymentProvider:
cart_id=cart_id, event=self.event
).values_list('subevent', flat=True)
)
- ]
- return aggregate_fn(dates) if dates else None
+ ] or [rel_date.datetime(self.event).date()] # Use event dates only on carts with no subevents
+ return aggregate_fn(dates)
elif self.event.has_subevents and order:
dates = [
rel_date.datetime(se).date()
for se in self.event.subevents.filter(
id__in=order.positions.values_list('subevent', flat=True)
)
- ]
- return aggregate_fn(dates) if dates else None
+ ] or [rel_date.datetime(self.event).date()] # Use event dates only on carts with no subevents
+ return aggregate_fn(dates)
elif self.event.has_subevents:
raise NotImplementedError('Payment provider is not subevent-ready.')
else:
diff --git a/src/pretix/base/services/cart.py b/src/pretix/base/services/cart.py
index a59735daf2..e86fd829ac 100644
--- a/src/pretix/base/services/cart.py
+++ b/src/pretix/base/services/cart.py
@@ -793,7 +793,7 @@ class CartManager:
operations = []
for i in items:
- if self.event.has_subevents and i.get('subevents'):
+ if self.event.has_subevents and i.get('subevent'):
subevent = self._subevents_cache[int(i.get('subevent'))]
else:
subevent = None
diff --git a/src/pretix/base/services/invoices.py b/src/pretix/base/services/invoices.py
index 75653e9081..c7ea6b70b1 100644
--- a/src/pretix/base/services/invoices.py
+++ b/src/pretix/base/services/invoices.py
@@ -289,7 +289,7 @@ def build_invoice(invoice: Invoice) -> Invoice:
answ.to_string_i18n()
)
- if invoice.event.has_subevents:
+ if invoice.event.has_subevents and p.subevent_id:
desc += "
" + pgettext("subevent", "Date: {}").format(p.subevent)
if invoice.event.settings.invoice_event_location and location and len(locations) > 1:
@@ -423,8 +423,7 @@ def _service_period_for_position(invoice, position, invoice_dt):
period_start = position.subevent.date_from
period_end = position.subevent.date_to
else:
- # Currently impossible case, but might not be in the future and never makes
- # sense to use the event date here
+ # Does not make sense to use the parent event date here
period_start = invoice_dt
period_end = invoice_dt
elif invoice.event.settings.invoice_period == "auto_no_event":
diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py
index 890fa06409..e8c61cc0c9 100644
--- a/src/pretix/base/services/orders.py
+++ b/src/pretix/base/services/orders.py
@@ -53,7 +53,7 @@ from django.db.models import (
Count, Exists, F, IntegerField, Max, Min, OuterRef, Q, QuerySet, Subquery,
Sum, Value,
)
-from django.db.models.functions import Cast, Greatest
+from django.db.models.functions import Cast, Coalesce, Greatest
from django.db.transaction import get_connection
from django.dispatch import receiver
from django.utils.functional import cached_property
@@ -1548,7 +1548,7 @@ def send_download_reminders(sender, **kwargs):
if event.has_subevents:
qs = qs.annotate(
- first_date=Min('all_positions__subevent__date_from')
+ first_date=Coalesce(Min('all_positions__subevent__date_from'), Value(event.date_from))
).filter(
Q(first_date__gte=today)
)
@@ -1954,8 +1954,6 @@ class OrderChangeManager:
is_bundled = True
else:
raise OrderError(self.error_messages['addon_invalid'])
- if self.order.event.has_subevents and not subevent:
- raise OrderError(self.error_messages['subevent_required'])
seated = item.seat_category_mappings.filter(subevent=subevent).exists()
if seated and not seat and self.event.settings.seating_choice:
diff --git a/src/pretix/control/forms/orders.py b/src/pretix/control/forms/orders.py
index fe7398333b..d212dd19f7 100644
--- a/src/pretix/control/forms/orders.py
+++ b/src/pretix/control/forms/orders.py
@@ -362,7 +362,7 @@ class OrderPositionAddForm(forms.Form):
subevent = forms.ModelChoiceField(
SubEvent.objects.none(),
label=pgettext_lazy('subevent', 'Date'),
- required=True,
+ required=False,
empty_label=None
)
@@ -416,7 +416,6 @@ class OrderPositionAddForm(forms.Form):
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
- self.fields['subevent'].required = True
else:
del self.fields['subevent']
change_decimal_field(self.fields['price'], order.event.currency)
diff --git a/src/pretix/control/forms/waitinglist.py b/src/pretix/control/forms/waitinglist.py
index cf77380f60..a68514e617 100644
--- a/src/pretix/control/forms/waitinglist.py
+++ b/src/pretix/control/forms/waitinglist.py
@@ -59,7 +59,7 @@ class WaitingListEntryEditForm(I18nModelForm):
super().__init__(*args, **kwargs)
- if self.event.has_subevents:
+ if self.event.has_subevents and self.instance.subevent_id:
self.fields['subevent'].required = True
self.fields['subevent'].queryset = self.event.subevents.all()
self.fields['subevent'].widget = Select2(
diff --git a/src/pretix/control/templates/pretixcontrol/items/quotas.html b/src/pretix/control/templates/pretixcontrol/items/quotas.html
index 0da36c9be5..10b5019044 100644
--- a/src/pretix/control/templates/pretixcontrol/items/quotas.html
+++ b/src/pretix/control/templates/pretixcontrol/items/quotas.html
@@ -150,7 +150,11 @@
{% if request.event.has_subevents %}