Improve add-on products

This commit is contained in:
Raphael Michel
2017-03-19 14:33:45 +01:00
parent 5bcfb958f0
commit b52f2f5a9e
36 changed files with 802 additions and 131 deletions

View File

@@ -29,35 +29,45 @@ class CartMixin:
cartpos = queryset.order_by(
'item', 'variation'
).select_related(
'item', 'variation'
'item', 'variation', 'addon_to'
).prefetch_related(
*prefetch
)
else:
cartpos = self.positions
lcp = list(cartpos)
has_addons = {cp.addon_to.pk for cp in lcp if cp.addon_to}
# Group items of the same variation
# We do this by list manipulations instead of a GROUP BY query, as
# Django is unable to join related models in a .values() query
def keyfunc(pos):
if isinstance(pos, OrderPosition):
i = pos.positionid
if pos.addon_to:
i = pos.addon_to.positionid
else:
i = pos.positionid
else:
i = pos.pk
if downloads:
return i, pos.pk, 0, 0, 0, 0,
if pos.addon_to:
i = pos.addon_to.pk
else:
i = pos.pk
has_attendee_data = pos.item.admission and (
self.request.event.settings.attendee_names_asked
or self.request.event.settings.attendee_emails_asked
)
addon_penalty = 1 if pos.addon_to else 0
if downloads or pos.pk in has_addons or pos.addon_to:
return i, addon_penalty, pos.pk, 0, 0, 0, 0,
if answers and (has_attendee_data or pos.item.questions.all()):
return i, pos.pk, 0, 0, 0, 0,
return 0, 0, pos.item_id, pos.variation_id, pos.price, (pos.voucher_id or 0)
return i, addon_penalty, pos.pk, 0, 0, 0, 0,
return 0, addon_penalty, 0, pos.item_id, pos.variation_id, pos.price, (pos.voucher_id or 0)
positions = []
for k, g in groupby(sorted(list(cartpos), key=keyfunc), key=keyfunc):
for k, g in groupby(sorted(lcp, key=keyfunc), key=keyfunc):
g = list(g)
group = g[0]
group.count = len(g)

View File

@@ -171,6 +171,7 @@ class RedeemView(EventViewMixin, TemplateView):
Q(active=True)
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now()))
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
& ~Q(category__is_addon=True)
)
vouchq = Q(hide_without_voucher=False)

View File

@@ -46,6 +46,7 @@ def get_grouped_items(event):
& Q(Q(available_from__isnull=True) | Q(available_from__lte=now()))
& Q(Q(available_until__isnull=True) | Q(available_until__gte=now()))
& Q(hide_without_voucher=False)
& ~Q(category__is_addon=True)
).select_related(
'category', # for re-grouping
).prefetch_related(

View File

@@ -3,7 +3,7 @@ from datetime import timedelta
from django.contrib import messages
from django.db import transaction
from django.db.models import Sum
from django.http import FileResponse, Http404, HttpResponse
from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import redirect, render
from django.utils.functional import cached_property
from django.utils.timezone import now
@@ -392,9 +392,7 @@ class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, Template
@cached_property
def positions(self):
return list(self.order.positions.order_by(
'item', 'variation'
).select_related(
return list(self.order.positions.select_related(
'item', 'variation'
).prefetch_related(
'variation', 'item__questions', 'answers'
@@ -445,7 +443,7 @@ class OrderModify(EventViewMixin, OrderDetailMixin, QuestionsViewMixin, Template
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['order'] = self.order
ctx['forms'] = self.forms
ctx['formgroups'] = self.formdict.items()
ctx['invoice_form'] = self.invoice_form
return ctx
@@ -501,6 +499,12 @@ class OrderCancelDo(EventViewMixin, OrderDetailMixin, AsyncAction, View):
class OrderDownload(EventViewMixin, OrderDetailMixin, View):
def get_self_url(self):
return eventreverse(self.request.event,
'presale:event.order.download' if 'position' in self.kwargs
else 'presale:event.order.download.combined',
kwargs=self.kwargs)
@cached_property
def output(self):
responses = register_ticket_outputs.send(self.request.event)
@@ -516,20 +520,30 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View):
except OrderPosition.DoesNotExist:
return None
def error(self, msg):
messages.error(self.request, msg)
if "ajax" in self.request.POST or "ajax" in self.request.GET:
return JsonResponse({
'ready': True,
'success': False,
'redirect': self.get_order_url(),
'message': msg,
})
return redirect(self.get_order_url())
def get(self, request, *args, **kwargs):
if not self.output or not self.output.is_enabled:
messages.error(request, _('You requested an invalid ticket output type.'))
return redirect(self.get_order_url())
return self.error(_('You requested an invalid ticket output type.'))
if not self.order or ('position' in kwargs and not self.order_position):
raise Http404(_('Unknown order code or not authorized to access this order.'))
if self.order.status != Order.STATUS_PAID:
messages.error(request, _('Order is not paid.'))
return redirect(self.get_order_url())
return self.error(_('Order is not paid.'))
if (not self.request.event.settings.ticket_download
or (self.request.event.settings.ticket_download_date is not None
and now() < self.request.event.settings.ticket_download_date)):
messages.error(request, _('Ticket download is not (yet) enabled.'))
return redirect(self.get_order_url())
return self.error(_('Ticket download is not (yet) enabled.'))
if 'position' in kwargs and (self.order_position.addon_to and not self.request.event.settings.ticket_download_addons):
return self.error(_('Ticket download is not enabled for add-on products.'))
if 'position' in kwargs:
return self._download_position()
@@ -555,7 +569,11 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View):
generate_order.apply_async(args=(self.order.id, self.output.identifier))
if 'ajax' in self.request.GET:
return HttpResponse('1' if ct and ct.file else '0')
return JsonResponse({
'ready': bool(ct and ct.file),
'success': False,
'redirect': self.get_self_url()
})
elif not ct.file:
return render(self.request, "pretixbase/cachedfiles/pending.html", {})
else:
@@ -584,7 +602,11 @@ class OrderDownload(EventViewMixin, OrderDetailMixin, View):
generate.apply_async(args=(self.order_position.id, self.output.identifier))
if 'ajax' in self.request.GET:
return HttpResponse('1' if ct and ct.file else '0')
return JsonResponse({
'ready': bool(ct and ct.file),
'success': False,
'redirect': self.get_self_url()
})
elif not ct.file:
return render(self.request, "pretixbase/cachedfiles/pending.html", {})
else:

View File

@@ -1,3 +1,5 @@
from collections import defaultdict
from django import forms
from django.utils.functional import cached_property
@@ -8,8 +10,18 @@ from pretix.presale.views import get_cart
class QuestionsViewMixin:
@staticmethod
def _keyfunc(pos):
# Sort addons after the item they are an addon to
if isinstance(pos, OrderPosition):
i = pos.addon_to.positionid if pos.addon_to else pos.positionid
else:
i = pos.addon_to.pk if pos.addon_to else pos.pk
addon_penalty = 1 if pos.addon_to else 0
return i, addon_penalty, pos.pk
def _positions_for_questions(self):
return get_cart(self.request)
return sorted(get_cart(self.request), key=self._keyfunc)
@cached_property
def forms(self):
@@ -32,6 +44,17 @@ class QuestionsViewMixin:
formlist.append(form)
return formlist
@cached_property
def formdict(self):
storage = defaultdict(list)
for f in self.forms:
pos = f.cartpos or f.orderpos
if pos.addon_to_id:
storage[pos.addon_to].append(f)
else:
storage[pos].append(f)
return storage
def save(self):
failed = False
for form in self.forms: