Enabled asynchronous cart/order actions

This commit is contained in:
Raphael Michel
2015-10-04 19:54:17 +02:00
parent 4c6b292968
commit c4638a3402
28 changed files with 572 additions and 196 deletions

View File

@@ -0,0 +1,122 @@
import logging
from django.conf import settings
from django.contrib import messages
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils.translation import ugettext as _
logger = logging.getLogger('pretix.presale.async')
class AsyncAction:
task = None
success_url = None
error_url = None
def do(self, *args):
if settings.HAS_CELERY:
from pretix.celery import app
if hasattr(self.task, 'task') and isinstance(self.task.task, app.Task):
return self._do_celery(args)
else:
raise TypeError('Method has no task attached')
else:
return self._do_sync(args)
def get_success_url(self, value):
return self.success_url
def get_error_url(self):
return self.error_url
def get_check_url(self, task_id, ajax):
return self.request.path + '?async_id=%s' % task_id + ('&ajax=1' if ajax else '')
def get(self, request, *args, **kwargs):
if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request)
return self.http_method_not_allowed(request)
def get_result(self, request):
from celery.result import AsyncResult
res = AsyncResult(request.GET.get('async_id'))
if 'ajax' in self.request.GET:
data = {
'async_id': res.id,
'ready': res.ready()
}
if res.ready():
if res.successful():
smes = self.get_success_message(res.info)
if smes:
messages.success(self.request, smes)
# TODO: Do not store message if the ajax client stats that it will not redirect
# but handle the mssage itself
data.update({
'redirect': self.get_success_url(res.info),
'message': self.get_success_message(res.info)
})
else:
messages.error(self.request, self.get_error_message(res.info))
# TODO: Do not store message if the ajax client stats that it will not redirect
# but handle the mssage itself
data.update({
'redirect': self.get_error_url(),
'message': self.get_error_message(res.info)
})
return JsonResponse(data)
else:
if res.ready():
if res.successful():
return self.success(res.info)
else:
return self.error(res.info)
return render(request, 'pretixpresale/waiting.html')
def _do_celery(self, args):
rs = self.task.task.apply_async(args=args)
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
return JsonResponse({
'async_id': rs.id,
'check_url': self.get_check_url(rs.id, True)
})
else:
return redirect(self.get_check_url(rs.id, False))
def _do_sync(self, args):
try:
rs = getattr(self.__class__, 'task')(*args)
return self.success(rs)
except Exception as e:
logger.exception('Error while executing task synchronously')
return self.error(e)
def success(self, value):
smes = self.get_success_message(value)
if smes:
messages.success(self.request, smes)
if "ajax" in self.request.POST or "ajax" in self.request.GET:
return JsonResponse({
'ready': True,
'redirect': self.get_success_url(value),
'message': self.get_success_message(value)
})
return redirect(self.get_success_url(value))
def error(self, exception):
messages.error(self.request, self.get_error_message(exception))
if "ajax" in self.request.POST or "ajax" in self.request.GET:
return JsonResponse({
'ready': True,
'redirect': self.get_error_url(),
'message': self.get_error_message(exception)
})
return redirect(self.get_error_url())
def get_error_message(self, exception):
return _('An unexpected error has occured')
def get_success_message(self, value):
return _('The task has been completed')

View File

