Commits

Luke Plant committed 13b69ec Merge

Merged from default

Comments (0)

Files changed (7)

cciw/officers/models.py

 # -*- coding: utf-8 -*-
-from datetime import datetime
+from datetime import datetime, timedelta
 import re
 
 from django.conf import settings
     def get_query_set(self):
         return super(CRBApplicationManager, self).get_query_set().select_related('officer')
 
+    def get_for_camp(self, camp):
+        """
+        Returns the CRBs that might be valid for a camp (ignoring the camp
+        officer list)
+        """
+        # CRBs are valid for 3 years, measuring from the start of the camp
+        return self.get_query_set().filter(completed__gte=camp.start_date - timedelta(3 * 365))
+
 class CRBApplication(models.Model):
     officer = models.ForeignKey(User)
     crb_number = models.CharField("Disclosure number", max_length=20)

cciw/officers/urls.py

     (r'^leaders/nag-by-officer/(?P<year>\d{4})/(?P<number>\d+)/$', 'nag_by_officer'),
     (r'^leaders/reference/(?P<ref_id>\d+)/$', 'view_reference'),
     (r'^leaders/edit-reference/(?P<ref_id>\d+)/$', 'edit_reference_form_manually'),
+    (r'^leaders/crbs/(?P<year>\d{4})/', 'manage_crbs'),
     (r'^leaders/stats/(?P<year>\d{4})/$', 'stats'),
     (r'^ref/(?P<ref_id>\d+)-(?P<prev_ref_id>\d*)-(?P<hash>.*)/$', 'create_reference_form'),
     (r'^ref/thanks/$', 'create_reference_thanks'),

cciw/officers/views.py

 import datetime
 import itertools
+import operator
 
 from django import forms
-from django import template
 from django.conf import settings
 from django.contrib.admin.views.decorators import staff_member_required
 from django.contrib.admin import widgets
 from django.db import models
 from django.core.validators import email_re
 from django.http import Http404, HttpResponseRedirect, HttpResponse
-from django.shortcuts import render_to_response, get_object_or_404
+from django.shortcuts import render, get_object_or_404
 from django.template.loader import render_to_string
 from django.template.defaultfilters import wordwrap
 from django.views.decorators.cache import never_cache
 def index(request):
     """Displays a list of links/buttons for various actions."""
     user = request.user
-    context = template.RequestContext(request)
+    c = {}
     if _is_camp_admin(user):
-        context['show_leader_links'] = True
-        context['show_admin_link'] = True
+        c['show_leader_links'] = True
+        c['show_admin_link'] = True
 
-    return render_to_response('cciw/officers/index.html',
-                              context_instance=context)
+    return render(request, 'cciw/officers/index.html', c)
 
 
 @staff_member_required
 def leaders_index(request):
     """Displays a list of links for actions for leaders"""
     user = request.user
-    context = template.RequestContext(request)
+    c = {}
     thisyear = common.get_thisyear()
-    context['current_camps'] = _camps_as_admin_or_leader(user).filter(year=thisyear)
-    context['old_camps'] = _camps_as_admin_or_leader(user).filter(year__lt=thisyear)
-    context['statsyears'] = [thisyear, thisyear - 1, thisyear - 2]
+    c['current_camps'] = _camps_as_admin_or_leader(user).filter(year=thisyear)
+    c['old_camps'] = _camps_as_admin_or_leader(user).filter(year__lt=thisyear)
+    c['statsyears'] = [thisyear, thisyear - 1, thisyear - 2]
 
-    return render_to_response('cciw/officers/leaders_index.html', context_instance=context)
+    return render(request, 'cciw/officers/leaders_index.html', c)
 
 
 @staff_member_required
 def applications(request):
     """Displays a list of tasks related to applications."""
     user = request.user
-    context = template.RequestContext(request)
+    c = {}
     finished_applications = user.application_set\
         .filter(finished=True)\
         .order_by('-date_submitted')
     has_thisyears_app = thisyears_applications(user).exists()
     has_completed_app = thisyears_applications(user).filter(finished=True).exists()
 
