Reorganize UI for exporters (#3025)

Co-authored-by: Richard Schreiber <schreiber@rami.io>
This commit is contained in:
Raphael Michel
2023-01-11 12:34:56 +01:00
committed by GitHub
parent cb4af51c01
commit 0ae66ab7f6
22 changed files with 275 additions and 121 deletions

View File

@@ -76,6 +76,10 @@ The exporter class
This is an abstract attribute, you **must** override this! This is an abstract attribute, you **must** override this!
.. autoattribute:: description
.. autoattribute:: category
.. autoattribute:: export_form_fields .. autoattribute:: export_form_fields
.. automethod:: render .. automethod:: render

View File

@@ -36,7 +36,7 @@ import io
import tempfile import tempfile
from collections import OrderedDict, namedtuple from collections import OrderedDict, namedtuple
from decimal import Decimal from decimal import Decimal
from typing import Tuple from typing import Optional, Tuple
import pytz import pytz
from defusedcsv import csv from defusedcsv import csv
@@ -84,6 +84,27 @@ class BaseExporter:
""" """
raise NotImplementedError() # NOQA raise NotImplementedError() # NOQA
@property
def description(self) -> str:
"""
A description for this exporter.
"""
return ""
@property
def category(self) -> Optional[str]:
"""
A category name for this exporter, or ``None``.
"""
return None
@property
def featured(self) -> bool:
"""
If ``True``, this exporter will be highlighted.
"""
return False
@property @property
def identifier(self) -> str: def identifier(self) -> str:
""" """

View File

@@ -39,7 +39,7 @@ from zipfile import ZipFile
from django import forms from django import forms
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import QuestionAnswer from pretix.base.models import QuestionAnswer
@@ -49,7 +49,10 @@ from ..signals import register_data_exporters
class AnswerFilesExporter(BaseExporter): class AnswerFilesExporter(BaseExporter):
identifier = 'answerfiles' identifier = 'answerfiles'
verbose_name = _('Answers to file upload questions') verbose_name = _('Question answer file uploads')
category = pgettext_lazy('export_category', 'Order data')
description = _('Download a ZIP file including all files that have been uploaded by your customers while creating '
'an order.')
@property @property
def export_form_fields(self): def export_form_fields(self):

View File

@@ -36,7 +36,7 @@ from collections import OrderedDict
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.timezone import get_current_timezone from django.utils.timezone import get_current_timezone
from django.utils.translation import gettext as _, gettext_lazy from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.base.settings import PERSON_NAME_SCHEMES
@@ -48,6 +48,8 @@ class CustomerListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'customerlist' identifier = 'customerlist'
verbose_name = gettext_lazy('Customer accounts') verbose_name = gettext_lazy('Customer accounts')
organizer_required_permission = 'can_manage_customers' organizer_required_permission = 'can_manage_customers'
category = pgettext_lazy('export_category', 'Customer accounts')
description = gettext_lazy('Download a spreadsheet of all currently registered customer accounts.')
@property @property
def additional_form_fields(self): def additional_form_fields(self):

View File

@@ -27,7 +27,7 @@ import dateutil
from django import forms from django import forms
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext, gettext_lazy from django.utils.translation import gettext, gettext_lazy, pgettext_lazy
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Invoice, OrderPayment from pretix.base.models import Invoice, OrderPayment
@@ -39,6 +39,8 @@ from ..signals import register_data_exporters
class DekodiNREIExporter(BaseExporter): class DekodiNREIExporter(BaseExporter):
identifier = 'dekodi_nrei' identifier = 'dekodi_nrei'
verbose_name = 'dekodi NREI (JSON)' verbose_name = 'dekodi NREI (JSON)'
category = pgettext_lazy('export_category', 'Invoices')
description = gettext_lazy("Download invoices in a format that can be used by the dekodi NREI conversion software.")
# Specification: http://manuals.dekodi.de/nexuspub/schnittstellenbuch/ # Specification: http://manuals.dekodi.de/nexuspub/schnittstellenbuch/

View File

@@ -35,7 +35,7 @@
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from ...control.forms.filter import get_all_payment_providers from ...control.forms.filter import get_all_payment_providers
from ..exporter import ListExporter from ..exporter import ListExporter
@@ -45,6 +45,8 @@ from ..signals import register_multievent_data_exporters
class EventDataExporter(ListExporter): class EventDataExporter(ListExporter):
identifier = 'eventdata' identifier = 'eventdata'
verbose_name = _('Event data') verbose_name = _('Event data')
category = pgettext_lazy('export_category', 'Event data')
description = _('Download a spreadsheet with information on all events in this organizer account.')
@cached_property @cached_property
def providers(self): def providers(self):

View File

@@ -44,7 +44,9 @@ from django.db.models import CharField, Exists, F, OuterRef, Q, Subquery, Sum
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import gettext, gettext_lazy as _, pgettext from django.utils.translation import (
gettext, gettext_lazy as _, pgettext, pgettext_lazy,
)
from pretix.base.models import Invoice, InvoiceLine, OrderPayment from pretix.base.models import Invoice, InvoiceLine, OrderPayment
@@ -60,6 +62,7 @@ from ..signals import (
class InvoiceExporterMixin: class InvoiceExporterMixin:
category = pgettext_lazy('export_category', 'Invoices')
@property @property
def invoice_exporter_form_fields(self): def invoice_exporter_form_fields(self):
@@ -129,6 +132,7 @@ class InvoiceExporterMixin:
class InvoiceExporter(InvoiceExporterMixin, BaseExporter): class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
identifier = 'invoices' identifier = 'invoices'
verbose_name = _('All invoices') verbose_name = _('All invoices')
description = _('Download all invoices created by the system as a ZIP file of PDF files.')
def render(self, form_data: dict, output_file=None): def render(self, form_data: dict, output_file=None):
qs = self.invoices_queryset(form_data).filter(shredded=False) qs = self.invoices_queryset(form_data).filter(shredded=False)
@@ -180,6 +184,10 @@ class InvoiceExporter(InvoiceExporterMixin, BaseExporter):
class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter): class InvoiceDataExporter(InvoiceExporterMixin, MultiSheetListExporter):
identifier = 'invoicedata' identifier = 'invoicedata'
verbose_name = _('Invoice data') verbose_name = _('Invoice data')
description = _('Download a spreadsheet with the data of all invoices created by the system. The spreadsheet '
'includes two sheets, one with a line for every invoice, and one with a line for every position of '
'every invoice.')
featured = True
@property @property
def additional_form_fields(self): def additional_form_fields(self):

View File

@@ -22,7 +22,7 @@
from django.db.models import Prefetch from django.db.models import Prefetch
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from openpyxl.styles import Alignment from openpyxl.styles import Alignment
from openpyxl.utils import get_column_letter from openpyxl.utils import get_column_letter
@@ -48,6 +48,8 @@ def _min(a1, a2):
class ItemDataExporter(ListExporter): class ItemDataExporter(ListExporter):
identifier = 'itemdata' identifier = 'itemdata'
verbose_name = _('Product data') verbose_name = _('Product data')
category = pgettext_lazy('export_category', 'Product data')
description = _('Download a spreadsheet with details about all products and variations.')
def iterate_list(self, form_data): def iterate_list(self, form_data):
locales = self.event.settings.locales locales = self.event.settings.locales

View File

@@ -38,6 +38,7 @@ from decimal import Decimal
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import Prefetch from django.db.models import Prefetch
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext_lazy, pgettext_lazy
from ..exporter import BaseExporter from ..exporter import BaseExporter
from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue from ..models import ItemMetaValue, ItemVariation, ItemVariationMetaValue
@@ -47,6 +48,9 @@ from ..signals import register_data_exporters
class JSONExporter(BaseExporter): class JSONExporter(BaseExporter):
identifier = 'json' identifier = 'json'
verbose_name = 'Order data (JSON)' verbose_name = 'Order data (JSON)'
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a structured JSON representation of all orders. This might be useful for the '
'import in third-party systems.')
def render(self, form_data): def render(self, form_data):
jo = { jo = {

View File

@@ -36,7 +36,7 @@ from collections import OrderedDict
from django import forms from django import forms
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _, pgettext_lazy
from pretix.base.models import OrderPosition from pretix.base.models import OrderPosition
@@ -50,6 +50,8 @@ from ..signals import (
class MailExporter(BaseExporter): class MailExporter(BaseExporter):
identifier = 'mailaddrs' identifier = 'mailaddrs'
verbose_name = _('Email addresses (text file)') verbose_name = _('Email addresses (text file)')
category = pgettext_lazy('export_category', 'Order data')
description = _("Download a text file with all email addresses collected either from buyers or from ticket holders.")
def render(self, form_data: dict): def render(self, form_data: dict):
qs = Order.objects.filter(event__in=self.events, status__in=form_data['status']).prefetch_related('event') qs = Order.objects.filter(event__in=self.events, status__in=form_data['status']).prefetch_related('event')

View File

@@ -47,7 +47,9 @@ from django.db.models.functions import Coalesce
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import get_current_timezone, make_aware, now from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext as _, gettext_lazy, pgettext from django.utils.translation import (
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
from pretix.base.models import ( from pretix.base.models import (
GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order, GiftCard, GiftCardTransaction, Invoice, InvoiceAddress, Order,
@@ -71,6 +73,11 @@ from ..signals import (
class OrderListExporter(MultiSheetListExporter): class OrderListExporter(MultiSheetListExporter):
identifier = 'orderlist' identifier = 'orderlist'
verbose_name = gettext_lazy('Order data') verbose_name = gettext_lazy('Order data')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all orders. The spreadsheet will include three sheets, one '
'with a line for every order, one with a line for every order position, and one with '
'a line for every additional fee charged in an order.')
featured = True
@cached_property @cached_property
def providers(self): def providers(self):
@@ -776,7 +783,10 @@ class OrderListExporter(MultiSheetListExporter):
class PaymentListExporter(ListExporter): class PaymentListExporter(ListExporter):
identifier = 'paymentlist' identifier = 'paymentlist'
verbose_name = gettext_lazy('Order payments and refunds') verbose_name = gettext_lazy('Payments and refunds')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds of every order.')
featured = True
@property @property
def additional_form_fields(self): def additional_form_fields(self):
@@ -855,6 +865,8 @@ class PaymentListExporter(ListExporter):
class QuotaListExporter(ListExporter): class QuotaListExporter(ListExporter):
identifier = 'quotalist' identifier = 'quotalist'
verbose_name = gettext_lazy('Quota availabilities') verbose_name = gettext_lazy('Quota availabilities')
category = pgettext_lazy('export_category', 'Product data')
description = gettext_lazy('Download a spreadsheet of all quotas including their current availability.')
def iterate_list(self, form_data): def iterate_list(self, form_data):
has_subevents = self.event.has_subevents has_subevents = self.event.has_subevents
@@ -908,6 +920,8 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardtransactionlist' identifier = 'giftcardtransactionlist'
verbose_name = gettext_lazy('Gift card transactions') verbose_name = gettext_lazy('Gift card transactions')
organizer_required_permission = 'can_manage_gift_cards' organizer_required_permission = 'can_manage_gift_cards'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift card transactions.')
@property @property
def additional_form_fields(self): def additional_form_fields(self):
@@ -978,6 +992,8 @@ class GiftcardTransactionListExporter(OrganizerLevelExportMixin, ListExporter):
class GiftcardRedemptionListExporter(ListExporter): class GiftcardRedemptionListExporter(ListExporter):
identifier = 'giftcardredemptionlist' identifier = 'giftcardredemptionlist'
verbose_name = gettext_lazy('Gift card redemptions') verbose_name = gettext_lazy('Gift card redemptions')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy('Download a spreadsheet of all payments or refunds that involve gift cards.')
def iterate_list(self, form_data): def iterate_list(self, form_data):
payments = OrderPayment.objects.filter( payments = OrderPayment.objects.filter(
@@ -1023,6 +1039,8 @@ class GiftcardListExporter(OrganizerLevelExportMixin, ListExporter):
identifier = 'giftcardlist' identifier = 'giftcardlist'
verbose_name = gettext_lazy('Gift cards') verbose_name = gettext_lazy('Gift cards')
organizer_required_permission = 'can_manage_gift_cards' organizer_required_permission = 'can_manage_gift_cards'
category = pgettext_lazy('export_category', 'Gift cards')
description = gettext_lazy('Download a spreadsheet of all gift cards including their current value.')
@property @property
def additional_form_fields(self): def additional_form_fields(self):

View File

@@ -39,6 +39,8 @@ from ..signals import (
class WaitingListExporter(ListExporter): class WaitingListExporter(ListExporter):
identifier = 'waitinglist' identifier = 'waitinglist'
verbose_name = _('Waiting list') verbose_name = _('Waiting list')
category = pgettext_lazy('export_category', 'Waiting list')
description = _('Download a spread sheet with all your waiting list data.')
# map selected status to label and queryset-filter # map selected status to label and queryset-filter
status_filters = [ status_filters = [

View File

@@ -4,35 +4,32 @@
{% load order_overview %} {% load order_overview %}
{% block title %}{% trans "Data export" %}{% endblock %} {% block title %}{% trans "Data export" %}{% endblock %}
{% block content %} {% block content %}
<h1> <h1>
{% trans "Data export" %} {% trans "Data export" %}
{% if "identifier" in request.GET %}
<a href="?" class="btn btn-default">{% trans "Show all" %}</a>
{% endif %}
</h1> </h1>
{% for e in exporters %} {% regroup exporters by category as category_list %}
<details class="panel panel-default" {% for c, c_ex in category_list %}
{% if request.GET.identifier == e.identifier or request.POST.exporter == e.identifier %}open{% endif %}> {% if c %}
<summary class="panel-heading"> <h2>{{ c }}</h2>
<h3 class="panel-title"> {% else %}
{{ e.verbose_name }} <h2>{% trans "Other exports" %}</h2>
<i class="fa fa-angle-down collapse-indicator"></i> {% endif %}
</h3> <div class="list-group large-link-group">
</summary> {% for e in c_ex %}
<div id="{{ e.identifier }}"> <a class="list-group-item" href="?identifier={{ e.identifier }}">
<div class="panel-body"> <h4>
<form action="{% url "control:event.orders.export.do" event=request.event.slug organizer=request.organizer.slug %}" {{ e.verbose_name }}
method="post" class="form-horizontal" data-asynctask data-asynctask-download {% if e.featured %}
data-asynctask-long> <span class="fa fa-star text-success" data-toggle="tooltip" title="{% trans "Recommended for new users" %}" aria-hidden="true"></span>
{% csrf_token %} {% endif %}
<input type="hidden" name="exporter" value="{{ e.identifier }}" /> </h4>
{% bootstrap_form e.form layout='control' %} {% if e.description %}
<button class="btn btn-primary pull-right flip" type="submit"> <p>
<span class="icon icon-upload"></span> {% trans "Start export" %} {{ e.description }}
</button> </p>
</form> {% endif %}
</div> </a>
</div> {% endfor %}
</details> </div>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load order_overview %}
{% block title %}{% trans "Data export" %}{% endblock %}
{% block content %}
<h1>
{% trans "Data export" %}
{% if exporter %}
<small>
{{ exporter.verbose_name }}
</small>
{% endif %}
</h1>
{% if exporter.description %}
<p class="help-block">{{ exporter.description }}</p>
{% endif %}
<form action="{% url "control:event.orders.export.do" event=request.event.slug organizer=request.organizer.slug %}"
method="post" class="form-horizontal" data-asynctask data-asynctask-download
data-asynctask-long>
{% csrf_token %}
<input type="hidden" name="exporter" value="{{ exporter.identifier }}"/>
{% bootstrap_form exporter.form layout='control' %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Start export" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -4,34 +4,27 @@
{% load order_overview %} {% load order_overview %}
{% block title %}{% trans "Data export" %}{% endblock %} {% block title %}{% trans "Data export" %}{% endblock %}
{% block content %} {% block content %}
<h1> <h1>
{% trans "Data export" %} {% trans "Data export" %}
{% if "identifier" in request.GET %}
<a href="?" class="btn btn-default">{% trans "Show all" %}</a>
{% endif %}
</h1> </h1>
{% for e in exporters %} {% regroup exporters by category as category_list %}
<details class="panel panel-default" {% if "identifier" in request.GET or "exporter" in request.POST %}open{% endif %}> {% for c, c_ex in category_list %}
<summary class="panel-heading"> {% if c %}
<h3 class="panel-title"> <h2>{{ c }}</h2>
{{ e.verbose_name }} {% else %}
<i class="fa fa-angle-down collapse-indicator"></i> <h2>{% trans "Other exports" %}</h2>
</h3> {% endif %}
</summary> <div class="list-group large-link-group">
<div id="{{ e.identifier }}"> {% for e in c_ex %}
<div class="panel-body"> <a class="list-group-item" href="?identifier={{ e.identifier }}">
<form action="{% url "control:organizer.export.do" organizer=request.organizer.slug %}" <h4>{{ e.verbose_name }}</h4>
method="post" class="form-horizontal" data-asynctask data-asynctask-download {% if e.description %}
data-asynctask-long> <p>
{% csrf_token %} {{ e.description }}
<input type="hidden" name="exporter" value="{{ e.identifier }}" /> </p>
{% bootstrap_form e.form layout='control' %} {% endif %}
<button class="btn btn-primary pull-right flip" type="submit"> </a>
<span class="icon icon-upload"></span> {% trans "Start export" %} {% endfor %}
</button> </div>
</form>
</div>
</div>
</details>
{% endfor %} {% endfor %}
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends "pretixcontrol/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% load order_overview %}
{% block title %}{% trans "Data export" %}{% endblock %}
{% block content %}
<h1>
{% trans "Data export" %}
{% if exporter %}
<small>
{{ exporter.verbose_name }}
</small>
{% endif %}
</h1>
{% if exporter.description %}
<p class="help-block">{{ exporter.description }}</p>
{% endif %}
<form action="{% url "control:organizer.export.do" organizer=request.organizer.slug %}"
method="post" class="form-horizontal" data-asynctask data-asynctask-download
data-asynctask-long>
{% csrf_token %}
<input type="hidden" name="exporter" value="{{ exporter.identifier }}"/>
{% bootstrap_form exporter.form layout='control' %}
<div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save">
{% trans "Start export" %}
</button>
</div>
</form>
{% endblock %}

View File

@@ -2237,11 +2237,19 @@ class OrderGo(EventPermissionRequiredMixin, View):
class ExportMixin: class ExportMixin:
@cached_property @cached_property
def exporters(self): def exporters(self):
exporters = []
responses = register_data_exporters.send(self.request.event) responses = register_data_exporters.send(self.request.event)
return sorted(
[response(self.request.event, self.request.organizer) for r, response in responses if response],
key=lambda ex: (0 if ex.category else 1, ex.category or "", 0 if ex.featured else 1, str(ex.verbose_name).lower())
)
@cached_property
def exporter(self):
id = self.request.GET.get("identifier") or self.request.POST.get("exporter") id = self.request.GET.get("identifier") or self.request.POST.get("exporter")
for ex in sorted([response(self.request.event, self.request.organizer) for r, response in responses if response], key=lambda ex: str(ex.verbose_name)): if not id:
if id and ex.identifier != id: return None
for ex in self.exporters:
if id != ex.identifier:
continue continue
# Use form parse cycle to generate useful defaults # Use form parse cycle to generate useful defaults
@@ -2258,12 +2266,12 @@ class ExportMixin:
initial=initial initial=initial
) )
ex.form.fields = ex.export_form_fields ex.form.fields = ex.export_form_fields
exporters.append(ex) return ex
return exporters
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['exporters'] = self.exporters ctx['exporters'] = self.exporters
ctx['exporter'] = self.exporter
return ctx return ctx
@@ -2288,16 +2296,6 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, Templ
def get_check_url(self, task_id, ajax): def get_check_url(self, task_id, ajax):
return self.request.path + '?async_id=%s&exporter=%s' % (task_id, self.exporter.identifier) + ('&ajax=1' if ajax else '') return self.request.path + '?async_id=%s&exporter=%s' % (task_id, self.exporter.identifier) + ('&ajax=1' if ajax else '')
@cached_property
def exporter(self):
if self.request.method == "POST":
identifier = self.request.POST.get("exporter")
else:
identifier = self.request.GET.get("exporter")
for ex in self.exporters:
if ex.identifier == identifier:
return ex
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'async_id' in request.GET and settings.HAS_CELERY: if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request) return self.get_result(request)
@@ -2324,7 +2322,11 @@ class ExportDoView(EventPermissionRequiredMixin, ExportMixin, AsyncAction, Templ
class ExportView(EventPermissionRequiredMixin, ExportMixin, TemplateView): class ExportView(EventPermissionRequiredMixin, ExportMixin, TemplateView):
permission = 'can_view_orders' permission = 'can_view_orders'
template_name = 'pretixcontrol/orders/export.html'
def get_template_names(self):
if self.exporter:
return ['pretixcontrol/orders/export_form.html']
return ['pretixcontrol/orders/export.html']
class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView): class RefundList(EventPermissionRequiredMixin, PaginationMixin, ListView):

View File

@@ -1507,29 +1507,13 @@ class GiftCardUpdateView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMi
class ExportMixin: class ExportMixin:
@cached_property @cached_property
def exporters(self): def exporter(self):
exporters = []
events = self.request.user.get_events_with_permission('can_view_orders', request=self.request).filter(
organizer=self.request.organizer
)
responses = register_multievent_data_exporters.send(self.request.organizer)
id = self.request.GET.get("identifier") or self.request.POST.get("exporter") id = self.request.GET.get("identifier") or self.request.POST.get("exporter")
raw_exporters = [ if not id:
response(Event.objects.none() if issubclass(response, OrganizerLevelExportMixin) else events, self.request.organizer) return None
for r, response in responses for ex in self.exporters:
if response if id != ex.identifier:
]
raw_exporters = [
ex for ex in raw_exporters
if (
not isinstance(ex, OrganizerLevelExportMixin) or
self.request.user.has_organizer_permission(self.request.organizer, ex.organizer_required_permission, self.request)
)
]
for ex in sorted(raw_exporters, key=lambda ex: str(ex.verbose_name)):
if id and ex.identifier != id:
continue continue
# Use form parse cycle to generate useful defaults # Use form parse cycle to generate useful defaults
test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier) test_form = ExporterForm(data=self.request.GET, prefix=ex.identifier)
test_form.fields = ex.export_form_fields test_form.fields = ex.export_form_fields
@@ -1548,8 +1532,8 @@ class ExportMixin:
ex.form.fields.update([ ex.form.fields.update([
('events', ('events',
forms.ModelMultipleChoiceField( forms.ModelMultipleChoiceField(
queryset=events, queryset=self.events,
initial=events, initial=self.events,
widget=forms.CheckboxSelectMultiple( widget=forms.CheckboxSelectMultiple(
attrs={'class': 'scrolling-multiple-choice'} attrs={'class': 'scrolling-multiple-choice'}
), ),
@@ -1557,11 +1541,37 @@ class ExportMixin:
required=True required=True
)), )),
]) ])
exporters.append(ex) return ex
return exporters
@cached_property
def events(self):
return self.request.user.get_events_with_permission('can_view_orders', request=self.request).filter(
organizer=self.request.organizer
)
@cached_property
def exporters(self):
responses = register_multievent_data_exporters.send(self.request.organizer)
raw_exporters = [
response(Event.objects.none() if issubclass(response, OrganizerLevelExportMixin) else self.events, self.request.organizer)
for r, response in responses
if response
]
raw_exporters = [
ex for ex in raw_exporters
if (
not isinstance(ex, OrganizerLevelExportMixin) or
self.request.user.has_organizer_permission(self.request.organizer, ex.organizer_required_permission, self.request)
)
]
return sorted(
raw_exporters,
key=lambda ex: (0 if ex.category else 1, ex.category or "", 0 if ex.featured else 1, str(ex.verbose_name).lower())
)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs) ctx = super().get_context_data(**kwargs)
ctx['exporter'] = self.exporter
ctx['exporters'] = self.exporters ctx['exporters'] = self.exporters
return ctx return ctx
@@ -1582,12 +1592,6 @@ class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, T
'organizer': self.request.organizer.slug 'organizer': self.request.organizer.slug
}) })
@cached_property
def exporter(self):
for ex in self.exporters:
if ex.identifier == self.request.POST.get("exporter"):
return ex
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
if 'async_id' in request.GET and settings.HAS_CELERY: if 'async_id' in request.GET and settings.HAS_CELERY:
return self.get_result(request) return self.get_result(request)
@@ -1621,7 +1625,10 @@ class ExportDoView(OrganizerPermissionRequiredMixin, ExportMixin, AsyncAction, T
class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView): class ExportView(OrganizerPermissionRequiredMixin, ExportMixin, TemplateView):
template_name = 'pretixcontrol/organizers/export.html' def get_template_names(self):
if self.exporter:
return ['pretixcontrol/organizers/export_form.html']
return ['pretixcontrol/organizers/export.html']
class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView): class GateListView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin, ListView):

