mirror of
https://github.com/pretix/pretix.git
synced 2026-06-12 01:35:16 +00:00
Refactor link_action into media policy, gift card support
This commit is contained in:
@@ -48,7 +48,6 @@ Checking a ticket in
|
|||||||
allows for a certain level of idempotency and enables you to re-try after a connection failure.
|
allows for a certain level of idempotency and enables you to re-try after a connection failure.
|
||||||
:<json string exchange_medium_type: To perform an exchange to a reusable medium, pass the type of the new reusable medium
|
:<json string exchange_medium_type: To perform an exchange to a reusable medium, pass the type of the new reusable medium
|
||||||
:<json string exchange_medium_identifier: To perform an exchange to a reusable media, pass the identifier of the new medium
|
:<json string exchange_medium_identifier: To perform an exchange to a reusable media, pass the identifier of the new medium
|
||||||
:<json string exchange_link_action: To perform an exchange to a reusable media, pass `"append"` or `"replace"` depending on whether any previous ticket links of the medium should be kept
|
|
||||||
:<json boolean use_order_locale: Specifies that pretix should use the customer's language (``locale`` field from the
|
:<json boolean use_order_locale: Specifies that pretix should use the customer's language (``locale`` field from the
|
||||||
order) when building texts (currently only the ``reason_explanation`` response field).
|
order) when building texts (currently only the ``reason_explanation`` response field).
|
||||||
Defaults to ``false`` in which case the server will determine the language (currently
|
Defaults to ``false`` in which case the server will determine the language (currently
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ allow_waitinglist boolean If ``false``,
|
|||||||
product when it is sold out.
|
product when it is sold out.
|
||||||
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
|
issue_giftcard boolean If ``true``, buying this product will yield a gift card.
|
||||||
media_policy string Policy on how to handle reusable media (experimental feature).
|
media_policy string Policy on how to handle reusable media (experimental feature).
|
||||||
Possible values are ``null``, ``"new"``, ``"reuse"``, and ``"reuse_or_new"``.
|
Possible values are ``null``, ``"new"``, ``"reuse"``, ``"reuse_or_new"``, ``"append"``, and ``"append_or_new"``.
|
||||||
media_type string Type of reusable media to work on (experimental feature). See :ref:`rest-reusablemedia` for possible choices.
|
media_type string Type of reusable media to work on (experimental feature). See :ref:`rest-reusablemedia` for possible choices.
|
||||||
show_quota_left boolean Publicly show how many tickets are still available.
|
show_quota_left boolean Publicly show how many tickets are still available.
|
||||||
If this is ``null``, the event default is used.
|
If this is ``null``, the event default is used.
|
||||||
|
|||||||
@@ -90,20 +90,15 @@ class CheckinRPCRedeemInputSerializer(serializers.Serializer):
|
|||||||
answers = serializers.JSONField(required=False, allow_null=True)
|
answers = serializers.JSONField(required=False, allow_null=True)
|
||||||
exchange_medium_type = serializers.ChoiceField(required=False, choices=MEDIA_TYPES)
|
exchange_medium_type = serializers.ChoiceField(required=False, choices=MEDIA_TYPES)
|
||||||
exchange_medium_identifier = serializers.CharField(required=False)
|
exchange_medium_identifier = serializers.CharField(required=False)
|
||||||
exchange_link_action = serializers.ChoiceField(required=False, choices=[
|
|
||||||
('append', 'append'),
|
|
||||||
('replace', 'replace'),
|
|
||||||
])
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.fields['lists'].child_relation.queryset = CheckinList.objects.filter(event__in=self.context['events']).select_related('event')
|
self.fields['lists'].child_relation.queryset = CheckinList.objects.filter(event__in=self.context['events']).select_related('event')
|
||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
exchange_fields = ["exchange_medium_type", "exchange_medium_identifier", "exchange_link_action"]
|
exchange_fields = ["exchange_medium_type", "exchange_medium_identifier"]
|
||||||
if any(attrs.get(k) is None for k in exchange_fields) and not all(attrs.get(k) is None for k in exchange_fields):
|
if any(attrs.get(k) is None for k in exchange_fields) and not all(attrs.get(k) is None for k in exchange_fields):
|
||||||
raise ValidationError("If you set any of exchange_medium_type, exchange_medium_identifier, or "
|
raise ValidationError("If you set any of exchange_medium_type or exchange_medium_identifier, you need to set both of them.")
|
||||||
"exchange_link_action, you need to set all of them.")
|
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ def _checkin_list_position_queryset(checkinlists, ignore_status=False, ignore_pr
|
|||||||
def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force, checkin_type, ignore_unpaid, nonce,
|
def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force, checkin_type, ignore_unpaid, nonce,
|
||||||
untrusted_input, user, auth, expand, pdf_data, request, questions_supported, canceled_supported,
|
untrusted_input, user, auth, expand, pdf_data, request, questions_supported, canceled_supported,
|
||||||
source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False,
|
source_type='barcode', legacy_url_support=False, simulate=False, gate=None, use_order_locale=False,
|
||||||
exchange_medium_type=None, exchange_medium_identifier=None, exchange_link_action=None):
|
exchange_medium_type=None, exchange_medium_identifier=None):
|
||||||
if not checkinlists:
|
if not checkinlists:
|
||||||
raise ValidationError('No check-in list passed.')
|
raise ValidationError('No check-in list passed.')
|
||||||
|
|
||||||
@@ -805,7 +805,7 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
|||||||
locale = op.order.event.settings.locale
|
locale = op.order.event.settings.locale
|
||||||
with language(locale):
|
with language(locale):
|
||||||
try:
|
try:
|
||||||
if exchange_link_action and medium:
|
if exchange_medium_identifier and medium:
|
||||||
# Cannot scan a medium and then request to exchange it
|
# Cannot scan a medium and then request to exchange it
|
||||||
raise CheckInError(
|
raise CheckInError(
|
||||||
gettext('You cannot exchange a medium for a medium.'),
|
gettext('You cannot exchange a medium for a medium.'),
|
||||||
@@ -833,14 +833,13 @@ def _redeem_process(*, checkinlists, raw_barcode, answers_data, datetime, force,
|
|||||||
reusable_medium=medium,
|
reusable_medium=medium,
|
||||||
)
|
)
|
||||||
|
|
||||||
if exchange_link_action: # other fields are filled, see CheckinRPCRedeemInputSerializer.validate
|
if exchange_medium_identifier: # other fields are filled, see CheckinRPCRedeemInputSerializer.validate
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
# Do exchange and check-in atomically, i.e. both succeed or both fail
|
# Do exchange and check-in atomically, i.e. both succeed or both fail
|
||||||
medium = perform_media_exchange(
|
medium = perform_media_exchange(
|
||||||
organizer=request.organizer,
|
organizer=request.organizer,
|
||||||
media_type=exchange_medium_type,
|
media_type=exchange_medium_type,
|
||||||
identifier=exchange_medium_identifier,
|
identifier=exchange_medium_identifier,
|
||||||
link_action=exchange_link_action,
|
|
||||||
link_orderposition=op,
|
link_orderposition=op,
|
||||||
user=user,
|
user=user,
|
||||||
auth=auth,
|
auth=auth,
|
||||||
@@ -1061,7 +1060,6 @@ class CheckinRPCRedeemView(views.APIView):
|
|||||||
legacy_url_support=False,
|
legacy_url_support=False,
|
||||||
exchange_medium_type=s.validated_data.get('exchange_medium_type'),
|
exchange_medium_type=s.validated_data.get('exchange_medium_type'),
|
||||||
exchange_medium_identifier=s.validated_data.get('exchange_medium_identifier'),
|
exchange_medium_identifier=s.validated_data.get('exchange_medium_identifier'),
|
||||||
exchange_link_action=s.validated_data.get('exchange_link_action'),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
class BaseMediaType:
|
class BaseMediaType:
|
||||||
medium_created_by_server = False
|
medium_created_by_server = False
|
||||||
|
medium_created_from_unknown_supported = False
|
||||||
supports_orderposition = False
|
supports_orderposition = False
|
||||||
supports_giftcard = False
|
supports_giftcard = False
|
||||||
|
|
||||||
@@ -56,7 +57,7 @@ class BaseMediaType:
|
|||||||
def is_active(self, organizer):
|
def is_active(self, organizer):
|
||||||
return organizer.settings.get(f'reusable_media_type_{self.identifier}', as_type=bool, default=False)
|
return organizer.settings.get(f'reusable_media_type_{self.identifier}', as_type=bool, default=False)
|
||||||
|
|
||||||
def handle_unknown(self, organizer, identifier, user, auth):
|
def handle_unknown(self, organizer, identifier, user, auth, force_create=False):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def handle_new(self, organizer, medium, user, auth):
|
def handle_new(self, organizer, medium, user, auth):
|
||||||
@@ -88,23 +89,32 @@ class NfcUidMediaType(BaseMediaType):
|
|||||||
verbose_name = _('NFC UID-based')
|
verbose_name = _('NFC UID-based')
|
||||||
icon = 'pretixbase/img/media/nfc_uid.svg'
|
icon = 'pretixbase/img/media/nfc_uid.svg'
|
||||||
medium_created_by_server = False
|
medium_created_by_server = False
|
||||||
|
medium_created_from_unknown_supported = True
|
||||||
supports_giftcard = True
|
supports_giftcard = True
|
||||||
supports_orderposition = True
|
supports_orderposition = True
|
||||||
|
|
||||||
def handle_unknown(self, organizer, identifier, user, auth):
|
def handle_unknown(self, organizer, identifier, user, auth, force_create=False):
|
||||||
from pretix.base.models import GiftCard, ReusableMedium
|
from pretix.base.models import GiftCard, ReusableMedium
|
||||||
|
|
||||||
if organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool):
|
create_giftcard = organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard', as_type=bool)
|
||||||
|
if create_giftcard or force_create:
|
||||||
if identifier.startswith("08"):
|
if identifier.startswith("08"):
|
||||||
# Don't create gift cards for NFC UIDs that start with 08, which represents NFC cards that issue random
|
# Don't create gift cards for NFC UIDs that start with 08, which represents NFC cards that issue random
|
||||||
# UIDs on every read, so they won't be useful.
|
# UIDs on every read, so they won't be useful.
|
||||||
return
|
return
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
gc = GiftCard.objects.create(
|
if create_giftcard:
|
||||||
issuer=organizer,
|
gc = GiftCard.objects.create(
|
||||||
expires=organizer.default_gift_card_expiry,
|
issuer=organizer,
|
||||||
currency=organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard_currency'),
|
expires=organizer.default_gift_card_expiry,
|
||||||
)
|
currency=organizer.settings.get(f'reusable_media_type_{self.identifier}_autocreate_giftcard_currency'),
|
||||||
|
)
|
||||||
|
gc.log_action(
|
||||||
|
'pretix.giftcards.created',
|
||||||
|
user=user, auth=auth,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
gc = None
|
||||||
m = ReusableMedium.objects.create(
|
m = ReusableMedium.objects.create(
|
||||||
type=self.identifier,
|
type=self.identifier,
|
||||||
identifier=identifier,
|
identifier=identifier,
|
||||||
@@ -116,10 +126,6 @@ class NfcUidMediaType(BaseMediaType):
|
|||||||
'pretix.reusable_medium.created.auto',
|
'pretix.reusable_medium.created.auto',
|
||||||
user=user, auth=auth,
|
user=user, auth=auth,
|
||||||
)
|
)
|
||||||
gc.log_action(
|
|
||||||
'pretix.giftcards.created',
|
|
||||||
user=user, auth=auth,
|
|
||||||
)
|
|
||||||
return m
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -452,11 +452,16 @@ class Item(LoggedModel):
|
|||||||
MEDIA_POLICY_REUSE = 'reuse'
|
MEDIA_POLICY_REUSE = 'reuse'
|
||||||
MEDIA_POLICY_NEW = 'new'
|
MEDIA_POLICY_NEW = 'new'
|
||||||
MEDIA_POLICY_REUSE_OR_NEW = 'reuse_or_new'
|
MEDIA_POLICY_REUSE_OR_NEW = 'reuse_or_new'
|
||||||
|
MEDIA_POLICY_APPEND = 'append'
|
||||||
|
MEDIA_POLICY_APPEND_OR_NEW = 'append_or_new'
|
||||||
MEDIA_POLICIES = (
|
MEDIA_POLICIES = (
|
||||||
(None, _("Don't use reusable media, use regular one-off tickets")),
|
(None, _("Don't use reusable media, use regular one-off tickets")),
|
||||||
(MEDIA_POLICY_REUSE, _('Require an existing medium to be reused')),
|
|
||||||
(MEDIA_POLICY_NEW, _('Require a previously unknown medium to be newly added')),
|
(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()
|
objects = ItemQuerySetManager()
|
||||||
|
|||||||
@@ -287,11 +287,11 @@ def _check_position_constraints(
|
|||||||
raise CartPositionError(error_messages['unavailable'])
|
raise CartPositionError(error_messages['unavailable'])
|
||||||
|
|
||||||
# Invalid media policy for online sale
|
# Invalid media policy for online sale
|
||||||
if item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
if item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
||||||
mt = MEDIA_TYPES[item.media_type]
|
mt = MEDIA_TYPES[item.media_type]
|
||||||
if not mt.medium_created_by_server:
|
if not mt.medium_created_by_server:
|
||||||
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||||
elif item.media_policy == Item.MEDIA_POLICY_REUSE:
|
elif item.media_policy in (Item.MEDIA_POLICY_REUSE, Item.MEDIA_POLICY_APPEND):
|
||||||
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
raise CartPositionError(error_messages['media_usage_not_implemented'])
|
||||||
|
|
||||||
# Item removed from sales channel
|
# Item removed from sales channel
|
||||||
|
|||||||
@@ -75,21 +75,18 @@ def get_keysets_for_organizer(organizer):
|
|||||||
return sets
|
return sets
|
||||||
|
|
||||||
|
|
||||||
def perform_media_exchange(organizer, media_type, identifier, link_action, link_orderposition, user, auth):
|
def perform_media_exchange(organizer, media_type, identifier, link_orderposition, user, auth):
|
||||||
"""
|
"""
|
||||||
Create or retrieve a medium, then link the order position to it. Expected to be called in a transaction.
|
Create or retrieve a medium, then link the order position to it. Expected to be called in a transaction.
|
||||||
|
|
||||||
:param organizer: Organizer to operate in
|
:param organizer: Organizer to operate in
|
||||||
:param media_type: Type of medium to operate with
|
:param media_type: Type of medium to operate with
|
||||||
:param identifier: Identifier of the medium
|
:param identifier: Identifier of the medium
|
||||||
:param link_action: one of `"append"` or `"replace"`
|
|
||||||
:param link_orderposition: Position to link to the medium
|
:param link_orderposition: Position to link to the medium
|
||||||
:return: ReusableMedium
|
:return: ReusableMedium
|
||||||
"""
|
"""
|
||||||
medium = None
|
medium = None
|
||||||
media_policy = link_orderposition.item.media_policy
|
media_policy = link_orderposition.item.media_policy
|
||||||
if link_action not in ('append', 'replace'):
|
|
||||||
raise ValueError("Invalid link_action")
|
|
||||||
|
|
||||||
if media_type not in MEDIA_TYPES: # should be caught by serializer already
|
if media_type not in MEDIA_TYPES: # should be caught by serializer already
|
||||||
raise CheckInError(
|
raise CheckInError(
|
||||||
@@ -119,7 +116,12 @@ def perform_media_exchange(organizer, media_type, identifier, link_action, link_
|
|||||||
reason=_('Ticket is already exchanged for reusable medium.'),
|
reason=_('Ticket is already exchanged for reusable medium.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
if media_policy == Item.MEDIA_POLICY_REUSE:
|
if media_policy in (Item.MEDIA_POLICY_APPEND, Item.MEDIA_POLICY_APPEND_OR_NEW, Item.MEDIA_POLICY_NEW):
|
||||||
|
link_action = "append"
|
||||||
|
else:
|
||||||
|
link_action = "replace"
|
||||||
|
|
||||||
|
if media_policy in (Item.MEDIA_POLICY_REUSE, Item.MEDIA_POLICY_APPEND):
|
||||||
try:
|
try:
|
||||||
medium = ReusableMedium.objects.get(
|
medium = ReusableMedium.objects.get(
|
||||||
type=media_type,
|
type=media_type,
|
||||||
@@ -140,19 +142,28 @@ def perform_media_exchange(organizer, media_type, identifier, link_action, link_
|
|||||||
reason=_('Reusable medium is inactive or expired.'),
|
reason=_('Reusable medium is inactive or expired.'),
|
||||||
)
|
)
|
||||||
|
|
||||||
elif media_policy == Item.MEDIA_POLICY_REUSE_OR_NEW:
|
elif media_policy in (Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||||
medium, created = ReusableMedium.objects.get_or_create(
|
try:
|
||||||
type=media_type,
|
medium = ReusableMedium.objects.get(
|
||||||
identifier=identifier,
|
type=media_type,
|
||||||
organizer=organizer,
|
identifier=identifier,
|
||||||
)
|
organizer=organizer,
|
||||||
if created:
|
|
||||||
medium.log_action(
|
|
||||||
'pretix.reusable_medium.created.auto',
|
|
||||||
user=user,
|
|
||||||
auth=auth,
|
|
||||||
)
|
)
|
||||||
elif medium.is_expired or not medium.active:
|
except ReusableMedium.DoesNotExist:
|
||||||
|
if not MEDIA_TYPES[media_type].medium_created_from_unknown_supported:
|
||||||
|
raise CheckInError(
|
||||||
|
_('Reusable medium not found and could not be created.'),
|
||||||
|
Checkin.REASON_MEDIUM_INVALID,
|
||||||
|
)
|
||||||
|
|
||||||
|
medium = MEDIA_TYPES[media_type].handle_unknown(organizer, identifier, user, auth, force_create=True)
|
||||||
|
if not medium:
|
||||||
|
raise CheckInError(
|
||||||
|
_('Reusable medium not found and could not be created.'),
|
||||||
|
Checkin.REASON_MEDIUM_INVALID,
|
||||||
|
)
|
||||||
|
|
||||||
|
if medium.is_expired or not medium.active:
|
||||||
raise CheckInError(
|
raise CheckInError(
|
||||||
_('Reusable medium is inactive or expired.'),
|
_('Reusable medium is inactive or expired.'),
|
||||||
Checkin.REASON_MEDIUM_INVALID,
|
Checkin.REASON_MEDIUM_INVALID,
|
||||||
@@ -160,23 +171,24 @@ def perform_media_exchange(organizer, media_type, identifier, link_action, link_
|
|||||||
)
|
)
|
||||||
|
|
||||||
elif media_policy == Item.MEDIA_POLICY_NEW:
|
elif media_policy == Item.MEDIA_POLICY_NEW:
|
||||||
try:
|
if not MEDIA_TYPES[media_type].medium_created_from_unknown_supported:
|
||||||
medium = ReusableMedium.objects.create(
|
raise CheckInError(
|
||||||
type=media_type,
|
_('Reusable medium not found and could not be created.'),
|
||||||
identifier=identifier,
|
Checkin.REASON_MEDIUM_INVALID,
|
||||||
organizer=organizer,
|
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
|
medium = MEDIA_TYPES[media_type].handle_unknown(organizer, identifier, user, auth, force_create=True)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
raise CheckInError(
|
raise CheckInError(
|
||||||
_('Reusable medium already exists.'),
|
_('Reusable medium already exists.'),
|
||||||
Checkin.REASON_MEDIUM_EXISTS,
|
Checkin.REASON_MEDIUM_EXISTS,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
medium.log_action(
|
if not medium:
|
||||||
'pretix.reusable_medium.created.auto',
|
raise CheckInError(
|
||||||
user=user,
|
_('Reusable medium could not be created.'),
|
||||||
auth=auth,
|
Checkin.REASON_MEDIUM_INVALID,
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise CheckInError(
|
raise CheckInError(
|
||||||
|
|||||||
@@ -3506,7 +3506,7 @@ def signal_listener_issue_media(sender: Event, order: Order, **kwargs):
|
|||||||
from pretix.base.models import ReusableMedium
|
from pretix.base.models import ReusableMedium
|
||||||
|
|
||||||
for p in order.positions.all():
|
for p in order.positions.all():
|
||||||
if p.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW):
|
if p.item.media_policy in (Item.MEDIA_POLICY_NEW, Item.MEDIA_POLICY_REUSE_OR_NEW, Item.MEDIA_POLICY_APPEND_OR_NEW):
|
||||||
mt = MEDIA_TYPES[p.item.media_type]
|
mt = MEDIA_TYPES[p.item.media_type]
|
||||||
if mt.medium_created_by_server and not p.linked_media.exists():
|
if mt.medium_created_by_server and not p.linked_media.exists():
|
||||||
rm = ReusableMedium.objects.create(
|
rm = ReusableMedium.objects.create(
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
|
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
{
|
{
|
||||||
const globals = this;
|
const globals = this;
|
||||||
|
|||||||
@@ -1263,8 +1263,7 @@ def test_exchange_incomplete_body(token_client, organizer, clist, event, order):
|
|||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data == {
|
assert resp.data == {
|
||||||
'non_field_errors': ['If you set any of exchange_medium_type, exchange_medium_identifier, or '
|
'non_field_errors': ['If you set any of exchange_medium_type or exchange_medium_identifier, you need to set both of them.']
|
||||||
'exchange_link_action, you need to set all of them.']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1281,7 +1280,6 @@ def test_exchange_medium_for_medium(token_client, organizer, clist, event, order
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "barcode",
|
"exchange_medium_type": "barcode",
|
||||||
"exchange_medium_identifier": "hijkl",
|
"exchange_medium_identifier": "hijkl",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data['status'] == 'error'
|
assert resp.data['status'] == 'error'
|
||||||
@@ -1294,7 +1292,6 @@ def test_exchange_unknown_media_type(token_client, organizer, clist, event, orde
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "unknown",
|
"exchange_medium_type": "unknown",
|
||||||
"exchange_medium_identifier": "hijkl",
|
"exchange_medium_identifier": "hijkl",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data == {"exchange_medium_type": ["\"unknown\" is not a valid choice."]}
|
assert resp.data == {"exchange_medium_type": ["\"unknown\" is not a valid choice."]}
|
||||||
@@ -1306,7 +1303,6 @@ def test_exchange_disabled_media_type(token_client, organizer, clist, event, ord
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "hijkl",
|
"exchange_medium_identifier": "hijkl",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data['status'] == 'error'
|
assert resp.data['status'] == 'error'
|
||||||
@@ -1323,7 +1319,6 @@ def test_exchange_mismatch_media_type(token_client, organizer, clist, event, ord
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data['status'] == 'error'
|
assert resp.data['status'] == 'error'
|
||||||
@@ -1340,7 +1335,6 @@ def test_exchange_no_item_policy(token_client, organizer, clist, event, order, i
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data['status'] == 'error'
|
assert resp.data['status'] == 'error'
|
||||||
@@ -1352,13 +1346,12 @@ def test_exchange_no_item_policy(token_client, organizer, clist, event, order, i
|
|||||||
def test_exchange_reuse_or_new_new(token_client, organizer, clist, event, order, item):
|
def test_exchange_reuse_or_new_new(token_client, organizer, clist, event, order, item):
|
||||||
organizer.settings.reusable_media_type_nfc_uid = True
|
organizer.settings.reusable_media_type_nfc_uid = True
|
||||||
item.media_type = "nfc_uid"
|
item.media_type = "nfc_uid"
|
||||||
item.media_policy = Item.MEDIA_POLICY_NEW
|
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
||||||
item.save()
|
item.save()
|
||||||
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
assert resp.data['status'] == 'ok'
|
assert resp.data['status'] == 'ok'
|
||||||
@@ -1388,7 +1381,6 @@ def test_exchange_reuse_or_new_reuse_replace(token_client, organizer, clist, eve
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
assert resp.data['status'] == 'ok'
|
assert resp.data['status'] == 'ok'
|
||||||
@@ -1401,7 +1393,7 @@ def test_exchange_reuse_or_new_reuse_replace(token_client, organizer, clist, eve
|
|||||||
def test_exchange_reuse_or_new_reuse_append(token_client, organizer, clist, event, order, item):
|
def test_exchange_reuse_or_new_reuse_append(token_client, organizer, clist, event, order, item):
|
||||||
organizer.settings.reusable_media_type_nfc_uid = True
|
organizer.settings.reusable_media_type_nfc_uid = True
|
||||||
item.media_type = "nfc_uid"
|
item.media_type = "nfc_uid"
|
||||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||||
item.save()
|
item.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
rm = ReusableMedium.objects.create(
|
rm = ReusableMedium.objects.create(
|
||||||
@@ -1414,7 +1406,6 @@ def test_exchange_reuse_or_new_reuse_append(token_client, organizer, clist, even
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "append",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
assert resp.data['status'] == 'ok'
|
assert resp.data['status'] == 'ok'
|
||||||
@@ -1428,7 +1419,7 @@ def test_exchange_reuse_or_new_reuse_append(token_client, organizer, clist, even
|
|||||||
def test_exchange_reuse_exists_append(token_client, organizer, clist, event, order, item):
|
def test_exchange_reuse_exists_append(token_client, organizer, clist, event, order, item):
|
||||||
organizer.settings.reusable_media_type_nfc_uid = True
|
organizer.settings.reusable_media_type_nfc_uid = True
|
||||||
item.media_type = "nfc_uid"
|
item.media_type = "nfc_uid"
|
||||||
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
|
item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
|
||||||
item.save()
|
item.save()
|
||||||
with scopes_disabled():
|
with scopes_disabled():
|
||||||
rm = ReusableMedium.objects.create(
|
rm = ReusableMedium.objects.create(
|
||||||
@@ -1441,7 +1432,6 @@ def test_exchange_reuse_exists_append(token_client, organizer, clist, event, ord
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "append",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 201
|
assert resp.status_code == 201
|
||||||
assert resp.data['status'] == 'ok'
|
assert resp.data['status'] == 'ok'
|
||||||
@@ -1469,7 +1459,6 @@ def test_exchange_reuse_expired(token_client, organizer, clist, event, order, it
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data['status'] == 'error'
|
assert resp.data['status'] == 'error'
|
||||||
@@ -1486,7 +1475,6 @@ def test_exchange_reuse_not_exists(token_client, organizer, clist, event, order,
|
|||||||
"source_type": "barcode",
|
"source_type": "barcode",
|
||||||
"exchange_medium_type": "nfc_uid",
|
"exchange_medium_type": "nfc_uid",
|
||||||
"exchange_medium_identifier": "12345678",
|
"exchange_medium_identifier": "12345678",
|
||||||
"exchange_link_action": "replace",
|
|
||||||
})
|
})
|
||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data['status'] == 'error'
|
assert resp.data['status'] == 'error'
|
||||||
@@ -1672,3 +1660,82 @@ def test_exchanged_double_exchange(token_client, organizer, clist, event, order,
|
|||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert resp.data['status'] == 'error'
|
assert resp.data['status'] == 'error'
|
||||||
assert resp.data['reason'] == 'already_exchanged'
|
assert resp.data['reason'] == 'already_exchanged'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"media_policy,media_type",
|
||||||
|
[
|
||||||
|
(Item.MEDIA_POLICY_NEW, "nfc_mf0aes"),
|
||||||
|
(Item.MEDIA_POLICY_REUSE_OR_NEW, "nfc_mf0aes"),
|
||||||
|
(Item.MEDIA_POLICY_APPEND_OR_NEW, "nfc_mf0aes"),
|
||||||
|
(Item.MEDIA_POLICY_NEW, "barcode"),
|
||||||
|
(Item.MEDIA_POLICY_REUSE_OR_NEW, "barcode"),
|
||||||
|
(Item.MEDIA_POLICY_APPEND_OR_NEW, "barcode"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_exchange_unsupported_media_type_for_new(token_client, organizer, clist, event, order, item, media_policy, media_type):
|
||||||
|
organizer.settings.set(f'reusable_media_type_{media_type}', True)
|
||||||
|
# Shouldn't be configurable, but test that the logic is solid anyway
|
||||||
|
item.media_type = media_type
|
||||||
|
item.media_policy = media_policy
|
||||||
|
item.save()
|
||||||
|
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||||
|
"source_type": "barcode",
|
||||||
|
"exchange_medium_type": media_type,
|
||||||
|
"exchange_medium_identifier": "12345678",
|
||||||
|
})
|
||||||
|
assert resp.status_code == 400
|
||||||
|
assert resp.data['status'] == 'error'
|
||||||
|
assert resp.data['reason'] == 'medium_invalid'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"media_policy",
|
||||||
|
[
|
||||||
|
Item.MEDIA_POLICY_NEW,
|
||||||
|
Item.MEDIA_POLICY_REUSE_OR_NEW,
|
||||||
|
Item.MEDIA_POLICY_APPEND_OR_NEW,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_exchange_rejected_media_identifier(token_client, organizer, clist, event, order, item, media_policy):
|
||||||
|
organizer.settings.reusable_media_type_nfc_uid = True
|
||||||
|
item.media_type = "nfc_uid"
|
||||||
|
item.media_policy = media_policy
|
||||||
|
item.save()
|
||||||
|
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||||
|
"source_type": "barcode",
|
||||||
|
"exchange_medium_type": "nfc_uid",
|
||||||
|
"exchange_medium_identifier": "08RANDOM",
|
||||||
|
})
|
||||||
|
assert resp.status_code == 400
|
||||||
|
assert resp.data['status'] == 'error'
|
||||||
|
assert resp.data['reason'] == 'medium_invalid'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"media_policy",
|
||||||
|
[
|
||||||
|
Item.MEDIA_POLICY_NEW,
|
||||||
|
Item.MEDIA_POLICY_REUSE_OR_NEW,
|
||||||
|
Item.MEDIA_POLICY_APPEND_OR_NEW,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_exchange_create_gift_card(token_client, organizer, clist, event, order, item, media_policy):
|
||||||
|
organizer.settings.reusable_media_type_nfc_uid = True
|
||||||
|
organizer.settings.reusable_media_type_nfc_uid_autocreate_giftcard = True
|
||||||
|
organizer.settings.reusable_media_type_nfc_uid_autocreate_giftcard_currency = "EUR"
|
||||||
|
item.media_type = "nfc_uid"
|
||||||
|
item.media_policy = media_policy
|
||||||
|
item.save()
|
||||||
|
resp = _redeem(token_client, organizer, clist, "z3fsn8jyufm5kpk768q69gkbyr5f4h6w", {
|
||||||
|
"source_type": "barcode",
|
||||||
|
"exchange_medium_type": "nfc_uid",
|
||||||
|
"exchange_medium_identifier": "0412345",
|
||||||
|
})
|
||||||
|
assert resp.status_code == 201
|
||||||
|
with scopes_disabled():
|
||||||
|
rm = ReusableMedium.objects.get(identifier="0412345")
|
||||||
|
assert rm.linked_giftcard.currency == "EUR"
|
||||||
|
|||||||
Reference in New Issue
Block a user