mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Allow changing ticket secret via API (Z#23188201) (#4989)
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
Reference in New Issue
Block a user