Drop add_to_reusable_medium, decide all by policy

This commit is contained in:
Raphael Michel
2026-06-09 18:23:59 +02:00
parent edf58198b4
commit 5bfc67cf29
3 changed files with 17 additions and 49 deletions

View File

@@ -1069,8 +1069,7 @@ Creating orders
* ``valid_from`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product) * ``valid_from`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
* ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product) * ``valid_until`` (optional, if both ``valid_from`` and ``valid_until`` are **missing** (not ``null``) the availability will be computed from the given product)
* ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected) * ``requested_valid_from`` (optional, can be set **instead** of ``valid_from`` and ``valid_until`` to signal a user choice for the start time that may or may not be respected)
* ``use_reusable_medium`` (optional, causes the new ticket to take over the given reusable medium, identified by its ID) * ``use_reusable_medium`` (optional, causes the new ticket to be connected to the given reusable medium, identified by its ID)
* ``add_to_reusable_medium`` (optional, causes the new ticket to be added to the given reusable medium, identified by its ID)
* ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run) * ``discount`` (optional, only possible if ``price`` is set; attention: if this is set to not-``null`` on any position, automatic calculation of discounts will not run)
* ``answers`` * ``answers``

View File

@@ -1043,15 +1043,13 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
requested_valid_from = serializers.DateTimeField(required=False, allow_null=True) requested_valid_from = serializers.DateTimeField(required=False, allow_null=True)
use_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(), use_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(),
required=False, allow_null=True) required=False, allow_null=True)
add_to_reusable_medium = serializers.PrimaryKeyRelatedField(queryset=ReusableMedium.objects.none(),
required=False, allow_null=True)
class Meta: class Meta:
model = OrderPosition model = OrderPosition
fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email', fields = ('positionid', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled', 'company', 'street', 'zipcode', 'city', 'country', 'state', 'is_bundled',
'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until', 'secret', 'addon_to', 'subevent', 'answers', 'seat', 'voucher', 'valid_from', 'valid_until',
'requested_valid_from', 'use_reusable_medium', 'add_to_reusable_medium', 'discount') 'requested_valid_from', 'use_reusable_medium', 'discount')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -1063,8 +1061,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
with scopes_disabled(): with scopes_disabled():
if 'use_reusable_medium' in self.fields: if 'use_reusable_medium' in self.fields:
self.fields['use_reusable_medium'].queryset = ReusableMedium.objects.all() self.fields['use_reusable_medium'].queryset = ReusableMedium.objects.all()
if 'add_to_reusable_medium' in self.fields:
self.fields['add_to_reusable_medium'].queryset = ReusableMedium.objects.all()
def validate_secret(self, secret): def validate_secret(self, secret):
if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists(): if secret and OrderPosition.all.filter(order__event=self.context['event'], secret=secret).exists():
@@ -1080,9 +1076,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
) )
return m return m
def validate_add_to_reusable_medium(self, m):
return self.validate_use_reusable_medium(m)
def validate_item(self, item): def validate_item(self, item):
if item.event != self.context['event']: if item.event != self.context['event']:
raise ValidationError( raise ValidationError(
@@ -1157,12 +1150,6 @@ class OrderPositionCreateSerializer(I18nAwareModelSerializer):
{'discount': ['You can only specify a discount if you do the price computation, but price is not set.']} {'discount': ['You can only specify a discount if you do the price computation, but price is not set.']}
) )
if 'use_reusable_medium' in data and 'add_to_reusable_medium' in data:
raise ValidationError({
'use_reusable_medium': ['You can only specify either use_reusable_medium or add_to_reusable_medium.'],
'add_to_reusable_medium': ['You can only specify either use_reusable_medium or add_to_reusable_medium.'],
})
return data return data
@@ -1602,7 +1589,7 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
pos_data['attendee_name_parts'] = { pos_data['attendee_name_parts'] = {
'_legacy': attendee_name '_legacy': attendee_name
} }
pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium', 'add_to_reusable_medium')}) pos = OrderPosition(**{k: v for k, v in pos_data.items() if k not in ('answers', '_quotas', 'use_reusable_medium')})
if simulate: if simulate:
pos.order = order._wrapped pos.order = order._wrapped
else: else:
@@ -1676,7 +1663,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
for pos_data in positions_data: for pos_data in positions_data:
answers_data = pos_data.pop('answers', []) answers_data = pos_data.pop('answers', [])
use_reusable_medium = pos_data.pop('use_reusable_medium', None) use_reusable_medium = pos_data.pop('use_reusable_medium', None)
add_to_reusable_medium = pos_data.pop('add_to_reusable_medium', None)
pos = pos_data['__instance'] pos = pos_data['__instance']
pos._calculate_tax(invoice_address=ia) pos._calculate_tax(invoice_address=ia)
@@ -1718,14 +1704,17 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
answ.options.add(*options) answ.options.add(*options)
if use_reusable_medium: if use_reusable_medium:
for op_pk in use_reusable_medium.linked_orderpositions.values_list('pk', flat=True): if pos.item.media_policy not in (Item.MEDIA_POLICY_APPEND, Item.MEDIA_POLICY_APPEND_OR_NEW):
use_reusable_medium.log_action( for op_pk in use_reusable_medium.linked_orderpositions.values_list('pk', flat=True):
'pretix.reusable_medium.linked_orderposition.removed', use_reusable_medium.log_action(
data={ 'pretix.reusable_medium.linked_orderposition.removed',
'linked_orderposition': op_pk, data={
} 'linked_orderposition': op_pk,
) }
use_reusable_medium.linked_orderpositions.set([pos]) )
use_reusable_medium.linked_orderpositions.set([pos])
else:
use_reusable_medium.linked_orderpositions.add(pos)
use_reusable_medium.log_action( use_reusable_medium.log_action(
'pretix.reusable_medium.linked_orderposition.added', 'pretix.reusable_medium.linked_orderposition.added',
data={ data={
@@ -1733,15 +1722,6 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
'linked_orderposition': pos.pk, 'linked_orderposition': pos.pk,
} }
) )
elif add_to_reusable_medium:
add_to_reusable_medium.linked_orderpositions.add(pos)
add_to_reusable_medium.log_action(
'pretix.reusable_medium.linked_orderposition.added',
data={
'by_order': order.code,
'linked_orderposition': pos.pk,
}
)
if not simulate: if not simulate:
for cp in delete_cps: for cp in delete_cps:

