Refactored some code out of pretix.presale

This commit is contained in:
Raphael Michel
2015-06-24 12:45:42 +02:00
parent 76b0189972
commit fa642ddffe
2 changed files with 126 additions and 116 deletions

View File

@@ -1,8 +1,10 @@
from django.conf import settings
from django.core.urlresolvers import reverse
from datetime import timedelta, datetime
from django.db import transaction
from django.utils.timezone import now
from pretix.base.models import Order, Quota
from pretix.base.models import Order, Quota, OrderPosition
from django.utils.translation import ugettext_lazy as _
from pretix.base.services.mail import mail
from pretix.helpers.urls import build_absolute_uri
def mark_order_paid(order, provider=None, info=None, date=None, manual=None, force=False):
@@ -47,13 +49,122 @@ def mark_order_paid(order, provider=None, info=None, date=None, manual=None, for
'user': order.user,
'order': order,
'event': order.event,
'url': settings.SITE_URL + reverse('presale:event.order', kwargs={
'url': build_absolute_uri('presale:event.order', kwargs={
'event': order.event.slug,
'organizer': order.event.organizer.slug,
'order': order.code
'order': order.code,
}),
'downloads': order.event.settings.get('ticket_download', as_type=bool)
},
order.event
)
return order
class OrderError(Exception):
pass
def perform_order(event, user, payment_provider, positions):
error_messages = {
'unavailable': _('Some of the products you selected were no longer available. '
'Please see below for details.'),
'in_part': _('Some of the products you selected were no longer available in '
'the quantity you selected. Please see below for details.'),
'price_changed': _('The price of some of the items in your cart has changed in the '
'meantime. Please see below for details.'),
'busy': _('We were not able to process your request completely as the '
'server was too busy. Please try again.'),
'max_items': _("You cannot select more than %s items per order"),
}
dt = now()
quotas_locked = set()
err = None
try:
for i, cp in enumerate(positions):
quotas = list(cp.item.quotas.all()) if cp.variation is None else list(cp.variation.quotas.all())
if cp.expires < dt:
price = cp.item.check_restrictions() if cp.variation is None else cp.variation.check_restrictions()
if price is False or len(quotas) == 0:
err = err or error_messages['unavailable']
continue
if price != cp.price:
cp = cp.clone()
positions[i] = cp
cp.price = price
cp.save()
err = err or error_messages['price_changed']
continue
quota_ok = True
for quota in quotas:
# Lock the quota, so no other thread is allowed to perform sales covered by this
# quota while we're doing so.
if quota not in quotas_locked:
quota.lock()
quotas_locked.add(quota)
avail = quota.availability()
if avail[0] != Quota.AVAILABILITY_OK:
# This quota is sold out/currently unavailable, so do not sell this at all
err = err or error_messages['unavailable']
quota_ok = False
break
if quota_ok:
if not event.presale_end or now() < event.presale_end:
cp = cp.clone()
positions[i] = cp
cp.expires = now() + timedelta(
minutes=event.settings.get('reservation_time', as_type=int))
cp.save()
else:
cp.delete() # Sorry!
if err:
raise OrderError(err)
else: # Everything went well
order = place_order(event, user, positions, dt, payment_provider)
mail(
user, _('Your order: %(code)s') % {'code': order.code},
'pretixpresale/email/order_placed.txt',
{
'user': user, 'order': order,
'event': event,
'url': build_absolute_uri('presale:event.order', kwargs={
'event': event.slug,
'organizer': event.organizer.slug,
'order': order.code,
}),
'payment': payment_provider.order_pending_mail_render(order)
},
event
)
return order
except Quota.LockTimeoutException:
# Is raised when there are too many threads asking for quota locks and we were
# unaible to get one
raise OrderError(error_messages['busy'])
finally:
# Release the locks. This is important ;)
for quota in quotas_locked:
quota.release()
@transaction.atomic()
def place_order(event, user, positions, dt, payment_provider):
total = sum([c.price for c in positions])
payment_fee = payment_provider.calculate_fee(total)
total += payment_fee
expires = [dt + timedelta(days=event.settings.get('payment_term_days', as_type=int))]
if event.settings.get('payment_term_last'):
expires.append(event.settings.get('payment_term_last', as_type=datetime))
order = Order.objects.create(
status=Order.STATUS_PENDING,
event=event,
user=user,
datetime=dt,
expires=min(expires),
total=total,
payment_fee=payment_fee,
payment_provider=payment_provider.identifier,
)
OrderPosition.transform_cart_positions(positions, order)
return order

View File

@@ -1,19 +1,14 @@
from datetime import timedelta, datetime
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.db import transaction
from django.db.models import Q, Sum
from django.http import HttpRequest
from django.shortcuts import redirect
from django.utils.functional import cached_property
from django.utils.timezone import now
from django.views.generic import TemplateView
from django.utils.translation import ugettext_lazy as _
from pretix.base.services.mail import mail
from pretix.base.models import CartPosition, QuestionAnswer, Quota, Order, OrderPosition
from pretix.base.models import CartPosition, QuestionAnswer, OrderPosition
from pretix.base.services.orders import perform_order, OrderError
from pretix.base.signals import register_payment_providers
from pretix.helpers.urls import build_absolute_uri
from pretix.presale.forms.checkout import QuestionsForm
from pretix.presale.views import EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin
@@ -199,18 +194,6 @@ class PaymentDetails(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin,
class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, CheckoutView):
template_name = "pretixpresale/event/checkout_confirm.html"
error_messages = {
'unavailable': _('Some of the products you selected were no longer available. '
'Please see below for details.'),
'in_part': _('Some of the products you selected were no longer available in '
'the quantity you selected. Please see below for details.'),
'price_changed': _('The price of some of the items in your cart has changed in the '
'meantime. Please see below for details.'),
'busy': _('We were not able to process your request completely as the '
'server was too busy. Please try again.'),
'max_items': _("You cannot select more than %s items per order"),
}
def __init__(self, *args, **kwargs):
self.msg_some_unavailable = False
super().__init__(*args, **kwargs)
@@ -269,99 +252,15 @@ class OrderConfirm(EventViewMixin, CartDisplayMixin, EventLoginRequiredMixin, Ch
return self.check_process(request) or self.perform_order(request)
def perform_order(self, request: HttpRequest):
dt = now()
quotas_locked = set()
try:
cartpos = self.positions
for i, cp in enumerate(cartpos):
quotas = list(cp.item.quotas.all()) if cp.variation is None else list(cp.variation.quotas.all())
if cp.expires < dt:
price = cp.item.check_restrictions() if cp.variation is None else cp.variation.check_restrictions()
if price is False or len(quotas) == 0:
self.error_message(self.error_messages['unavailable'])
continue
if price != cp.price:
cp = cp.clone()
cartpos[i] = cp
cp.price = price
cp.save()
self.error_message(self.error_messages['price_changed'])
continue
quota_ok = True
for quota in quotas:
# Lock the quota, so no other thread is allowed to perform sales covered by this
# quota while we're doing so.
if quota not in quotas_locked:
quota.lock()
quotas_locked.add(quota)
avail = quota.availability()
if avail[0] != Quota.AVAILABILITY_OK:
# This quota is sold out/currently unavailable, so do not sell this at all
self.error_message(self.error_messages['unavailable'])
quota_ok = False
break
if quota_ok:
if not self.request.event.presale_end or now() < self.request.event.presale_end:
cp = cp.clone()
cartpos[i] = cp
cp.expires = now() + timedelta(
minutes=self.request.event.settings.get('reservation_time', as_type=int))
cp.save()
else:
cp.delete() # Sorry!
if not self.msg_some_unavailable: # Everything went well
order = self._place_order(cartpos, dt)
messages.success(request, _('Your order has been placed.'))
mail(
request.user, _('Your order: %(code)s') % {'code': order.code},
'pretixpresale/email/order_placed.txt',
{
'user': request.user, 'order': order,
'event': request.event,
'url': build_absolute_uri('presale:event.order', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'order': order.code,
}),
'payment': self.payment_provider.order_pending_mail_render(order)
},
request.event
)
resp = self.payment_provider.checkout_perform(request, order)
return redirect(resp or self.get_order_url(order))
except Quota.LockTimeoutException:
# Is raised when there are too many threads asking for quota locks and we were
# unaible to get one
self.error_message(self.error_messages['busy'], important=True)
finally:
# Release the locks. This is important ;)
for quota in quotas_locked:
quota.release()
return redirect(self.get_confirm_url())
@transaction.atomic()
def _place_order(self, cartpos, dt):
total = sum([c.price for c in cartpos])
payment_fee = self.payment_provider.calculate_fee(total)
total += payment_fee
expires = [dt + timedelta(days=self.request.event.settings.get('payment_term_days', as_type=int))]
if self.request.event.settings.get('payment_term_last'):
expires.append(self.request.event.settings.get('payment_term_last', as_type=datetime))
order = Order.objects.create(
status=Order.STATUS_PENDING,
event=self.request.event,
user=self.request.user,
datetime=dt,
expires=min(expires),
total=total,
payment_fee=payment_fee,
payment_provider=self.payment_provider.identifier,
)
OrderPosition.transform_cart_positions(cartpos, order)
return order
order = perform_order(self.request.event, self.request.user, self.payment_provider, self.positions)
except OrderError as e:
messages.error(request, str(e))
return redirect(self.get_confirm_url())
else:
messages.success(request, _('Your order has been placed.'))
resp = self.payment_provider.checkout_perform(request, order)
return redirect(resp or self.get_order_url(order))
def get_previous_url(self):
if self.payment_provider.identifier != "free":