forked from CGM_Public/pretix_original
Optimize availability queries
This commit is contained in:
@@ -41,6 +41,7 @@ from django_filters.rest_framework import DjangoFilterBackend, FilterSet
|
||||
from django_scopes import scopes_disabled
|
||||
from rest_framework import serializers, views, viewsets
|
||||
from rest_framework.exceptions import PermissionDenied, ValidationError
|
||||
from rest_framework.generics import get_object_or_404
|
||||
from rest_framework.response import Response
|
||||
|
||||
from pretix.api.auth.permission import EventCRUDPermission
|
||||
@@ -162,7 +163,13 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
qs = filter_qs_by_attr(qs, self.request)
|
||||
|
||||
if 'with_availability_for' in self.request.GET:
|
||||
qs = Event.annotated(qs, channel=self.request.GET.get('with_availability_for'))
|
||||
qs = Event.annotated(
|
||||
qs,
|
||||
channel=get_object_or_404(
|
||||
self.request.organizer.sales_channels,
|
||||
identifier=self.request.GET.get('with_availability_for')
|
||||
)
|
||||
)
|
||||
|
||||
return qs.prefetch_related(
|
||||
'organizer',
|
||||
@@ -442,7 +449,13 @@ class SubEventViewSet(ConditionalListView, viewsets.ModelViewSet):
|
||||
qs = filter_qs_by_attr(qs, self.request)
|
||||
|
||||
if 'with_availability_for' in self.request.GET:
|
||||
qs = SubEvent.annotated(qs, channel=self.request.GET.get('with_availability_for'))
|
||||
qs = SubEvent.annotated(
|
||||
qs,
|
||||
channel=get_object_or_404(
|
||||
self.request.organizer.sales_channels,
|
||||
identifier=self.request.GET.get('with_availability_for')
|
||||
)
|
||||
)
|
||||
|
||||
return qs.prefetch_related(
|
||||
'event',
|
||||
|
||||
@@ -304,10 +304,13 @@ class EventMixin:
|
||||
return safe_string(json.dumps(eventdict))
|
||||
|
||||
@classmethod
|
||||
def annotated(cls, qs, channel='web', voucher=None):
|
||||
from pretix.base.models import Item, ItemVariation, Quota
|
||||
def annotated(cls, qs, channel, voucher=None):
|
||||
# Channel can currently be a SalesChannel or a str, since we need that compatibility, but a SalesChannel
|
||||
# makes the query SIGNIFICANTLY faster
|
||||
from pretix.base.models import Item, ItemVariation, Quota, SalesChannel
|
||||
|
||||
assert isinstance(channel, (SalesChannel, str))
|
||||
|
||||
assert isinstance(channel, str)
|
||||
sq_active_item = Item.objects.using(settings.DATABASE_REPLICA).filter_available(channel=channel, voucher=voucher).filter(
|
||||
Q(variations__isnull=True)
|
||||
& Q(quotas__pk=OuterRef('pk'))
|
||||
@@ -317,18 +320,23 @@ class EventMixin:
|
||||
|
||||
q_variation = (
|
||||
Q(active=True)
|
||||
& Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()))
|
||||
& Q(item__active=True)
|
||||
& Q(Q(item__available_from__isnull=True) | Q(item__available_from__lte=time_machine_now()))
|
||||
& Q(Q(item__available_until__isnull=True) | Q(item__available_until__gte=time_machine_now()))
|
||||
& Q(Q(item__category__isnull=True) | Q(item__category__is_addon=False))
|
||||
& Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels__identifier=channel))
|
||||
& Q(item__require_bundling=False)
|
||||
& Q(quotas__pk=OuterRef('pk'))
|
||||
)
|
||||
|
||||
if isinstance(channel, str):
|
||||
q_variation &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
|
||||
q_variation &= Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels__identifier=channel))
|
||||
else:
|
||||
q_variation &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels=channel))
|
||||
q_variation &= Q(Q(item__all_sales_channels=True) | Q(item__limit_sales_channels=channel))
|
||||
|
||||
if voucher:
|
||||
if voucher.variation_id:
|
||||
q_variation &= Q(pk=voucher.variation_id)
|
||||
@@ -1536,8 +1544,11 @@ class SubEvent(EventMixin, LoggedModel):
|
||||
return qs_annotated
|
||||
|
||||
@classmethod
|
||||
def annotated(cls, qs, channel='web', voucher=None):
|
||||
def annotated(cls, qs, channel, voucher=None):
|
||||
from .items import SubEventItem, SubEventItemVariation
|
||||
from .organizer import SalesChannel
|
||||
|
||||
assert isinstance(channel, (str, SalesChannel))
|
||||
|
||||
qs = super().annotated(qs, channel, voucher=voucher)
|
||||
qs = qs.annotate(
|
||||
|
||||
@@ -271,16 +271,24 @@ class SubEventItemVariation(models.Model):
|
||||
|
||||
|
||||
def filter_available(qs, channel='web', voucher=None, allow_addons=False):
|
||||
assert isinstance(channel, str)
|
||||
# Channel can currently be a SalesChannel or a str, since we need that compatibility, but a SalesChannel
|
||||
# makes the query SIGNIFICANTLY faster
|
||||
from .organizer import SalesChannel
|
||||
|
||||
assert isinstance(channel, (SalesChannel, str))
|
||||
q = (
|
||||
# IMPORTANT: If this is updated, also update the ItemVariation query
|
||||
# in models/event.py: EventMixin.annotated()
|
||||
Q(active=True)
|
||||
& Q(Q(available_from__isnull=True) | Q(available_from__lte=time_machine_now()) | Q(available_from_mode='info'))
|
||||
& Q(Q(available_until__isnull=True) | Q(available_until__gte=time_machine_now()) | Q(available_until_mode='info'))
|
||||
& Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
|
||||
& Q(require_bundling=False)
|
||||
)
|
||||
if isinstance(channel, str):
|
||||
q &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel))
|
||||
else:
|
||||
q &= Q(Q(all_sales_channels=True) | Q(limit_sales_channels=channel))
|
||||
|
||||
if not allow_addons:
|
||||
q &= Q(Q(category__isnull=True) | Q(category__is_addon=False))
|
||||
|
||||
|
||||
@@ -151,7 +151,7 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No
|
||||
),
|
||||
).filter(
|
||||
variation_q,
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=channel),
|
||||
active=True,
|
||||
quotas__isnull=False,
|
||||
subevent_disabled=False
|
||||
@@ -685,7 +685,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
add_subevents_for_days(
|
||||
filter_qs_by_attr(
|
||||
self.request.event.subevents_annotated(
|
||||
self.request.sales_channel.identifier,
|
||||
self.request.sales_channel,
|
||||
voucher,
|
||||
).using(settings.DATABASE_REPLICA),
|
||||
self.request
|
||||
@@ -744,7 +744,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
add_subevents_for_days(
|
||||
filter_qs_by_attr(
|
||||
self.request.event.subevents_annotated(
|
||||
self.request.sales_channel.identifier,
|
||||
self.request.sales_channel,
|
||||
voucher=voucher,
|
||||
).using(settings.DATABASE_REPLICA),
|
||||
self.request
|
||||
@@ -793,7 +793,7 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView):
|
||||
context['subevent_list'] = self.request.event.subevents_sorted(
|
||||
filter_qs_by_attr(
|
||||
self.request.event.subevents_annotated(
|
||||
self.request.sales_channel.identifier,
|
||||
self.request.sales_channel,
|
||||
voucher=voucher,
|
||||
).using(settings.DATABASE_REPLICA),
|
||||
self.request
|
||||
|
||||
@@ -185,7 +185,7 @@ class EventListMixin:
|
||||
def _get_event_list_queryset(self):
|
||||
query = Q(is_public=True) & Q(live=True)
|
||||
qs = self.request.organizer.events.using(settings.DATABASE_REPLICA).filter(query)
|
||||
qs = qs.filter(Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier))
|
||||
qs = qs.filter(Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel))
|
||||
qs = qs.annotate(
|
||||
min_from=Min('subevents__date_from'),
|
||||
min_to=Min('subevents__date_to'),
|
||||
@@ -213,7 +213,7 @@ class EventListMixin:
|
||||
).order_by('order_from')
|
||||
qs = Event.annotated(filter_qs_by_attr(
|
||||
qs, self.request, match_subevents_with_conditions=Q(active=True) & Q(is_public=True) & date_q
|
||||
))
|
||||
), self.request.sales_channel)
|
||||
return qs
|
||||
|
||||
def _set_month_to_next_subevent(self):
|
||||
@@ -724,10 +724,10 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
|
||||
ctx['has_before'], ctx['has_after'] = has_before_after(
|
||||
self.request.organizer.events.filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
),
|
||||
SubEvent.objects.filter(
|
||||
Q(event__all_sales_channels=True) | Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__all_sales_channels=True) | Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
@@ -746,14 +746,14 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
def _events_by_day(self, before, after):
|
||||
ebd = defaultdict(list)
|
||||
timezones = set()
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, self.request.sales_channel).using(
|
||||
settings.DATABASE_REPLICA
|
||||
).filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
), before, after, ebd, timezones)
|
||||
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
|
||||
Q(event__all_sales_channels=True) |
|
||||
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
@@ -768,7 +768,7 @@ class CalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
)
|
||||
)
|
||||
)
|
||||
)), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
), self.request.sales_channel), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
self._multiple_timezones = len(timezones) > 1
|
||||
return ebd
|
||||
|
||||
@@ -807,11 +807,11 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
|
||||
ctx['has_before'], ctx['has_after'] = has_before_after(
|
||||
self.request.organizer.events.filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
),
|
||||
SubEvent.objects.filter(
|
||||
Q(event__all_sales_channels=True) |
|
||||
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
@@ -842,14 +842,14 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
def _events_by_day(self, before, after):
|
||||
ebd = defaultdict(list)
|
||||
timezones = set()
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, self.request.sales_channel).using(
|
||||
settings.DATABASE_REPLICA
|
||||
).filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
), before, after, ebd, timezones)
|
||||
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
|
||||
Q(event__all_sales_channels=True) |
|
||||
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
@@ -864,7 +864,7 @@ class WeekCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
)
|
||||
)
|
||||
)
|
||||
)), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
), self.request.sales_channel), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
self._multiple_timezones = len(timezones) > 1
|
||||
return ebd
|
||||
|
||||
@@ -946,11 +946,11 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
|
||||
ctx['has_before'], ctx['has_after'] = has_before_after(
|
||||
self.request.organizer.events.filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
),
|
||||
SubEvent.objects.filter(
|
||||
Q(event__all_sales_channels=True) |
|
||||
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
@@ -1194,14 +1194,14 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
def _events_by_day(self, before, after):
|
||||
ebd = defaultdict(list)
|
||||
timezones = set()
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, 'web').using(
|
||||
add_events_for_days(self.request, Event.annotated(self.request.organizer.events, self.request.sales_channel).using(
|
||||
settings.DATABASE_REPLICA
|
||||
).filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
), before, after, ebd, timezones)
|
||||
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
|
||||
Q(event__all_sales_channels=True) |
|
||||
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
@@ -1216,7 +1216,7 @@ class DayCalendarView(OrganizerViewMixin, EventListMixin, TemplateView):
|
||||
)
|
||||
)
|
||||
)
|
||||
)), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
), self.request.sales_channel), self.request).using(settings.DATABASE_REPLICA), before, after, ebd, timezones)
|
||||
self._multiple_timezones = len(timezones) > 1
|
||||
return ebd
|
||||
|
||||
@@ -1229,7 +1229,7 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
|
||||
filter_qs_by_attr(
|
||||
self.request.organizer.events.filter(
|
||||
Q(date_from__gt=cutoff) | Q(date_to__gt=cutoff),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
is_public=True,
|
||||
live=True,
|
||||
has_subevents=False,
|
||||
@@ -1250,7 +1250,7 @@ class OrganizerIcalDownload(OrganizerViewMixin, View):
|
||||
SubEvent.objects.filter(
|
||||
Q(date_from__gt=cutoff) | Q(date_to__gt=cutoff),
|
||||
Q(event__all_sales_channels=True) |
|
||||
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
event__organizer=self.request.organizer,
|
||||
event__is_public=True,
|
||||
event__live=True,
|
||||
|
||||
@@ -545,9 +545,9 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
if hasattr(self.request, 'event'):
|
||||
add_subevents_for_days(
|
||||
filter_qs_by_attr(
|
||||
self.request.event.subevents_annotated('web').filter(
|
||||
self.request.event.subevents_annotated(self.request.sales_channel).filter(
|
||||
Q(event__all_sales_channels=True) |
|
||||
Q(event__limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Q(event__limit_sales_channels=self.request.sales_channel),
|
||||
), self.request
|
||||
),
|
||||
limit_before, after, ebd, set(), self.request.event,
|
||||
@@ -558,8 +558,8 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
add_events_for_days(
|
||||
self.request,
|
||||
filter_qs_by_attr(
|
||||
Event.annotated(self.request.organizer.events, 'web').filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels__identifier=self.request.sales_channel.identifier),
|
||||
Event.annotated(self.request.organizer.events, self.request.sales_channel).filter(
|
||||
Q(all_sales_channels=True) | Q(limit_sales_channels=self.request.sales_channel),
|
||||
), self.request
|
||||
),
|
||||
limit_before, after, ebd, timezones
|
||||
@@ -572,7 +572,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
event__live=True,
|
||||
).prefetch_related(
|
||||
'event___settings_objects', 'event__organizer___settings_objects'
|
||||
)), self.request), limit_before, after, ebd, timezones)
|
||||
), self.request.sales_channel), self.request), limit_before, after, ebd, timezones)
|
||||
|
||||
data['weeks'] = weeks_for_template(ebd, self.year, self.month)
|
||||
for w in data['weeks']:
|
||||
@@ -605,7 +605,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
ebd = defaultdict(list)
|
||||
if hasattr(self.request, 'event'):
|
||||
add_subevents_for_days(
|
||||
filter_qs_by_attr(self.request.event.subevents_annotated('web'), self.request),
|
||||
filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel), self.request),
|
||||
limit_before, after, ebd, set(), self.request.event,
|
||||
kwargs.get('cart_namespace')
|
||||
)
|
||||
@@ -613,7 +613,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
timezones = set()
|
||||
add_events_for_days(
|
||||
self.request,
|
||||
filter_qs_by_attr(Event.annotated(self.request.organizer.events, 'web'), self.request),
|
||||
filter_qs_by_attr(Event.annotated(self.request.organizer.events, self.request.sales_channel), self.request),
|
||||
limit_before, after, ebd, timezones
|
||||
)
|
||||
add_subevents_for_days(filter_qs_by_attr(SubEvent.annotated(SubEvent.objects.filter(
|
||||
@@ -622,7 +622,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
event__live=True,
|
||||
).prefetch_related(
|
||||
'event___settings_objects', 'event__organizer___settings_objects'
|
||||
)), self.request), limit_before, after, ebd, timezones)
|
||||
), self.request.sales_channel), self.request), limit_before, after, ebd, timezones)
|
||||
|
||||
data['days'] = days_for_template(ebd, week)
|
||||
for d in data['days']:
|
||||
@@ -632,7 +632,7 @@ class WidgetAPIProductList(EventListMixin, View):
|
||||
limit = 50
|
||||
if hasattr(self.request, 'event'):
|
||||
evs = filter_qs_by_attr(
|
||||
self.request.event.subevents_annotated(self.request.sales_channel.identifier),
|
||||
self.request.event.subevents_annotated(self.request.sales_channel),
|
||||
self.request,
|
||||
match_subevents_with_conditions=(
|
||||
Q(Q(date_to__isnull=True) & Q(date_from__gte=now() - timedelta(hours=24)))
|
||||
|
||||
@@ -2329,8 +2329,8 @@ class EventTest(TestCase):
|
||||
item2 = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=False)
|
||||
q.items.add(item)
|
||||
q.items.add(item2)
|
||||
assert Event.annotated(Event.objects).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, 'foo').first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, self.organizer.sales_channels.get(identifier="bar")).first().active_quotas == []
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_active_quotas_annotation_product_inactive(self):
|
||||
@@ -2341,7 +2341,7 @@ class EventTest(TestCase):
|
||||
q = Quota.objects.create(event=event, name='Quota', size=2)
|
||||
item = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=False)
|
||||
q.items.add(item)
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_active_quotas_annotation_product_hidden_by_voucher(self):
|
||||
@@ -2354,16 +2354,16 @@ class EventTest(TestCase):
|
||||
q.items.add(item)
|
||||
|
||||
voucher = Voucher.objects.create(event=event, code='a', item=item, show_hidden_items=True)
|
||||
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
|
||||
|
||||
voucher = Voucher.objects.create(event=event, code='b', item=item, show_hidden_items=False)
|
||||
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == []
|
||||
|
||||
voucher = Voucher.objects.create(event=event, code='c', show_hidden_items=True)
|
||||
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
|
||||
|
||||
voucher = Voucher.objects.create(event=event, code='d', quota=q, show_hidden_items=True)
|
||||
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
|
||||
|
||||
item2 = Item.objects.create(event=event, name='Early-bird ticket', default_price=0)
|
||||
var = item2.variations.create(item=item2, value='Test', hide_without_voucher=True)
|
||||
@@ -2373,13 +2373,13 @@ class EventTest(TestCase):
|
||||
q.variations.add(var)
|
||||
|
||||
voucher = Voucher.objects.create(event=event, code='e', item=item2, variation=var, show_hidden_items=True)
|
||||
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
|
||||
|
||||
voucher = Voucher.objects.create(event=event, code='f', item=item2, variation=var2, show_hidden_items=True)
|
||||
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == []
|
||||
|
||||
voucher = Voucher.objects.create(event=event, code='g', quota=q, show_hidden_items=True)
|
||||
assert Event.annotated(Event.objects, voucher=voucher).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, "web", voucher=voucher).first().active_quotas == [q]
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_active_quotas_annotation_product_addon(self):
|
||||
@@ -2395,7 +2395,7 @@ class EventTest(TestCase):
|
||||
item.category = cat
|
||||
item.save()
|
||||
q.items.add(item)
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_active_quotas_annotation_product_unavailable(self):
|
||||
@@ -2407,7 +2407,7 @@ class EventTest(TestCase):
|
||||
item = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=True,
|
||||
available_until=now() - timedelta(days=1))
|
||||
q.items.add(item)
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_active_quotas_annotation_variation_not_in_quota(self):
|
||||
@@ -2419,7 +2419,7 @@ class EventTest(TestCase):
|
||||
item = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, active=True)
|
||||
item.variations.create(value="foo")
|
||||
q.items.add(item)
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_active_quotas_annotation_variation(self):
|
||||
@@ -2434,29 +2434,29 @@ class EventTest(TestCase):
|
||||
v.limit_sales_channels.add(self.organizer.sales_channels.get(identifier="web"))
|
||||
q.items.add(item)
|
||||
q.variations.add(v)
|
||||
assert Event.annotated(Event.objects).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == [q]
|
||||
item.available_until = now() - timedelta(days=1)
|
||||
item.save()
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
item.available_until = None
|
||||
item.available_from = now() + timedelta(days=1)
|
||||
item.save()
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
item.available_until = None
|
||||
item.available_from = None
|
||||
item.active = False
|
||||
item.save()
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
item.active = True
|
||||
item.save()
|
||||
assert Event.annotated(Event.objects).first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, 'foo').first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == [q]
|
||||
assert Event.annotated(Event.objects, self.organizer.sales_channels.get(identifier="bar")).first().active_quotas == []
|
||||
v.active = False
|
||||
v.save()
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
item.hide_without_voucher = True
|
||||
item.save()
|
||||
assert Event.annotated(Event.objects).first().active_quotas == []
|
||||
assert Event.annotated(Event.objects, 'web').first().active_quotas == []
|
||||
|
||||
|
||||
class SubEventTest(TestCase):
|
||||
@@ -2502,8 +2502,10 @@ class SubEventTest(TestCase):
|
||||
all_sales_channels=False)
|
||||
item.limit_sales_channels.add(self.organizer.sales_channels.get(identifier="web"))
|
||||
q.items.add(item)
|
||||
assert SubEvent.annotated(SubEvent.objects).first().active_quotas == [q]
|
||||
assert SubEvent.annotated(SubEvent.objects, 'foo').first().active_quotas == []
|
||||
assert SubEvent.annotated(SubEvent.objects, 'web').first().active_quotas == [q]
|
||||
assert SubEvent.annotated(SubEvent.objects, 'bar').first().active_quotas == []
|
||||
assert SubEvent.annotated(SubEvent.objects, self.organizer.sales_channels.get(identifier="web")).first().active_quotas == [q]
|
||||
assert SubEvent.annotated(SubEvent.objects, self.organizer.sales_channels.get(identifier="bar")).first().active_quotas == []
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_active_quotas_annotation_no_interference(self):
|
||||
@@ -2514,8 +2516,8 @@ class SubEventTest(TestCase):
|
||||
subevent=se2)
|
||||
item = Item.objects.create(event=self.event, name='Early-bird ticket', default_price=0, active=True)
|
||||
q.items.add(item)
|
||||
assert SubEvent.annotated(SubEvent.objects).filter(pk=self.se.pk).first().active_quotas == []
|
||||
assert SubEvent.annotated(SubEvent.objects).filter(pk=se2.pk).first().active_quotas == [q]
|
||||
assert SubEvent.annotated(SubEvent.objects, 'web').filter(pk=self.se.pk).first().active_quotas == []
|
||||
assert SubEvent.annotated(SubEvent.objects, 'web').filter(pk=se2.pk).first().active_quotas == [q]
|
||||
|
||||
@classscope(attr='organizer')
|
||||
def test_best_availability(self):
|
||||
@@ -2540,7 +2542,7 @@ class SubEventTest(TestCase):
|
||||
q = Quota.objects.create(event=self.event, name='Quota', size=1,
|
||||
subevent=self.se)
|
||||
q.items.add(item)
|
||||
obj = SubEvent.annotated(SubEvent.objects).first()
|
||||
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
|
||||
assert len(obj.active_quotas) == 1
|
||||
assert obj.best_availability == (Quota.AVAILABILITY_GONE, 0, 1)
|
||||
|
||||
@@ -2548,14 +2550,14 @@ class SubEventTest(TestCase):
|
||||
q2 = Quota.objects.create(event=self.event, name='Quota 2', size=2,
|
||||
subevent=self.se)
|
||||
q2.items.add(item)
|
||||
obj = SubEvent.annotated(SubEvent.objects).first()
|
||||
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
|
||||
assert len(obj.active_quotas) == 2
|
||||
assert obj.best_availability == (Quota.AVAILABILITY_GONE, 0, 1)
|
||||
|
||||
# 2 quotas - 2 items. Higher quota wins since second item is only connected to second quota.
|
||||
item2 = Item.objects.create(event=self.event, name='Regular ticket', default_price=10, active=True)
|
||||
q2.items.add(item2)
|
||||
obj = SubEvent.annotated(SubEvent.objects).first()
|
||||
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
|
||||
assert len(obj.active_quotas) == 2
|
||||
assert obj.best_availability == (Quota.AVAILABILITY_OK, 1, 2)
|
||||
assert obj.best_availability_is_low
|
||||
@@ -2564,7 +2566,7 @@ class SubEventTest(TestCase):
|
||||
q.size = 10
|
||||
q.save()
|
||||
q2.delete()
|
||||
obj = SubEvent.annotated(SubEvent.objects).first()
|
||||
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
|
||||
assert len(obj.active_quotas) == 1
|
||||
assert obj.best_availability == (Quota.AVAILABILITY_OK, 9, 10)
|
||||
assert not obj.best_availability_is_low
|
||||
@@ -2572,7 +2574,7 @@ class SubEventTest(TestCase):
|
||||
# Unlimited quota
|
||||
q.size = None
|
||||
q.save()
|
||||
obj = SubEvent.annotated(SubEvent.objects).first()
|
||||
obj = SubEvent.annotated(SubEvent.objects, 'web').first()
|
||||
assert obj.best_availability == (Quota.AVAILABILITY_OK, None, None)
|
||||
assert not obj.best_availability_is_low
|
||||
|
||||
|
||||
Reference in New Issue
Block a user