From 478d8e4116956cc72090d514ed222a42e70d27b0 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Fri, 30 Nov 2018 16:10:32 +0100 Subject: [PATCH] Add export to .xlsx for lists --- src/pretix/base/exporter.py | 43 ++++++++++++++++---- src/pretix/base/exporters/orderlist.py | 6 +-- src/pretix/plugins/checkinlists/exporters.py | 2 +- src/requirements/production.txt | 1 + src/setup.py | 1 + 5 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/pretix/base/exporter.py b/src/pretix/base/exporter.py index 9f7125db82..d1737857d4 100644 --- a/src/pretix/base/exporter.py +++ b/src/pretix/base/exporter.py @@ -1,10 +1,13 @@ import io +import tempfile from collections import OrderedDict from typing import Tuple from defusedcsv import csv from django import forms from django.utils.translation import ugettext_lazy as _ +from openpyxl import Workbook +from openpyxl.cell.cell import KNOWN_TYPES class BaseExporter: @@ -61,7 +64,7 @@ class BaseExporter: """ return {} - def render(self, form_data: dict) -> Tuple[str, str, str]: + def render(self, form_data: dict) -> Tuple[str, str, bytes]: """ Render the exported file and return a tuple consisting of a filename, a file type and file content. @@ -87,6 +90,7 @@ class ListExporter(BaseExporter): forms.ChoiceField( label=_('Export format'), choices=( + ('xlsx', _('Excel (.xlsx)')), ('default', _('CSV (with commas)')), ('excel', _('CSV (Excel-style)')), ('semicolon', _('CSV (with semicolons)')), @@ -107,14 +111,35 @@ class ListExporter(BaseExporter): def get_filename(self): return 'export.csv' - def render(self, form_data: dict) -> Tuple[str, str, str]: + def _render_csv(self, form_data, **kwargs): output = io.StringIO() - if form_data.get('_format') == 'default': - writer = csv.writer(output, quoting=csv.QUOTE_NONNUMERIC, delimiter=",") - elif form_data.get('_format') == 'excel': - writer = csv.writer(output, dialect='excel') - elif form_data.get('_format') == 'semicolon': - writer = csv.writer(output, dialect='excel', delimiter=";") + writer = csv.writer(output, **kwargs) for line in self.iterate_list(form_data): writer.writerow(line) - return self.get_filename(), 'text/csv', output.getvalue().encode("utf-8") + return self.get_filename() + '.csv', 'text/csv', output.getvalue().encode("utf-8") + + def _render_xlsx(self, form_data): + wb = Workbook() + ws = wb.get_active_sheet() + try: + ws.title = str(self.verbose_name) + except: + pass + for i, line in enumerate(self.iterate_list(form_data)): + for j, val in enumerate(line): + ws.cell(row=i + 1, column=j + 1).value = str(val) if not isinstance(val, KNOWN_TYPES) else val + + with tempfile.NamedTemporaryFile(suffix='.xlsx') as f: + wb.save(f.name) + f.seek(0) + return self.get_filename() + '.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', f.read() + + def render(self, form_data: dict) -> Tuple[str, str, bytes]: + if form_data.get('_format') == 'xlsx': + return self._render_xlsx(form_data) + elif form_data.get('_format') == 'default': + return self._render_csv(form_data, quoting=csv.QUOTE_NONNUMERIC, delimiter=',') + elif form_data.get('_format') == 'csv-excel': + return self._render_csv(form_data, dialect='excel') + elif form_data.get('_format') == 'semicolon': + return self._render_csv(form_data, dialect='excel', delimiter=';') diff --git a/src/pretix/base/exporters/orderlist.py b/src/pretix/base/exporters/orderlist.py index af5b4b6709..0044776c24 100644 --- a/src/pretix/base/exporters/orderlist.py +++ b/src/pretix/base/exporters/orderlist.py @@ -161,7 +161,7 @@ class OrderListExporter(ListExporter): yield row def get_filename(self): - return '{}_orders.csv'.format(self.event.slug) + return '{}_orders'.format(self.event.slug) class PaymentListExporter(ListExporter): @@ -231,7 +231,7 @@ class PaymentListExporter(ListExporter): yield row def get_filename(self): - return '{}_payments.csv'.format(self.event.slug) + return '{}_payments'.format(self.event.slug) class QuotaListExporter(ListExporter): @@ -260,7 +260,7 @@ class QuotaListExporter(ListExporter): yield row def get_filename(self): - return '{}_quotas.csv'.format(self.event.slug) + return '{}_quotas'.format(self.event.slug) @receiver(register_data_exporters, dispatch_uid="exporter_orderlist") diff --git a/src/pretix/plugins/checkinlists/exporters.py b/src/pretix/plugins/checkinlists/exporters.py index 42fcc497d6..a919b9cab6 100644 --- a/src/pretix/plugins/checkinlists/exporters.py +++ b/src/pretix/plugins/checkinlists/exporters.py @@ -375,4 +375,4 @@ class CSVCheckinList(CheckInListMixin, ListExporter): yield row def get_filename(self): - return '{}_checkin.csv'.format(self.event.slug) + return '{}_checkin'.format(self.event.slug) diff --git a/src/requirements/production.txt b/src/requirements/production.txt index d336138a06..bc56507e52 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -33,6 +33,7 @@ raven babel django-i18nfield>=1.4.0 django-hijack>=2.1.10,<2.2.0 +openpyxl django-oauth-toolkit==1.2.* django-jsonfallback psycopg2-binary diff --git a/src/setup.py b/src/setup.py index d6e2886b73..70b27809e1 100644 --- a/src/setup.py +++ b/src/setup.py @@ -137,6 +137,7 @@ setup( 'vat_moss==0.11.0', 'django-localflavor', 'django-hijack>=2.1.10,<2.2.0', + 'openpyxl', 'django-oauth-toolkit==1.2.*', 'idna==2.6', # required by current requests ],