From 51b2201f3bbb1ee28a86b297b7a0aa0c20de3825 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 17 Apr 2015 21:54:38 +0200 Subject: [PATCH] Add basic sale statistics (closes #57) --- src/locale/de/LC_MESSAGES/django.po | 6 +- .../static/pretixcontrol/less/main.less | 1 + .../templates/pretixcontrol/event/base.html | 18 +++- src/pretix/control/urls.py | 1 + src/pretix/control/views/orders.py | 91 ++++++++++++++++++- 5 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/locale/de/LC_MESSAGES/django.po b/src/locale/de/LC_MESSAGES/django.po index af1de06fb5..b7bc3c79b1 100644 --- a/src/locale/de/LC_MESSAGES/django.po +++ b/src/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: 1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-04-15 10:03+0200\n" -"PO-Revision-Date: 2015-04-15 10:31+0100\n" +"PO-Revision-Date: 2015-04-17 21:32+0100\n" "Last-Translator: Raphael Michel \n" "Language-Team: Raphael Michel \n" "Language: de\n" @@ -1113,7 +1113,7 @@ msgstr "Zahlung ausstehend" #: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:6 #: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:6 msgid "Paid" -msgstr "Bezahlt" +msgstr "bezahlt" #: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:10 #: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:10 @@ -1123,7 +1123,7 @@ msgstr "storniert" #: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:12 #: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:12 msgid "Refunded" -msgstr "Erstattet" +msgstr "erstattet" #: pretix/control/templates/pretixcontrol/orders/index.html:11 msgid "Order total" diff --git a/src/pretix/control/static/pretixcontrol/less/main.less b/src/pretix/control/static/pretixcontrol/less/main.less index 9da33b3574..f5f766bbc6 100644 --- a/src/pretix/control/static/pretixcontrol/less/main.less +++ b/src/pretix/control/static/pretixcontrol/less/main.less @@ -6,6 +6,7 @@ @import "sb-admin-2.less"; @import "forms.less"; @import "flags.less"; +@import "orders.less"; footer { text-align: center; diff --git a/src/pretix/control/templates/pretixcontrol/event/base.html b/src/pretix/control/templates/pretixcontrol/event/base.html index 3d42ccd513..93021e2a49 100644 --- a/src/pretix/control/templates/pretixcontrol/event/base.html +++ b/src/pretix/control/templates/pretixcontrol/event/base.html @@ -81,11 +81,25 @@
  • - + {% trans "Orders" %} + +
  • {% for nav in nav_event %}
  • diff --git a/src/pretix/control/urls.py b/src/pretix/control/urls.py index 129b76443d..f7746383c9 100644 --- a/src/pretix/control/urls.py +++ b/src/pretix/control/urls.py @@ -52,6 +52,7 @@ urlpatterns = [ 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/overview/$', orders.OverView.as_view(), name='event.orders.overview'), 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 21201c05f6..44c2feb4e7 100644 --- a/src/pretix/control/views/orders.py +++ b/src/pretix/control/views/orders.py @@ -1,15 +1,16 @@ from itertools import groupby from django.contrib import messages from django.core.urlresolvers import reverse +from django.db.models import Count 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 django.views.generic import ListView, DetailView, TemplateView from pretix.base.forms import VersionedModelForm -from pretix.base.models import Order, Quota +from pretix.base.models import Order, Quota, OrderPosition from pretix.base.signals import register_payment_providers from pretix.control.permissions import EventPermissionRequiredMixin @@ -203,3 +204,89 @@ class OrderExtend(OrderView): def form(self): return ExtendForm(instance=self.order, data=self.request.POST if self.request.method == "POST" else None) + + +class OverView(EventPermissionRequiredMixin, TemplateView): + template_name = 'pretixcontrol/orders/overview.html' + permission = 'can_view_orders' + + def get_context_data(self, **kwargs): + ctx = super().get_context_data() + items = self.request.event.items.all().select_related( + 'category', # for re-grouping + ).prefetch_related( + 'properties', # for .get_all_available_variations() + ).order_by('category__position', 'category_id', 'name') + + num_total = { + (p['item'], p['variation']): p['cnt'] + for p in OrderPosition.objects.current.filter( + order__event=self.request.event + ).values('item', 'variation').annotate(cnt=Count('id')) + } + num_cancelled = { + (p['item'], p['variation']): p['cnt'] + for p in OrderPosition.objects.current.filter( + order__event=self.request.event, order__status=Order.STATUS_CANCELLED + ).values('item', 'variation').annotate(cnt=Count('id')) + } + num_refunded = { + (p['item'], p['variation']): p['cnt'] + for p in OrderPosition.objects.current.filter( + order__event=self.request.event, order__status=Order.STATUS_REFUNDED + ).values('item', 'variation').annotate(cnt=Count('id')) + } + num_pending = { + (p['item'], p['variation']): p['cnt'] + for p in OrderPosition.objects.current.filter( + order__event=self.request.event, order__status__in=(Order.STATUS_PENDING, Order.STATUS_EXPIRED) + ).values('item', 'variation').annotate(cnt=Count('id')) + } + num_paid = { + (p['item'], p['variation']): p['cnt'] + for p in OrderPosition.objects.current.filter( + order__event=self.request.event, order__status=Order.STATUS_PAID + ).values('item', 'variation').annotate(cnt=Count('id')) + } + + for item in items: + item.all_variations = sorted(item.get_all_variations(), + key=lambda vd: vd.ordered_values()) + for var in item.all_variations: + variid = var['variation'].identity if 'variation' in var else None + var.num_total = num_total.get((item.identity, variid), 0) + var.num_pending = num_pending.get((item.identity, variid), 0) + var.num_cancelled = num_cancelled.get((item.identity, variid), 0) + var.num_refunded = num_refunded.get((item.identity, variid), 0) + var.num_paid = num_paid.get((item.identity, variid), 0) + item.has_variations = (len(item.all_variations) != 1 + or not item.all_variations[0].empty()) + item.num_total = sum(var.num_total for var in item.all_variations) + item.num_pending = sum(var.num_pending for var in item.all_variations) + item.num_cancelled = sum(var.num_cancelled for var in item.all_variations) + item.num_refunded = sum(var.num_refunded for var in item.all_variations) + item.num_paid = sum(var.num_paid for var in item.all_variations) + + # Regroup those by category + ctx['items_by_category'] = sorted([ + # a group is a tuple of a category and a list of items + (cat, [i for i in items if i.category == cat]) + for cat in set([i.category for i in items]) # insert categories into a set for uniqueness + # a set is unsorted, so sort again by category + ], key=lambda group: (group[0].position, group[0].identity) if group[0] is not None else (0, "")) + for c in ctx['items_by_category']: + c[0].num_total = sum(item.num_total for item in c[1]) + c[0].num_pending = sum(item.num_pending for item in c[1]) + c[0].num_cancelled = sum(item.num_cancelled for item in c[1]) + c[0].num_refunded = sum(item.num_refunded for item in c[1]) + c[0].num_paid = sum(item.num_paid for item in c[1]) + + ctx['total'] = { + 'num_total': sum(c.num_total for c, i in ctx['items_by_category']), + 'num_pending': sum(c.num_pending for c, i in ctx['items_by_category']), + 'num_cancelled': sum(c.num_cancelled for c, i in ctx['items_by_category']), + 'num_refunded': sum(c.num_refunded for c, i in ctx['items_by_category']), + 'num_paid': sum(c.num_total for c, i in ctx['items_by_category']) + } + + return ctx