diff --git a/src/pretix/base/models.py b/src/pretix/base/models.py index eeb6491076..7ba232143c 100644 --- a/src/pretix/base/models.py +++ b/src/pretix/base/models.py @@ -1568,22 +1568,27 @@ class Order(Versionable): order.save() return order - def _can_be_paid(self): + def _can_be_paid(self, keep_locked=False): error_messages = { - 'unavailable': _('Some of the ordered products were no longer available.'), - 'busy': _('We were not able to process the request completely as the ' - 'server was too busy. Please try again.'), 'late': _("The payment is too late to be accepted."), } if self.event.settings.get('payment_term_last') \ and now() > self.event.settings.get('payment_term_last'): - return error_messages['late'] + return error_messages['late'], None if now() < self.expires: - return True + return True, None if not self.event.settings.get('payment_term_accept_late'): - return error_messages['late'] + return error_messages['late'], None + return self._is_still_available(keep_locked) + + def _is_still_available(self, keep_locked=False): + error_messages = { + 'unavailable': _('Some of the ordered products were no longer available.'), + 'busy': _('We were not able to process the request completely as the ' + 'server was too busy. Please try again.'), + } positions = list(self.positions.all().select_related( 'item', 'variation' ).prefetch_related( @@ -1591,6 +1596,7 @@ class Order(Versionable): 'item__questions', 'answers' )) quotas_locked = set() + release = True try: for i, op in enumerate(positions): @@ -1613,16 +1619,19 @@ class Order(Versionable): # This quota is sold out/currently unavailable, so do not sell this at all raise Quota.QuotaExceededException(error_messages['unavailable']) except Quota.QuotaExceededException as e: - return str(e) + return str(e), None except Quota.LockTimeoutException: # Is raised when there are too many threads asking for quota locks and we were # unaible to get one - return error_messages['busy'] + return error_messages['busy'], None + else: + release = False finally: # Release the locks. This is important ;) - for quota in quotas_locked: - quota.release() - return True + if release or not keep_locked: + for quota in quotas_locked: + quota.release() + return True, quotas_locked def mark_paid(self, provider=None, info=None, date=None, manual=None, force=False): """ @@ -1641,7 +1650,7 @@ class Order(Versionable): :type force: boolean :raises Quota.QuotaExceededException: if the quota is exceeded and ``force`` is ``False`` """ - can_be_paid = self._can_be_paid() + can_be_paid, quotas_locked = self._can_be_paid(keep_locked=True) if not force and can_be_paid is not True: raise Quota.QuotaExceededException(can_be_paid) order = self.clone() @@ -1653,6 +1662,10 @@ class Order(Versionable): order.status = Order.STATUS_PAID order.save() + if quotas_locked: + for quota in quotas_locked: + quota.release() + from pretix.base.mail import mail mail( order.user, _('Payment received for your order: %(code)s') % {'code': order.code}, diff --git a/src/pretix/control/templates/pretixcontrol/order/index.html b/src/pretix/control/templates/pretixcontrol/order/index.html index e3e4417b23..9a7092c5ad 100644 --- a/src/pretix/control/templates/pretixcontrol/order/index.html +++ b/src/pretix/control/templates/pretixcontrol/order/index.html @@ -20,6 +20,9 @@
[0-9A-Z]+)/transition$', orders.OrderTransition.as_view(),
name='event.order.transition'),
+ url(r'^orders/(?P[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
+ name='event.order.extend'),
url(r'^orders/(?P[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'),
url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'),
])),
diff --git a/src/pretix/control/views/orders.py b/src/pretix/control/views/orders.py
index 7dd0a550da..97cb355deb 100644
--- a/src/pretix/control/views/orders.py
+++ b/src/pretix/control/views/orders.py
@@ -2,11 +2,13 @@ from itertools import groupby
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db.models import Q
+from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse
from django.shortcuts import redirect, render
from django.utils.functional import cached_property
from django.views.generic import ListView, DetailView
+from pretix.base.forms import VersionedModelForm
from pretix.base.models import Order, Quota
from pretix.base.signals import register_payment_providers
@@ -26,7 +28,7 @@ class OrderList(EventPermissionRequiredMixin, ListView):
).select_related("user")
-class OrderView(DetailView):
+class OrderView(EventPermissionRequiredMixin, DetailView):
context_object_name = 'order'
model = Order
@@ -49,7 +51,13 @@ class OrderView(DetailView):
return provider
-class OrderDetail(EventPermissionRequiredMixin, OrderView):
+class ExtendForm(VersionedModelForm):
+ class Meta:
+ model = Order
+ fields = ['expires']
+
+
+class OrderDetail(OrderView):
template_name = 'pretixcontrol/order/index.html'
permission = 'can_view_orders'
@@ -99,8 +107,8 @@ class OrderDetail(EventPermissionRequiredMixin, OrderView):
}
-class OrderTransition(EventPermissionRequiredMixin, OrderView):
- permission = 'can_view_orders'
+class OrderTransition(OrderView):
+ permission = 'can_change_orders'
def post(self, *args, **kwargs):
to = self.request.POST.get('status', '')
@@ -148,3 +156,51 @@ class OrderTransition(EventPermissionRequiredMixin, OrderView):
})
else:
return HttpResponse(status=405)
+
+
+class OrderExtend(OrderView):
+ permission = 'can_change_orders'
+
+ def post(self, *args, **kwargs):
+ if self.order.status != Order.STATUS_PENDING:
+ messages.error(self.request, _('This action is only allowed for pending orders.'))
+ return self._redirect_back()
+ oldvalue = self.order.expires
+
+ if self.form.is_valid():
+ if oldvalue > now():
+ self.form.save()
+ else:
+ is_available, _quotas_locked = self.order._is_still_available(keep_locked=False)
+ if is_available is True:
+ self.form.save()
+ messages.success(self.request, _('The payment term has been changed.'))
+ else:
+ messages.error(self.request, is_available)
+ return self._redirect_back()
+ else:
+ return self.get(*args, **kwargs)
+
+ def _redirect_back(self):
+ return redirect(reverse(
+ 'control:event.order',
+ kwargs={
+ 'event': self.request.event.slug,
+ 'organizer': self.request.event.organizer.slug,
+ 'code': self.order.code,
+ }
+ ))
+
+ def get(self, *args, **kwargs):
+ if self.order.status != Order.STATUS_PENDING:
+ messages.error(self.request, _('This action is only allowed for pending orders.'))
+ return self._redirect_back()
+ return render(self.request, 'pretixcontrol/order/extend.html', {
+ 'order': self.order,
+ 'form': self.form,
+ })
+
+ @cached_property
+ def form(self):
+ return ExtendForm(instance=self.order,
+ data=self.request.POST if self.request.method == "POST" else None)