-    context['finished_applications'] = finished_applications
-    context['unfinished_applications'] = unfinished_applications
-    context['has_thisyears_app'] = has_thisyears_app
-    context['has_completed_app'] = has_completed_app
+    c['finished_applications'] = finished_applications
+    c['unfinished_applications'] = unfinished_applications
+    c['has_thisyears_app'] = has_thisyears_app
+    c['has_completed_app'] = has_completed_app
 
     if request.POST.has_key('edit'):
         # Edit existing application
         # Delete an unfinished application
         pass
 
-    return render_to_response('cciw/officers/applications.html',
-                              context_instance=context)
+    return render(request, 'cciw/officers/applications.html', c)
 
 
 @staff_member_required
 @never_cache
 def manage_applications(request, year=None, number=None):
     camp = _get_camp_or_404(year, number)
-    context = template.RequestContext(request)
-    context['finished_applications'] = applications_for_camp(camp).order_by('officer__first_name', 'officer__last_name')
-    context['camp'] = camp
+    c = {}
+    c['finished_applications'] = applications_for_camp(camp).order_by('officer__first_name', 'officer__last_name')
+    c['camp'] = camp
 
-    return render_to_response('cciw/officers/manage_applications.html',
-                              context_instance=context)
-
+    return render(request, 'cciw/officers/manage_applications.html', c)
 
 def _get_camp_or_404(year, number):
     try:
 @camp_admin_required # we don't care which camp they are admin for.
 @never_cache
 def manage_references(request, year=None, number=None):
-    c = template.RequestContext(request)
+    c = {}
 
     # If ref_id is set, we just want to update part of the page.
     ref_id = request.GET.get('ref_id')
             c['ref'] = notrequested[0]
         template_name = 'cciw/officers/manage_reference.html'
 
-    return render_to_response(template_name, context_instance=c)
+    return render(request, template_name, c)
 
 
 def email_sending_failed_response():
     if 'manual' in request.GET:
         return manage_reference_manually(request, ref)
 
-    c = template.RequestContext(request)
+    c = {}
 
     # Need to handle any changes to the referees first, for correctness of what
     # follows
     c['is_update'] = update
     c['emailform'] = emailform
     c['messageform'] = messageform
-    return render_to_response('cciw/officers/request_reference.html',
-                              context_instance=c)
+    return render(request, 'cciw/officers/request_reference.html', c)
 
 
 class SendNagByOfficerForm(SendMessageForm):
     app = ref.application
     officer = app.officer
 
-    c = template.RequestContext(request)
+    c = {}
     messageform_info = dict(referee=ref.referee,
                             officer=officer,
                             camp=camp)
     c['officer'] = officer
     c['messageform'] = messageform
     c['is_popup'] = True
-    return render_to_response('cciw/officers/nag_by_officer.html',
-                              context_instance=c)
+    return render(request, 'cciw/officers/nag_by_officer.html', c)
 
 
 class ReferenceFormForm(forms.ModelForm):
     """
     Returns page for manually editing Reference and ReferenceForm details.
     """
-    c = template.RequestContext(request)
+    c = {}
     c['ref'] = ref
     c['referee'] = ref.referee
     c['officer'] = ref.application.officer
         form = ReferenceEditForm(instance=ref)
     c['form'] = form
     c['is_popup'] = True
-    return render_to_response("cciw/officers/manage_reference_manual.html",
-                              context_instance=c)
+    return render(request, "cciw/officers/manage_reference_manual.html", c)
 
 
 @staff_member_required
     """
     View for allowing referee to submit reference (create the ReferenceForm object)
     """
-    c = template.RequestContext(request)
+    c = {}
     if hash != make_ref_form_url_hash(ref_id, prev_ref_id):
         c['incorrect_url'] = True
     else:
             c['already_submitted'] = True
         else:
             if request.method == 'POST':