@@ -1,13 +1,15 @@
from django.contrib import messages
from django.core.urlresolvers import reverse
from django.http import JsonResponse
from django.shortcuts import redirect
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as _
from django.views.generic import View
from pretix.base.services.cart import (
CartError, add_items_to_cart, remove_items_from_cart,
)
from pretix.presale.views import EventViewMixin
from pretix.presale.views.async import AsyncAction
class CartActionMixin:
@@ -15,18 +17,16 @@ class CartActionMixin:
def get_next_url(self):
if "next" in self.request.GET and '://' not in self.request.GET:
return self.request.GET.get('next')
elif "HTTP_REFERER" in self.request.META:
return self.request.META.get('HTTP_REFERER')
else:
return reverse('presale:event.index', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
})
def get_success_url(self):
def get_success_url(self, value=None):
return self.get_next_url()
def get_failure_url(self):
def get_error_url(self):
return self.get_next_url()
def _items_from_post_data(self):
@@ -61,27 +61,34 @@ class CartRemove(EventViewMixin, CartActionMixin, View):
def post(self, *args, **kwargs):
items = self._items_from_post_data()
if not items:
return redirect(self.get_failure_url())
return redirect(self.get_error_url())
remove_items_from_cart(self.request.event.identity, items, self.request.session.session_key)
messages.success(self.request, _('Your cart has been updated.'))
return redirect(self.get_success_url())
class CartAdd(EventViewMixin, CartActionMixin, View):
class CartAdd(EventViewMixin, CartActionMixin, AsyncAction, View):
task = add_items_to_cart
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def get_success_message(self, value):
return _('The products have been successfully added to your cart.')
def get_error_message(self, exception):
if isinstance(exception, dict) and exception['exc_type'] == 'CartError':
return exception['exc_message']
elif isinstance(exception, CartError):
return str(exception)
return super().get_error_message(exception)
def post(self, request, *args, **kwargs):
items = self._items_from_post_data()
return self.process(items)
def process(self, items):
try:
add_items_to_cart(self.request.event.identity, items, self.request.session.session_key)
messages.success(self.request, _('The products have been successfully added to your cart.'))
return redirect(self.get_success_url())
except CartError as e:
messages.error(self.request, str(e))
return redirect(self.get_failure_url())
if items:
return self.do(self.request.event.identity, items, self.request.session.session_key)
else:
if 'ajax' in self.request.GET or 'ajax' in self.request.POST:
return JsonResponse({
'redirect': self.get_error_url()
})
else:
return redirect(self.get_error_url())

View File

@@ -12,7 +12,7 @@ from pretix.presale.views import CartMixin
class CheckoutView(CartMixin, View):
def dispatch(self, request, *args, **kwargs):
self.request = request
if not self.positions:
if not self.positions and "async_id" not in request.GET:
messages.error(request, _("Your cart is empty"))
return redirect(reverse('presale:event.index', kwargs={
'organizer': self.request.event.organizer.slug,

View File

@@ -107,7 +107,7 @@ class OrderPay(EventViewMixin, OrderDetailMixin, TemplateView):
or not self.payment_provider.order_can_retry(self.order)
or not self.payment_provider.is_enabled):
messages.error(request, _('The payment for this order cannot be continued.'))
return redirect(self.get_order_url())
return redirect(self.get_order_url() + '?paid=yes')
return super().dispatch(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
@@ -168,9 +168,30 @@ class OrderPayDo(EventViewMixin, OrderDetailMixin, TemplateView):
ctx['payment_provider'] = self.payment_provider
return ctx
@cached_property
def form(self):
return self.payment_provider.payment_form_render(self.request)
def get_payment_url(self):
return reverse('presale:event.order.pay', kwargs={
'event': self.request.event.slug,
'organizer': self.request.event.organizer.slug,
'order': self.order.code,
'secret': self.order.secret
})
class OrderPayComplete(EventViewMixin, OrderDetailMixin, View):
def dispatch(self, request, *args, **kwargs):
self.request = request
if not self.order:
raise Http404(_('Unknown order code or not authorized to access this order.'))
if (not self.payment_provider.payment_is_valid_session(request)
or not self.payment_provider.is_enabled
or not self.payment_provider.is_allowed(request)):
messages.error(request, _('The payment information you entered was incomplete.'))
return redirect(self.get_payment_url())
return super().dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
resp = self.payment_provider.payment_perform(request, self.order)
return redirect(resp or self.get_order_url() + '?paid=yes')
def get_payment_url(self):
return reverse('presale:event.order.pay', kwargs={