Commits

Luke Plant committed 46dd057 Merge

Merged from default

Comments (0)

Files changed (10)

cciw/bookings/tests.py

 from cciw.bookings.management.commands.expire_bookings import Command as ExpireBookingsCommand
 from cciw.bookings.models import BookingAccount, Price, Booking, Payment, ChequePayment, RefundPayment, book_basket_now
 from cciw.bookings.models import PRICE_FULL, PRICE_2ND_CHILD, PRICE_3RD_CHILD, PRICE_CUSTOM, PRICE_SOUTH_WALES_TRANSPORT, PRICE_DEPOSIT, BOOKING_APPROVED, BOOKING_INFO_COMPLETE, BOOKING_BOOKED, BOOKING_CANCELLED, BOOKING_CANCELLED_FULL_REFUND, BOOKING_CANCELLED_HALF_REFUND
-from cciw.bookings.utils import camp_bookings_to_xls
+from cciw.bookings.utils import camp_bookings_to_spreadsheet
 from cciw.cciwmain.common import get_thisyear
 from cciw.cciwmain.models import Camp
 from cciw.cciwmain.tests.mailhelpers import read_email_url
 from cciw.officers.tests.references import OFFICER_USERNAME, OFFICER_PASSWORD, BOOKING_SEC_USERNAME, BOOKING_SEC_PASSWORD, BOOKING_SEC
 from cciw.sitecontent.models import HtmlChunk
+from cciw.utils.spreadsheet import ExcelFormatter
 from cciw.utils.tests.twillhelpers import TwillMixin, make_django_url
 
 
         acc = self.get_account()
         acc.bookings.update(state=BOOKING_BOOKED)
 
-        workbook = camp_bookings_to_xls(self.camp)
+        workbook = camp_bookings_to_spreadsheet(self.camp, ExcelFormatter())
         wkbk = xlrd.open_workbook(file_contents=workbook)
         wksh_all = wkbk.sheet_by_index(0)
 
         acc = self.get_account()
         acc.bookings.update(state=BOOKING_BOOKED)
 
-        workbook = camp_bookings_to_xls(self.camp)
+        workbook = camp_bookings_to_spreadsheet(self.camp, ExcelFormatter())
         wkbk = xlrd.open_workbook(file_contents=workbook)
-        wksh_bdays = wkbk.sheet_by_index(1)
+        wksh_bdays = wkbk.sheet_by_index(2)
 
         self.assertEqual(wksh_bdays.cell(0, 0).value, u"First name")
         self.assertEqual(wksh_bdays.cell(1, 0).value, acc.bookings.all()[0].first_name)

cciw/bookings/utils.py

 from dateutil.relativedelta import relativedelta
-import xlwt
 
-from cciw.utils.xl import add_sheet_with_header_row, workbook_to_string
 
-def camp_bookings_to_xls(camp):
+def camp_bookings_to_spreadsheet(camp, spreadsheet):
     bookings = list(camp.bookings.confirmed().order_by('first_name', 'last_name'))
 
-    wkbk = xlwt.Workbook(encoding='utf8')
     columns = [('First name', lambda b: b.first_name),
                ('Last name', lambda b: b.last_name),
                ('Sex', lambda b: b.get_sex_display()),
                ('Date of birth', lambda b: b.date_of_birth),
                ('Age on camp', lambda b: b.age_on_camp().years),
                ('Address', lambda b: b.address),
+               ('Church', lambda b: b.church),
+               ('Dietary requirements', lambda b: b.dietary_requirements),
                ]
 
+    spreadsheet.add_sheet_with_header_row("Summary",
+                                          [n for n, f in columns],
+                                          [[f(b) for n, f in columns]
+                                           for b in bookings])
 
-    wksh_campers = add_sheet_with_header_row(wkbk,
-                                             "Summary",
-                                             [n for n, f in columns],
-                                             [[f(b) for n, f in columns]
-                                              for b in bookings])
+    medical_columns = \
+        [('First name', lambda b: b.first_name),
+         ('Last name', lambda b: b.last_name),
+         ('Sex', lambda b: b.get_sex_display()),
+         ('Date of birth', lambda b: b.date_of_birth),
+         ('Parent/guardian', lambda b: b.account.name),
+         ('Contact phone number', lambda b: b.contact_phone_number),
+         ('Contact address', lambda b: b.contact_address + ((u'\n' + b.contact_post_code) if
+                                                            b.contact_post_code else '')),
+         ('GP', lambda b: b.gp_name),
+         ('GP phone number', lambda b: b.gp_phone_number),
+         ('Medical card number', lambda b: b.medical_card_number),
+         ('Last tetanus injection', lambda b: b.last_tetanus_injection),
+         ('Allergies', lambda b: b.allergies),
+         ('Medication', lambda b: b.regular_medication_required),
+         ('Illnesses', lambda b: b.illnesses),
+         ('Learning difficulties', lambda b: b.learning_difficulties),
+         ]
+
+    spreadsheet.add_sheet_with_header_row("Medical",
+                                          [n for n, f in medical_columns],
+                                          [[f(b) for n, f in medical_columns]
+                                           for b in bookings])
 
     def get_birthday(b):
         start = camp.start_date
                     ]
 
 