-                form = ReferenceFormForm(request.POST, instance=instance) # A form bound to the POST data
+                form = ReferenceFormForm(request.POST, instance=instance)
                 if form.is_valid():
                     obj = form.save(commit=False)
                     obj.reference_info = ref
                     form = ReferenceFormForm(initial=initial_data)
             c['form'] = form
         c['officer'] = ref.application.officer
-    return render_to_response('cciw/officers/create_reference.html',
-                              context_instance=c)
+    return render(request, 'cciw/officers/create_reference.html', c)
 
 
 def create_reference_thanks(request):
-    return render_to_response('cciw/officers/create_reference_thanks.html',
-                              context_instance=template.RequestContext(request))
+    return render(request, 'cciw/officers/create_reference_thanks.html', {})
 
 
 @staff_member_required
 def view_reference(request, ref_id=None):
     ref = get_object_or_404(Reference.objects.filter(id=ref_id))
     ref_form = ref.reference_form
-    c = template.RequestContext(request)
+    c = {}
     if ref_form is not None:
         c['refform'] = ref_form
         c['info'] = reference_form_info(ref_form)
     c['referee'] = ref.referee
     c['is_popup'] = True
 
-    return render_to_response("cciw/officers/view_reference_form.html",
-                              context_instance=c)
+    return render(request, "cciw/officers/view_reference_form.html", c)
 
 
 @staff_member_required
 def officer_list(request, year=None, number=None):
     camp = _get_camp_or_404(year, number)
 
-    c = template.RequestContext(request)
+    c = {}
     c['camp'] = camp
     # Make sure these queries come after the above data modification
     officer_list = camp_officer_list(camp)
         return HttpResponse(python_to_json(retval),
                             mimetype="text/javascript")
     else:
-        return render_to_response("cciw/officers/officer_list.html", context_instance=c)
+        return render(request, "cciw/officers/officer_list.html", c)
 
 
 @staff_member_required
             u.email = email
             u.save()
 
-    return render_to_response('cciw/officers/email_update.html',
-                              context_instance=template.RequestContext(request, c))
+    return render(request, 'cciw/officers/email_update.html', c)
 
 
 class StripStringsMixin(object):
          'message': message,
          'is_popup': True,
          }
-    return render_to_response('cciw/officers/create_officer.html',
-                              context_instance=template.RequestContext(request, c))
+    return render(request, 'cciw/officers/create_officer.html', c)
 
 
 @staff_member_required
     thisyear = common.get_thisyear()
     stats = []
     all_past = True
-    for c in Camp.objects.filter(year=year).order_by('number'):
+    for camp in Camp.objects.filter(year=year).order_by('number'):
         stat = {}
-        if not c.is_past():
+        if not camp.is_past():
             all_past = False
-        stat['camp'] = c
-        invited_officers_count = c.invitation_set.count()
+        # For efficiency, we are careful about what DB queries we do and what is
+        # done in Python.
+        stat['camp'] = camp
+        invited_officers = [i.officer for i in camp.invitation_set.all()]
+        invited_officers_count = len(invited_officers)
         stat['invited_officers_count'] = invited_officers_count
-        application_forms = applications_for_camp(c)
+        application_forms = applications_for_camp(camp)
         app_ids = [a.id for a in application_forms]
 
         application_forms_count = len(app_ids)
         reference_forms = ReferenceForm.objects.filter(reference_info__application__in=app_ids)
         ref_dates = [r.date_created for r in reference_forms]
         ref_dates.sort()
+        crb_dates = list(CRBApplication.objects.get_for_camp(camp).filter(officer__in=[o.id for o in invited_officers]).values_list('completed', flat=True))
+        crb_dates.sort()
 
         # Make a plot by going through each day in the year before the camp and
