Check-in API: Add reusable media exchange (#6115)

* Add Reusable Media Exchange to Checkin API

* isort

* Remove debugging leftover

* Apply suggestions from code review

Co-authored-by: robbi5 <maxi@richt.name>

* Add media_exchange_supported to CheckinRPCRedeemInputSerializer

* SecurityProfiles: Add api-v1:reusablemedia-lookup and -detail for SCAN

* Simplify media exchange checks

* Apply suggestions from code review

Co-authored-by: Raphael Michel <mail@raphaelmichel.de>

* Wording: re-usable --> reusable

* Deny checkins if media-exchange is required but device does not support it.

* Remove media_exchange_supported-Flag: Checkin will always be denied if media needs to be exchanged; apps will fall back to explanation text

* CheckinRPC: Also perform media exchange

* Use media_policy from item, not as a checkinrpc parameter

* my own review notes

* Fixes, cleanup, rebase

* block expired media

* Fix query

* add logging

* Refactor link_action into media policy, gift card support

* Block illegal policy-type combination

* Drop add_to_reusable_medium, decide all by policy

* Fix test failure

* fix test on postgres

* Expose reusable_media_usage_enforced to devies

* Explicitly set update view

---------

Co-authored-by: robbi5 <maxi@richt.name>
Co-authored-by: Maximilian Richt <richt@pretix.eu>
Co-authored-by: Raphael Michel <mail@raphaelmichel.de>
Co-authored-by: Raphael Michel <michel@rami.io>
Co-authored-by: Raphael Michel <michel@pretix.eu>
This commit is contained in:
Martin Gross
2026-06-11 16:25:13 +02:00
committed by GitHub
co-authored by robbi5 Raphael Michel Maximilian Richt Raphael Michel Raphael Michel
parent 784577d86f
commit 775fdd1ccb
31 changed files with 875 additions and 95 deletions
+16 -6
View File
@@ -452,11 +452,16 @@ class Item(LoggedModel):
MEDIA_POLICY_REUSE = 'reuse'
MEDIA_POLICY_NEW = 'new'
MEDIA_POLICY_REUSE_OR_NEW = 'reuse_or_new'
MEDIA_POLICY_APPEND = 'append'
MEDIA_POLICY_APPEND_OR_NEW = 'append_or_new'
MEDIA_POLICIES = (
(None, _("Don't use re-usable media, use regular one-off tickets")),
(MEDIA_POLICY_REUSE, _('Require an existing medium to be re-used')),
(None, _("Don't use reusable media, use regular one-off tickets")),
(MEDIA_POLICY_NEW, _('Require a previously unknown medium to be newly added')),
(MEDIA_POLICY_REUSE_OR_NEW, _('Require either an existing or a new medium to be used')),
(MEDIA_POLICY_REUSE, _('Require an existing medium to be reused, replacing any previous tickets')),
(MEDIA_POLICY_REUSE_OR_NEW, _('Require either an existing or a new medium to be used, replacing any previous tickets')),
(MEDIA_POLICY_APPEND, _('Require an existing medium to be reused, adding to any previous tickets')),
(MEDIA_POLICY_APPEND_OR_NEW,
_('Require either an existing or a new medium to be used, adding to any previous tickets')),
)
objects = ItemQuerySetManager()
@@ -769,7 +774,7 @@ class Item(LoggedModel):
null=True, blank=True, max_length=16,
verbose_name=_('Reusable media policy'),
help_text=_(
'If this product should be stored on a re-usable physical medium, you can attach a physical media policy. '
'If this product should be stored on a reusable physical medium, you can attach a physical media policy. '
'This is not required for regular tickets, which just use a one-time barcode, but only for products like '
'renewable season tickets or re-chargeable gift card wristbands. '
'This is an advanced feature that also requires specific configuration of ticketing and printing settings.'
@@ -778,7 +783,7 @@ class Item(LoggedModel):
media_type = models.CharField(
max_length=100,
null=True, blank=True,
choices=[(None, _("Don't use re-usable media, use regular one-off tickets"))] + [(k, v) for k, v in MEDIA_TYPES.items()],
choices=[(None, _("Don't use reusable media, use regular one-off tickets"))] + [(k, v) for k, v in MEDIA_TYPES.items()],
verbose_name=_('Reusable media type'),
help_text=_(
'Select the type of physical medium that should be used for this product. Note that not all media types '
@@ -995,6 +1000,11 @@ class Item(LoggedModel):
raise ValidationError(_('The selected media type does not support usage for tickets currently.'))
if not mt.supports_giftcard and issue_giftcard:
raise ValidationError(_('The selected media type does not support usage for gift cards currently.'))
if media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
if not mt.medium_created_by_server and not mt.medium_created_from_unknown_supported:
raise ValidationError(_('The selected media type requires all media to be registered in the system '
'prior to their usage. Therefore, the selected media policy does not make '
'sense for this media type.'))
if issue_giftcard:
raise ValidationError(_('You currently cannot create gift cards with a reusable media policy. Instead, '
'gift cards for some reusable media types can be created or re-charged directly '
@@ -2220,7 +2230,7 @@ class Quota(LoggedModel):
class ItemMetaProperty(LoggedModel):
"""
An event can have ItemMetaProperty objects attached to define meta information fields
for its items. This information can be re-used for example in ticket layouts.
for its items. This information can be reused for example in ticket layouts.
:param event: The event this property is defined for.
:type event: Event