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 %}