From 42ecd9c5f90150c6c405929e8864617c14272061 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 14 Apr 2015 18:00:00 +0200 Subject: [PATCH] Allow to change payment term (closes #10) --- src/pretix/base/models.py | 39 +++++++---- .../templates/pretixcontrol/order/index.html | 3 + src/pretix/control/urls.py | 2 + src/pretix/control/views/orders.py | 64 +++++++++++++++++-- 4 files changed, 91 insertions(+), 17 deletions(-) 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 @@
{% if order.status == 'n' %} + + {% trans "Extend payment term" %} + {% trans "Cancel order" %} diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 35bc0eff80..43311029ed 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -48,6 +48,8 @@ urlpatterns = [ url(r'^quotas/add$', item.QuotaCreate.as_view(), name='event.items.quotas.add'), url(r'^orders/(?P[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)