Commits

Luke Plant committed 1e94bb5

Added 'export officer data' functionality

Comments (0)

Files changed (7)

cciw/officers/tests/__init__.py

+from cciw.officers.tests.applicationform import *
 from cciw.officers.tests.applications import *
+from cciw.officers.tests.officerlist import *
 from cciw.officers.tests.references import *
-from cciw.officers.tests.applicationform import *
 from cciw.officers.tests.utils import *

cciw/officers/tests/officerlist.py

+from django.contrib.auth.models import User
+from django.test import TestCase
+import xlrd
+
+from cciw.cciwmain.models import Camp
+from cciw.officers.models import Invitation, Application
+from cciw.officers.tests.references import OFFICER, LEADER
+from cciw.officers.utils import officer_data_to_xls
+
+
+class TestExport(TestCase):
+
+    fixtures = ['basic.json', 'officers_users.json', 'references.json']
+
+    def test_export_no_application(self):
+        """
+        Test that the export data view generates an Excel file with all the data
+        we expect if there is no application form.
+        """
+        c = Camp.objects.get(pk=1)
+        officers = list(c.officers.all())
+        first_names = [o.first_name for o in officers]
+
+        # In this test, delete completed applications, so we can test what
+        # happens with no application.
+        Application.objects.all().delete()
+
+        for i, inv in enumerate(c.invitation_set.all()):
+            inv.notes = "Some notes %s" % i
+            inv.save()
+
+        workbook = officer_data_to_xls(c)
+
+        self.assertTrue(workbook is not None)
+        wkbk = xlrd.open_workbook(file_contents=workbook)
+        wksh = wkbk.sheet_by_index(0)
+
+        # Spot checks on different types of data
+        # From User model
+        self.assertEqual(wksh.cell(0, 0).value, "First name")
+        self.assertTrue(wksh.cell(1, 0).value in first_names)
+
+        # From Invitation model
+        self.assertEqual(wksh.cell(0, 3).value, "Notes")
+        self.assertTrue(wksh.cell(1, 3).value.startswith('Some notes'))
+
+    def test_export_with_application(self):
+        """
+        Test that the export data view generates an Excel file with all the data
+        we expect if there are application forms.
+        """
+        c = Camp.objects.get(pk=1)
+        officers = list(c.officers.all())
+
+        # Data from fixtures
+        u = User.objects.get(pk=2)
+        app = Application.objects.get(pk=1)
+        assert app.officer == u
+
+        workbook = officer_data_to_xls(c)
+
+        wkbk = xlrd.open_workbook(file_contents=workbook)
+        wksh = wkbk.sheet_by_index(0)
+
+        # Check data from Application model
+        self.assertEqual(wksh.cell(0, 4).value, "Address")
+        self.assertTrue(app.address_firstline in wksh.col_values(4))

cciw/officers/urls.py

     (r'^leaders/applications/(?P<year>\d{4})/(?P<number>\d+)/$', 'manage_applications'),
     (r'^leaders/references/(?P<year>\d{4})/(?P<number>\d+)/$', 'manage_references'),
     (r'^leaders/officer-list/(?P<year>\d{4})/(?P<number>\d+)/$', 'officer_list'),
+    (r'^leaders/export-officer-data/(?P<year>\d{4})/(?P<number>\d+)/$', 'export_officer_data'),
     (r'^leaders/remove-officer/(?P<year>\d{4})/(?P<number>\d+)/$', 'remove_officer'),
     (r'^leaders/add-officers/(?P<year>\d{4})/(?P<number>\d+)/$', 'add_officers'),
     (r'^leaders/update-officer/$', 'update_officer'),

cciw/officers/utils.py

 """
 Utility functions for officers app.
 """
+from datetime import date, datetime
+from StringIO import StringIO
+
+import xlwt
+
+from cciw.officers.applications import applications_for_camp
 
 
 def camp_officer_list(camp):
     finished_apps_ids = applications_for_camp(camp).values_list('officer__id', flat=True)
     return list(camp.officers.order_by('first_name', 'last_name', 'email').exclude(id__in=finished_apps_ids))
 