-    wksh_bdays = add_sheet_with_header_row(wkbk,
-                                           "Birthdays",
-                                           [n for n, f in bday_columns],
-                                           [[f(b) for n, f in bday_columns]
-                                            for b in bookings if
-                                            camp.start_date <= get_birthday(b) <= camp.end_date])
+    spreadsheet.add_sheet_with_header_row("Birthdays on camp",
+                                          [n for n, f in bday_columns],
+                                          [[f(b) for n, f in bday_columns]
+                                           for b in bookings if
+                                           camp.start_date <= get_birthday(b) <= camp.end_date])
 
-    return workbook_to_string(wkbk)
+    return spreadsheet.to_string()
 
 

cciw/cciwmain/views/misc.py

 CONTACT_CHOICE_GENERAL = 'general'
 CONTACT_CHOICE_WEBSITE = 'website'
 CONTACT_CHOICE_BOOKINGS = 'bookings'
+CONTACT_CHOICE_BOOKINGFORM = 'bookingform'
 
 CONTACT_CHOICES = [
     (CONTACT_CHOICE_WEBSITE, 'Web site problems'),
+    (CONTACT_CHOICE_BOOKINGFORM, 'Booking form request'),
     (CONTACT_CHOICE_BOOKINGS, 'Bookings'),
     (CONTACT_CHOICE_GENERAL, 'Other'),
 ]
 
 CONTACT_CHOICE_DESTS = {
     CONTACT_CHOICE_GENERAL: ['FEEDBACK_EMAIL'],
+    CONTACT_CHOICE_BOOKINGFORM: ['BOOKING_FORM_EMAIL', 'FEEDBACK_EMAIL'],
     CONTACT_CHOICE_WEBSITE: ['WEBMASTER_EMAIL', 'FEEDBACK_EMAIL'],
     CONTACT_CHOICE_BOOKINGS: ['BOOKING_SECRETARY_EMAIL', 'FEEDBACK_EMAIL'],
 }

cciw/officers/tests/officerlist.py

 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
+from cciw.officers.utils import officer_data_to_spreadsheet
+from cciw.utils.spreadsheet import ExcelFormatter
 
 
 class TestExport(TestCase):
             inv.notes = "Some notes %s" % i
             inv.save()
 
-        workbook = officer_data_to_xls(c)
+        workbook = officer_data_to_spreadsheet(c, ExcelFormatter())
 
         self.assertTrue(workbook is not None)
         wkbk = xlrd.open_workbook(file_contents=workbook)
         app = Application.objects.get(pk=1)
         assert app.officer == u
 
-        workbook = officer_data_to_xls(c)
+        workbook = officer_data_to_spreadsheet(c, ExcelFormatter())
 
         wkbk = xlrd.open_workbook(file_contents=workbook)
         wksh = wkbk.sheet_by_index(0)

cciw/officers/utils.py

 """
 Utility functions for officers app.
 """
