Luke Plant avatar Luke Plant committed a0d0f96 Merge

Merged from default

Comments (0)

Files changed (9)

cciw/cciwmain/views/memberadmin.py

 from django.core.urlresolvers import reverse
 from django.core.validators import email_re
 from django.http import Http404, HttpResponseRedirect
+from django.utils.crypto import salted_hmac
 from django.views.generic.edit import ModelFormMixin
 from django import forms
 from cciw.cciwmain.common import standard_extra_context, DefaultMetaData, AjaxyFormView, member_username_re
 from cciw.cciwmain import common
 from cciw.cciwmain import imageutils
 from cciw.cciwmain.forms import CciwFormMixin
-import md5
 import urllib
 import re
 import datetime
         return m
 
 def email_hash(email):
-    """Gets a hash of an email address, to be used in the sign process"""
+    """Gets a hash of an email address, to be used in the signup process"""
     # Use every other character to make it shorter and friendlier
-    return md5.new(settings.SECRET_KEY + email).hexdigest()[::2]
+    return salted_hmac("cciw.cciwmain.memberadmin.signupemail", email).hexdigest()[::2]
 
 def email_address_used(email):
     return Member.all_objects.filter(email__iexact=email).count() != 0
 def email_and_username_hash(email, user_name):
     """Gets a hash of an email address + user_name"""
     # Use every other character to make it shorter and friendlier
-    return md5.new(settings.SECRET_KEY + email + user_name).hexdigest()[::2]
+    return salted_hmac("cciw.cciwmain.memberadmin.changeemail", email + ":" + user_name).hexdigest()[::2]
 
 def validate_email_username_and_hash(email, user_name, hash):
     if email_address_used(email):

cciw/officers/admin.py

         ("Confirmation",
             {'fields': ('finished',),
              'classes': ('wide',),
-             'description': """By ticking this box and pressing save, you confirm
-             that the information you have submitted is correct and complete, and your
-             information will then be sent to the camp leader.  By leaving this box un-ticked,
-             you can save what you have done so far and edit it later."""
+             'description': """<div>By ticking the following box and pressing save, you confirm
+             that:</div> <ol><li>the information you have submitted is <strong>correct and complete</strong>,</li>
+             <li>you have <strong>read and understood</strong> the relevant sections of the
+             <a rel="external" href="/officers/files/CCIW%20CPP.doc">camp manual</a>.</li></ol>
+             <div>Your information will then be sent to the camp leader.  By leaving this
+             box un-ticked, you can save what you have done so far and edit it later.</div>"""
              }
         ),
     )

cciw/officers/applications.py

 def _application_filename_stem(app):
     return 'Application_%s_%s' % (app.officer.username, app.camp.year)
 
-def application_diff(app1, app2):
-    import difflib
-    conv = lambda app: application_to_text(app).split('\n')
-    return '\n'.join(difflib.unified_diff(conv(app1), conv(app2), lineterm=''))
+def application_difference(app1, app2):
+    from diff_match_patch import diff_match_patch
+    differ = diff_match_patch()
+    diffs = differ.diff_main(application_to_text(app1),
+                             application_to_text(app2))
+    differ.diff_cleanupSemantic(diffs)
+    html = differ.diff_prettyHtml(diffs)
+    # It looks better without the '&para;'
+    html = html.replace('&para;', '')
+
+    # Use custom colours etc.
+    html = html.replace('background:#E6FFE6;', '')
+    html = html.replace('background:#FFE6E6;', '')
+    html = html.replace(' STYLE=""', '')
+
+    return """<html>
+<style>
+body {
+    font-family:monospace;
+}
+
+ins {
+    background: #51FF17;
+    text-decoration: none;
+    font-weight: bold;
+}
+
+del {
+   background: #FF6989;
+   text-decoration: strike-through;
+}
+</style>
+<body><pre>%s</pre></body></html>""" % html

cciw/officers/email.py

 from cciw.cciwmain import common
-from cciw.officers.applications import application_to_text, application_to_rtf, application_rtf_filename
+from cciw.cciwmain.models import Camp
+from cciw.officers.applications import application_to_text, application_to_rtf, application_rtf_filename, application_difference
 from cciw.officers.email_utils import send_mail_with_attachments, formatted_email
 from cciw.officers.references import reference_form_to_text
 from django.conf import settings
 from django.contrib import messages
 from django.core.mail import send_mail
 from django.core.urlresolvers import reverse
