diff --git a/src/pretix/base/models/event.py b/src/pretix/base/models/event.py index 5ce088fca..97a91968a 100644 --- a/src/pretix/base/models/event.py +++ b/src/pretix/base/models/event.py @@ -290,19 +290,19 @@ class EventMixin: return safe_string(json.dumps(eventdict)) @classmethod - def annotated(cls, qs, channel='web'): + def annotated(cls, qs, channel='web', voucher=None): from pretix.base.models import Item, ItemVariation, Quota - sq_active_item = Item.objects.using(settings.DATABASE_REPLICA).filter_available(channel=channel).filter( + 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')) ).order_by().values_list('quotas__pk').annotate( items=GroupConcat('pk', delimiter=',') ).values('items') - sq_active_variation = ItemVariation.objects.filter( + + q_variation = ( Q(active=True) & Q(sales_channels__contains=channel) - & Q(hide_without_voucher=False) & Q(Q(available_from__isnull=True) | Q(available_from__lte=now())) & Q(Q(available_until__isnull=True) | Q(available_until__gte=now())) & Q(item__active=True) @@ -310,10 +310,23 @@ class EventMixin: & Q(Q(item__available_until__isnull=True) | Q(item__available_until__gte=now())) & Q(Q(item__category__isnull=True) | Q(item__category__is_addon=False)) & Q(item__sales_channels__contains=channel) - & Q(item__hide_without_voucher=False) & Q(item__require_bundling=False) & Q(quotas__pk=OuterRef('pk')) - ).order_by().values_list('quotas__pk').annotate( + ) + + if voucher: + if voucher.variation_id: + q_variation &= Q(pk=voucher.variation_id) + elif voucher.item_id: + q_variation &= Q(item_id=voucher.item_id) + elif voucher.quota_id: + q_variation &= Q(quotas__in=[voucher.quota_id]) + + if not voucher or not voucher.show_hidden_items: + q_variation &= Q(hide_without_voucher=False) + q_variation &= Q(item__hide_without_voucher=False) + + sq_active_variation = ItemVariation.objects.filter(q_variation).order_by().values_list('quotas__pk').annotate( items=GroupConcat('pk', delimiter=',') ).values('items') quota_base_qs = Quota.objects.using(settings.DATABASE_REPLICA).filter( @@ -1133,8 +1146,8 @@ class Event(EventMixin, LoggedModel): irs = self.get_invoice_renderers() return irs[self.settings.invoice_renderer] - def subevents_annotated(self, channel): - return SubEvent.annotated(self.subevents, channel) + def subevents_annotated(self, channel, voucher=None): + return SubEvent.annotated(self.subevents, channel, voucher) def subevents_sorted(self, queryset): ordering = self.settings.get('frontpage_subevent_ordering', default='date_ascending', as_type=str) @@ -1454,10 +1467,10 @@ class SubEvent(EventMixin, LoggedModel): return qs_annotated @classmethod - def annotated(cls, qs, channel='web'): + def annotated(cls, qs, channel='web', voucher=None): from .items import SubEventItem, SubEventItemVariation - qs = super().annotated(qs, channel) + qs = super().annotated(qs, channel, voucher=voucher) qs = qs.annotate( disabled_items=Coalesce( Subquery( diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index cd40fb9e9..b691d619a 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -597,7 +597,13 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView): ebd = defaultdict(list) add_subevents_for_days( - filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel.identifier).using(settings.DATABASE_REPLICA), self.request), + filter_qs_by_attr( + self.request.event.subevents_annotated( + self.request.sales_channel.identifier, + voucher, + ).using(settings.DATABASE_REPLICA), + self.request + ), limit_before, after, ebd, set(), self.request.event, self.kwargs.get('cart_namespace'), voucher, @@ -649,7 +655,13 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView): ebd = defaultdict(list) add_subevents_for_days( - filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel.identifier).using(settings.DATABASE_REPLICA), self.request), + filter_qs_by_attr( + self.request.event.subevents_annotated( + self.request.sales_channel.identifier, + voucher=voucher, + ).using(settings.DATABASE_REPLICA), + self.request + ), limit_before, after, ebd, set(), self.request.event, self.kwargs.get('cart_namespace'), voucher, @@ -692,7 +704,13 @@ class EventIndex(EventViewMixin, EventListMixin, CartMixin, TemplateView): ) else: context['subevent_list'] = self.request.event.subevents_sorted( - filter_qs_by_attr(self.request.event.subevents_annotated(self.request.sales_channel.identifier).using(settings.DATABASE_REPLICA), self.request) + filter_qs_by_attr( + self.request.event.subevents_annotated( + self.request.sales_channel.identifier, + voucher=voucher, + ).using(settings.DATABASE_REPLICA), + self.request + ) ) if self.request.event.settings.event_list_available_only and not voucher: context['subevent_list'] = [ diff --git a/src/tests/base/test_models.py b/src/tests/base/test_models.py index f22cc3f5a..222e44a41 100644 --- a/src/tests/base/test_models.py +++ b/src/tests/base/test_models.py @@ -2206,6 +2206,44 @@ class EventTest(TestCase): q.items.add(item) assert Event.annotated(Event.objects).first().active_quotas == [] + @classscope(attr='organizer') + def test_active_quotas_annotation_product_hidden_by_voucher(self): + event = Event.objects.create( + organizer=self.organizer, name='Download', slug='download', + date_from=now() + ) + q = Quota.objects.create(event=event, name='Quota', size=2) + item = Item.objects.create(event=event, name='Early-bird ticket', default_price=0, hide_without_voucher=True) + 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] + + voucher = Voucher.objects.create(event=event, code='b', item=item, show_hidden_items=False) + assert Event.annotated(Event.objects, 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] + + 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] + + 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) + var2 = item2.variations.create(item=item2, value='Other test', hide_without_voucher=True, active=False) + q.items.remove(item) + q.items.add(item2) + 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] + + 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 == [] + + 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] + @classscope(attr='organizer') def test_active_quotas_annotation_product_addon(self): event = Event.objects.create(