diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 7d0819ccde..46d0874640 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -94,39 +94,51 @@ def mark_order_paid(order: Order, provider: str=None, info: str=None, date: date @transaction.atomic -def mark_order_refunded(order: Order, user: User=None): +def mark_order_refunded(order, user=None): """ Mark this order as refunded. This sets the payment status and returns the order object. :param order: The order to change :param user: The user that performed the change """ - order.status = Order.STATUS_REFUNDED - order.save() - order.log_action('pretix.event.order.refunded', user=user) + if isinstance(order, int): + order = Order.objects.get(pk=order) + if isinstance(user, int): + user = User.objects.get(pk=user) + with order.event.lock(): + order.status = Order.STATUS_REFUNDED + order.save() + order.log_action('pretix.event.order.refunded', user=user) - i = order.invoices.filter(is_cancellation=False).last() - if i: - generate_cancellation(i) + i = order.invoices.filter(is_cancellation=False).last() + if i: + generate_cancellation(i) - return order + return order @transaction.atomic -def cancel_order(order: Order, user: User=None): +def cancel_order(order, user=None): """ Mark this order as canceled :param order: The order to change :param user: The user that performed the change """ - order.status = Order.STATUS_CANCELLED - order.save() - order.log_action('pretix.event.order.cancelled', user=user) + if isinstance(order, int): + order = Order.objects.get(pk=order) + if isinstance(user, int): + user = User.objects.get(pk=user) + with order.event.lock(): + if order.status not in (Order.STATUS_PENDING, Order.STATUS_EXPIRED): + raise OrderError(_('You cannot cancel this order')) + order.status = Order.STATUS_CANCELLED + order.save() + order.log_action('pretix.event.order.cancelled', user=user) - i = order.invoices.filter(is_cancellation=False).last() - if i: - generate_cancellation(i) + i = order.invoices.filter(is_cancellation=False).last() + if i: + generate_cancellation(i) - return order + return order class OrderError(Exception): @@ -310,4 +322,12 @@ if settings.HAS_CELERY: except EventLock.LockTimeoutException: self.retry(exc=OrderError(error_messages['busy'])) + @app.task(bind=True, max_retries=5, default_retry_delay=2) + def cancel_order_task(self, order: int, user: int=None): + try: + return cancel_order(order, user) + except EventLock.LockTimeoutException: + self.retry(exc=OrderError(error_messages['busy'])) + perform_order.task = perform_order_task + cancel_order.task = cancel_order_task diff --git a/src/pretix/presale/templates/pretixpresale/event/order_cancel.html b/src/pretix/presale/templates/pretixpresale/event/order_cancel.html index fe4214d632..940c6835fe 100644 --- a/src/pretix/presale/templates/pretixpresale/event/order_cancel.html +++ b/src/pretix/presale/templates/pretixpresale/event/order_cancel.html @@ -12,7 +12,7 @@ Do you really want to cancel this order? You cannot revert this action. {% endblocktrans %}

-
+ {% csrf_token %}
diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index 0ee8e7cb09..5f31d9bfe1 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -21,6 +21,9 @@ event_patterns = [ url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/cancel$', pretix.presale.views.order.OrderCancel.as_view(), name='event.order.cancel'), + url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/cancel/do$', + pretix.presale.views.order.OrderCancelDo.as_view(), + name='event.order.cancel.do'), url(r'^order/(?P[^/]+)/(?P[A-Za-z0-9]+)/modify$', pretix.presale.views.order.OrderModify.as_view(), name='event.order.modify'), diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 8517068027..b430c8e158 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -6,7 +6,7 @@ from django.http import Http404 from django.shortcuts import redirect from django.utils.functional import cached_property from django.utils.timezone import now -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, gettext from django.views.generic import TemplateView, View from pretix.base.models import ( @@ -14,7 +14,7 @@ from pretix.base.models import ( ) from pretix.base.models.orders import InvoiceAddress from pretix.base.services.invoices import invoice_pdf -from pretix.base.services.orders import cancel_order +from pretix.base.services.orders import cancel_order, OrderError from pretix.base.services.tickets import generate from pretix.base.signals import ( register_payment_providers, register_ticket_outputs, @@ -22,6 +22,7 @@ from pretix.base.signals import ( from pretix.multidomain.urlreverse import eventreverse from pretix.presale.forms.checkout import InvoiceAddressForm from pretix.presale.views import CartMixin, EventViewMixin +from pretix.presale.views.async import AsyncAction from pretix.presale.views.questions import QuestionsViewMixin @@ -270,10 +271,6 @@ class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView): return redirect(self.get_order_url()) return super().dispatch(request, *args, **kwargs) - def post(self, request, *args, **kwargs): - cancel_order(self.order) - return redirect(self.get_order_url()) - def get(self, request, *args, **kwargs): return super().get(request, *args, **kwargs) @@ -283,6 +280,36 @@ class OrderCancel(EventViewMixin, OrderDetailMixin, TemplateView): return ctx +class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View): + task = cancel_order + + def get_success_url(self, value): + return self.get_order_url() + + def get_error_url(self): + return self.get_order_url() + + def post(self, request, *args, **kwargs): + if not self.order: + raise Http404(_('Unknown order code or not authorized to access this order.')) + return self.do(self.order) + + def get_context_data(self, **kwargs): + ctx = super().get_context_data(**kwargs) + ctx['order'] = self.order + return ctx + + def get_success_message(self, value): + return _('The order has been cancelled.') + + def get_error_message(self, exception): + if isinstance(exception, dict) and exception['exc_type'] == 'OrderError': + return gettext(exception['exc_message']) + elif isinstance(exception, OrderError): + return str(exception) + return super().get_error_message(exception) + + class OrderDownload(EventViewMixin, OrderDetailMixin, View): @cached_property def output(self):