View File

@@ -49,7 +49,7 @@ from django.db import DataError, models
from django.db.models import Exists, OuterRef, Q, Subquery from django.db.models import Exists, OuterRef, Q, Subquery
from django.db.models.functions import Cast, Coalesce from django.db.models.functions import Cast, Coalesce
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
from django.utils.translation import gettext as _, gettext_lazy from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from PyPDF2 import PdfMerger, PdfReader, PdfWriter, Transformation from PyPDF2 import PdfMerger, PdfReader, PdfWriter, Transformation
from PyPDF2.generic import RectangleObject from PyPDF2.generic import RectangleObject
from reportlab.lib import pagesizes from reportlab.lib import pagesizes
@@ -246,6 +246,9 @@ def render_pdf(event, positions, opt):
class BadgeExporter(BaseExporter): class BadgeExporter(BaseExporter):
identifier = "badges" identifier = "badges"
verbose_name = _("Attendee badges") verbose_name = _("Attendee badges")
category = pgettext_lazy('export_category', 'PDF collections')
description = gettext_lazy('Download all attendee badges as one large PDF for printing.')
featured = True
@property @property
def export_form_fields(self): def export_form_fields(self):

View File

@@ -45,7 +45,9 @@ from django.db.models.functions import Coalesce, NullIf
from django.urls import reverse from django.urls import reverse
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.timezone import is_aware, make_aware from django.utils.timezone import is_aware, make_aware
from django.utils.translation import gettext as _, gettext_lazy, pgettext from django.utils.translation import (
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
from pytz import UTC from pytz import UTC
from reportlab.lib.units import mm from reportlab.lib.units import mm
from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle from reportlab.platypus import Flowable, Paragraph, Spacer, Table, TableStyle
@@ -263,6 +265,9 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
name = "overview" name = "overview"
identifier = 'checkinlistpdf' identifier = 'checkinlistpdf'
verbose_name = gettext_lazy('Check-in list (PDF)') verbose_name = gettext_lazy('Check-in list (PDF)')
category = pgettext_lazy('export_category', 'Check-in')
description = gettext_lazy("Download a PDF version of a check-in list that can be used to check people in at the "
"event without digital methods.")
@property @property
def export_form_fields(self): def export_form_fields(self):
@@ -420,6 +425,9 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
name = "overview" name = "overview"
identifier = 'checkinlist' identifier = 'checkinlist'
verbose_name = gettext_lazy('Check-in list') verbose_name = gettext_lazy('Check-in list')
category = pgettext_lazy('export_category', 'Check-in')
description = gettext_lazy("Download a spreadsheet with all attendees that are included in a check-in list.")
featured = True
@property @property
def additional_form_fields(self): def additional_form_fields(self):
@@ -601,6 +609,9 @@ class CheckinLogList(ListExporter):
name = "checkinlog" name = "checkinlog"
identifier = 'checkinlog' identifier = 'checkinlog'
verbose_name = gettext_lazy('Check-in log (all scans)') verbose_name = gettext_lazy('Check-in log (all scans)')
category = pgettext_lazy('export_category', 'Check-in')
description = gettext_lazy("Download a spreadsheet with one line for every scan that happened at your check-in "
"stations.")
@property @property
def additional_form_fields(self): def additional_form_fields(self):

View File

@@ -48,7 +48,9 @@ from django.db.models import DateTimeField, Max, OuterRef, Subquery, Sum
from django.template.defaultfilters import floatformat from django.template.defaultfilters import floatformat
from django.utils.formats import date_format, localize from django.utils.formats import date_format, localize
from django.utils.timezone import get_current_timezone, make_aware, now from django.utils.timezone import get_current_timezone, make_aware, now
from django.utils.translation import gettext as _, gettext_lazy, pgettext from django.utils.translation import (
gettext as _, gettext_lazy, pgettext, pgettext_lazy,
)
from django_countries.fields import Country from django_countries.fields import Country
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER from reportlab.lib.enums import TA_CENTER
@@ -197,6 +199,9 @@ class OverviewReport(Report):
name = "overview" name = "overview"
identifier = 'pdfreport' identifier = 'pdfreport'
verbose_name = gettext_lazy('Order overview (PDF)') verbose_name = gettext_lazy('Order overview (PDF)')
category = pgettext_lazy('export_category', 'Analysis')
description = gettext_lazy('Download a PDF version of the key sales numbers per ticket type.')
featured = True
@property @property
def pagesize(self): def pagesize(self):
@@ -382,7 +387,9 @@ class OverviewReport(Report):
class OrderTaxListReportPDF(Report): class OrderTaxListReportPDF(Report):
name = "ordertaxlist" name = "ordertaxlist"
identifier = 'ordertaxes' identifier = 'ordertaxes'
verbose_name = gettext_lazy('List of orders with taxes (PDF)') verbose_name = gettext_lazy('Tax split list (PDF)')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy("Download a PDF list with the tax amounts included in each order.")
@property @property
def export_form_fields(self): def export_form_fields(self):
@@ -553,7 +560,9 @@ class OrderTaxListReportPDF(Report):
class OrderTaxListReport(MultiSheetListExporter): class OrderTaxListReport(MultiSheetListExporter):
identifier = 'ordertaxeslist' identifier = 'ordertaxeslist'
verbose_name = gettext_lazy('List of orders with taxes') verbose_name = gettext_lazy('Tax split list')
category = pgettext_lazy('export_category', 'Order data')
description = gettext_lazy("Download a spreadsheet with the tax amounts included in each order.")
@property @property
def sheets(self): def sheets(self):

View File

@@ -44,7 +44,7 @@ from django.db import DataError, models
from django.db.models import OuterRef, Q, Subquery from django.db.models import OuterRef, Q, Subquery
from django.db.models.functions import Cast, Coalesce from django.db.models.functions import Cast, Coalesce
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
from django.utils.translation import gettext as _, gettext_lazy from django.utils.translation import gettext as _, gettext_lazy, pgettext_lazy
from PyPDF2 import PdfMerger from PyPDF2 import PdfMerger
from pretix.base.exporter import BaseExporter from pretix.base.exporter import BaseExporter
@@ -63,7 +63,9 @@ logger = logging.getLogger(__name__)
class AllTicketsPDF(BaseExporter): class AllTicketsPDF(BaseExporter):
name = "alltickets" name = "alltickets"
verbose_name = gettext_lazy("All PDF tickets in one file") verbose_name = gettext_lazy("Tickets")
category = pgettext_lazy('export_category', 'PDF collections')
description = gettext_lazy("Download PDF versions of all tickets in your event as one large PDF file.")
identifier = "pdfoutput_all_tickets" identifier = "pdfoutput_all_tickets"
@property @property