forked from CGM_Public/pretix_original
Add export to .xlsx for lists
This commit is contained in:
@@ -1,10 +1,13 @@
|
|||||||
import io
|
import io
|
||||||
|
import tempfile
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from defusedcsv import csv
|
from defusedcsv import csv
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from openpyxl import Workbook
|
||||||
|
from openpyxl.cell.cell import KNOWN_TYPES
|
||||||
|
|
||||||
|
|
||||||
class BaseExporter:
|
class BaseExporter:
|
||||||
@@ -61,7 +64,7 @@ class BaseExporter:
|
|||||||
"""
|
"""
|
||||||
return {}
|
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
|
Render the exported file and return a tuple consisting of a filename, a file type
|
||||||
and file content.
|
and file content.
|
||||||
@@ -87,6 +90,7 @@ class ListExporter(BaseExporter):
|
|||||||
forms.ChoiceField(
|
forms.ChoiceField(
|
||||||
label=_('Export format'),
|
label=_('Export format'),
|
||||||
choices=(
|
choices=(
|
||||||
|
('xlsx', _('Excel (.xlsx)')),
|
||||||
('default', _('CSV (with commas)')),
|
('default', _('CSV (with commas)')),
|
||||||
('excel', _('CSV (Excel-style)')),
|
('excel', _('CSV (Excel-style)')),
|
||||||
('semicolon', _('CSV (with semicolons)')),
|
('semicolon', _('CSV (with semicolons)')),
|
||||||
@@ -107,14 +111,35 @@ class ListExporter(BaseExporter):
|
|||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
return 'export.csv'
|
return 'export.csv'
|
||||||
|
|
||||||
def render(self, form_data: dict) -> Tuple[str, str, str]:
|
def _render_csv(self, form_data, **kwargs):
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
if form_data.get('_format') == 'default':
|
writer = csv.writer(output, **kwargs)
|
||||||
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=";")
|
|
||||||
for line in self.iterate_list(form_data):
|
for line in self.iterate_list(form_data):
|
||||||
writer.writerow(line)
|
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=';')
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ class OrderListExporter(ListExporter):
|
|||||||
yield row
|
yield row
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
return '{}_orders.csv'.format(self.event.slug)
|
return '{}_orders'.format(self.event.slug)
|
||||||
|
|
||||||
|
|
||||||
class PaymentListExporter(ListExporter):
|
class PaymentListExporter(ListExporter):
|
||||||
@@ -231,7 +231,7 @@ class PaymentListExporter(ListExporter):
|
|||||||
yield row
|
yield row
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
return '{}_payments.csv'.format(self.event.slug)
|
return '{}_payments'.format(self.event.slug)
|
||||||
|
|
||||||
|
|
||||||
class QuotaListExporter(ListExporter):
|
class QuotaListExporter(ListExporter):
|
||||||
@@ -260,7 +260,7 @@ class QuotaListExporter(ListExporter):
|
|||||||
yield row
|
yield row
|
||||||
|
|
||||||
def get_filename(self):
|
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")
|
@receiver(register_data_exporters, dispatch_uid="exporter_orderlist")
|
||||||
|
|||||||
@@ -375,4 +375,4 @@ class CSVCheckinList(CheckInListMixin, ListExporter):
|
|||||||
yield row
|
yield row
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
return '{}_checkin.csv'.format(self.event.slug)
|
return '{}_checkin'.format(self.event.slug)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ raven
|
|||||||
babel
|
babel
|
||||||
django-i18nfield>=1.4.0
|
django-i18nfield>=1.4.0
|
||||||
django-hijack>=2.1.10,<2.2.0
|
django-hijack>=2.1.10,<2.2.0
|
||||||
|
openpyxl
|
||||||
django-oauth-toolkit==1.2.*
|
django-oauth-toolkit==1.2.*
|
||||||
django-jsonfallback
|
django-jsonfallback
|
||||||
psycopg2-binary
|
psycopg2-binary
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ setup(
|
|||||||
'vat_moss==0.11.0',
|
'vat_moss==0.11.0',
|
||||||
'django-localflavor',
|
'django-localflavor',
|
||||||
'django-hijack>=2.1.10,<2.2.0',
|
'django-hijack>=2.1.10,<2.2.0',
|
||||||
|
'openpyxl',
|
||||||
'django-oauth-toolkit==1.2.*',
|
'django-oauth-toolkit==1.2.*',
|
||||||
'idna==2.6', # required by current requests
|
'idna==2.6', # required by current requests
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user