+
+def officer_data_to_xls(camp):
+    # All the data we need:
+    invites = camp.invitation_set.all().select_related('officer').order_by('officer__first_name',
+                                                                           'officer__last_name')
+    apps = applications_for_camp(camp)
+    app_dict = dict((app.officer.id, app) for app in apps)
+
+    # Attributes we need
+    app_attr_getter = lambda attr: lambda user, inv, app: getattr(app, attr) if app is not None else ''
+    columns = [('First name', lambda u, inv, app: u.first_name),
+               ('Last name', lambda u, inv, app: u.last_name),
+               ('E-mail', lambda u, inv, app: u.email),
+               ('Notes', lambda u, inv, app: inv.notes),
+               ('Address', app_attr_getter('address_firstline')),
+               ('Town', app_attr_getter('address_town')),
+               ('County', app_attr_getter('address_county')),
+               ('Post code', app_attr_getter('address_postcode')),
+               ('Country', app_attr_getter('address_country')),
+               ('Tel', app_attr_getter('address_tel')),
+               ('Mobile', app_attr_getter('address_mobile')),
+               ('Email', app_attr_getter('address_email')),
+               ('Birth date', app_attr_getter('birth_date')),
+               ]
+
+    wkbk = xlwt.Workbook(encoding='utf8')
+    wksh = wkbk.add_sheet("Officers")
+
+    # Headers:
+    font_header = xlwt.Font()
+    font_header.bold = True
+    style_header = xlwt.XFStyle()
+    style_header.font = font_header
+    for c, (header, f) in enumerate(columns):
+        wksh.write(0, c, header, style=style_header)
+
+    # Data:
+    date_style = xlwt.XFStyle()
+    date_style.num_format_str = 'YYYY/MM/DD'
+    for r, inv in enumerate(invites):
+        user = inv.officer
+        app = app_dict.get(user.id, None)
+        for c, (header, f) in enumerate(columns):
+            val = f(user, inv, app)
+            if isinstance(val, (datetime, date)):
+                style = date_style
+            else:
+                style = xlwt.Style.default_style
+            wksh.write(r + 1, c, val, style=style)
+
+    # Write out to string:
+    s = StringIO()
+    wkbk.save(s)
+    s.seek(0)
+    return s.read()

cciw/officers/views.py

 from cciw.officers.email import make_update_email_hash, send_reference_request_email, make_ref_form_url, make_ref_form_url_hash, send_leaders_reference_email, send_nag_by_officer, send_crb_consent_problem_email
 from cciw.officers.widgets import ExplicitBooleanFieldSelect
 from cciw.officers.models import Application, Reference, ReferenceForm, Invitation, CRBApplication, CRBFormLog
-from cciw.officers.utils import camp_slacker_list
+from cciw.officers.utils import camp_slacker_list, officer_data_to_xls
 from cciw.officers.references import reference_form_info
 from cciw.utils.views import close_window_response
 from securedownload.views import access_folder_securely
     return {'status':'success'}
 
 
+@staff_member_required
+@camp_admin_required
+def export_officer_data(request, year=None, number=None):
+    camp = _get_camp_or_404(year, number)
+    response = HttpResponse(officer_data_to_xls(camp), mimetype="application/vnd.ms-excel")
+    response['Content-Disposition'] = ('attachment; filename=camp-%d-%d-officers.xls'
+                                       % (camp.year, camp.number))
+    return response
+
+
 officer_files = access_folder_securely("officers",
                                        lambda request: _is_camp_officer(request.user))
 
 twill==0.9
 zc.lockfile==1.0.0
 south==0.7.2
+xlwt==0.7.2
+xlrd==0.7.1
+

templates/cciw/officers/officer_list.html

 {% include "cciw/officers/officer_list_noapplicationform.html" %}
 </div>
 
+
+<h2>Export officer data</h2>
+
+<p>The officer list above, along with address details from this year's
+  application forms, can be downloaded from the link below:</p>
+
+<p><a href="{% url 'cciw.officers.views.export_officer_data' year=camp.year number=camp.number %}">Download officer data, camp {{ camp.number }}, {{ camp.year }}</a></p>
+
 </div> {# id=content-main #}
 
 <div id="id_add_officer_popup" class="inlinepopup">