From c0bd7a70dacf6d72da2ee0ee2aeb3d6e98760b5b Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Thu, 19 Oct 2017 09:26:00 +0200 Subject: [PATCH] Factorized openpyxl export code to exports.py --- stages/exports.py | 41 +++++++++ stages/tests.py | 2 +- stages/views.py | 208 +++++++++++++++------------------------------- 3 files changed, 110 insertions(+), 141 deletions(-) create mode 100644 stages/exports.py diff --git a/stages/exports.py b/stages/exports.py new file mode 100644 index 0000000..672b380 --- /dev/null +++ b/stages/exports.py @@ -0,0 +1,41 @@ +from datetime import date + +from openpyxl import Workbook +from openpyxl.cell import get_column_letter +from openpyxl.styles import Font, Style +from openpyxl.writer.excel import save_virtual_workbook + +from django.http import HttpResponse + +openxml_contenttype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + + +class OpenXMLExport: + def __init__(self, sheet_title): + self.wb = Workbook() + self.ws = self.wb.active + self.ws.title = sheet_title + self.bold = Style(font=Font(bold=True)) + self.row_idx = 1 + + def write_line(self, values, bold=False, col_widths=()): + for col_idx, value in enumerate(values, start=1): + cell = self.ws.cell(row=self.row_idx, column=col_idx) + try: + cell.value = value + except KeyError: + # Ugly workaround for https://bugs.python.org/issue28969 + from openpyxl.utils.datetime import to_excel + to_excel.cache_clear() + cell.value = value + if bold: + cell.style = self.bold + if col_widths: + self.ws.column_dimensions[get_column_letter(col_idx)].width = col_widths[col_idx - 1] + self.row_idx += 1 + + def get_http_response(self, filename_base): + response = HttpResponse(save_virtual_workbook(self.wb), content_type=openxml_contenttype) + response['Content-Disposition'] = 'attachment; filename=%s_%s.xlsx' % ( + filename_base, date.strftime(date.today(), '%Y-%m-%d')) + return response diff --git a/stages/tests.py b/stages/tests.py index 010bd97..a86e2c7 100644 --- a/stages/tests.py +++ b/stages/tests.py @@ -274,7 +274,7 @@ class TeacherTests(TestCase): response = self.client.get(reverse('imputations_export')) self.assertEqual( response['Content-Disposition'], - 'attachment; filename=Imputations_export%s.xlsx' % date.strftime(date.today(), '%Y-%m-%d') + 'attachment; filename=Imputations_export_%s.xlsx' % date.strftime(date.today(), '%Y-%m-%d') ) diff --git a/stages/views.py b/stages/views.py index 0b6e1d0..4182f6b 100644 --- a/stages/views.py +++ b/stages/views.py @@ -6,10 +6,6 @@ from collections import OrderedDict from datetime import date, datetime, timedelta from tabimport import CSVImportedFile, FileFactory -from openpyxl import Workbook -from openpyxl.cell import get_column_letter -from openpyxl.styles import Font, Style -from openpyxl.writer.excel import save_virtual_workbook from django.conf import settings from django.contrib import messages @@ -21,6 +17,7 @@ from django.urls import reverse from django.utils.translation import ugettext as _ from django.views.generic import DetailView, FormView, TemplateView, ListView +from .exports import OpenXMLExport from .forms import PeriodForm, StudentImportForm, UploadHPFileForm from .models import ( Klass, Section, Option, Student, Teacher, Corporation, CorpContact, Course, Period, @@ -29,8 +26,6 @@ from .models import ( from .pdf import UpdateDataFormPDF from .utils import is_int -openxml_contenttype = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - def school_year_start(): """ Return first official day of current school year """ @@ -97,39 +92,26 @@ class KlassView(DetailView): if self.request.GET.get('format') != 'xls': return super().render_to_response(context, **response_kwargs) - wb = Workbook() - ws = wb.active - ws.title = self.object.name - bold = Style(font=Font(bold=True)) - headers = [ + export = OpenXMLExport(self.object.name) + # Headers + export.write_line([ 'Nom', 'Prénom', 'Domicile', 'Date de naissance', 'Stage 1', 'Domaine 1', 'Stage 2', 'Domaine 2', 'Stage 3', 'Domaine 3', - ] - col_widths = [18, 15, 20, 14, 25, 12, 25, 12, 25, 12] - # Headers - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=1, column=col_idx) - cell.value = header - cell.style = bold - ws.column_dimensions[get_column_letter(col_idx)].width = col_widths[col_idx - 1] + ], bold=True, col_widths=[18, 15, 20, 14, 25, 12, 25, 12, 25, 12]) # Data - for row_idx, student in enumerate(context['students'], start=2): - ws.cell(row=row_idx, column=1).value = student.last_name - ws.cell(row=row_idx, column=2).value = student.first_name - ws.cell(row=row_idx, column=3).value = " ".join([student.pcode, student.city]) - ws.cell(row=row_idx, column=4).value = student.birth_date - col_idx = 5 + for student in context['students']: + values = [ + student.last_name, student.first_name, + " ".join([student.pcode, student.city]), student.birth_date, + ] for training in student.training_set.select_related( 'availability', 'availability__corporation', 'availability__domain' ).all(): - ws.cell(row=row_idx, column=col_idx).value = training.availability.corporation.name - ws.cell(row=row_idx, column=col_idx + 1).value = training.availability.domain.name - col_idx += 2 + values.append(training.availability.corporation.name) + values.append(training.availability.domain.name) + export.write_line(values) - response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype) - response['Content-Disposition'] = 'attachment; filename=%s_export_%s.xlsx' % ( - self.object.name.replace(' ', '_'), date.strftime(date.today(), '%Y-%m-%d')) - return response + return export.get_http_response('%s_export' % self.object.name.replace(' ', '_')) class AttributionView(TemplateView): @@ -616,49 +598,32 @@ def stages_export(request, scope=None): if not default_contacts[contact.corporation.name][sname]: default_contacts[contact.corporation.name][sname] = contact - wb = Workbook() - ws = wb.active - ws.title = 'Stages' - bold = Style(font=Font(bold=True)) - # Headers - for col_idx, header in enumerate(export_fields.keys(), start=1): - cell = ws.cell(row=1, column=col_idx) - cell.value = header - cell.style = bold + export = OpenXMLExport('Stages') + export.write_line(export_fields.keys(), bold=True) # Headers # Data query_keys = [f for f in export_fields.values() if f is not None] - for row_idx, tr in enumerate(query.values(*query_keys), start=2): - for col_idx, field in enumerate(query_keys, start=1): - value = tr[field] + for line in query.values(*query_keys): + values = [] + for field in query_keys: + value = line[field] if 'gender' in field: value = {'F': 'Madame', 'M': 'Monsieur', '': ''}[value] - try: - ws.cell(row=row_idx, column=col_idx).value = value - except KeyError: - # Ugly workaround for https://bugs.python.org/issue28969 - from openpyxl.utils.datetime import to_excel - to_excel.cache_clear() - ws.cell(row=row_idx, column=col_idx).value = value - if tr[contact_test_field] is None: + values.append(value) + if line[contact_test_field] is None: # Use default contact - contact = default_contacts.get(tr[corp_name_field], {}).get(tr[export_fields['Filière']]) + contact = default_contacts.get(line[corp_name_field], {}).get(line[export_fields['Filière']]) if contact: - contact_col_idx = list(export_fields.keys()).index('Civilité contact') + 1 - ws.cell(row=row_idx, column=contact_col_idx).value = contact.title - ws.cell(row=row_idx, column=contact_col_idx + 1).value = contact.first_name - ws.cell(row=row_idx, column=contact_col_idx + 2).value = contact.last_name - ws.cell(row=row_idx, column=contact_col_idx + 3).value = contact.ext_id - ws.cell(row=row_idx, column=contact_col_idx + 4).value = contact.tel - ws.cell(row=row_idx, column=contact_col_idx + 5).value = contact.email - if always_ccs[tr[corp_name_field]].get(tr[export_fields['Filière']]): - ws.cell(row=row_idx, column=col_idx+1).value = "; ".join( - [c.email for c in always_ccs[tr[corp_name_field]].get(tr[export_fields['Filière']])] - ) + values = values[:-6] + [ + contact.title, contact.first_name, contact.last_name, contact.ext_id, + contact.tel, contact.email + ] + if always_ccs[line[corp_name_field]].get(line[export_fields['Filière']]): + values.append("; ".join( + [c.email for c in always_ccs[line[corp_name_field]].get(line[export_fields['Filière']])] + )) + export.write_line(values) - response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype) - response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % ( - 'stages_export_', date.strftime(date.today(), '%Y-%m-%d')) - return response + return export.get_http_response('stages_export') IMPUTATIONS_EXPORT_FIELDS = [ @@ -669,37 +634,22 @@ IMPUTATIONS_EXPORT_FIELDS = [ def imputations_export(request): - wb = Workbook() - ws = wb.active - ws.title = 'Imputations' - bold = Style(font=Font(bold=True)) - for col_idx, header in enumerate(IMPUTATIONS_EXPORT_FIELDS, start=1): - cell = ws.cell(row=1, column=col_idx) - cell.value = header - cell.style = bold + export = OpenXMLExport('Imputations') + export.write_line(IMPUTATIONS_EXPORT_FIELDS, bold=True) # Headers - for row_idx, teacher in enumerate(Teacher.objects.filter(archived=False), start=2): + for teacher in Teacher.objects.filter(archived=False): activities, imputations = teacher.calc_imputations() - ws.cell(row=row_idx, column=1).value = teacher.last_name - ws.cell(row=row_idx, column=2).value = teacher.first_name - ws.cell(row=row_idx, column=3).value = teacher.previous_report - ws.cell(row=row_idx, column=4).value = activities['tot_ens'] - ws.cell(row=row_idx, column=5).value = 'Ens. prof.' - ws.cell(row=row_idx, column=6).value = activities['tot_mandats'] + activities['tot_formation'] - ws.cell(row=row_idx, column=7).value = 'Accompagnement' - ws.cell(row=row_idx, column=8).value = activities['tot_paye'] - ws.cell(row=row_idx, column=9).value = 'Charge globale' - ws.cell(row=row_idx, column=10).value = '{0:.2f}'.format(activities['tot_paye']/21.50) - ws.cell(row=row_idx, column=11).value = teacher.next_report - col_idx = 12 - for k, v in imputations.items(): - ws.cell(row=row_idx, column=col_idx).value = v - col_idx += 1 + values = [ + teacher.last_name, teacher.first_name, teacher.previous_report, + activities['tot_ens'], 'Ens. prof.', activities['tot_mandats'] + activities['tot_formation'], + 'Accompagnement', activities['tot_paye'], 'Charge globale', + '{0:.2f}'.format(activities['tot_paye']/21.50), + teacher.next_report, + ] + values.extend(imputations.values()) + export.write_line(values) - response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype) - response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % ( - 'Imputations_export', date.strftime(date.today(), '%Y-%m-%d')) - return response + return export.get_http_response('Imputations_export') def print_update_form(request): @@ -768,39 +718,23 @@ def general_export(request): Export all current students data """ export_fields = OrderedDict(GENERAL_EXPORT_FIELDS) - wb = Workbook() - ws = wb.active - ws.title = 'Exportation' - bold = Style(font=Font(bold=True)) - for col_idx, header in enumerate(export_fields.keys(), start=1): - cell = ws.cell(row=1, column=col_idx) - cell.value = header - cell.style = bold + export = OpenXMLExport('Exportation') + export.write_line(export_fields.keys(), bold=True) # Headers # Data query_keys = [f for f in export_fields.values() if f is not None] query = Student.objects.filter(archived=False).order_by('klass__name', 'last_name', 'first_name') - for row_idx, tr in enumerate(query.values(*query_keys), start=2): - for col_idx, field in enumerate(query_keys, start=1): + for line in query.values(*query_keys): + values = [] + for field in query_keys: if field == 'gender': - tr[field] = ('Madame', 'Monsieur')[tr[field] == 'M'] - if field == 'dispense_ecg': - tr[field] = ('', 'Oui')[tr[field]==1] - if field == 'dispense_eps': - tr[field] = ('', 'Oui')[tr[field]==1] - if field == 'soutien_dys': - tr[field] = ('', 'Oui')[tr[field]==1] - try: - ws.cell(row=row_idx, column=col_idx).value = tr[field] - except KeyError: - # Ugly workaround for https://bugs.python.org/issue28969 - from openpyxl.utils.datetime import to_excel - to_excel.cache_clear() - ws.cell(row=row_idx, column=col_idx).value = tr[field] + values.append(('Madame', 'Monsieur')[line[field] == 'M']) + elif field in ('dispense_ecg', 'dispense_eps', 'soutien_dys'): + values.append('Oui' if line[field] is True else '') + else: + values.append(line[field]) + export.write_line(values) - response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype) - response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % ( - 'general_export_', date.strftime(date.today(), '%Y-%m-%d')) - return response + return export.get_http_response('general_export') ORTRA_EXPORT_FIELDS = [ @@ -841,14 +775,8 @@ def ortra_export(request): Export students data from sections ASAFE, ASEFE and ASSCFE """ export_fields = OrderedDict(ORTRA_EXPORT_FIELDS) - wb = Workbook() - ws = wb.active - ws.title = 'Exportation' - bold = Style(font=Font(bold=True)) - for col_idx, header in enumerate(export_fields.keys(), start=1): - cell = ws.cell(row=1, column=col_idx) - cell.value = header - cell.style = bold + export = OpenXMLExport('Exportation') + export.write_line(export_fields.keys(), bold=True) # Headers # Data query_keys = [f for f in export_fields.values() if f is not None] query = Student.objects.filter(Q(klass__name__contains='ASAFE') | @@ -858,13 +786,13 @@ def ortra_export(request): 'last_name', 'first_name') - for row_idx, tr in enumerate(query.values(*query_keys), start=2): - for col_idx, field in enumerate(query_keys, start=1): + for line in query.values(*query_keys): + values = [] + for field in query_keys: if field == 'gender': - tr[field] = ('Madame', 'Monsieur')[tr[field] == 'M'] - ws.cell(row=row_idx, column=col_idx).value = tr[field] + values.append(('Madame', 'Monsieur')[line[field] == 'M']) + else: + values.append(line[field]) + export.write_line(values) - response = HttpResponse(save_virtual_workbook(wb), content_type=openxml_contenttype) - response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % ( - 'ortra_export_', date.strftime(date.today(), '%Y-%m-%d')) - return response \ No newline at end of file + return export.get_http_response('ortra_export')