Allow changing ticket secret via API (Z#23188201) (#4989)

This commit is contained in:
luelista
2025-04-07 13:49:19 +02:00
committed by GitHub
parent 1bbefddc11
commit 277a5bffa8
4 changed files with 52 additions and 1 deletions

View File

@@ -1943,9 +1943,14 @@ Manipulating individual positions
* ``valid_until`` * ``valid_until``
* ``secret``
Changing parameters such as ``item`` or ``price`` will **not** automatically trigger creation of a new invoice, Changing parameters such as ``item`` or ``price`` will **not** automatically trigger creation of a new invoice,
you need to take care of that yourself. you need to take care of that yourself.
Changing ``secret`` does not cause a new PDF ticket to be sent to the customer, nor does it cause the old secret
to be added to the revocation list, even if your ticket generator uses one.
**Example request**: **Example request**:
.. sourcecode:: http .. sourcecode:: http

View File

@@ -251,7 +251,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = OrderPosition model = OrderPosition
fields = ( fields = (
'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule', 'valid_from', 'valid_until' 'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule', 'valid_from', 'valid_until', 'secret'
) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -319,6 +319,7 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
tax_rule = validated_data.get('tax_rule', instance.tax_rule) tax_rule = validated_data.get('tax_rule', instance.tax_rule)
valid_from = validated_data.get('valid_from', instance.valid_from) valid_from = validated_data.get('valid_from', instance.valid_from)
valid_until = validated_data.get('valid_until', instance.valid_until) valid_until = validated_data.get('valid_until', instance.valid_until)
secret = validated_data.get('secret', instance.secret)
change_item = None change_item = None
if item != instance.item or variation != instance.variation: if item != instance.item or variation != instance.variation:
@@ -351,6 +352,9 @@ class OrderPositionChangeSerializer(serializers.ModelSerializer):
if valid_until != instance.valid_until: if valid_until != instance.valid_until:
ocm.change_valid_until(instance, valid_until) ocm.change_valid_until(instance, valid_until)
if secret != instance.secret:
ocm.change_ticket_secret(instance, secret)
if self.context.get('commit', True): if self.context.get('commit', True):
ocm.commit() ocm.commit()
instance.refresh_from_db() instance.refresh_from_db()

View File

@@ -1564,6 +1564,7 @@ class OrderChangeManager:
AddFeeOperation = namedtuple('AddFeeOperation', ('fee', 'price_diff')) AddFeeOperation = namedtuple('AddFeeOperation', ('fee', 'price_diff'))
CancelFeeOperation = namedtuple('CancelFeeOperation', ('fee', 'price_diff')) CancelFeeOperation = namedtuple('CancelFeeOperation', ('fee', 'price_diff'))
RegenerateSecretOperation = namedtuple('RegenerateSecretOperation', ('position',)) RegenerateSecretOperation = namedtuple('RegenerateSecretOperation', ('position',))
ChangeSecretOperation = namedtuple('ChangeSecretOperation', ('position', 'new_secret'))
ChangeValidFromOperation = namedtuple('ChangeValidFromOperation', ('position', 'valid_from')) ChangeValidFromOperation = namedtuple('ChangeValidFromOperation', ('position', 'valid_from'))
ChangeValidUntilOperation = namedtuple('ChangeValidUntilOperation', ('position', 'valid_until')) ChangeValidUntilOperation = namedtuple('ChangeValidUntilOperation', ('position', 'valid_until'))
AddBlockOperation = namedtuple('AddBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked')) AddBlockOperation = namedtuple('AddBlockOperation', ('position', 'block_name', 'ignore_from_quota_while_blocked'))
@@ -1671,6 +1672,9 @@ class OrderChangeManager:
def regenerate_secret(self, position: OrderPosition): def regenerate_secret(self, position: OrderPosition):
self._operations.append(self.RegenerateSecretOperation(position)) self._operations.append(self.RegenerateSecretOperation(position))
def change_ticket_secret(self, position: OrderPosition, new_secret: str):
self._operations.append(self.ChangeSecretOperation(position, new_secret))
def change_valid_from(self, position: OrderPosition, new_value: datetime): def change_valid_from(self, position: OrderPosition, new_value: datetime):
self._operations.append(self.ChangeValidFromOperation(position, new_value)) self._operations.append(self.ChangeValidFromOperation(position, new_value))
@@ -2441,6 +2445,19 @@ class OrderChangeManager:
'position': op.position.pk, 'position': op.position.pk,
'positionid': op.position.positionid, 'positionid': op.position.positionid,
}) })
elif isinstance(op, self.ChangeSecretOperation):
if OrderPosition.all.filter(order__event=self.event, secret=op.new_secret).exists():
raise OrderError('You cannot assign a position secret that already exists.')
op.position.secret = op.new_secret
op.position.save(update_fields=["secret"])
if op.position in secret_dirty:
secret_dirty.remove(op.position)
tickets.invalidate_cache.apply_async(kwargs={'event': self.event.pk,
'order': self.order.pk})
self.order.log_action('pretix.event.order.changed.secret', user=self.user, auth=self.auth, data={
'position': op.position.pk,
'positionid': op.position.positionid,
})
elif isinstance(op, self.ChangeValidFromOperation): elif isinstance(op, self.ChangeValidFromOperation):
self.order.log_action('pretix.event.order.changed.valid_from', user=self.user, auth=self.auth, data={ self.order.log_action('pretix.event.order.changed.valid_from', user=self.user, auth=self.auth, data={
'position': op.position.pk, 'position': op.position.pk,

View File

@@ -1468,6 +1468,31 @@ def test_position_update_change_price_and_tax_rule(token_client, organizer, even
assert op.tax_rule == tr assert op.tax_rule == tr
@pytest.mark.django_db
def test_position_update_secret(token_client, organizer, event, order, item):
with scopes_disabled():
order.positions.create(item=item, price=Decimal('23.00'), secret='alreadyused')
p = order.positions.first()
psw = p.web_secret
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format(
organizer.slug, event.slug, p.pk,
), format='json', data={'secret': 'nobodyknows'}
)
assert resp.status_code == 200
p.refresh_from_db()
with scopes_disabled():
assert 'nobodyknows' == p.secret
assert psw == p.web_secret
resp = token_client.patch(
'/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format(
organizer.slug, event.slug, p.pk,
), format='json', data={'secret': 'alreadyused'}
)
assert resp.status_code == 400
@pytest.mark.django_db @pytest.mark.django_db
def test_position_add_simple(token_client, organizer, event, order, quota, item): def test_position_add_simple(token_client, organizer, event, order, quota, item):
with scopes_disabled(): with scopes_disabled():