From f014a9bbd342d21a08bfb880b20bfccedb56909c Mon Sep 17 00:00:00 2001 From: Mira Weller Date: Fri, 24 Jan 2025 14:48:28 +0100 Subject: [PATCH] Reapply "Implement hidden_if_item_available_mode option (Z#23177008) (#4776)" This reverts commit 5cd7959e863324160af1dfdb3aa0fd0b7605a9ce. --- doc/api/resources/items.rst | 13 +++++++++ src/pretix/api/serializers/item.py | 2 +- ...0276_item_hidden_if_item_available_mode.py | 18 +++++++++++++ src/pretix/base/models/items.py | 13 ++++++++- src/pretix/control/forms/item.py | 27 ++++++++----------- .../templates/pretixcontrol/item/index.html | 2 +- .../fragment_unavail_mode_indicator.html | 2 +- .../event/fragment_availability.html | 4 +++ src/pretix/presale/views/event.py | 8 +++--- .../static/pretixpresale/js/widget/widget.js | 1 + src/tests/api/test_items.py | 1 + 11 files changed, 67 insertions(+), 24 deletions(-) create mode 100644 src/pretix/base/migrations/0276_item_hidden_if_item_available_mode.py diff --git a/doc/api/resources/items.rst b/doc/api/resources/items.rst index 071c1f7d1..2214c54ba 100644 --- a/doc/api/resources/items.rst +++ b/doc/api/resources/items.rst @@ -69,6 +69,10 @@ hidden_if_available integer **DEPRECATED* hidden_if_item_available integer The internal ID of a different item, or ``null``. If set, this item won't be shown publicly as long as this other item is available. +hidden_if_item_available_mode string If ``hide`` (the default), this item is hidden in the shop + if unavailable due to the ``hidden_if_item_available`` setting. + If ``info``, the item is visible, but can't be purchased, + and a note explaining the unavailability is displayed. require_voucher boolean If ``true``, this item can only be bought using a voucher that is specifically assigned to this item. hide_without_voucher boolean If ``true``, this item is only shown during the voucher @@ -239,6 +243,10 @@ meta_data object Values set fo The ``hidden_if_item_available`` attributes has been added, the ``hidden_if_available`` attribute has been deprecated. +.. versionchanged:: 2025.01 + + The ``hidden_if_item_available_mode`` attributes has been added. + Notes ----- @@ -308,6 +316,7 @@ Endpoints "available_until_mode": "hide", "hidden_if_available": null, "hidden_if_item_available": null, + "hidden_if_item_available_mode": "hide", "require_voucher": false, "hide_without_voucher": false, "allow_cancel": true, @@ -459,6 +468,7 @@ Endpoints "available_until_mode": "hide", "hidden_if_available": null, "hidden_if_item_available": null, + "hidden_if_item_available_mode": "hide", "require_voucher": false, "hide_without_voucher": false, "allow_cancel": true, @@ -589,6 +599,7 @@ Endpoints "available_until_mode": "hide", "hidden_if_available": null, "hidden_if_item_available": null, + "hidden_if_item_available_mode": "hide", "require_voucher": false, "hide_without_voucher": false, "allow_cancel": true, @@ -705,6 +716,7 @@ Endpoints "available_until_mode": "hide", "hidden_if_available": null, "hidden_if_item_available": null, + "hidden_if_item_available_mode": "hide", "require_voucher": false, "hide_without_voucher": false, "allow_cancel": true, @@ -855,6 +867,7 @@ Endpoints "available_until_mode": "hide", "hidden_if_available": null, "hidden_if_item_available": null, + "hidden_if_item_available_mode": "hide", "require_voucher": false, "hide_without_voucher": false, "generate_tickets": null, diff --git a/src/pretix/api/serializers/item.py b/src/pretix/api/serializers/item.py index 7133776f8..21b7d07ec 100644 --- a/src/pretix/api/serializers/item.py +++ b/src/pretix/api/serializers/item.py @@ -272,7 +272,7 @@ class ItemSerializer(SalesChannelMigrationMixin, I18nAwareModelSerializer): 'require_voucher', 'hide_without_voucher', 'allow_cancel', 'require_bundling', 'min_per_order', 'max_per_order', 'checkin_attention', 'checkin_text', 'has_variations', 'variations', 'addons', 'bundles', 'original_price', 'require_approval', 'generate_tickets', - 'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'allow_waitinglist', + 'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', 'hidden_if_item_available_mode', 'allow_waitinglist', 'issue_giftcard', 'meta_data', 'require_membership', 'require_membership_types', 'require_membership_hidden', 'grant_membership_type', 'grant_membership_duration_like_event', 'grant_membership_duration_days', diff --git a/src/pretix/base/migrations/0276_item_hidden_if_item_available_mode.py b/src/pretix/base/migrations/0276_item_hidden_if_item_available_mode.py new file mode 100644 index 000000000..c22056196 --- /dev/null +++ b/src/pretix/base/migrations/0276_item_hidden_if_item_available_mode.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.16 on 2025-01-23 11:27 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("pretixbase", "0275_alter_question_valid_number_max_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="item", + name="hidden_if_item_available_mode", + field=models.CharField(default="hide", max_length=16), + ), + ] diff --git a/src/pretix/base/models/items.py b/src/pretix/base/models/items.py index 69bd61752..35deab286 100644 --- a/src/pretix/base/models/items.py +++ b/src/pretix/base/models/items.py @@ -442,8 +442,12 @@ class Item(LoggedModel): UNAVAIL_MODE_INFO = "info" UNAVAIL_MODES = ( (UNAVAIL_MODE_HIDDEN, _("Hide product if unavailable")), - (UNAVAIL_MODE_INFO, _("Show info text if unavailable")), + (UNAVAIL_MODE_INFO, _("Show product with info on why it’s unavailable")), ) + UNAVAIL_MODE_ICONS = { + UNAVAIL_MODE_HIDDEN: 'eye-slash', + UNAVAIL_MODE_INFO: 'info' + } MEDIA_POLICY_REUSE = 'reuse' MEDIA_POLICY_NEW = 'new' @@ -596,6 +600,11 @@ class Item(LoggedModel): "be a short period in which both products are visible while all tickets of the referenced " "product are reserved, but not yet sold.") ) + hidden_if_item_available_mode = models.CharField( + choices=UNAVAIL_MODES, + default=UNAVAIL_MODE_HIDDEN, + max_length=16, + ) require_voucher = models.BooleanField( verbose_name=_('This product can only be bought using a voucher.'), default=False, @@ -885,6 +894,8 @@ class Item(LoggedModel): return 'available_from' elif subevent_item and subevent_item.available_until and subevent_item.available_until < now_dt: return 'available_until' + elif self.hidden_if_item_available and self._dependency_available: + return 'hidden_if_item_available' else: return None diff --git a/src/pretix/control/forms/item.py b/src/pretix/control/forms/item.py index 1d7831284..f1a218c67 100644 --- a/src/pretix/control/forms/item.py +++ b/src/pretix/control/forms/item.py @@ -476,6 +476,7 @@ class ItemCreateForm(I18nModelForm): 'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', + 'hidden_if_item_available_mode', 'require_bundling', 'require_membership', 'grant_membership_type', @@ -646,18 +647,12 @@ class ItemUpdateForm(I18nModelForm): self.fields['available_from_mode'].widget = ButtonGroupRadioSelect( choices=self.fields['available_from_mode'].choices, - option_icons={ - Item.UNAVAIL_MODE_HIDDEN: 'eye-slash', - Item.UNAVAIL_MODE_INFO: 'info' - } + option_icons=Item.UNAVAIL_MODE_ICONS ) self.fields['available_until_mode'].widget = ButtonGroupRadioSelect( choices=self.fields['available_until_mode'].choices, - option_icons={ - Item.UNAVAIL_MODE_HIDDEN: 'eye-slash', - Item.UNAVAIL_MODE_INFO: 'info' - } + option_icons=Item.UNAVAIL_MODE_ICONS ) self.fields['hide_without_voucher'].widget = ButtonGroupRadioSelect( @@ -672,6 +667,11 @@ class ItemUpdateForm(I18nModelForm): attrs={'data-checkbox-dependency': '#id_require_voucher'} ) + self.fields['hidden_if_item_available_mode'].widget = ButtonGroupRadioSelect( + choices=self.fields['hidden_if_item_available_mode'].choices, + option_icons=Item.UNAVAIL_MODE_ICONS + ) + if self.instance.hidden_if_available_id: self.fields['hidden_if_available'].queryset = self.event.quotas.all() self.fields['hidden_if_available'].help_text = format_html( @@ -853,6 +853,7 @@ class ItemUpdateForm(I18nModelForm): 'show_quota_left', 'hidden_if_available', 'hidden_if_item_available', + 'hidden_if_item_available_mode', 'issue_giftcard', 'require_membership', 'require_membership_types', @@ -970,18 +971,12 @@ class ItemVariationForm(I18nModelForm): self.fields['available_from_mode'].widget = ButtonGroupRadioSelect( choices=self.fields['available_from_mode'].choices, - option_icons={ - Item.UNAVAIL_MODE_HIDDEN: 'eye-slash', - Item.UNAVAIL_MODE_INFO: 'info' - } + option_icons=Item.UNAVAIL_MODE_ICONS ) self.fields['available_until_mode'].widget = ButtonGroupRadioSelect( choices=self.fields['available_until_mode'].choices, - option_icons={ - Item.UNAVAIL_MODE_HIDDEN: 'eye-slash', - Item.UNAVAIL_MODE_INFO: 'info' - } + option_icons=Item.UNAVAIL_MODE_ICONS ) self.meta_fields = [] diff --git a/src/pretix/control/templates/pretixcontrol/item/index.html b/src/pretix/control/templates/pretixcontrol/item/index.html index abea1a90a..6aa712436 100644 --- a/src/pretix/control/templates/pretixcontrol/item/index.html +++ b/src/pretix/control/templates/pretixcontrol/item/index.html @@ -172,7 +172,7 @@ {% if form.hidden_if_available %} {% bootstrap_field form.hidden_if_available layout="control" horizontal_field_class="col-md-7" %} {% endif %} - {% bootstrap_field form.hidden_if_item_available layout="control" horizontal_field_class="col-md-7" %} + {% bootstrap_field form.hidden_if_item_available visibility_field=form.hidden_if_item_available_mode layout="control_with_visibility" %} {% for v in formsets.values %}
diff --git a/src/pretix/control/templates/pretixcontrol/subevents/fragment_unavail_mode_indicator.html b/src/pretix/control/templates/pretixcontrol/subevents/fragment_unavail_mode_indicator.html index 9be415651..516ee2896 100644 --- a/src/pretix/control/templates/pretixcontrol/subevents/fragment_unavail_mode_indicator.html +++ b/src/pretix/control/templates/pretixcontrol/subevents/fragment_unavail_mode_indicator.html @@ -2,5 +2,5 @@ {% if mode == "hide" %} {% else %} - + {% endif %} \ No newline at end of file diff --git a/src/pretix/presale/templates/pretixpresale/event/fragment_availability.html b/src/pretix/presale/templates/pretixpresale/event/fragment_availability.html index 62a391d61..bd9f326ce 100644 --- a/src/pretix/presale/templates/pretixpresale/event/fragment_availability.html +++ b/src/pretix/presale/templates/pretixpresale/event/fragment_availability.html @@ -5,6 +5,10 @@ +{% elif item.current_unavailability_reason == 'hidden_if_item_available' %} +
+

