API: Allow to delete order positions

This commit is contained in:
Raphael Michel
2018-08-13 18:09:10 +02:00
parent 723fedc066
commit 779756f1ab
6 changed files with 100 additions and 18 deletions

View File

@@ -9,7 +9,7 @@ from django.http import FileResponse
from django.shortcuts import get_object_or_404
from django.utils.timezone import make_aware, now
from django_filters.rest_framework import DjangoFilterBackend, FilterSet
from rest_framework import serializers, status, viewsets
from rest_framework import mixins, serializers, status, viewsets
from rest_framework.decorators import detail_route
from rest_framework.exceptions import (
APIException, NotFound, PermissionDenied, ValidationError,
@@ -35,8 +35,8 @@ from pretix.base.services.invoices import (
)
from pretix.base.services.mail import SendMailException
from pretix.base.services.orders import (
OrderError, cancel_order, extend_order, mark_order_expired,
mark_order_refunded,
OrderChangeManager, OrderError, cancel_order, extend_order,
mark_order_expired, mark_order_refunded,
)
from pretix.base.services.tickets import (
get_cachedticket_for_order, get_cachedticket_for_position,
@@ -340,7 +340,7 @@ class OrderPositionFilter(FilterSet):
}
class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
class OrderPositionViewSet(mixins.DestroyModelMixin, viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPositionSerializer
queryset = OrderPosition.objects.none()
filter_backends = (DjangoFilterBackend, OrderingFilter)
@@ -348,6 +348,7 @@ class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
ordering_fields = ('order__code', 'order__datetime', 'positionid', 'attendee_name', 'order__status',)
filterset_class = OrderPositionFilter
permission = 'can_view_orders'
write_permission = 'can_change_orders'
def get_queryset(self):
return OrderPosition.objects.filter(order__event=self.request.event).prefetch_related(
@@ -388,6 +389,21 @@ class OrderPositionViewSet(viewsets.ReadOnlyModelViewSet):
)
return resp
def perform_destroy(self, instance):
try:
ocm = OrderChangeManager(
instance.order,
user=self.request.user if self.request.user.is_authenticated else None,
auth=self.request.auth,
notify=False
)
ocm.cancel(instance)
ocm.commit()
except OrderError as e:
raise ValidationError(str(e))
except Quota.QuotaExceededException as e:
raise ValidationError(str(e))
class PaymentViewSet(viewsets.ReadOnlyModelViewSet):
serializer_class = OrderPaymentSerializer

View File

@@ -471,7 +471,8 @@ class Order(LoggedModel):
def send_mail(self, subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent',
user: User=None, headers: dict=None, sender: str=None, invoices: list=None):
user: User=None, headers: dict=None, sender: str=None, invoices: list=None,
auth=None):
"""
Sends an email to the user that placed this order. Basically, this method does two things:
@@ -508,6 +509,7 @@ class Order(LoggedModel):
self.log_action(
log_entry_type,
user=user,
auth=auth,
data={
'subject': subject,
'message': email_content,

View File

@@ -607,9 +607,10 @@ class OrderChangeManager:
SplitOperation = namedtuple('SplitOperation', ('position',))
RegenerateSecretOperation = namedtuple('RegenerateSecretOperation', ('position',))
def __init__(self, order: Order, user, notify=True):
def __init__(self, order: Order, user=None, auth=None, notify=True):
self.order = order
self.user = user
self.auth = auth
self.split_order = None
self._committed = False
self._totaldiff = 0
@@ -779,7 +780,7 @@ class OrderChangeManager:
fee=None
)
try:
p.confirm(send_mail=False, count_waitinglist=False, user=self.user)
p.confirm(send_mail=False, count_waitinglist=False, user=self.user, auth=self.auth)
except Quota.QuotaExceededException:
raise OrderError(self.error_messages['paid_to_free_exceeded'])
@@ -791,7 +792,7 @@ class OrderChangeManager:
fee=None
)
try:
p.confirm(send_mail=False, count_waitinglist=False, user=self.user)
p.confirm(send_mail=False, count_waitinglist=False, user=self.user, auth=self.auth)
except Quota.QuotaExceededException:
raise OrderError(self.error_messages['paid_to_free_exceeded'])
@@ -801,7 +802,7 @@ class OrderChangeManager:
for op in self._operations:
if isinstance(op, self.ItemOperation):
self.order.log_action('pretix.event.order.changed.item', user=self.user, data={
self.order.log_action('pretix.event.order.changed.item', user=self.user, auth=self.auth, data={
'position': op.position.pk,
'positionid': op.position.positionid,
'old_item': op.position.item.pk,
@@ -820,7 +821,7 @@ class OrderChangeManager:
op.position.tax_rule = op.item.tax_rule
op.position.save()
elif isinstance(op, self.SubeventOperation):
self.order.log_action('pretix.event.order.changed.subevent', user=self.user, data={
self.order.log_action('pretix.event.order.changed.subevent', user=self.user, auth=self.auth, data={
'position': op.position.pk,
'positionid': op.position.positionid,
'old_subevent': op.position.subevent.pk,
@@ -835,7 +836,7 @@ class OrderChangeManager:
op.position.tax_rule = op.position.item.tax_rule
op.position.save()
elif isinstance(op, self.PriceOperation):
self.order.log_action('pretix.event.order.changed.price', user=self.user, data={
self.order.log_action('pretix.event.order.changed.price', user=self.user, auth=self.auth, data={
'position': op.position.pk,
'positionid': op.position.positionid,
'old_price': op.position.price,
@@ -849,7 +850,7 @@ class OrderChangeManager:
op.position.save()
elif isinstance(op, self.CancelOperation):
for opa in op.position.addons.all():
self.order.log_action('pretix.event.order.changed.cancel', user=self.user, data={
self.order.log_action('pretix.event.order.changed.cancel', user=self.user, auth=self.auth, data={
'position': opa.pk,
'positionid': opa.positionid,
'old_item': opa.item.pk,
@@ -857,7 +858,7 @@ class OrderChangeManager:
'addon_to': opa.addon_to_id,
'old_price': opa.price,
})
self.order.log_action('pretix.event.order.changed.cancel', user=self.user, data={
self.order.log_action('pretix.event.order.changed.cancel', user=self.user, auth=self.auth, data={
'position': op.position.pk,
'positionid': op.position.positionid,
'old_item': op.position.item.pk,
@@ -874,7 +875,7 @@ class OrderChangeManager:
positionid=nextposid, subevent=op.subevent
)
nextposid += 1
self.order.log_action('pretix.event.order.changed.add', user=self.user, data={
self.order.log_action('pretix.event.order.changed.add', user=self.user, auth=self.auth, data={
'position': pos.pk,
'item': op.item.pk,
'variation': op.variation.pk if op.variation else None,
@@ -890,7 +891,7 @@ class OrderChangeManager:
op.position.save()
CachedTicket.objects.filter(order_position__order=self.order).delete()
CachedCombinedTicket.objects.filter(order=self.order).delete()
self.order.log_action('pretix.event.order.changed.secret', user=self.user, data={
self.order.log_action('pretix.event.order.changed.secret', user=self.user, auth=self.auth, data={
'position': op.position.pk,
'positionid': op.position.positionid,
})
@@ -905,12 +906,12 @@ class OrderChangeManager:
split_order.datetime = now()
split_order.secret = generate_secret()
split_order.save()
split_order.log_action('pretix.event.order.changed.split_from', user=self.user, data={
split_order.log_action('pretix.event.order.changed.split_from', user=self.user, auth=self.auth, data={
'original_order': self.order.code
})
for op in split_positions:
self.order.log_action('pretix.event.order.changed.split', user=self.user, data={
self.order.log_action('pretix.event.order.changed.split', user=self.user, auth=self.auth, data={
'position': op.pk,
'positionid': op.positionid,
'old_item': op.item.pk,
@@ -1080,7 +1081,7 @@ class OrderChangeManager:
try:
order.send_mail(
email_subject, email_template, email_context,
'pretix.event.order.email.order_changed', self.user
'pretix.event.order.email.order_changed', self.user, auth=self.auth
)
except SendMailException:
logger.exception('Order changed email could not be sent')

View File

@@ -672,6 +672,37 @@ def test_orderposition_detail(token_client, organizer, event, order, item, quest
assert len(resp.data['downloads']) == 1
@pytest.mark.django_db
def test_orderposition_delete(token_client, organizer, event, order, item, question):
op = order.positions.first()
resp = token_client.delete('/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format(
organizer.slug, event.slug, op.pk
))
assert resp.status_code == 400
assert resp.data == ['This operation would leave the order empty. Please cancel the order itself instead.']
op2 = OrderPosition.objects.create(
order=order,
item=item,
variation=None,
price=Decimal("23"),
attendee_name="Peter",
secret="foobar",
pseudonymization_id="BAZ",
)
order.refresh_from_db()
assert order.total == Decimal('46')
assert order.positions.count() == 2
resp = token_client.delete('/api/v1/organizers/{}/events/{}/orderpositions/{}/'.format(
organizer.slug, event.slug, op2.pk
))
assert resp.status_code == 204
assert order.positions.count() == 1
order.refresh_from_db()
assert order.total == Decimal('23')
@pytest.fixture
def invoice(order):
testtime = datetime.datetime(2017, 12, 10, 10, 0, 0, tzinfo=UTC)

View File

@@ -23,6 +23,7 @@ event_urls = [
event_permission_sub_urls = [
('get', 'can_view_orders', 'orders/', 200),
('get', 'can_view_orders', 'orderpositions/', 200),
('delete', 'can_change_orders', 'orderpositions/1/', 404),
('get', 'can_view_vouchers', 'vouchers/', 200),
('get', 'can_view_orders', 'invoices/', 200),
('get', 'can_view_orders', 'invoices/1/', 404),