View File

@@ -3141,23 +3141,11 @@ def test_order_create_use_medium(token_client, organizer, event, item, quota, qu
@pytest.mark.django_db @pytest.mark.django_db
def test_order_create_add_to_medium(token_client, organizer, event, item, quota, question, medium): def test_order_create_add_to_medium(token_client, organizer, event, item, quota, question, medium):
item.media_type = medium.type item.media_type = medium.type
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW item.media_policy = Item.MEDIA_POLICY_APPEND_OR_NEW
item.save() item.save()
res = copy.deepcopy(ORDER_CREATE_PAYLOAD) res = copy.deepcopy(ORDER_CREATE_PAYLOAD)
res['positions'][0]['item'] = item.pk res['positions'][0]['item'] = item.pk
res['positions'][0]['use_reusable_medium'] = medium.pk res['positions'][0]['use_reusable_medium'] = medium.pk
res['positions'][0]['add_to_reusable_medium'] = medium.pk
res['positions'][0]['answers'][0]['question'] = question.pk
# do not use use_reusable_medium and add_to_reusable_medium
resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
organizer.slug, event.slug
), format='json', data=res
)
assert resp.status_code == 400
del res['positions'][0]['use_reusable_medium']
resp = token_client.post( resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format( '/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
@@ -3179,8 +3167,9 @@ def test_order_create_add_to_medium(token_client, organizer, event, item, quota,
medium.refresh_from_db() medium.refresh_from_db()
assert medium.linked_orderpositions.count() == 2 assert medium.linked_orderpositions.count() == 2
item.media_policy = Item.MEDIA_POLICY_REUSE_OR_NEW
item.save()
res['positions'][0]['use_reusable_medium'] = medium.pk res['positions'][0]['use_reusable_medium'] = medium.pk
del res['positions'][0]['add_to_reusable_medium']
resp = token_client.post( resp = token_client.post(
'/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format( '/api/v1/organizers/{}/events/{}/orders/?pdf_data=true'.format(
organizer.slug, event.slug organizer.slug, event.slug