Add export to .xlsx for lists

This commit is contained in:
Raphael Michel
2018-11-30 16:10:32 +01:00
parent 81693e042c
commit 478d8e4116
5 changed files with 40 additions and 13 deletions

View File

@@ -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=';')

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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
], ],