{% trans "Not available yet." %}

+
{% elif item.current_unavailability_reason == 'available_from' or var.current_unavailability_reason == 'available_from' %}

{% trans "Not available yet." %}

diff --git a/src/pretix/presale/views/event.py b/src/pretix/presale/views/event.py index 06b9128b2..63e66e526 100644 --- a/src/pretix/presale/views/event.py +++ b/src/pretix/presale/views/event.py @@ -70,7 +70,7 @@ from pretix.base.models import ( ) from pretix.base.models.event import Event, SubEvent from pretix.base.models.items import ( - ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation, + Item, ItemAddOn, ItemBundle, SubEventItem, SubEventItemVariation, ) from pretix.base.services.placeholders import PlaceholderContext from pretix.base.services.quotas import QuotaAvailability @@ -302,14 +302,14 @@ def get_grouped_items(event, *, channel: SalesChannel, subevent=None, voucher=No if item.hidden_if_item_available: if item.hidden_if_item_available.has_variations: - dependency_available = any( + item._dependency_available = any( var.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True)[0] == Quota.AVAILABILITY_OK for var in item.hidden_if_item_available.available_variations ) else: q = item.hidden_if_item_available.check_quotas(subevent=subevent, _cache=quota_cache, include_bundled=True) - dependency_available = q[0] == Quota.AVAILABILITY_OK - if dependency_available: + item._dependency_available = q[0] == Quota.AVAILABILITY_OK + if item._dependency_available and item.hidden_if_item_available_mode == Item.UNAVAIL_MODE_HIDDEN: item._remove = True continue diff --git a/src/pretix/static/pretixpresale/js/widget/widget.js b/src/pretix/static/pretixpresale/js/widget/widget.js index 92e76af03..bd737a25e 100644 --- a/src/pretix/static/pretixpresale/js/widget/widget.js +++ b/src/pretix/static/pretixpresale/js/widget/widget.js @@ -38,6 +38,7 @@ var strings = { 'unavailable_available_from': django.pgettext('widget', 'Not yet available'), 'unavailable_available_until': django.pgettext('widget', 'Not available anymore'), 'unavailable_active': django.pgettext('widget', 'Currently not available'), + 'unavailable_hidden_if_item_available': django.pgettext('widget', 'Not yet available'), 'order_min': django.pgettext('widget', 'minimum amount to order: %s'), 'exit': django.pgettext('widget', 'Close ticket shop'), 'loading_error': django.pgettext('widget', 'The ticket shop could not be loaded.'), diff --git a/src/tests/api/test_items.py b/src/tests/api/test_items.py index 3d440e74d..58b7b2839 100644 --- a/src/tests/api/test_items.py +++ b/src/tests/api/test_items.py @@ -323,6 +323,7 @@ TEST_ITEM_RES = { "max_per_order": None, "hidden_if_available": None, "hidden_if_item_available": None, + "hidden_if_item_available_mode": "hide", "checkin_attention": False, "checkin_text": None, "has_variations": False,