-import xlwt
-
-from cciw.utils.xl import add_sheet_with_header_row, workbook_to_string
-
 
 def camp_officer_list(camp):
     """
     return list(camp.officers.order_by('first_name', 'last_name', 'email').exclude(id__in=finished_apps_ids))
 
 
-def officer_data_to_xls(camp):
+def officer_data_to_spreadsheet(camp, spreadsheet):
     # Import here to avoid import cycle when starting from handle_mail script
     from cciw.officers.applications import applications_for_camp
 
                ('Birth date', app_attr_getter('birth_date')),
                ]
 
-    wkbk = xlwt.Workbook(encoding='utf8')
     header_row = [h for h,f in columns]
     def data_rows():
         for inv in invites:
                 row.append(f(user, inv, app))
             yield row
 
-    add_sheet_with_header_row(wkbk, "Officers", header_row, data_rows())
-    return workbook_to_string(wkbk)
+    spreadsheet.add_sheet_with_header_row("Officers", header_row, data_rows())
+    return spreadsheet.to_string()

cciw/officers/views.py

 from django.views.generic.base import TemplateView
 
 from cciw.auth import is_camp_admin, is_wiki_user, is_cciw_secretary, is_camp_officer, is_booking_secretary
-from cciw.bookings.utils import camp_bookings_to_xls
+from cciw.bookings.utils import camp_bookings_to_spreadsheet
 from cciw.cciwmain import common
 from cciw.cciwmain.decorators import json_response
 from cciw.cciwmain.models import Camp
 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, officer_data_to_xls
+from cciw.officers.utils import camp_slacker_list, officer_data_to_spreadsheet
 from cciw.officers.references import reference_form_info
-from cciw.utils.views import close_window_response, user_passes_test_improved
+from cciw.utils.views import close_window_response, user_passes_test_improved, get_spreadsheet_formatter
 from securedownload.views import access_folder_securely
 
 
 @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))
+    formatter = get_spreadsheet_formatter(request)
+    response = HttpResponse(officer_data_to_spreadsheet(camp, formatter),
+                            mimetype=formatter.mimetype)
+    response['Content-Disposition'] = ('attachment; filename=camp-%d-%d-officers.%s'
+                                       % (camp.year, camp.number, formatter.file_ext))
     return response
 
 
 @camp_admin_required
 def export_camper_data(request, year=None, number=None):
     camp = _get_camp_or_404(year, number)
-    response = HttpResponse(camp_bookings_to_xls(camp), mimetype="application/vnd.ms-excel")
-    response['Content-Disposition'] = ('attachment; filename=camp-%d-%d-campers.xls'
-                                       % (camp.year, camp.number))
+    formatter = get_spreadsheet_formatter(request)
+    response = HttpResponse(camp_bookings_to_spreadsheet(camp, formatter),
+                            mimetype=formatter.mimetype)
+    response['Content-Disposition'] = ('attachment; filename=camp-%d-%d-campers.%s'
+                                       % (camp.year, camp.number, formatter.file_ext))
     return response
 
 
 ESV_BROWSE_URL = "http://www.gnpcb.org/esv/search/"
 FEEDBACK_EMAIL = "feedback@cciw.co.uk"
 BOOKING_SECRETARY_EMAIL = "bookings@cciw.co.uk"
+BOOKING_FORM_EMAIL = "bookingforms@cciw.co.uk"
 BOOKINGFORMDIR = "downloads"
 MEMBERS_PAGINATE_MESSAGES_BY = 20
 WEBMASTER_EMAIL = "webmaster@cciw.co.uk"

cciw/utils/spreadsheet.py

+# Simple spreadsheet abstraction that does what we need for returning data in
+# spreadsheets, supporting .xls and .ods
+
+import xlwt
+
+from cciw.utils import xl
+
+class ExcelFormatter(object):
+    mimetype = "application/vnd.ms-excel"
+    file_ext = "xls"
+
+    def __init__(self):
+        # A formatter is only be used once, so we can create workbook here.
+        self.wkbk = xlwt.Workbook(encoding='utf8')
+
+    def add_sheet_with_header_row(self, name, headers, contents):
+        xl.add_sheet_with_header_row(self.wkbk, name, headers, contents)
+
+    def to_string(self):
+        return xl.workbook_to_string(self.wkbk)

cciw/utils/views.py

 from django.contrib.auth.views import redirect_to_login
 from django.http import HttpResponse, HttpResponseForbidden
 
+from cciw.utils.spreadsheet import ExcelFormatter
+
 
 def close_window_response():
     return HttpResponse("""<!DOCTYPE html><html><head><title>Close</title><script type="text/javascript">window.close()</script></head><body></body></html>""")
             return redirect_to_login(path, login_url, redirect_field_name)
         return _wrapped_view
     return decorator
+
+
+formatters = {'xls': ExcelFormatter}
+
+def get_spreadsheet_formatter(request):
+    format = request.GET.get('format', 'xls')
+    if format not in formatters:
+        raise Http404
+    return formatters[format]()
 """
 Simplified xlwt interface
 """
+from copy import deepcopy
 from datetime import datetime, date
 from StringIO import StringIO
 
     """
     wksh = wkbk.add_sheet(name)
 
+    normal_style = xlwt.XFStyle()
+    normal_style.alignment.vert = xlwt.Alignment.VERT_CENTER
+    normal_style.borders.left = xlwt.Borders.THIN
+    normal_style.borders.right = xlwt.Borders.THIN
+    normal_style.borders.top = xlwt.Borders.THIN
+    normal_style.borders.bottom = xlwt.Borders.THIN
+
+    wrapped_style = deepcopy(normal_style)
+    wrapped_style.alignment.wrap = True
+
+    date_style = deepcopy(normal_style)
+    date_style.num_format_str = 'YYYY/MM/DD'
+
+    style_header = deepcopy(normal_style)
     font_header = xlwt.Font()
     font_header.bold = True
-    style_header = xlwt.XFStyle()
     style_header.font = font_header
+
     for c, header in enumerate(headers):
         wksh.write(0, c, header, style=style_header)
 
-    date_style = xlwt.XFStyle()
-    date_style.num_format_str = 'YYYY/MM/DD'
+    for r, row in enumerate(contents):
+        row_height = normal_style.font.height
+        for c, val in enumerate(row):
+            if isinstance(val, str):
+                # normalise newlines to style expected by Excel
+                val = val.replace('\r\n', '\n')
+            elif isinstance(val, unicode):
+                val = val.replace(u'\r\n', u'\n')
 
-    for r, row in enumerate(contents):
-        for c, val in enumerate(row):
             if isinstance(val, (datetime, date)):
                 style = date_style
             else:
-                style = xlwt.Style.default_style
+                style = normal_style
+                if isinstance(val, basestring) and '\n' in val:
+                    # This is needed or Excel displays box character for
+                    # newlines.
+                    style = wrapped_style
+                    # Set height to be able to see all lines
+                    row_height = max(row_height, normal_style.font.height * (val.count('\n') + 1))
             wksh.write(r + 1, c, val, style=style)
-
+        wksh.rows[r + 1].height = row_height + 100 # fudge for margin, based on OpenOffice
 
 def workbook_to_string(wkbk):
     s = StringIO()