diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 9c752ae4d6..1afd83c941 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -1207,6 +1207,10 @@ class DisplaySettingsForm(SettingsForm): label=_("Show variations of a product expanded by default"), required=False ) + hide_sold_out = forms.BooleanField( + label=_("Hide all products that are sold out"), + required=False + ) frontpage_subevent_ordering = forms.ChoiceField( label=pgettext('subevent', 'Date ordering'), choices=[ diff --git a/src/pretix/control/templates/pretixcontrol/event/display.html b/src/pretix/control/templates/pretixcontrol/event/display.html index d377962306..d196227841 100644 --- a/src/pretix/control/templates/pretixcontrol/event/display.html +++ b/src/pretix/control/templates/pretixcontrol/event/display.html @@ -18,6 +18,7 @@ {% bootstrap_field form.presale_has_ended_text layout="control" %} {% bootstrap_field form.voucher_explanation_text layout="control" %} {% bootstrap_field form.show_variations_expanded layout="control" %} + {% bootstrap_field form.hide_sold_out layout="control" %} {% bootstrap_field form.meta_noindex layout="control" %} {% if form.frontpage_subevent_ordering %} {% bootstrap_field form.frontpage_subevent_ordering layout="control" %} diff --git a/src/pretix/presale/forms/checkout.py b/src/pretix/presale/forms/checkout.py index 966b9db7f7..9cdf5b5b04 100644 --- a/src/pretix/presale/forms/checkout.py +++ b/src/pretix/presale/forms/checkout.py @@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _ from pretix.base.forms.questions import ( BaseInvoiceAddressForm, BaseQuestionsForm, ) -from pretix.base.models import ItemVariation +from pretix.base.models import ItemVariation, Quota from pretix.base.models.tax import TAXED_ZERO from pretix.base.services.cart import CartError, error_messages from pretix.base.signals import validate_cart_addons @@ -171,9 +171,9 @@ class AddOnsForm(forms.Form): taxes=number_format(price.rate), taxname=price.name ) - if avail[0] < 20: + if avail[0] < Quota.AVAILABILITY_RESERVED: n += ' – {}'.format(_('SOLD OUT')) - elif avail[0] < 100: + elif avail[0] < Quota.AVAILABILITY_OK: n += ' – {}'.format(_('Currently unavailable')) else: if avail[1] is not None and item.do_show_quota_left: @@ -258,6 +258,9 @@ class AddOnsForm(forms.Form): choices = [('', _('no selection'), '')] for v in i.available_variations: cached_availability = v.check_quotas(subevent=subevent, _cache=quota_cache) + if self.event.settings.hide_sold_out and cached_availability[0] < Quota.AVAILABILITY_RESERVED: + continue + if v._subevent_quotas: self.vars_cache[v.pk] = v choices.append( @@ -295,6 +298,8 @@ class AddOnsForm(forms.Form): if not i._subevent_quotas: continue cached_availability = i.check_quotas(subevent=subevent, _cache=quota_cache) + if self.event.settings.hide_sold_out and cached_availability[0] < Quota.AVAILABILITY_RESERVED: + continue field = forms.BooleanField( label=self._label(self.event, i, cached_availability, override_price=item_price_override.get(i.pk)), diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index 8f428086e1..a967bacdb7 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -120,7 +120,10 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require max_per_order = item.max_per_order or int(event.settings.max_items_per_order) if not item.has_variations: - item._remove = not bool(item._subevent_quotas) + item._remove = False + if not bool(item._subevent_quotas): + item._remove = True + continue if voucher and (voucher.allow_ignore_quota or voucher.block_quota): item.cached_availability = ( @@ -131,6 +134,10 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require item.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True) ) + if event.settings.hide_sold_out and item.cached_availability[0] < Quota.AVAILABILITY_RESERVED: + item._remove = True + continue + item.order_max = min( item.cached_availability[1] if item.cached_availability[1] is not None else sys.maxsize, @@ -203,6 +210,10 @@ def get_grouped_items(event, subevent=None, voucher=None, channel='web', require ) ] + if event.settings.hide_sold_out: + item.available_variations = [v for v in item.available_variations + if v.cached_availability[0] >= Quota.AVAILABILITY_RESERVED] + if voucher and voucher.variation_id: item.available_variations = [v for v in item.available_variations if v.pk == voucher.variation_id] diff --git a/src/tests/presale/test_event.py b/src/tests/presale/test_event.py index 512ac6ce23..9660ab5afe 100644 --- a/src/tests/presale/test_event.py +++ b/src/tests/presale/test_event.py @@ -348,6 +348,22 @@ class ItemDisplayTest(EventTestMixin, SoupTest): doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug)) self.assertEqual(1, len(doc.select(".availability-box"))) + def test_hide_sold_out(self): + with scopes_disabled(): + q = Quota.objects.create(event=self.event, name='Quota', size=0) + item = Item.objects.create(event=self.event, name='Early-bird ticket', default_price=12) + q.items.add(item) + + doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug)) + self.assertIn("Early-bird", doc.select("section:nth-of-type(1) div:nth-of-type(1)")[0].text) + self.assertIn("SOLD OUT", doc.select("section:nth-of-type(1)")[0].text) + + self.event.settings.hide_sold_out = True + + doc = self.get_doc('/%s/%s/' % (self.orga.slug, self.event.slug)) + self.assertNotIn("Early-bird", doc.select("section:nth-of-type(1) div:nth-of-type(1)")[0].text) + self.assertNotIn("SOLD OUT", doc.select("section:nth-of-type(1)")[0].text) + def test_bundle_sold_out(self): with scopes_disabled(): q = Quota.objects.create(event=self.event, name='Quota', size=2)