mirror of
https://github.com/pretix/pretix.git
synced 2026-05-05 15:14:04 +00:00
Add basic sale statistics (closes #57)
This commit is contained in:
@@ -8,7 +8,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: 1\n"
|
"Project-Id-Version: 1\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2015-04-15 10:03+0200\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 <michel@rami.io>\n"
|
"Last-Translator: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language-Team: Raphael Michel <michel@rami.io>\n"
|
"Language-Team: Raphael Michel <michel@rami.io>\n"
|
||||||
"Language: de\n"
|
"Language: de\n"
|
||||||
@@ -1113,7 +1113,7 @@ msgstr "Zahlung ausstehend"
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:6
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:6
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:6
|
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:6
|
||||||
msgid "Paid"
|
msgid "Paid"
|
||||||
msgstr "Bezahlt"
|
msgstr "bezahlt"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:10
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:10
|
||||||
#: pretix/presale/templates/pretixpresale/event/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/control/templates/pretixcontrol/orders/fragment_order_status.html:12
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:12
|
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:12
|
||||||
msgid "Refunded"
|
msgid "Refunded"
|
||||||
msgstr "Erstattet"
|
msgstr "erstattet"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:11
|
#: pretix/control/templates/pretixcontrol/orders/index.html:11
|
||||||
msgid "Order total"
|
msgid "Order total"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@import "sb-admin-2.less";
|
@import "sb-admin-2.less";
|
||||||
@import "forms.less";
|
@import "forms.less";
|
||||||
@import "flags.less";
|
@import "flags.less";
|
||||||
|
@import "orders.less";
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|||||||
@@ -81,11 +81,25 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="{% url 'control:event.orders' organizer=request.event.organizer.slug event=request.event.slug %}"
|
<a href="#">
|
||||||
{% if "event.order" in url_name %}class="active"{% endif %}>
|
|
||||||
<i class="fa fa-shopping-cart fa-fw"></i>
|
<i class="fa fa-shopping-cart fa-fw"></i>
|
||||||
{% trans "Orders" %}
|
{% trans "Orders" %}
|
||||||
|
<span class="fa arrow"></span>
|
||||||
</a>
|
</a>
|
||||||
|
<ul class="nav nav-second-level">
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'control:event.orders' organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
|
{% if url_name == "event.orders" or "event.order." in url_name %}class="active"{% endif %}>
|
||||||
|
{% trans "All orders" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'control:event.orders.overview' organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||||
|
{% if url_name == "event.orders.overview" %}class="active"{% endif %}>
|
||||||
|
{% trans "Overview" %}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
{% for nav in nav_event %}
|
{% for nav in nav_event %}
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ urlpatterns = [
|
|||||||
url(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
|
url(r'^orders/(?P<code>[0-9A-Z]+)/extend$', orders.OrderExtend.as_view(),
|
||||||
name='event.order.extend'),
|
name='event.order.extend'),
|
||||||
url(r'^orders/(?P<code>[0-9A-Z]+)/$', orders.OrderDetail.as_view(), name='event.order'),
|
url(r'^orders/(?P<code>[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'),
|
url(r'^orders/$', orders.OrderList.as_view(), name='event.orders'),
|
||||||
])),
|
])),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db.models import Count
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.utils.functional import cached_property
|
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.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.base.signals import register_payment_providers
|
||||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||||
|
|
||||||
@@ -203,3 +204,89 @@ class OrderExtend(OrderView):
|
|||||||
def form(self):
|
def form(self):
|
||||||
return ExtendForm(instance=self.order,
|
return ExtendForm(instance=self.order,
|
||||||
data=self.request.POST if self.request.method == "POST" else None)
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user