From 491753008d75e7cf3ed5d59757ea900c377514e7 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Jul 2019 16:08:21 +0200 Subject: [PATCH] Introduce Item.show_quota_left --- doc/api/resources/items.rst | 11 +++++++++++ src/pretix/api/serializers/item.py | 3 ++- .../migrations/0126_item_show_quota_left.py | 18 ++++++++++++++++++ src/pretix/base/models/items.py | 11 +++++++++++ src/pretix/control/forms/item.py | 14 +++++++++++++- .../templates/pretixcontrol/item/index.html | 1 + src/pretix/presale/forms/checkout.py | 3 ++- .../event/fragment_product_list.html | 4 ++-- src/pretix/presale/views/widget.py | 4 ++-- src/tests/api/test_items.py | 1 + 10 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 src/pretix/base/migrations/0126_item_show_quota_left.py diff --git a/doc/api/resources/items.rst b/doc/api/resources/items.rst index df2c69c1ef..d613048076 100644 --- a/doc/api/resources/items.rst +++ b/doc/api/resources/items.rst @@ -72,6 +72,8 @@ generate_tickets boolean If ``false``, t non-admission or add-on product, regardless of event settings. If this is ``null``, regular ticketing rules apply. +show_quota_left boolean Publicly show how many tickets are still available. + If this is ``null``, the event default is used. has_variations boolean Shows whether or not this item has variations. variations list of objects A list with one object for each variation of this item. Can be empty. Only writable during creation, @@ -142,6 +144,10 @@ bundles list of objects Definition of b The ``bundles`` and ``require_bundling`` attributes have been added. +.. versionchanged:: 3.0 + + The ``show_quota_left`` attribute has been added. + Notes ----- @@ -207,6 +213,7 @@ Endpoints "checkin_attention": false, "has_variations": false, "generate_tickets": null, + "show_quota_left": null, "require_approval": false, "require_bundling": false, "variations": [ @@ -294,6 +301,7 @@ Endpoints "hide_without_voucher": false, "allow_cancel": true, "generate_tickets": null, + "show_quota_left": null, "min_per_order": null, "max_per_order": null, "checkin_attention": false, @@ -366,6 +374,7 @@ Endpoints "hide_without_voucher": false, "allow_cancel": true, "generate_tickets": null, + "show_quota_left": null, "min_per_order": null, "max_per_order": null, "checkin_attention": false, @@ -427,6 +436,7 @@ Endpoints "min_per_order": null, "max_per_order": null, "generate_tickets": null, + "show_quota_left": null, "checkin_attention": false, "has_variations": true, "require_approval": false, @@ -515,6 +525,7 @@ Endpoints "require_voucher": false, "hide_without_voucher": false, "generate_tickets": null, + "show_quota_left": null, "allow_cancel": true, "min_per_order": null, "max_per_order": null, diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index 5dd2ab168e..7fb7fac146 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -118,7 +118,8 @@ class ItemSerializer(I18nAwareModelSerializer): 'position', 'picture', 'available_from', 'available_until', 'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling', 'min_per_order', 'max_per_order', 'checkin_attention', 'has_variations', 'variations', - 'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets') + 'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets', + 'show_quota_left') read_only_fields = ('has_variations', 'picture') def get_serializer_context(self): diff --git a/src/pretix/base/migrations/0126_item_show_quota_left.py b/src/pretix/base/migrations/0126_item_show_quota_left.py new file mode 100644 index 0000000000..cef78f179a --- /dev/null +++ b/src/pretix/base/migrations/0126_item_show_quota_left.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.1 on 2019-07-10 13:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('pretixbase', '0125_voucher_show_hidden_items'), + ] + + operations = [ + migrations.AddField( + model_name='item', + name='show_quota_left', + field=models.NullBooleanField(), + ), + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 6e81ae255e..70afbf6fd7 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -308,6 +308,11 @@ class Item(LoggedModel): verbose_name=_("Generate tickets"), blank=True, null=True, ) + show_quota_left = models.NullBooleanField( + verbose_name=_("Show number of tickets left"), + help_text=_("Publicly show how many tickets are still available."), + blank=True, null=True, + ) position = models.IntegerField( default=0 ) @@ -410,6 +415,12 @@ class Item(LoggedModel): if self.event: self.event.cache.clear() + @property + def do_show_quota_left(self): + if self.show_quota_left is None: + return self.event.settings.show_quota_left + return self.show_quota_left + def tax(self, price=None, base_price_is='auto', currency=None, include_bundled=False): price = price if price is not None else self.default_price diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index f01119a7b6..be6df06f44 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -356,6 +356,16 @@ class ItemCreateForm(I18nModelForm): ] +class ShowQuotaNullBooleanSelect(forms.NullBooleanSelect): + def __init__(self, attrs=None): + choices = ( + ('1', _('(Event default)')), + ('2', _('Yes')), + ('3', _('No')), + ) + super(forms.NullBooleanSelect, self).__init__(attrs, choices) + + class TicketNullBooleanSelect(forms.NullBooleanSelect): def __init__(self, attrs=None): choices = ( @@ -415,6 +425,7 @@ class ItemUpdateForm(I18nModelForm): 'generate_tickets', 'original_price', 'require_bundling', + 'show_quota_left' ] field_classes = { 'available_from': SplitDateTimeField, @@ -423,7 +434,8 @@ class ItemUpdateForm(I18nModelForm): widgets = { 'available_from': SplitDateTimePickerWidget(), 'available_until': SplitDateTimePickerWidget(attrs={'data-date-after': '#id_available_from_0'}), - 'generate_tickets': TicketNullBooleanSelect() + 'generate_tickets': TicketNullBooleanSelect(), + 'show_quota_left': ShowQuotaNullBooleanSelect() } diff --git a/src/pretix/control/templates/pretixcontrol/item/index.html b/src/pretix/control/templates/pretixcontrol/item/index.html index 0b863af599..f5093bea5d 100644 --- a/src/pretix/control/templates/pretixcontrol/item/index.html +++ b/src/pretix/control/templates/pretixcontrol/item/index.html @@ -45,6 +45,7 @@ {% bootstrap_field form.original_price addon_after=request.event.currency layout="control" %} {% bootstrap_field form.require_approval layout="control" %} {% bootstrap_field form.generate_tickets layout="control" %} + {% bootstrap_field form.show_quota_left layout="control" %} {% for f in plugin_forms %} {% bootstrap_form f layout="control" %} {% endfor %} diff --git a/src/pretix/presale/forms/checkout.py b/src/pretix/presale/forms/checkout.py index 1322a6b3b1..710ed06b53 100644 --- a/src/pretix/presale/forms/checkout.py +++ b/src/pretix/presale/forms/checkout.py @@ -174,7 +174,7 @@ class AddOnsForm(forms.Form): elif avail[0] < 100: n += ' – {}'.format(_('Currently unavailable')) else: - if avail[1] is not None and event.settings.show_quota_left: + if avail[1] is not None and item.do_show_quota_left: n += ' – {}'.format(_('%(num)s currently available') % {'num': avail[1]}) if not isinstance(item_or_variation, ItemVariation) and item.picture: @@ -236,6 +236,7 @@ class AddOnsForm(forms.Form): to_attr='_subevent_quotas', queryset=event.quotas.filter(subevent=subevent)) ).distinct()), + 'event' ).annotate( quotac=Count('quotas'), has_variations=Count('variations') diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html b/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html index 8346744c2b..62b6527551 100644 --- a/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html +++ b/src/pretix/presale/templates/pretixpresale/event/fragment_product_list.html @@ -81,7 +81,7 @@ {{ var.description|localize|rich_text }} {% endif %} - {% if event.settings.show_quota_left %} + {% if item.do_show_quota_left %} {% include "pretixpresale/event/fragment_quota_left.html" with avail=var.cached_availability %} {% endif %} @@ -187,7 +187,7 @@ {{ item.description|localize|rich_text }} {% endif %} - {% if event.settings.show_quota_left %} + {% if item.do_show_quota_left %} {% include "pretixpresale/event/fragment_quota_left.html" with avail=item.cached_availability %} {% endif %} {% if item.min_per_order and item.min_per_order > 1 %} diff --git a/src/pretix/presale/views/widget.py b/src/pretix/presale/views/widget.py index 599b03afd8..3a167a94b9 100644 --- a/src/pretix/presale/views/widget.py +++ b/src/pretix/presale/views/widget.py @@ -198,7 +198,7 @@ class WidgetAPIProductList(EventListMixin, View): 'free_price': item.free_price, 'avail': [ item.cached_availability[0], - item.cached_availability[1] if self.request.event.settings.show_quota_left else None + item.cached_availability[1] if item.do_show_quota_left else None ] if not item.has_variations else None, 'original_price': ( (item.original_price.net @@ -228,7 +228,7 @@ class WidgetAPIProductList(EventListMixin, View): ), 'avail': [ var.cached_availability[0], - var.cached_availability[1] if self.request.event.settings.show_quota_left else None + var.cached_availability[1] if item.do_show_quota_left else None ], } for var in item.available_variations ] diff --git a/src/tests/api/test_items.py b/src/tests/api/test_items.py index a070e8c5fa..98194b20ed 100644 --- a/src/tests/api/test_items.py +++ b/src/tests/api/test_items.py @@ -240,6 +240,7 @@ TEST_ITEM_RES = { "variations": [], "addons": [], "bundles": [], + "show_quota_left": None, "original_price": None }