-        # incrementing a counter.
-        graph_start_date = c.start_date - datetime.timedelta(365)
-        graph_end_date = min(c.start_date, datetime.date.today())
+        # incrementing a counter. This requires the data to be sorted already,
+        # as above.
+        graph_start_date = camp.start_date - datetime.timedelta(365)
+        graph_end_date = min(camp.start_date, datetime.date.today())
         a = 0 # applications
         r = 0 # references
+        c = 0 # CRBs
         app_dates_data = []
         ref_dates_data = []
+        crb_dates_data = []
         d = graph_start_date
         while d <= graph_end_date:
             # Application forms
             while a < len(app_dates) and app_dates[a] <= d:
                 a += 1
+            # References
             while r < len(ref_dates) and ref_dates[r] <= d:
                 r += 1
+            # CRBs
+            while c < len(crb_dates) and crb_dates[c] <= d:
+                c += 1
             # Formats are those needed by 'flot' library
             ts = date_to_js_ts(d)
             app_dates_data.append([ts, a])
             ref_dates_data.append([ts, r/2.0])
+            crb_dates_data.append([ts, c])
             d = d + datetime.timedelta(1)
         stat['application_dates_data'] = app_dates_data
         stat['reference_dates_data'] = ref_dates_data
+        stat['crb_dates_data'] = crb_dates_data
         stat['officer_list_data'] = [[date_to_js_ts(graph_start_date), invited_officers_count],
-                                     [date_to_js_ts(graph_end_date), invited_officers_count]]
+                                     [date_to_js_ts(camp.start_date), invited_officers_count]]
         stats.append(stat)
 
     # Those with no application forms yet are losing, then it goes on the
     d['stats'] = stats
     d['year'] = year
     d['all_past'] = all_past
-    return render_to_response('cciw/officers/stats.html',
-                              context_instance=template.RequestContext(request, d))
+    return render(request, 'cciw/officers/stats.html', d)
 
 
 class AddCrbForm(forms.ModelForm):
         form = AddCrbForm()
     c = {'form': form}
 
-    return render_to_response('cciw/officers/add_crb.html',
-                              context_instance=template.RequestContext(request, c))
+    return render(request, 'cciw/officers/add_crb.html', c)
+
+
+@staff_member_required
+@camp_admin_required
+def manage_crbs(request, year=None):
+    year = int(year)
+    # We need a lot of information. Try to get it in a few up-front queries
+    camps = list(Camp.objects.filter(year=year).order_by('number'))
+    camps_officers = [[i.officer for i in c.invitation_set.all()] for c in camps]
+    all_officers = reduce(operator.or_, map(set, camps_officers))
+    all_officers = sorted(all_officers, key=lambda o: (o.first_name, o.last_name))
+    apps = list(reduce(operator.or_, map(applications_for_camp, camps)))
+    valid_crb_officer_ids = set(reduce(operator.or_, map(CRBApplication.objects.get_for_camp, camps)).values_list('officer_id', flat=True))
+    all_crb_officer_ids = set(CRBApplication.objects.values_list('officer_id', flat=True))
+    # Work out, without doing any more queries:
+    #   which camps each officer is on
+    #   if they have an application form
+    #   if they have an up to date CRB
+    officer_ids = dict([(camp.id, set([o.id for o in officers]))
+                        for camp, officers in zip(camps, camps_officers)])
+    officer_apps = dict([(a.officer_id, a) for a in apps])
+
+    for o in all_officers:
+        o.temp = {}
+        officer_camps = []
+        for c in camps:
+            if o.id in officer_ids[c.id]:
+                officer_camps.append(c)
+        o.temp['camps'] = officer_camps
+        o.temp['has_application_form'] = o.id in officer_apps
+        o.temp['has_crb'] = o.id in all_crb_officer_ids
+        o.temp['has_valid_crb'] = o.id in valid_crb_officer_ids
+
+    c = {'all_officers': all_officers}
+    return render(request, 'cciw/officers/manage_crbs.html', c)

templates/cciw/officers/manage_crbs.html

