API: Allow to change orders (#2552)

This commit is contained in:
Raphael Michel
2022-03-30 17:36:10 +02:00
committed by GitHub
parent 76475039b5
commit 69375f4092
10 changed files with 5678 additions and 3568 deletions

View File

@@ -424,88 +424,7 @@ class OrderPositionSerializer(I18nAwareModelSerializer):
self.fields.pop('pdf_data', None)
def validate(self, data):
if data.get('attendee_name') and data.get('attendee_name_parts'):
raise ValidationError(
{'attendee_name': ['Do not specify attendee_name if you specified attendee_name_parts.']}
)
if data.get('attendee_name_parts') and '_scheme' not in data.get('attendee_name_parts'):
data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
if data.get('country'):
if not pycountry.countries.get(alpha_2=data.get('country').code):
raise ValidationError(
{'country': ['Invalid country code.']}
)
if data.get('state'):
cc = str(data.get('country') or self.instance.country or '')
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
raise ValidationError(
{'state': ['States are not supported in country "{}".'.format(cc)]}
)
if not pycountry.subdivisions.get(code=cc + '-' + data.get('state')):
raise ValidationError(
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
)
return data
def update(self, instance, validated_data):
# Even though all fields that shouldn't be edited are marked as read_only in the serializer
# (hopefully), we'll be extra careful here and be explicit about the model fields we update.
update_fields = [
'attendee_name_parts', 'company', 'street', 'zipcode', 'city', 'country',
'state', 'attendee_email',
]
answers_data = validated_data.pop('answers', None)
name = validated_data.pop('attendee_name', '')
if name and not validated_data.get('attendee_name_parts'):
validated_data['attendee_name_parts'] = {
'_legacy': name
}
for attr, value in validated_data.items():
if attr in update_fields:
setattr(instance, attr, value)
instance.save(update_fields=update_fields)
if answers_data is not None:
qs_seen = set()
answercache = {
a.question_id: a for a in instance.answers.all()
}
for answ_data in answers_data:
options = answ_data.pop('options', [])
if answ_data['question'].pk in qs_seen:
raise ValidationError(f'Question {answ_data["question"]} was sent twice.')
if answ_data['question'].pk in answercache:
a = answercache[answ_data['question'].pk]
if isinstance(answ_data['answer'], File):
a.file.save(answ_data['answer'].name, answ_data['answer'], save=False)
a.answer = 'file://' + a.file.name
elif a.answer.startswith('file://') and answ_data['answer'] == "file:keep":
pass # keep current file
else:
for attr, value in answ_data.items():
setattr(a, attr, value)
a.save()
else:
if isinstance(answ_data['answer'], File):
an = answ_data.pop('answer')
a = instance.answers.create(**answ_data, answer='')
a.file.save(os.path.basename(an.name), an, save=False)
a.answer = 'file://' + a.file.name
a.save()
else:
a = instance.answers.create(**answ_data)
a.options.set(options)
qs_seen.add(a.question_id)
for qid, a in answercache.items():
if qid not in qs_seen:
a.delete()
return instance
raise TypeError("this serializer is readonly")
class RequireAttentionField(serializers.Field):
@@ -593,7 +512,7 @@ class OrderPaymentDateField(serializers.DateField):
class OrderFeeSerializer(I18nAwareModelSerializer):
class Meta:
model = OrderFee
fields = ('fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule', 'canceled')
fields = ('id', 'fee_type', 'value', 'description', 'internal_type', 'tax_rate', 'tax_value', 'tax_rule', 'canceled')
class PaymentURLField(serializers.URLField):
@@ -1361,14 +1280,18 @@ class OrderCreateSerializer(I18nAwareModelSerializer):
f.order = order._wrapped if simulate else order
f._calculate_tax()
fees.append(f)
if not simulate:
if simulate:
f.id = 0
else:
f.save()
else:
f = OrderFee(**fee_data)
f.order = order._wrapped if simulate else order
f._calculate_tax()
fees.append(f)
if not simulate:
if simulate:
f.id = 0
else:
f.save()
order.total += sum([f.value for f in fees])

View File

@@ -0,0 +1,424 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
import logging
import os
import pycountry
from django.core.files import File
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from pretix.api.serializers.order import (
AnswerCreateSerializer, AnswerSerializer, CompatibleCountryField,
OrderPositionCreateSerializer,
)
from pretix.base.models import ItemVariation, Order, OrderFee, OrderPosition
from pretix.base.services.orders import OrderError
from pretix.base.settings import COUNTRIES_WITH_STATE_IN_ADDRESS
logger = logging.getLogger(__name__)
class OrderPositionCreateForExistingOrderSerializer(OrderPositionCreateSerializer):
order = serializers.SlugRelatedField(slug_field='code', queryset=Order.objects.none(), required=True, allow_null=False)
answers = AnswerCreateSerializer(many=True, required=False)
addon_to = serializers.IntegerField(required=False, allow_null=True)
secret = serializers.CharField(required=False)
attendee_name = serializers.CharField(required=False, allow_null=True)
seat = serializers.CharField(required=False, allow_null=True)
price = serializers.DecimalField(required=False, allow_null=True, decimal_places=2,
max_digits=10)
country = CompatibleCountryField(source='*')
class Meta:
model = OrderPosition
fields = ('order', 'item', 'variation', 'price', 'attendee_name', 'attendee_name_parts', 'attendee_email',
'company', 'street', 'zipcode', 'city', 'country', 'state',
'secret', 'addon_to', 'subevent', 'answers', 'seat')
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context:
return
self.fields['order'].queryset = self.context['event'].orders.all()
self.fields['item'].queryset = self.context['event'].items.all()
self.fields['subevent'].queryset = self.context['event'].subevents.all()
self.fields['seat'].queryset = self.context['event'].seats.all()
self.fields['variation'].queryset = ItemVariation.objects.filter(item__event=self.context['event'])
if 'order' in self.context:
del self.fields['order']
def validate(self, data):
data = super().validate(data)
if data.get('addon_to'):
try:
data['addon_to'] = data['order'].positions.get(positionid=data['addon_to'])
except OrderPosition.DoesNotExist:
raise ValidationError({
'addon_to': ['addon_to refers to an unknown position ID for this order.']
})
return data
def create(self, validated_data):
ocm = self.context['ocm']
try:
ocm.add_position(
item=validated_data['item'],
variation=validated_data.get('variation'),
price=validated_data.get('price'),
addon_to=validated_data.get('addon_to'),
subevent=validated_data.get('subevent'),
seat=validated_data.get('seat'),
)
if self.context.get('commit', True):
ocm.commit()
return validated_data['order'].positions.order_by('-positionid').first()
else:
return OrderPosition() # fake to appease DRF
except OrderError as e:
raise ValidationError(str(e))
class OrderPositionInfoPatchSerializer(serializers.ModelSerializer):
answers = AnswerSerializer(many=True)
country = CompatibleCountryField(source='*')
attendee_name = serializers.CharField(required=False)
class Meta:
model = OrderPosition
fields = (
'attendee_name', 'attendee_name_parts', 'company', 'street', 'zipcode', 'city', 'country',
'state', 'attendee_email', 'answers',
)
def validate(self, data):
if data.get('attendee_name') and data.get('attendee_name_parts'):
raise ValidationError(
{'attendee_name': ['Do not specify attendee_name if you specified attendee_name_parts.']}
)
if data.get('attendee_name_parts') and '_scheme' not in data.get('attendee_name_parts'):
data['attendee_name_parts']['_scheme'] = self.context['request'].event.settings.name_scheme
if data.get('country'):
if not pycountry.countries.get(alpha_2=data.get('country').code):
raise ValidationError(
{'country': ['Invalid country code.']}
)
if data.get('state'):
cc = str(data.get('country') or self.instance.country or '')
if cc not in COUNTRIES_WITH_STATE_IN_ADDRESS:
raise ValidationError(
{'state': ['States are not supported in country "{}".'.format(cc)]}
)
if not pycountry.subdivisions.get(code=cc + '-' + data.get('state')):
raise ValidationError(
{'state': ['"{}" is not a known subdivision of the country "{}".'.format(data.get('state'), cc)]}
)
return data
def update(self, instance, validated_data):
answers_data = validated_data.pop('answers', None)
name = validated_data.pop('attendee_name', '')
if name and not validated_data.get('attendee_name_parts'):
validated_data['attendee_name_parts'] = {
'_legacy': name
}
for attr, value in validated_data.items():
if attr in self.fields:
setattr(instance, attr, value)
instance.save(update_fields=list(validated_data.keys()))
if answers_data is not None:
qs_seen = set()
answercache = {
a.question_id: a for a in instance.answers.all()
}
for answ_data in answers_data:
options = answ_data.pop('options', [])
if answ_data['question'].pk in qs_seen:
raise ValidationError(f'Question {answ_data["question"]} was sent twice.')
if answ_data['question'].pk in answercache:
a = answercache[answ_data['question'].pk]
if isinstance(answ_data['answer'], File):
a.file.save(answ_data['answer'].name, answ_data['answer'], save=False)
a.answer = 'file://' + a.file.name
elif a.answer.startswith('file://') and answ_data['answer'] == "file:keep":
pass # keep current file
else:
for attr, value in answ_data.items():
setattr(a, attr, value)
a.save()
else:
if isinstance(answ_data['answer'], File):
an = answ_data.pop('answer')
a = instance.answers.create(**answ_data, answer='')
a.file.save(os.path.basename(an.name), an, save=False)
a.answer = 'file://' + a.file.name
a.save()
else:
a = instance.answers.create(**answ_data)
a.options.set(options)
qs_seen.add(a.question_id)
for qid, a in answercache.items():
if qid not in qs_seen:
a.delete()
return instance
class OrderPositionChangeSerializer(serializers.ModelSerializer):
seat = serializers.CharField(source='seat.seat_guid', allow_null=True, required=False)
class Meta:
model = OrderPosition
fields = (
'item', 'variation', 'subevent', 'seat', 'price', 'tax_rule',
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context:
return
self.fields['item'].queryset = self.context['event'].items.all()
self.fields['subevent'].queryset = self.context['event'].subevents.all()
self.fields['tax_rule'].queryset = self.context['event'].tax_rules.all()
if kwargs.get('partial'):
for k, v in self.fields.items():
self.fields[k].required = False
def validate_item(self, item):
if item.event != self.context['event']:
raise ValidationError(
'The specified item does not belong to this event.'
)
return item
def validate_subevent(self, subevent):
if self.context['event'].has_subevents:
if not subevent:
raise ValidationError(
'You need to set a subevent.'
)
if subevent.event != self.context['event']:
raise ValidationError(
'The specified subevent does not belong to this event.'
)
elif subevent:
raise ValidationError(
'You cannot set a subevent for this event.'
)
return subevent
def validate(self, data, instance=None):
instance = instance or self.instance
if instance is None:
return data # needs to be done later
if data.get('item', instance.item):
if data.get('item', instance.item).has_variations:
if not data.get('variation', instance.variation):
raise ValidationError({'variation': ['You should specify a variation for this item.']})
else:
if data.get('variation', instance.variation).item != data.get('item', instance.item):
raise ValidationError(
{'variation': ['The specified variation does not belong to the specified item.']}
)
elif data.get('variation', instance.variation):
raise ValidationError(
{'variation': ['You cannot specify a variation for this item.']}
)
return data
def update(self, instance, validated_data):
ocm = self.context['ocm']
current_seat = {'seat_guid': instance.seat.seat_guid} if instance.seat else None
item = validated_data.get('item', instance.item)
variation = validated_data.get('variation', instance.variation)
subevent = validated_data.get('subevent', instance.subevent)
price = validated_data.get('price', instance.price)
seat = validated_data.get('seat', current_seat)
tax_rule = validated_data.get('tax_rule', instance.tax_rule)
change_item = None
if item != instance.item or variation != instance.variation:
change_item = (item, variation)
change_subevent = None
if self.context['event'].has_subevents and subevent != instance.subevent:
change_subevent = (subevent,)
try:
if change_item is not None and change_subevent is not None:
ocm.change_item_and_subevent(instance, *change_item, *change_subevent)
elif change_item is not None:
ocm.change_item(instance, *change_item)
elif change_subevent is not None:
ocm.change_subevent(instance, *change_subevent)
if seat != current_seat or change_subevent:
ocm.change_seat(instance, seat['seat_guid'] if seat else None)
if price != instance.price:
ocm.change_price(instance, price)
if tax_rule != instance.tax_rule:
ocm.change_tax_rule(instance, tax_rule)
if self.context.get('commit', True):
ocm.commit()
instance.refresh_from_db()
except OrderError as e:
raise ValidationError(str(e))
return instance
class PatchPositionSerializer(serializers.Serializer):
position = serializers.PrimaryKeyRelatedField(queryset=OrderPosition.all.none())
def validate_position(self, value):
self.fields['body'].instance = value # hack around DRFs validation order
return value
def validate(self, data):
OrderPositionChangeSerializer(context=self.context, partial=True).validate(data['body'], data['position'])
return data
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['position'].queryset = self.context['order'].positions.all()
self.fields['body'] = OrderPositionChangeSerializer(context=self.context, partial=True)
class SelectPositionSerializer(serializers.Serializer):
position = serializers.PrimaryKeyRelatedField(queryset=OrderPosition.all.none())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['position'].queryset = self.context['order'].positions.all()
class OrderFeeChangeSerializer(serializers.ModelSerializer):
class Meta:
model = OrderFee
fields = (
'value',
)
def update(self, instance, validated_data):
ocm = self.context['ocm']
value = validated_data.get('value', instance.value)
try:
if value != instance.value:
ocm.change_fee(instance, value)
if self.context.get('commit', True):
ocm.commit()
instance.refresh_from_db()
except OrderError as e:
raise ValidationError(str(e))
return instance
class PatchFeeSerializer(serializers.Serializer):
fee = serializers.PrimaryKeyRelatedField(queryset=OrderFee.all.none())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['fee'].queryset = self.context['order'].fees.all()
self.fields['body'] = OrderFeeChangeSerializer(context=self.context)
class SelectFeeSerializer(serializers.Serializer):
fee = serializers.PrimaryKeyRelatedField(queryset=OrderFee.all.none())
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if not self.context:
return
self.fields['fee'].queryset = self.context['order'].fees.all()
class OrderChangeOperationSerializer(serializers.Serializer):
send_email = serializers.BooleanField(default=False, required=False)
reissue_invoice = serializers.BooleanField(default=True, required=False)
recalculate_taxes = serializers.ChoiceField(default=None, allow_null=True, required=False, choices=[
('keep_net', 'keep_net'),
('keep_gross', 'keep_gross'),
])
def __init__(self, *args, **kwargs):
super().__init__(self, *args, **kwargs)
self.fields['patch_positions'] = PatchPositionSerializer(
many=True, required=False, context=self.context
)
self.fields['cancel_positions'] = SelectPositionSerializer(
many=True, required=False, context=self.context
)
self.fields['create_positions'] = OrderPositionCreateForExistingOrderSerializer(
many=True, required=False, context=self.context
)
self.fields['split_positions'] = SelectPositionSerializer(
many=True, required=False, context=self.context
)
self.fields['patch_fees'] = PatchFeeSerializer(
many=True, required=False, context=self.context
)
self.fields['cancel_fees'] = SelectFeeSerializer(
many=True, required=False, context=self.context
)
def validate(self, data):
seen_positions = set()
for d in data.get('patch_positions', []):
print(d, seen_positions)
if d['position'] in seen_positions:
raise ValidationError({'patch_positions': ['You have specified the same object twice.']})
seen_positions.add(d['position'])
seen_positions = set()
for d in data.get('cancel_positions', []):
if d['position'] in seen_positions:
raise ValidationError({'cancel_positions': ['You have specified the same object twice.']})
seen_positions.add(d['position'])
seen_positions = set()
for d in data.get('split_positions', []):
if d['position'] in seen_positions:
raise ValidationError({'split_positions': ['You have specified the same object twice.']})
seen_positions.add(d['position'])
seen_fees = set()
for d in data.get('patch_fees', []):
if d['fee'] in seen_fees:
raise ValidationError({'patch_fees': ['You have specified the same object twice.']})
seen_positions.add(d['fee'])
seen_fees = set()
for d in data.get('cancel_fees', []):
if d['fee'] in seen_fees:
raise ValidationError({'cancel_fees': ['You have specified the same object twice.']})
seen_positions.add(d['fee'])
return data

View File

@@ -36,7 +36,7 @@ from django.utils.translation import gettext as _
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from django_scopes import scopes_disabled
from PIL import Image
from rest_framework import mixins, serializers, status, viewsets
from rest_framework import serializers, status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import (
APIException, NotFound, PermissionDenied, ValidationError,
@@ -53,6 +53,12 @@ from pretix.api.serializers.order import (
PriceCalcSerializer, RevokedTicketSecretSerializer,
SimulatedOrderSerializer,
)
from pretix.api.serializers.orderchange import (
OrderChangeOperationSerializer, OrderFeeChangeSerializer,
OrderPositionChangeSerializer,
OrderPositionCreateForExistingOrderSerializer,
OrderPositionInfoPatchSerializer,
)
from pretix.base.i18n import language
from pretix.base.models import (
CachedCombinedTicket, CachedTicket, Checkin, Device, Event, Invoice,
@@ -782,6 +788,79 @@ class OrderViewSet(viewsets.ModelViewSet):
with transaction.atomic():
self.get_object().gracefully_delete(user=self.request.user if self.request.user.is_authenticated else None, auth=self.request.auth)
@action(detail=True, methods=['POST'])
def change(self, request, **kwargs):
order = self.get_object()
serializer = OrderChangeOperationSerializer(
context={'order': order, **self.get_serializer_context()},
data=request.data,
)
serializer.is_valid(raise_exception=True)
try:
ocm = OrderChangeManager(
order=order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=request.auth,
notify=serializer.validated_data.get('send_email', False),
reissue_invoice=serializer.validated_data.get('reissue_invoice', True),
)
canceled_positions = set()
for r in serializer.validated_data.get('cancel_positions', []):
ocm.cancel(r['position'])
canceled_positions.add(r['position'])
for r in serializer.validated_data.get('patch_positions', []):
if r['position'] in canceled_positions:
continue
pos_serializer = OrderPositionChangeSerializer(
context={'ocm': ocm, 'commit': False, 'event': request.event, **self.get_serializer_context()},
partial=True,
)
pos_serializer.update(r['position'], r['body'])
for r in serializer.validated_data.get('split_positions', []):
if r['position'] in canceled_positions:
continue
ocm.split(r['position'])
for r in serializer.validated_data.get('create_positions', []):
pos_serializer = OrderPositionCreateForExistingOrderSerializer(
context={'ocm': ocm, 'commit': False, 'event': request.event, **self.get_serializer_context()},
)
pos_serializer.create(r)
canceled_fees = set()
for r in serializer.validated_data.get('cancel_fees', []):
ocm.cancel_fee(r['fee'])
canceled_fees.add(r['fee'])
for r in serializer.validated_data.get('patch_fees', []):
if r['fee'] in canceled_fees:
continue
pos_serializer = OrderFeeChangeSerializer(
context={'ocm': ocm, 'commit': False, 'event': request.event, **self.get_serializer_context()},
)
pos_serializer.update(r['fee'], r['body'])
if serializer.validated_data.get('recalculate_taxes') == 'keep_net':
ocm.recalculate_taxes(keep='net')
elif serializer.validated_data.get('recalculate_taxes') == 'keep_gross':
ocm.recalculate_taxes(keep='gross')
ocm.commit()
except OrderError as e:
raise ValidationError(str(e))
order.refresh_from_db()
serializer = OrderSerializer(
instance=order,
context=self.get_serializer_context(),
)
return Response(serializer.data)
with scopes_disabled():
class OrderPositionFilter(FilterSet):
@@ -823,7 +902,7 @@ with scopes_disabled():
}
class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, viewsets.ReadOnlyModelViewSet):
class OrderPositionViewSet(viewsets.ModelViewSet):
serializer_class = OrderPositionSerializer
queryset = OrderPosition.all.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
@@ -1060,6 +1139,25 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
)
return resp
@action(detail=True, methods=['POST'])
def regenerate_secrets(self, request, **kwargs):
instance = self.get_object()
try:
ocm = OrderChangeManager(
instance.order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth,
notify=False,
reissue_invoice=False,
)
ocm.regenerate_secret(instance)
ocm.commit()
except OrderError as e:
raise ValidationError(str(e))
except Quota.QuotaExceededException as e:
raise ValidationError(str(e))
return self.retrieve(request, [], **kwargs)
def perform_destroy(self, instance):
try:
ocm = OrderChangeManager(
@@ -1075,18 +1173,33 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
except Quota.QuotaExceededException as e:
raise ValidationError(str(e))
def update(self, request, *args, **kwargs):
partial = kwargs.get('partial', False)
if not partial:
return Response(
{"detail": "Method \"PUT\" not allowed."},
status=status.HTTP_405_METHOD_NOT_ALLOWED,
)
return super().update(request, *args, **kwargs)
def perform_update(self, serializer):
def create(self, request, *args, **kwargs):
with transaction.atomic():
old_data = self.get_serializer_class()(instance=serializer.instance, context=self.get_serializer_context()).data
serializer = OrderPositionCreateForExistingOrderSerializer(
data=request.data,
context=self.get_serializer_context(),
)
serializer.is_valid(raise_exception=True)
order = serializer.validated_data['order']
ocm = OrderChangeManager(
order=order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=request.auth,
notify=False,
reissue_invoice=False,
)
serializer.context['ocm'] = ocm
serializer.save()
# Fields that can be easily patched after the position was added
old_data = OrderPositionInfoPatchSerializer(instance=serializer.instance, context=self.get_serializer_context()).data
serializer = OrderPositionInfoPatchSerializer(
instance=serializer.instance,
context=self.get_serializer_context(),
partial=True,
data=request.data
)
serializer.is_valid(raise_exception=True)
serializer.save()
new_data = serializer.data
@@ -1109,9 +1222,77 @@ class OrderPositionViewSet(mixins.DestroyModelMixin, mixins.UpdateModelMixin, vi
]
}
)
tickets.invalidate_cache.apply_async(
kwargs={'event': serializer.instance.order.event.pk, 'order': serializer.instance.order.pk})
order_modified.send(sender=serializer.instance.order.event, order=serializer.instance.order)
return Response(
OrderPositionSerializer(serializer.instance, context=self.get_serializer_context()).data,
status=status.HTTP_201_CREATED,
)
tickets.invalidate_cache.apply_async(kwargs={'event': serializer.instance.order.event.pk, 'order': serializer.instance.order.pk})
order_modified.send(sender=serializer.instance.order.event, order=serializer.instance.order)
def update(self, request, *args, **kwargs):
partial = kwargs.get('partial', False)
if not partial:
return Response(
{"detail": "Method \"PUT\" not allowed."},
status=status.HTTP_405_METHOD_NOT_ALLOWED,
)
with transaction.atomic():
instance = self.get_object()
ocm = OrderChangeManager(
order=instance.order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=request.auth,
notify=False,
reissue_invoice=False,
)
# Field that need to go through OrderChangeManager
serializer = OrderPositionChangeSerializer(
instance=instance,
context={'ocm': ocm, **self.get_serializer_context()},
partial=True,
data=request.data
)
serializer.is_valid(raise_exception=True)
serializer.save()
# Fields that can be easily patched
old_data = OrderPositionInfoPatchSerializer(instance=instance, context=self.get_serializer_context()).data
serializer = OrderPositionInfoPatchSerializer(
instance=instance,
context=self.get_serializer_context(),
partial=True,
data=request.data
)
serializer.is_valid(raise_exception=True)
serializer.save()
new_data = serializer.data
if old_data != new_data:
log_data = self.request.data
if 'answers' in log_data:
for a in new_data['answers']:
log_data[f'question_{a["question"]}'] = a["answer"]
log_data.pop('answers', None)
serializer.instance.order.log_action(
'pretix.event.order.modified',
user=self.request.user,
auth=self.request.auth,
data={
'data': [
dict(
position=serializer.instance.pk,
**log_data
)
]
}
)
tickets.invalidate_cache.apply_async(kwargs={'event': serializer.instance.order.event.pk, 'order': serializer.instance.order.pk})
order_modified.send(sender=serializer.instance.order.event, order=serializer.instance.order)
return Response(self.get_serializer_class()(instance=serializer.instance, context=self.get_serializer_context()).data)
class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):