-from django.utils.hashcompat import sha_constructor
+from django.utils.crypto import salted_hmac
 import cciw.middleware.threadlocals as threadlocals
 import urllib
 
     """
     Returns a hash for use in confirmation of e-mail change.
     """
-    return sha_constructor("emailupdate" + settings.SECRET_KEY + ':' + oldemail + ':' + newemail).hexdigest()[::2]
+    return salted_hmac("cciw.officers.emailupdate", oldemail + ':' + newemail).hexdigest()[::2]
 
 def admin_emails_for_application(application):
     leaders = [user for leader in application.camp.leaders.all()
     application_rtf = application_to_rtf(application)
     rtf_attachment = (application_rtf_filename(application), application_rtf, 'text/rtf')
 
+    # Did the officer submit one last year for the 'same' camp?
+    previous_camp = application.camp.previous_camp
+    application_diff = None
+    if previous_camp is not None:
+        officer = application.officer
+        try:
+            previous_app = officer.application_set.filter(camp=previous_camp, finished=True)[0]
+        except IndexError:
+            previous_app = None
+        if previous_app is not None:
+            application_diff = ("differences_from_last_year.html",
+                                application_difference(previous_app, application),
+                                "text/html")
+
     if len(leader_emails) > 0:
-        send_leader_email(leader_emails, application, application_text, rtf_attachment)
+        send_leader_email(leader_emails, application, application_text, rtf_attachment,
+                          application_diff)
+        messages.info(request, "The completed application form has been sent to the leaders via e-mail.")
 
     # If an admin user corrected an application, we don't send the user a copy
     # (usually they just get the year of the camp wrong(!))
-    user = request.user
-    if len(leader_emails) > 0:
-        messages.info(request, "The completed application form has been sent to the leaders via e-mail.")
-
-    if user == application.officer:
+    if request.user == application.officer:
         send_officer_email(application.officer, application, application_text, rtf_attachment)
         messages.info(request, "A copy of the application form has been sent to you via e-mail.")
 
         if application.officer.email.lower() != application.address_email.lower():
-            send_email_change_emails(user, application)
+            send_email_change_emails(application.officer, application)
 
 def send_officer_email(officer, application, application_text, rtf_attachment):
     subject = "CCIW application form submitted"
         send_mail_with_attachments(subject, user_msg, settings.SERVER_EMAIL,
                                    [user_email], attachments=[rtf_attachment])
 
-def send_leader_email(leader_emails, application, application_text, rtf_attachment):
+def send_leader_email(leader_emails, application, application_text, rtf_attachment,
+                      application_diff):
     subject = "CCIW application form from %s" % application.full_name
     body = \
 u"""The following application form has been submitted via the
 CCIW website.  It is also attached to this e-mail as an RTF file.
 
-""" + application_text
+"""
+    if application_diff is not None:
+        body += \
+u"""The second attachment shows the differences between this year's
+application form and last year's - pink indicates information that has
+been removed, green indicates new information.
+
+"""
+    body += application_text
+
+    attachments = [rtf_attachment]
+    if application_diff is not None:
+        attachments.append(application_diff)
 
     send_mail_with_attachments(subject, body, settings.SERVER_EMAIL,
-                               leader_emails, attachments=[rtf_attachment])
+                               leader_emails, attachments=attachments)
 
 def make_update_email_url(application):
     email = application.address_email
 
 def send_email_change_emails(officer, application):
     subject = "E-mail change on CCIW"
-    user_email = formatted_email(application.officer)
+    user_email = formatted_email(officer)
     user_msg = (
 u"""%(name)s,
 
               [user_email, application.address_email] , fail_silently=True)
 
 def make_ref_form_url_hash(ref_id, prev_ref_id):
-    return sha_constructor("create_reference_form%s:%s:%s" % (settings.SECRET_KEY, ref_id, prev_ref_id)).hexdigest()[::2]
+    return salted_hmac("cciw.officers.create_reference_form", "%s:%s" % (ref_id, prev_ref_id)).hexdigest()[::2]
 
 def make_ref_form_url(ref_id, prev_ref_id):
     if prev_ref_id is None: prev_ref_id = ""

cciw/officers/tests/applicationform.py

 from cciw.cciwmain.tests.mailhelpers import read_email_url
 from cciw.cciwmain.models import Camp
 from cciw.officers.models import Application
