Compare commits

...
Author SHA1 Message Date
Raphael Michel 450ee7ef44 Fix test failures 2026-09-23 12:04:58 +02:00
Raphael Michel 999754f541 first round of fixes 2026-09-23 11:33:55 +02:00
Raphael Michel 0545fda71d first step 2026-09-23 11:33:55 +02:00
34 changed files with 203 additions and 237 deletions
+1 -5
View File
@@ -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.'
)
+1 -5
View File
@@ -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.'
)
+6 -2
View File
@@ -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.'
)
+1
View File
@@ -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')),
+3 -13
View File
@@ -889,14 +889,13 @@ class Item(LoggedModel):
check_quotas = set(getattr(
self, '_subevent_quotas', # Utilize cache in product list
self.quotas.filter(subevent=subevent).select_related('subevent')
if subevent else self.quotas.all()
))
if ignored_quotas:
check_quotas -= set(ignored_quotas)
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,15 +905,11 @@ 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.
Please use the method on the ItemVariation object you are interested in.
"""
if not trust_parameters and not subevent and self.event.has_subevents:
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
@@ -1302,14 +1297,13 @@ class ItemVariation(models.Model):
check_quotas = set(getattr(
self, '_subevent_quotas', # Utilize cache in product list
self.quotas.filter(subevent=subevent).select_related('subevent')
if subevent else self.quotas.all()
))
if ignored_quotas:
check_quotas -= set(ignored_quotas)
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.
@@ -1321,8 +1315,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
@@ -2203,9 +2195,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:
+9 -9
View File
@@ -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:
+1 -3
View File
@@ -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:
+5 -4
View File
@@ -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:
+1 -3
View File
@@ -793,9 +793,7 @@ class CartManager:
operations = []
for i in items:
if self.event.has_subevents:
if not i.get('subevent') or int(i.get('subevent')) not in self._subevents_cache:
raise CartError(error_messages['subevent_required'])
if self.event.has_subevents and i.get('subevent'):
subevent = self._subevents_cache[int(i.get('subevent'))]
else:
subevent = None
+2 -2
View File
@@ -58,9 +58,9 @@ class CrossSellingService:
result = (
(DummyCategory(category, subevent),
self._prepare_items(subevent, items_qs, discount_info),
f'subevent_{subevent.pk}_')
f'subevent_{subevent.pk}_' if subevent else '')
for subevent in subevents
for (category, items_qs, discount_info) in self._applicable_categories(subevent.pk)
for (category, items_qs, discount_info) in self._applicable_categories(subevent.pk if subevent else 0)
)
else:
result = (
+2 -3
View File
@@ -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 += "<br />" + 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":
+2 -4
View File
@@ -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:
-1
View File
@@ -342,7 +342,6 @@ class QuotaForm(I18nModelForm):
}
)
self.fields['subevent'].widget.choices = self.fields['subevent'].choices
self.fields['subevent'].required = True
else:
del self.fields['subevent']
+1 -2
View File
@@ -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)
+1 -1
View File
@@ -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(
@@ -150,7 +150,11 @@
</td>
{% if request.event.has_subevents %}
<td>
{{ q.subevent.name }} – {{ q.subevent.get_date_range_display_with_times }}
{% if q.subevent %}
{{ q.subevent.name }} – {{ q.subevent.get_date_range_display_with_times }}
{% else %}
–
{% endif %}
</td>
{% endif %}
<td>{% if q.size == None %}Unlimited{% else %}{{ q.size }}{% endif %}</td>
@@ -113,7 +113,7 @@
{% endif %}
</div>
<div class="col-sm-5">
{{ position.subevent }}
{{ position.subevent|default_if_none:"–" }}
</div>
<div class="col-sm-4">
{% bootstrap_field position.form.subevent layout='inline' %}
@@ -203,7 +203,11 @@
</td>
{% if request.event.has_subevents %}
<td>
{{ v.subevent.name }} – {{ v.subevent.get_date_range_display_with_times }}
{% if v.subevent %}
{{ v.subevent.name }} – {{ v.subevent.get_date_range_display_with_times }}
{% else %}
–
{% endif %}
</td>
{% endif %}
<td class="text-right flip">
@@ -209,7 +209,13 @@
{% endif %}
</td>
{% if request.event.has_subevents %}
<td>{{ e.subevent }}</td>
<td>
{% if e.subevent %}
{{ e.subevent }}
{% else %}
–
{% endif %}
</td>
{% endif %}
<td>
{{ e.created|date:"SHORT_DATETIME_FORMAT" }}
+1 -1
View File
@@ -358,7 +358,7 @@ class WaitingListView(EventPermissionRequiredMixin, WaitingListQuerySetMixin, Pa
str(w.priority)
]
if self.request.event.has_subevents:
row.append(str(w.subevent))
row.append(str(w.subevent) if w.subevent else '')
writer.writerow(row)
r = HttpResponse(output.getvalue().encode("utf-8"), content_type='text/csv')
+23 -13
View File
@@ -418,7 +418,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
str(op.item) + (" – " + str(op.variation.value) if op.variation else ""),
money_filter(op.price, self.event.currency),
)
if self.event.has_subevents and not cl.subevent:
if self.event.has_subevents and op.subevent and not cl.subevent:
item += '\n{} ({})'.format(
op.subevent.name,
date_format(op.subevent.date_from.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT')
@@ -612,14 +612,19 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
row.append(op.attendee_email or (op.addon_to.attendee_email if op.addon_to else '') or op.order.email or '')
row.append(str(op.order.phone) if op.order.phone else '')
if self.event.has_subevents:
row.append(str(op.subevent.name))
row.append(date_format(op.subevent.date_from.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT'))
if op.subevent.date_to:
row.append(
date_format(op.subevent.date_to.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT')
)
if op.subevent:
row.append(str(op.subevent.name))
row.append(date_format(op.subevent.date_from.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT'))
if op.subevent.date_to:
row.append(
date_format(op.subevent.date_to.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT')
)
else:
row.append('')
else:
row.append('')
row.append('')
row.append('')
acache = {}
if op.addon_to:
for a in op.addon_to.answers.all():
@@ -722,14 +727,19 @@ class CSVCheckinCodeList(CheckInListMixin, ListExporter):
_('Yes') if op.order.status == Order.STATUS_PAID else _('No'),
]
if self.event.has_subevents:
row.append(str(op.subevent.name))
row.append(date_format(op.subevent.date_from.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT'))
if op.subevent.date_to:
row.append(
date_format(op.subevent.date_to.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT')
)
if op.subevent:
row.append(str(op.subevent.name))
row.append(date_format(op.subevent.date_from.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT'))
if op.subevent.date_to:
row.append(
date_format(op.subevent.date_to.astimezone(self.event.timezone), 'SHORT_DATETIME_FORMAT')
)
else:
row.append('')
else:
row.append('')
row.append('')
row.append('')
row += [
date_format(op.valid_from, 'SHORT_DATETIME_FORMAT') if op.valid_from else '',
+1 -1
View File
@@ -55,7 +55,7 @@ class ScheduledMail(models.Model):
id = models.BigAutoField(primary_key=True)
rule = models.ForeignKey("Rule", on_delete=models.CASCADE)
subevent = models.ForeignKey(SubEvent, null=True, on_delete=models.CASCADE)
subevent = models.ForeignKey(SubEvent, null=True, on_delete=models.CASCADE) # must be set in a series, other case unsupported
event = models.ForeignKey(Event, on_delete=models.CASCADE)
last_computed = models.DateTimeField(auto_now_add=True)
@@ -185,69 +185,68 @@
</div>
{% eventsignal event "pretix.presale.signals.front_page_top" request=request subevent=subevent %}
{% endif %}
{% if ev.presale_is_running or event.settings.show_items_outside_presale_period %}
<form method="post" data-asynctask
data-asynctask-headline="{% trans "We're now trying to reserve this for you!" %}"
data-asynctask-text="{% blocktrans with time=event.settings.reservation_time %}Once the items are in your cart, you will have {{ time }} minutes to complete your purchase.{% endblocktrans %}"
action="{% eventurl request.event "presale:event.cart.add" cart_namespace=cart_namespace %}?next={{ cart_redirect|urlencode }}&next_error={{ request.path|urlencode }}">
{% csrf_token %}
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}" />
{% if ev.seating_plan_id and event.settings.seating_choice %}
{% if event.has_subevents %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent %}
{% else %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request %}
{% endif %}
{% endif %}
{% if ev.presale_is_running or event.settings.show_items_outside_presale_period %}
<form method="post" data-asynctask
data-asynctask-headline="{% trans "We're now trying to reserve this for you!" %}"
data-asynctask-text="{% blocktrans with time=event.settings.reservation_time %}Once the items are in your cart, you will have {{ time }} minutes to complete your purchase.{% endblocktrans %}"
action="{% eventurl request.event "presale:event.cart.add" cart_namespace=cart_namespace %}?next={{ cart_redirect|urlencode }}&next_error={{ request.path|urlencode }}">
{% csrf_token %}
<input type="hidden" name="subevent" value="{{ subevent.id|default_if_none:"" }}" />
{% if ev.seating_plan_id and event.settings.seating_choice %}
{% if event.has_subevents %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request subevent=subevent %}
{% else %}
{% eventsignal event "pretix.presale.signals.render_seating_plan" request=request %}
{% endif %}
{% endif %}
{% if waitinglist_seated %}
<aside class="front-page" aria-labelledby="waiting-list">
<h3 id="waiting-list" class="sr-only">{% trans "Waiting list" %}</h3>
<div class="row">
<div class="col-md-8 col-sm-6 col-xs-12">
<p>
{% blocktrans trimmed %}
Some of the categories in the seating plan above are currently sold out. If you want, you can add yourself to the
waiting list. We will then notify if seats are available again.
{% endblocktrans %}
</p>
</div>
<div class="col-md-4 col-sm-6 col-xs-12">
<a href="{% eventurl event "presale:event.waitinglist" cart_namespace=cart_namespace|default_if_none:"" %}{% if subevent %}?subevent={{ subevent.pk }}{% endif %}" class="btn btn-default btn-block">
<span class="fa fa-plus-circle" aria-hidden="true"></span>
{% trans "Join waiting list" %}
</a>
</div>
<div class="clearfix"></div>
{% if waitinglist_seated %}
<aside class="front-page" aria-labelledby="waiting-list">
<h3 id="waiting-list" class="sr-only">{% trans "Waiting list" %}</h3>
<div class="row">
<div class="col-md-8 col-sm-6 col-xs-12">
<p>
{% blocktrans trimmed %}
Some of the categories in the seating plan above are currently sold out. If you want, you can add yourself to the
waiting list. We will then notify if seats are available again.
{% endblocktrans %}
</p>
</div>
</aside>
{% endif %}
<h2 class="sr-only">{% trans "Products" %}</h2>
{% include "pretixpresale/event/fragment_product_list.html" %}
{% if ev.presale_is_running and display_add_to_cart %}
<div class="front-page">
<div class="row">
<div class="col-md-4 col-md-offset-8 col-xs-12">
<button class="btn btn-block btn-primary btn-lg" type="submit" id="btn-add-to-cart">
{% if request.event.settings.redirect_to_checkout_directly %}
{% if allfree %}
<i class="fa fa-check" aria-hidden="true"></i> {% trans "Register" context "free_tickets" %}
{% else %}
<i class="fa fa-shopping-cart" aria-hidden="true"></i> {% trans "Proceed with checkout" %}
{% endif %}
{% else %}
<i class="fa fa-shopping-cart" aria-hidden="true"></i> {% trans "Add to cart" %}
{% endif %}
</button>
</div>
<div class="clearfix"></div>
<div class="col-md-4 col-sm-6 col-xs-12">
<a href="{% eventurl event "presale:event.waitinglist" cart_namespace=cart_namespace|default_if_none:"" %}{% if subevent %}?subevent={{ subevent.pk }}{% endif %}" class="btn btn-default btn-block">
<span class="fa fa-plus-circle" aria-hidden="true"></span>
{% trans "Join waiting list" %}
</a>
</div>
<div class="clearfix"></div>
</div>
{% endif %}
</form>
{% endif %}
</aside>
{% endif %}
<h2 class="sr-only">{% trans "Products" %}</h2>
{% include "pretixpresale/event/fragment_product_list.html" %}
{% if ev.presale_is_running and display_add_to_cart %}
<div class="front-page">
<div class="row">
<div class="col-md-4 col-md-offset-8 col-xs-12">
<button class="btn btn-block btn-primary btn-lg" type="submit" id="btn-add-to-cart">
{% if request.event.settings.redirect_to_checkout_directly %}
{% if allfree %}
<i class="fa fa-check" aria-hidden="true"></i> {% trans "Register" context "free_tickets" %}
{% else %}
<i class="fa fa-shopping-cart" aria-hidden="true"></i> {% trans "Proceed with checkout" %}
{% endif %}
{% else %}
<i class="fa fa-shopping-cart" aria-hidden="true"></i> {% trans "Add to cart" %}
{% endif %}
</button>
</div>
<div class="clearfix"></div>
</div>
</div>
{% endif %}
</form>
{% endif %}
{% if show_vouchers %}
<aside class="front-page" aria-labelledby="redeem-a-voucher">
+1 -1
View File
@@ -814,7 +814,7 @@ class RedeemView(NoSearchIndexViewMixin, EventViewMixin, CartMixin, TemplateView
if hasattr(self, 'voucher') and self.voucher.subevent:
self.subevent = self.voucher.subevent
if not err and not self.subevent:
if not err and not self.subevent: # TODO
return redirect_to_url(
eventreverse(
self.request.event, 'presale:event.index',
+42 -43
View File
@@ -194,55 +194,54 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
context['allow_waitinglist'] = context['ev'].waiting_list_active and context['ev'].presale_is_running
if not self.request.event.has_subevents or self.subevent:
# Fetch all items
items, display_add_to_cart = prepare_item_list_for_shop(
self.request.event,
subevent=self.subevent,
filter_items=self.request.GET.getlist('item'),
filter_categories=self.request.GET.getlist('category'),
require_seat=None,
channel=self.request.sales_channel,
memberships=(
self.request.customer.usable_memberships(
for_event=self.subevent or self.request.event,
testmode=self.request.event.testmode
) if getattr(self.request, 'customer', None) else None
),
)
# Fetch all items
items, display_add_to_cart = prepare_item_list_for_shop(
self.request.event,
subevent=self.subevent,
filter_items=self.request.GET.getlist('item'),
filter_categories=self.request.GET.getlist('category'),
require_seat=None,
channel=self.request.sales_channel,
memberships=(
self.request.customer.usable_memberships(
for_event=self.subevent or self.request.event,
testmode=self.request.event.testmode
) if getattr(self.request, 'customer', None) else None
),
)
context['waitinglist_seated'] = False
if context['allow_waitinglist']:
for i in items:
if not i.allow_waitinglist or not i.requires_seat:
continue
context['waitinglist_seated'] = False
if context['allow_waitinglist']:
for i in items:
if not i.allow_waitinglist or not i.requires_seat:
continue
if i.has_variations:
for v in i.available_variations:
if v.cached_availability[0] != Quota.AVAILABILITY_OK:
context['waitinglist_seated'] = True
break
else:
if i.cached_availability[0] != Quota.AVAILABILITY_OK:
if i.has_variations:
for v in i.available_variations:
if v.cached_availability[0] != Quota.AVAILABILITY_OK:
context['waitinglist_seated'] = True
break
else:
if i.cached_availability[0] != Quota.AVAILABILITY_OK:
context['waitinglist_seated'] = True
break
items = [i for i in items if not i.requires_seat]
context['itemnum'] = len(items)
context['allfree'] = all(
item.display_price.gross == Decimal('0.00') and not item.mandatory_priced_addons
for item in items if not item.has_variations
) and all(
all(
var.display_price.gross == Decimal('0.00')
for var in item.available_variations
) and not item.mandatory_priced_addons
for item in items if item.has_variations
)
items = [i for i in items if not i.requires_seat]
context['itemnum'] = len(items)
context['allfree'] = all(
item.display_price.gross == Decimal('0.00') and not item.mandatory_priced_addons
for item in items if not item.has_variations
) and all(
all(
var.display_price.gross == Decimal('0.00')
for var in item.available_variations
) and not item.mandatory_priced_addons
for item in items if item.has_variations
)
# Regroup those by category
context['items_by_category'] = item_group_by_category(items)
context['display_add_to_cart'] = display_add_to_cart
# Regroup those by category
context['items_by_category'] = item_group_by_category(items)
context['display_add_to_cart'] = display_add_to_cart
context['cart'] = self.get_cart()
context['has_addon_choices'] = any(cp.has_addon_choices for cp in get_cart(self.request))
+2 -3
View File
@@ -29,7 +29,7 @@ from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _, pgettext_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import FormView, TemplateView
from pretix.base.models import Quota, SubEvent
@@ -158,8 +158,7 @@ class WaitingView(EventViewMixin, FormView):
except ValueError:
raise Http404()
else:
messages.error(request, pgettext_lazy('subevent', "You need to select a date."))
return redirect(self.get_index_url())
self.subevent = None
if not (self.subevent or self.request.event).waiting_list_active:
messages.error(request, _("Waiting lists are disabled for this event."))
-8
View File
@@ -293,14 +293,6 @@ def test_cartpos_cart_id_optional(token_client, organizer, event, item, quota, q
def test_cartpos_create_subevent_validation(token_client, organizer, event, item, subevent, subevent2, quota, question):
res = copy.deepcopy(CARTPOS_CREATE_PAYLOAD)
res['item'] = item.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'subevent': ['You need to set a subevent.']}
res['subevent'] = subevent2.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/cartpositions/'.format(
-14
View File
@@ -2323,20 +2323,6 @@ def test_quota_create_with_subevent(token_client, organizer, event, event3, item
)
assert resp.status_code == 201
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
{
"name": "Ticket Quota",
"size": 200,
"items": [item.pk],
"variations": [variations[0].pk],
"subevent": None
},
format='json'
)
assert resp.status_code == 400
assert resp.content.decode() == '{"non_field_errors":["Subevent cannot be null for event series."]}'
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/quotas/'.format(organizer.slug, event.slug),
{
-17
View File
@@ -1859,23 +1859,6 @@ def test_position_add_subevent(token_client, organizer, event, order, quota, ite
assert op.subevent == subevent
@pytest.mark.django_db
def test_position_add_subevent_required(token_client, organizer, event, order, quota, item, subevent):
with scopes_disabled():
assert order.positions.count() == 1
payload = {
'order': order.code,
'item': item.pk,
}
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orderpositions/'.format(
organizer.slug, event.slug,
), format='json', data=payload
)
assert resp.status_code == 400
assert 'subevent' in str(resp.data)
@pytest.mark.django_db
def test_position_add_quota_empty(token_client, organizer, event, order, quota, item):
with scopes_disabled():
-8
View File
@@ -1164,14 +1164,6 @@ def test_order_create_subevent_validation(token_client, organizer, event, item,
res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk
res['positions'][0]['answers'][0]['question'] = question.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
assert resp.data == {'positions': [{'subevent': ['You need to set a subevent.']}]}
res['positions'][0]['subevent'] = subevent2.pk
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/'.format(
+18 -2
View File
@@ -638,8 +638,10 @@ class QuotaTestCase(BaseQuotaTestCase):
self.event.save()
se1 = self.event.subevents.create(date_from=now(), name="SE 1")
se2 = self.event.subevents.create(date_from=now(), name="SE 2")
q0 = self.event.quotas.create(name="Q0", subevent=None, size=50)
q1 = self.event.quotas.create(name="Q1", subevent=se1, size=50)
q2 = self.event.quotas.create(name="Q2", subevent=se2, size=50)
q0.items.add(self.item1)
q1.items.add(self.item1)
q2.items.add(self.item1)
@@ -659,11 +661,17 @@ class QuotaTestCase(BaseQuotaTestCase):
OrderPosition.objects.create(order=order, item=self.item1, subevent=se1, price=2)
OrderPosition.objects.create(order=order, item=self.item1, subevent=se1, price=2)
OrderPosition.objects.create(order=order, item=self.item1, subevent=se2, price=2)
order = Order.objects.create(event=self.event, status=Order.STATUS_PENDING,
sales_channel=self.event.organizer.sales_channels.get(identifier="web"),
expires=now() + timedelta(days=3),
total=2)
OrderPosition.objects.create(order=order, item=self.item1, subevent=None, price=2)
Voucher.objects.create(item=self.item1, event=self.event, valid_until=now() + timedelta(days=5),
block_quota=True, max_usages=6, subevent=se1)
Voucher.objects.create(item=self.item1, event=self.event, valid_until=now() + timedelta(days=5),
block_quota=True, max_usages=4, subevent=se2)
# TODO how to deal with subevent=None?
for i in range(8):
CartPosition.objects.create(event=self.event, item=self.item1, price=2, subevent=se1,
@@ -673,6 +681,10 @@ class QuotaTestCase(BaseQuotaTestCase):
CartPosition.objects.create(event=self.event, item=self.item1, price=2, subevent=se2,
expires=now() + timedelta(days=3))
for i in range(2):
CartPosition.objects.create(event=self.event, item=self.item1, price=2, subevent=None,
expires=now() + timedelta(days=3))
for i in range(16):
WaitingListEntry.objects.create(
event=self.event, item=self.item1, email='foo@bar.com', subevent=se1
@@ -683,11 +695,15 @@ class QuotaTestCase(BaseQuotaTestCase):
event=self.event, item=self.item1, email='foo@bar.com', subevent=se2
)
with self.assertRaises(TypeError):
self.item1.check_quotas()
for i in range(6):
WaitingListEntry.objects.create(
event=self.event, item=self.item1, email='foo@bar.com', subevent=None
)
self.assertEqual(self.item1.check_quotas(subevent=se1), (Quota.AVAILABILITY_OK, 50 - 5 - 6 - 8 - 16))
self.assertEqual(self.item1.check_quotas(subevent=se2), (Quota.AVAILABILITY_OK, 50 - 2 - 4 - 5 - 13))
self.assertEqual(self.item1.check_quotas(subevent=None), (Quota.AVAILABILITY_OK, 50 - 1 - 2 - 6))
self.assertEqual(q0.availability(), (Quota.AVAILABILITY_OK, 50 - 1 - 2 - 6))
self.assertEqual(q1.availability(), (Quota.AVAILABILITY_OK, 50 - 5 - 6 - 8 - 16))
self.assertEqual(q2.availability(), (Quota.AVAILABILITY_OK, 50 - 2 - 4 - 5 - 13))
self.event.has_subevents = False
-7
View File
@@ -2466,13 +2466,6 @@ class OrderChangeManagerTests(BaseOrderChangeManagerTestCase, TestCase):
with self.assertRaises(OrderError):
self.ocm.add_position(self.shirt, None, Decimal('13.00'), None)
@classscope(attr='o')
def test_add_item_subevent_required(self):
self.event.has_subevents = True
self.event.save()
with self.assertRaises(OrderError):
self.ocm.add_position(self.ticket, None, None, None)
@classscope(attr='o')
def test_add_item_subevent_price(self):
self.event.has_subevents = True
+2
View File
@@ -323,6 +323,8 @@ class CartTest(CartTestMixin, TestCase):
se = self.event.subevents.create(name='Foo', date_from=now(), active=True)
self.quota_tickets.subevent = se
self.quota_tickets.save()
self.quota_all.subevent = se
self.quota_all.save()
q = se.quotas.create(name="foo", size=None, event=self.event)
q.items.add(self.ticket)
self.client.post('/%s/%s/cart/add' % (self.orga.slug, self.event.slug), {
+2
View File
@@ -1274,6 +1274,8 @@ class WaitingListTest(EventTestMixin, SoupTest):
self.event.save()
with scopes_disabled():
se1 = self.event.subevents.create(name="Foo", date_from=now(), active=False)
self.q.subevent = se1
self.q.save()
response = self.client.get(
'/%s/%s/waitinglist/?item=%d' % (self.orga.slug, self.event.slug, self.item.pk)
)