forked from CGM_Public/pretix_original
Added PDF exporter for the order overview
This commit is contained in:
@@ -1668,7 +1668,7 @@ msgstr "Exportieren"
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:4
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:4
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:20
|
#: pretix/control/templates/pretixcontrol/orders/index.html:20
|
||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "Zahlung ausstehend"
|
msgstr "ausstehend"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:6
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:6
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:19
|
#: pretix/control/templates/pretixcontrol/orders/index.html:19
|
||||||
@@ -1680,7 +1680,7 @@ msgstr "bezahlt"
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:8
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:8
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:21
|
#: pretix/control/templates/pretixcontrol/orders/index.html:21
|
||||||
msgid "Pending (expired)"
|
msgid "Pending (expired)"
|
||||||
msgstr "Zahlung ausstehend (abgelaufen)"
|
msgstr "ausstehend (abgelaufen)"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:10
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:10
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:22
|
#: pretix/control/templates/pretixcontrol/orders/index.html:22
|
||||||
@@ -1736,7 +1736,7 @@ msgstr "Zeige %(currency)s"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:20
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:20
|
||||||
msgid "Total orders"
|
msgid "Total orders"
|
||||||
msgstr "Bestellungen gesamt"
|
msgstr "bestellt"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:21
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:21
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:4
|
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:4
|
||||||
|
|||||||
@@ -1664,7 +1664,7 @@ msgstr "Exportieren"
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:4
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:4
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:20
|
#: pretix/control/templates/pretixcontrol/orders/index.html:20
|
||||||
msgid "Pending"
|
msgid "Pending"
|
||||||
msgstr "Zahlung ausstehend"
|
msgstr "ausstehend"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:6
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:6
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:19
|
#: pretix/control/templates/pretixcontrol/orders/index.html:19
|
||||||
@@ -1676,7 +1676,7 @@ msgstr "bezahlt"
|
|||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:8
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:8
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:21
|
#: pretix/control/templates/pretixcontrol/orders/index.html:21
|
||||||
msgid "Pending (expired)"
|
msgid "Pending (expired)"
|
||||||
msgstr "Zahlung ausstehend (abgelaufen)"
|
msgstr "ausstehend (abgelaufen)"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:10
|
#: pretix/control/templates/pretixcontrol/orders/fragment_order_status.html:10
|
||||||
#: pretix/control/templates/pretixcontrol/orders/index.html:22
|
#: pretix/control/templates/pretixcontrol/orders/index.html:22
|
||||||
@@ -1732,7 +1732,7 @@ msgstr "Zeige %(currency)s"
|
|||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:20
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:20
|
||||||
msgid "Total orders"
|
msgid "Total orders"
|
||||||
msgstr "Bestellungen gesamt"
|
msgstr "bestellt"
|
||||||
|
|
||||||
#: pretix/control/templates/pretixcontrol/orders/overview.html:21
|
#: pretix/control/templates/pretixcontrol/orders/overview.html:21
|
||||||
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:4
|
#: pretix/presale/templates/pretixpresale/event/fragment_order_status.html:4
|
||||||
|
|||||||
99
src/pretix/base/services/stats.py
Normal file
99
src/pretix/base/services/stats.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
from django.db.models import Count, Sum
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from pretix.base.models import ItemCategory, Order, OrderPosition
|
||||||
|
|
||||||
|
|
||||||
|
def tuplesum(tuples):
|
||||||
|
return tuple(map(sum, zip(*list(tuples))))
|
||||||
|
|
||||||
|
|
||||||
|
def order_overview(event):
|
||||||
|
items = 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'], p['price'])
|
||||||
|
for p in
|
||||||
|
OrderPosition.objects.current.filter(order__event=event).values('item', 'variation').annotate(
|
||||||
|
cnt=Count('id'), price=Sum('price'))
|
||||||
|
}
|
||||||
|
num_cancelled = {
|
||||||
|
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||||
|
for p in (OrderPosition.objects.current
|
||||||
|
.filter(order__event=event, order__status=Order.STATUS_CANCELLED)
|
||||||
|
.values('item', 'variation')
|
||||||
|
.annotate(cnt=Count('id'), price=Sum('price')))
|
||||||
|
}
|
||||||
|
num_refunded = {
|
||||||
|
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||||
|
for p in (OrderPosition.objects.current
|
||||||
|
.filter(order__event=event, order__status=Order.STATUS_REFUNDED)
|
||||||
|
.values('item', 'variation')
|
||||||
|
.annotate(cnt=Count('id'), price=Sum('price')))
|
||||||
|
}
|
||||||
|
num_pending = {
|
||||||
|
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||||
|
for p in (OrderPosition.objects.current
|
||||||
|
.filter(order__event=event,
|
||||||
|
order__status__in=(Order.STATUS_PENDING, Order.STATUS_EXPIRED))
|
||||||
|
.values('item', 'variation')
|
||||||
|
.annotate(cnt=Count('id'), price=Sum('price')))
|
||||||
|
}
|
||||||
|
num_paid = {
|
||||||
|
(p['item'], p['variation']): (p['cnt'], p['price'])
|
||||||
|
for p in (OrderPosition.objects.current
|
||||||
|
.filter(order__event=event, order__status=Order.STATUS_PAID)
|
||||||
|
.values('item', 'variation')
|
||||||
|
.annotate(cnt=Count('id'), price=Sum('price')))
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 0))
|
||||||
|
var.num_pending = num_pending.get((item.identity, variid), (0, 0))
|
||||||
|
var.num_cancelled = num_cancelled.get((item.identity, variid), (0, 0))
|
||||||
|
var.num_refunded = num_refunded.get((item.identity, variid), (0, 0))
|
||||||
|
var.num_paid = num_paid.get((item.identity, variid), (0, 0))
|
||||||
|
item.has_variations = (len(item.all_variations) != 1
|
||||||
|
or not item.all_variations[0].empty())
|
||||||
|
item.num_total = tuplesum(var.num_total for var in item.all_variations)
|
||||||
|
item.num_pending = tuplesum(var.num_pending for var in item.all_variations)
|
||||||
|
item.num_cancelled = tuplesum(var.num_cancelled for var in item.all_variations)
|
||||||
|
item.num_refunded = tuplesum(var.num_refunded for var in item.all_variations)
|
||||||
|
item.num_paid = tuplesum(var.num_paid for var in item.all_variations)
|
||||||
|
|
||||||
|
nonecat = ItemCategory(name=_('Uncategorized'))
|
||||||
|
# Regroup those by category
|
||||||
|
items_by_category = sorted(
|
||||||
|
[
|
||||||
|
# a group is a tuple of a category and a list of items
|
||||||
|
(cat if cat is not None else nonecat, [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 items_by_category:
|
||||||
|
c[0].num_total = tuplesum(item.num_total for item in c[1])
|
||||||
|
c[0].num_pending = tuplesum(item.num_pending for item in c[1])
|
||||||
|
c[0].num_cancelled = tuplesum(item.num_cancelled for item in c[1])
|
||||||
|
c[0].num_refunded = tuplesum(item.num_refunded for item in c[1])
|
||||||
|
c[0].num_paid = tuplesum(item.num_paid for item in c[1])
|
||||||
|
|
||||||
|
total = {
|
||||||
|
'num_total': tuplesum(c.num_total for c, i in items_by_category),
|
||||||
|
'num_pending': tuplesum(c.num_pending for c, i in items_by_category),
|
||||||
|
'num_cancelled': tuplesum(c.num_cancelled for c, i in items_by_category),
|
||||||
|
'num_refunded': tuplesum(c.num_refunded for c, i in items_by_category),
|
||||||
|
'num_paid': tuplesum(c.num_paid for c, i in items_by_category)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items_by_category, total
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.views.generic import CreateView, ListView, TemplateView
|
from django.views.generic import CreateView, ListView, TemplateView
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
from pretix.base.models import Event, EventPermission, OrganizerPermission
|
from pretix.base.models import Event, EventPermission, OrganizerPermission
|
||||||
from pretix.control.forms.event import EventCreateForm
|
from pretix.control.forms.event import EventCreateForm
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from itertools import groupby
|
|||||||
from django import forms
|
from django import forms
|
||||||
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 Q, Count, Sum
|
from django.db.models import Q
|
||||||
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
|
||||||
@@ -11,8 +11,9 @@ from django.utils.timezone import now
|
|||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.views.generic import DetailView, ListView, TemplateView, View
|
from django.views.generic import DetailView, ListView, TemplateView, View
|
||||||
|
|
||||||
from pretix.base.models import Item, ItemCategory, Order, OrderPosition, Quota
|
from pretix.base.models import Item, Order, Quota
|
||||||
from pretix.base.services.orders import mark_order_paid
|
from pretix.base.services.orders import mark_order_paid
|
||||||
|
from pretix.base.services.stats import order_overview
|
||||||
from pretix.base.signals import (
|
from pretix.base.signals import (
|
||||||
register_data_exporters, register_payment_providers,
|
register_data_exporters, register_payment_providers,
|
||||||
register_ticket_outputs,
|
register_ticket_outputs,
|
||||||
@@ -260,103 +261,13 @@ class OrderExtend(OrderView):
|
|||||||
data=self.request.POST if self.request.method == "POST" else None)
|
data=self.request.POST if self.request.method == "POST" else None)
|
||||||
|
|
||||||
|
|
||||||
def tuplesum(tuples):
|
|
||||||
return tuple(map(sum, zip(*list(tuples))))
|
|
||||||
|
|
||||||
|
|
||||||
class OverView(EventPermissionRequiredMixin, TemplateView):
|
class OverView(EventPermissionRequiredMixin, TemplateView):
|
||||||
template_name = 'pretixcontrol/orders/overview.html'
|
template_name = 'pretixcontrol/orders/overview.html'
|
||||||
permission = 'can_view_orders'
|
permission = 'can_view_orders'
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
ctx = super().get_context_data()
|
ctx = super().get_context_data()
|
||||||
items = self.request.event.items.all().select_related(
|
ctx['items_by_category'], ctx['total'] = order_overview(self.request.event)
|
||||||
'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'], p['price'])
|
|
||||||
for p in
|
|
||||||
OrderPosition.objects.current.filter(order__event=self.request.event).values('item', 'variation').annotate(
|
|
||||||
cnt=Count('id'), price=Sum('price'))
|
|
||||||
}
|
|
||||||
num_cancelled = {
|
|
||||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
|
||||||
for p in (OrderPosition.objects.current
|
|
||||||
.filter(order__event=self.request.event, order__status=Order.STATUS_CANCELLED)
|
|
||||||
.values('item', 'variation')
|
|
||||||
.annotate(cnt=Count('id'), price=Sum('price')))
|
|
||||||
}
|
|
||||||
num_refunded = {
|
|
||||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
|
||||||
for p in (OrderPosition.objects.current
|
|
||||||
.filter(order__event=self.request.event, order__status=Order.STATUS_REFUNDED)
|
|
||||||
.values('item', 'variation')
|
|
||||||
.annotate(cnt=Count('id'), price=Sum('price')))
|
|
||||||
}
|
|
||||||
num_pending = {
|
|
||||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
|
||||||
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'), price=Sum('price')))
|
|
||||||
}
|
|
||||||
num_paid = {
|
|
||||||
(p['item'], p['variation']): (p['cnt'], p['price'])
|
|
||||||
for p in (OrderPosition.objects.current
|
|
||||||
.filter(order__event=self.request.event, order__status=Order.STATUS_PAID)
|
|
||||||
.values('item', 'variation')
|
|
||||||
.annotate(cnt=Count('id'), price=Sum('price')))
|
|
||||||
}
|
|
||||||
|
|
||||||
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, 0))
|
|
||||||
var.num_pending = num_pending.get((item.identity, variid), (0, 0))
|
|
||||||
var.num_cancelled = num_cancelled.get((item.identity, variid), (0, 0))
|
|
||||||
var.num_refunded = num_refunded.get((item.identity, variid), (0, 0))
|
|
||||||
var.num_paid = num_paid.get((item.identity, variid), (0, 0))
|
|
||||||
item.has_variations = (len(item.all_variations) != 1
|
|
||||||
or not item.all_variations[0].empty())
|
|
||||||
item.num_total = tuplesum(var.num_total for var in item.all_variations)
|
|
||||||
item.num_pending = tuplesum(var.num_pending for var in item.all_variations)
|
|
||||||
item.num_cancelled = tuplesum(var.num_cancelled for var in item.all_variations)
|
|
||||||
item.num_refunded = tuplesum(var.num_refunded for var in item.all_variations)
|
|
||||||
item.num_paid = tuplesum(var.num_paid for var in item.all_variations)
|
|
||||||
|
|
||||||
nonecat = ItemCategory(name=_('Uncategorized'))
|
|
||||||
# Regroup those by category
|
|
||||||
ctx['items_by_category'] = sorted(
|
|
||||||
[
|
|
||||||
# a group is a tuple of a category and a list of items
|
|
||||||
(cat if cat is not None else nonecat, [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 = tuplesum(item.num_total for item in c[1])
|
|
||||||
c[0].num_pending = tuplesum(item.num_pending for item in c[1])
|
|
||||||
c[0].num_cancelled = tuplesum(item.num_cancelled for item in c[1])
|
|
||||||
c[0].num_refunded = tuplesum(item.num_refunded for item in c[1])
|
|
||||||
c[0].num_paid = tuplesum(item.num_paid for item in c[1])
|
|
||||||
|
|
||||||
ctx['total'] = {
|
|
||||||
'num_total': tuplesum(c.num_total for c, i in ctx['items_by_category']),
|
|
||||||
'num_pending': tuplesum(c.num_pending for c, i in ctx['items_by_category']),
|
|
||||||
'num_cancelled': tuplesum(c.num_cancelled for c, i in ctx['items_by_category']),
|
|
||||||
'num_refunded': tuplesum(c.num_refunded for c, i in ctx['items_by_category']),
|
|
||||||
'num_paid': tuplesum(c.num_paid for c, i in ctx['items_by_category'])
|
|
||||||
}
|
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
32
src/pretix/plugins/reports/__init__.py
Normal file
32
src/pretix/plugins/reports/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from pretix import __version__ as version
|
||||||
|
from pretix.base.plugins import PluginType
|
||||||
|
|
||||||
|
|
||||||
|
class ReportsApp(AppConfig):
|
||||||
|
name = 'pretix.plugins.reports'
|
||||||
|
verbose_name = _("Report exporter")
|
||||||
|
|
||||||
|
class PretixPluginMeta:
|
||||||
|
type = PluginType.PAYMENT
|
||||||
|
name = _("Report exporter")
|
||||||
|
author = _("the pretix team")
|
||||||
|
version = version
|
||||||
|
description = _("This plugin allows you to generate printable reports about your sales.")
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from . import signals # NOQA
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def compatibility_errors(self):
|
||||||
|
errs = []
|
||||||
|
try:
|
||||||
|
import reportlab # NOQA
|
||||||
|
except ImportError:
|
||||||
|
errs.append("Python package 'reportlab' is not installed.")
|
||||||
|
return errs
|
||||||
|
|
||||||
|
default_app_config = 'pretix.plugins.reports.ReportsApp'
|
||||||
214
src/pretix/plugins/reports/exporters.py
Normal file
214
src/pretix/plugins/reports/exporters.py
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import tempfile
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.staticfiles import finders
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.utils.timezone import now
|
||||||
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
from pretix.base.exporter import BaseExporter
|
||||||
|
from pretix.base.services.stats import order_overview
|
||||||
|
|
||||||
|
|
||||||
|
class Report:
|
||||||
|
name = "report"
|
||||||
|
|
||||||
|
def __init__(self, event):
|
||||||
|
self.event = event
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pagesize(self):
|
||||||
|
from reportlab.lib import pagesizes
|
||||||
|
|
||||||
|
return pagesizes.portrait(pagesizes.A4)
|
||||||
|
|
||||||
|
def get_filename(self):
|
||||||
|
return "%s-%s.pdf" % (self.name, now().strftime("%Y-%m-%d-%H-%M-%S"))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def register_fonts():
|
||||||
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
|
||||||
|
pdfmetrics.registerFont(TTFont('OpenSans', finders.find('fonts/OpenSans-Regular.ttf')))
|
||||||
|
pdfmetrics.registerFont(TTFont('OpenSansIt', finders.find('fonts/OpenSans-Italic.ttf')))
|
||||||
|
pdfmetrics.registerFont(TTFont('OpenSansBd', finders.find('fonts/OpenSans-Bold.ttf')))
|
||||||
|
|
||||||
|
def create(self):
|
||||||
|
from reportlab.platypus import BaseDocTemplate, PageTemplate
|
||||||
|
from reportlab.lib.units import mm
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".pdf") as f:
|
||||||
|
Report.register_fonts()
|
||||||
|
doc = BaseDocTemplate(f.name, pagesize=self.pagesize,
|
||||||
|
leftMargin=15 * mm,
|
||||||
|
rightMargin=15 * mm,
|
||||||
|
topMargin=20 * mm,
|
||||||
|
bottomMargin=15 * mm)
|
||||||
|
doc.addPageTemplates([
|
||||||
|
PageTemplate(id='All', frames=self.get_frames(doc), onPage=self.on_page, pagesize=self.pagesize)
|
||||||
|
])
|
||||||
|
doc.build(self.get_story(doc))
|
||||||
|
f.seek(0)
|
||||||
|
return f.read()
|
||||||
|
|
||||||
|
def get_frames(self, doc):
|
||||||
|
from reportlab.platypus import Frame
|
||||||
|
|
||||||
|
self.frame = Frame(doc.leftMargin, doc.bottomMargin,
|
||||||
|
doc.width,
|
||||||
|
doc.height,
|
||||||
|
leftPadding=0,
|
||||||
|
rightPadding=0,
|
||||||
|
topPadding=0,
|
||||||
|
bottomPadding=0,
|
||||||
|
id='normal')
|
||||||
|
return [self.frame]
|
||||||
|
|
||||||
|
def get_story(self, doc):
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_style(self):
|
||||||
|
from reportlab.lib.styles import getSampleStyleSheet
|
||||||
|
|
||||||
|
styles = getSampleStyleSheet()
|
||||||
|
style = styles["Normal"]
|
||||||
|
style.fontName = 'OpenSans'
|
||||||
|
return style
|
||||||
|
|
||||||
|
def on_page(self, canvas, doc):
|
||||||
|
canvas.saveState()
|
||||||
|
self.page_footer(canvas, doc)
|
||||||
|
self.page_header(canvas, doc)
|
||||||
|
canvas.restoreState()
|
||||||
|
|
||||||
|
def page_footer(self, canvas, doc):
|
||||||
|
from reportlab.lib.units import mm
|
||||||
|
|
||||||
|
canvas.setFont('OpenSans', 8)
|
||||||
|
canvas.drawString(15 * mm, 10 * mm, _("Page %d") % (doc.page,))
|
||||||
|
canvas.drawRightString(self.pagesize[0] - 15 * mm, 10 * mm,
|
||||||
|
_("Created: %s") % now().strftime("%d.%m.%Y %H:%M:%S"))
|
||||||
|
|
||||||
|
def page_header(self, canvas, doc):
|
||||||
|
from reportlab.lib.units import mm
|
||||||
|
|
||||||
|
canvas.setFont('OpenSans', 10)
|
||||||
|
canvas.drawString(15 * mm, self.pagesize[1] - 15 * mm,
|
||||||
|
"%s – %s" % (self.event.organizer.name, self.event.name))
|
||||||
|
canvas.drawRightString(self.pagesize[0] - 15 * mm, self.pagesize[1] - 15 * mm,
|
||||||
|
settings.PRETIX_INSTANCE_NAME)
|
||||||
|
canvas.setStrokeColorRGB(0, 0, 0)
|
||||||
|
canvas.line(15 * mm, self.pagesize[1] - 17 * mm,
|
||||||
|
self.pagesize[0] - 15 * mm, self.pagesize[1] - 17 * mm)
|
||||||
|
|
||||||
|
|
||||||
|
class OverviewReport(Report):
|
||||||
|
name = "overview"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def pagesize(self):
|
||||||
|
from reportlab.lib import pagesizes
|
||||||
|
|
||||||
|
return pagesizes.landscape(pagesizes.A4)
|
||||||
|
|
||||||
|
def get_story(self, doc):
|
||||||
|
from reportlab.platypus import Paragraph, Spacer, TableStyle, Table
|
||||||
|
from reportlab.lib.units import mm
|
||||||
|
|
||||||
|
headlinestyle = self.get_style()
|
||||||
|
headlinestyle.fontSize = 15
|
||||||
|
headlinestyle.fontName = 'OpenSansBd'
|
||||||
|
colwidths = [a * doc.width for a in (.30, .06, .08, .06, .08, .06, .08, .06, .08, .06, .08)]
|
||||||
|
tstyledata = [
|
||||||
|
('SPAN', (1, 0), (2, 0)),
|
||||||
|
('SPAN', (3, 0), (4, 0)),
|
||||||
|
('SPAN', (5, 0), (6, 0)),
|
||||||
|
('SPAN', (7, 0), (8, 0)),
|
||||||
|
('SPAN', (9, 0), (10, 0)),
|
||||||
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
||||||
|
('ALIGN', (1, 1), (-1, -1), 'RIGHT'),
|
||||||
|
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||||
|
('FONTNAME', (0, 0), (-1, 0), 'OpenSansBd'),
|
||||||
|
('FONTNAME', (0, -1), (-1, -1), 'OpenSansBd'),
|
||||||
|
]
|
||||||
|
|
||||||
|
story = [
|
||||||
|
Paragraph(_('Orders by product'), headlinestyle),
|
||||||
|
Spacer(1, 5 * mm)
|
||||||
|
]
|
||||||
|
tdata = [
|
||||||
|
[
|
||||||
|
_('Product'), _('Total orders'), '', _('Pending'), '', _('Cancelled'), '', _('Refunded'), '',
|
||||||
|
_('Paid'), ''
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'',
|
||||||
|
_('Number'), self.event.currency,
|
||||||
|
_('Number'), self.event.currency,
|
||||||
|
_('Number'), self.event.currency,
|
||||||
|
_('Number'), self.event.currency,
|
||||||
|
_('Number'), self.event.currency
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
items_by_category, total = order_overview(self.event)
|
||||||
|
|
||||||
|
for tup in items_by_category:
|
||||||
|
if tup[0]:
|
||||||
|
tstyledata.append(('FONTNAME', (0, len(tdata)), (-1, len(tdata)), 'OpenSansBd'))
|
||||||
|
tdata.append([
|
||||||
|
tup[0].name,
|
||||||
|
str(tup[0].num_total[0]), str(tup[0].num_total[1]),
|
||||||
|
str(tup[0].num_pending[0]), str(tup[0].num_pending[1]),
|
||||||
|
str(tup[0].num_cancelled[0]), str(tup[0].num_cancelled[1]),
|
||||||
|
str(tup[0].num_refunded[0]), str(tup[0].num_refunded[1]),
|
||||||
|
str(tup[0].num_paid[0]), str(tup[0].num_paid[1])
|
||||||
|
])
|
||||||
|
for item in tup[1]:
|
||||||
|
tdata.append([
|
||||||
|
item.name,
|
||||||
|
str(item.num_total[0]), str(item.num_total[1]),
|
||||||
|
str(item.num_pending[0]), str(item.num_pending[1]),
|
||||||
|
str(item.num_cancelled[0]), str(item.num_cancelled[1]),
|
||||||
|
str(item.num_refunded[0]), str(item.num_refunded[1]),
|
||||||
|
str(item.num_paid[0]), str(item.num_paid[1])
|
||||||
|
])
|
||||||
|
if item.has_variations:
|
||||||
|
for var in item.all_variations:
|
||||||
|
tdata.append([
|
||||||
|
" " + str(var),
|
||||||
|
str(var.num_total[0]), str(var.num_total[1]),
|
||||||
|
str(var.num_pending[0]), str(var.num_pending[1]),
|
||||||
|
str(var.num_cancelled[0]), str(var.num_cancelled[1]),
|
||||||
|
str(var.num_refunded[0]), str(var.num_refunded[1]),
|
||||||
|
str(var.num_paid[0]), str(var.num_paid[1])
|
||||||
|
])
|
||||||
|
|
||||||
|
tdata.append([
|
||||||
|
_("Total"),
|
||||||
|
str(total['num_total'][0]), str(total['num_total'][1]),
|
||||||
|
str(total['num_pending'][0]), str(total['num_pending'][1]),
|
||||||
|
str(total['num_cancelled'][0]), str(total['num_cancelled'][1]),
|
||||||
|
str(total['num_refunded'][0]), str(total['num_refunded'][1]),
|
||||||
|
str(total['num_paid'][0]), str(total['num_paid'][1])
|
||||||
|
])
|
||||||
|
|
||||||
|
table = Table(tdata, colWidths=colwidths, repeatRows=2)
|
||||||
|
table.setStyle(TableStyle(tstyledata))
|
||||||
|
story.append(table)
|
||||||
|
return story
|
||||||
|
|
||||||
|
|
||||||
|
class OverviewReportExporter(BaseExporter):
|
||||||
|
identifier = 'pdfreport'
|
||||||
|
verbose_name = _('Order overview (PDF)')
|
||||||
|
|
||||||
|
def render(self, request):
|
||||||
|
response = HttpResponse(content_type='application/pdf')
|
||||||
|
response['Content-Disposition'] = 'inline; filename="report-%s.pdf"' % request.event.slug
|
||||||
|
|
||||||
|
report = OverviewReport(request.event)
|
||||||
|
|
||||||
|
response.write(report.create())
|
||||||
|
return response
|
||||||
9
src/pretix/plugins/reports/signals.py
Normal file
9
src/pretix/plugins/reports/signals.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from pretix.base.signals import register_data_exporters
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(register_data_exporters, dispatch_uid="export_overview_report_pdf")
|
||||||
|
def register_report_pdf(sender, **kwargs):
|
||||||
|
from .exporters import OverviewReportExporter
|
||||||
|
return OverviewReportExporter
|
||||||
@@ -142,6 +142,7 @@ INSTALLED_APPS = (
|
|||||||
'pretix.plugins.ticketoutputpdf',
|
'pretix.plugins.ticketoutputpdf',
|
||||||
'pretix.plugins.sendmail',
|
'pretix.plugins.sendmail',
|
||||||
'pretix.plugins.statistics',
|
'pretix.plugins.statistics',
|
||||||
|
'pretix.plugins.reports',
|
||||||
'pretix.plugins.pretixdroid',
|
'pretix.plugins.pretixdroid',
|
||||||
'easy_thumbnails',
|
'easy_thumbnails',
|
||||||
)
|
)
|
||||||
|
|||||||
BIN
src/static/fonts/OpenSans-Bold.ttf
Normal file
BIN
src/static/fonts/OpenSans-Bold.ttf
Normal file
Binary file not shown.
BIN
src/static/fonts/OpenSans-Italic.ttf
Normal file
BIN
src/static/fonts/OpenSans-Italic.ttf
Normal file
Binary file not shown.
BIN
src/static/fonts/OpenSans-Regular.ttf
Normal file
BIN
src/static/fonts/OpenSans-Regular.ttf
Normal file
Binary file not shown.
Reference in New Issue
Block a user