+from cciw.officers.applications import application_difference
 from cciw.officers.tests.references import OFFICER, LEADER
 from cciw.utils.tests.twillhelpers import TwillMixin, make_django_url, make_twill_url
 
         return make_django_url('admin:officers_application_change', app_id)
 
     def setUp(self):
-        # make sure camp 1 has end date in future, otherwise
-        # we won't be able to save
-        c = Camp.objects.get(id=1)
-        c.end_date = datetime.date.today() + datetime.timedelta(100)
-        c.save()
-
+        # make sure camps have end date in future, otherwise we won't be able to
+        # save
+        Camp.objects.filter(id=1).update(end_date=datetime.date.today() + datetime.timedelta(100))
+        Camp.objects.filter(id=2).update(end_date=datetime.date.today() + datetime.timedelta(465))
         super(ApplicationFormView, self).setUp()
 
     def _add_application(self, camp_id=1, officer=OFFICER):
 
         tc.find("%s %s" % (u.first_name, u.last_name))
         tc.find(u.email)
+
+    def test_application_differences_email(self):
+        """
+        Tests the 'application difference' e-mail that is sent when an
+        application form is submitted
+        """
+        u = User.objects.get(username=OFFICER[0])
+
+        # Create one application
+        self.test_finish_complete()
+
+        # Empty outbox
+        mail.outbox[:] = []
+
+        # Create another application
+        tc.go(self._application_add_url())
+        self._finish_application_form()
+        # Now change some values
+        tc.formvalue('1', 'camp', '2')
+        tc.formvalue('1', 'full_name', 'New Full Name')
+        tc.submit('_save')
+        tc.url(reverse("cciw.officers.views.applications"))
+
+        emails = self._get_application_form_emails()
+        self.assertEqual(len(emails), 2)
+        leader_email = [e for e in emails
+                        if e.subject == u'CCIW application form from New Full Name'][0]
+        msg = leader_email.message()
+
+        # E-mail will have 3 parts - text, RTF, and differences from last year
+        # as an HTML file.
+        attachments = msg.get_payload()
+        self.assertEqual(len(attachments), 3)
+
+        # Testing the actual content is hard from this point, due to e-mail
+        # formatting, so we do it manually:
+
+        apps = u.application_set.order_by('camp__year')
+        assert len(apps) == 2
+
+        application_diff = application_difference(apps[0], apps[1])
+        self.assertTrue('<INS TITLE="i=95">New Full Name</INS>'
+                        in application_diff)
+        self.assertTrue('<DEL TITLE="i=95">x</DEL>'
+                        in application_diff)
+
+

cciw/officers/views.py

     for field in Application._meta.fields:
         if field.attname != 'id':
             setattr(new_obj, field.attname, getattr(application, field.attname))
-    new_obj.camp = None
     new_obj.youth_work_declined = None
     new_obj.relevant_illness = None
     new_obj.crime_declaration = None
 
 #####  EMAIL  #######
 
-EMAIL_BACKEND = "mailer.backend.DbBackend"
+if LIVEBOX:
+    EMAIL_BACKEND = "mailer.backend.DbBackend"
 
 if DEVBOX:
     # For e-mail testing, run:
 
 def test():
     ensure_dependencies()
-    local("./manage.py test cciwmain officers utils --settings=cciw.settings_tests", capture=False)
+    local("./manage.py test cciwmain officers --settings=cciw.settings_tests", capture=False)
 
 
 def _prepare_deploy():

securedownload/views.py

 import urllib
 from django.conf import settings
 from django.http import Http404, HttpResponseRedirect, HttpResponseForbidden
-from django.utils.hashcompat import sha_hmac
+from django.utils.crypto import salted_hmac
 
 def serve_secure_file(filename):
     """
     ts = datetime.datetime.now().strftime("%s")
     # Make a directory that cannot be guessed, and that contains the timestamp
     # so that we can remove it easily by timestamp later.
-    key = "cciw.officers.secure_file" + settings.SECRET_KEY
-    nonce = hmac.new(key, "%s-%s" % (ts, filename), sha_hmac).hexdigest()
+    key = "cciw.officers.secure_file"
+    nonce = salted_hmac(key, "%s-%s" % (ts, filename)).hexdigest()
     dirname = "%s-%s" % (ts, nonce)
     abs_destdir = os.path.join(settings.SECUREDOWNLOAD_SERVE_ROOT, dirname)
     if not os.path.isdir(abs_destdir):
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.