+{% extends "cciw/officers/base.html" %}
+{% load url from future %}
+{% block content %}
+<table>
+  <thead>
+    <tr>
+      <th>Name</th>
+      <th>Camps</th>
+      <th>Application form</th>
+      <th>CRB</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for officer in all_officers %}
+      <tr>
+        <td>{{ officer.first_name }} {{ officer.last_name }}</td>
+        <td>{% for camp in officer.temp.camps %}
+              <a href="{% url 'cciw.officers.views.officer_list' year=camp.year number=camp.number %}">{{ camp.number }}</a>{% if not forloop.last %}, {% endif %}
+            {% endfor %}
+        </td>
+        <td>{% if officer.temp.has_application_form %}<img src="{{ STATIC_URL }}admin/img/admin/icon-yes.gif">{% endif %}</td>
+        <td>{% if not officer.temp.has_crb %}<img src="{{ STATIC_URL }}admin/img/admin/icon_error.gif"> Never{% else %}{% if officer.temp.has_valid_crb %}<img src="{{ STATIC_URL }}admin/img/admin/icon_success.gif"> Recent{% else %}<img src="{{ STATIC_URL }}admin/img/admin/icon_alert.gif"> Out of date{% endif %}{% endif %}</td>
+      </tr>
+    {% endfor %}
+  </tbody>
+</table>
+{% endblock %}

templates/cciw/officers/officer_list_table_editable.html

 <div  id="id_officer_list_table">
 <p>Total: {{ officers_all|length }}</p>
 <table style="width:43em;">
+ <thead>
   <tr>
     <th>Name</th>
     <th>E-mail</th>
     <th colspan="3">Edit</th>
   </tr>
+ </thead>
+ <tbody>
 {% for officer in officers_all %}
   <tr id="id_officer_table_tr_{{ officer.id }}">
     <td>{{ officer.first_name }} {{ officer.last_name }}</td>
     </td>
   </tr>
 {% endfor %}
+ </tbody>
 </table>
 </div>

templates/cciw/officers/stats.html

 {# Graphs #}
 
 <h1 style="margin-top: 1em;">Timelines</h1>
+<p>Charts end at the start date of the camp, and start a year before.</p>
 
 {% for stat in stats %}
+<div style="float:left; margin-right: 20px;">
 <h2>{{ stat.camp.number }} - {{ stat.camp.leaders_formatted }}</h2>
 
 <div id="camp-graph-{{ stat.camp.number }}" style="width:600px;height:150px;">
 $(function() {
     var app_dates_data = {{ stat.application_dates_data|jsonify }};
     var ref_dates_data = {{ stat.reference_dates_data|jsonify }};
+    var crb_dates_data = {{ stat.crb_dates_data|jsonify }};
     var officer_list_data = {{ stat.officer_list_data|jsonify }};
     $.plot("#camp-graph-{{ stat.camp.number }}",
            [
             {label: "Final officer list",
              data: officer_list_data},
+            {label: "Up-to-date CRBs",
+             data: crb_dates_data},
             {label: "Application forms",
              data: app_dates_data},
             {label: "References received / 2",
              data: ref_dates_data}
            ],
-           { xaxis: { mode: "time" },
+           { xaxis: { mode: "time",
+                      ticks: 12 },
+             yaxis: { min: 0 },
              legend: { position: "nw" }
            });
 
 });
 </script>
+</div>
 {% endfor %}
 
 
+<div style="float: clear;">
 <canvas height="50">
   Your browser is obsolete, and won't display the graphs above. Please upgrade
   to <a href="http://abetterbrowser.org/">a better browser</a>.
 </canvas>
+</div>
 
 
 </div>

templates/registration/logged_out.html

+{% extends "admin/base_site.html" %}
+{% load url from future %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="{% url 'cciw.officers.views.index' %}">Home</a></div>{% endblock %}
+
+{% block content %}
+
+<p>Thanks for spending some quality time with the site today</p>
+
+<p><a href="{% url 'cciw.officers.views.index' %}">Log in again</a></p>
+
+{% endblock %}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.