Repository: kartoza/prj.app

Issue:
We should add preview support for certification course certificate
When the user uploads a certificate template, we should show them a preview of the template as it will be rendered (using some lorem ipsum data for the participant details and real data for the logos etc.)

<img width="797" alt="screen shot 2017-12-13 at 11 24 35" src="https://user-images.githubusercontent.com/178003/33932627-20799826-dffc-11e7-8040-95a79d2e3328.png">



Discussion:
@timlinux +1 I just spent one credit to try and see how my certificates would look like. The alternative would be to start every account with one free credit to test the whole process
@mbernasocchi since we work on an honour system, do not worry about spending credits on testing - you only need to pay for certificates that are actually used. Alternatively play around on our staging server http://staging.changelog.qgis.org
Also please add some details on the course form on the ideal resolution etc to use for logos.

Resolving patch:
diff --git a/django_project/certification/urls.py b/django_project/certification/urls.py
--- a/django_project/certification/urls.py
+++ b/django_project/certification/urls.py
@@ -53,6 +53,7 @@
     email_all_attendees,
     regenerate_certificate,
     regenerate_all_certificate,
+    preview_certificate,
 
     # Validate Certificate.
     ValidateCertificate,
@@ -228,6 +229,9 @@
         '(?P<organisation_slug>[\w-]+)/course/'
         '(?P<course_slug>[\w-]+)/regenerate-all-certificate/$',
         regenerate_all_certificate, name='regenerate-all-certificate'),
+    url(r'^(?P<project_slug>[\w-]+)/certifyingorganisation/'
+        '(?P<organisation_slug>[\w-]+)/preview-certificate/$',
+        preview_certificate, name='preview-certificate'),
 
     # Course.
     url(regex='^(?P<project_slug>[\w-]+)/certifyingorganisation/'
diff --git a/django_project/certification/views/certificate.py b/django_project/certification/views/certificate.py
--- a/django_project/certification/views/certificate.py
+++ b/django_project/certification/views/certificate.py
@@ -1,7 +1,11 @@
 # coding=utf-8
+import datetime
 import StringIO
 import os
 import zipfile
+import cStringIO
+from PIL import Image
+import re
 from django.contrib import messages
 from django.core.mail import send_mail
 from django.http import Http404, HttpResponse, HttpResponseRedirect
@@ -14,7 +18,15 @@
 from reportlab.pdfgen import canvas
 from reportlab.lib.pagesizes import A4, landscape
 from reportlab.lib.utils import ImageReader
-from ..models import Certificate, Course, Attendee, CertifyingOrganisation
+from ..models import (
+    Certificate,
+    Course,
+    Attendee,
+    CertifyingOrganisation,
+    CourseConvener,
+    TrainingCenter,
+    CourseType
+)
 from ..forms import CertificateForm
 from base.models.project import Project
 
@@ -730,3 +742,104 @@ def regenerate_all_certificate(request, **kwargs):
             'has_pending_organisations': has_pending,
             'certificates': certificates_dict,
             'course': course})
+
+
+class DummyAttendee(object):
+    """Dummy object for preview certificate."""
+
+    def __init__(self):
+        self.firstname = 'Jane'
+        self.surname = 'Doe'
+
+
+class DummyCourse(object):
+    """Dummy object for preview certificate."""
+
+    def __init__(
+            self, course_convener, course_type, training_center, start_date,
+            end_date, certifying_organisation, template_certificate):
+        self.course_convener = course_convener
+        self.course_type = course_type
+        self.training_center = training_center
+        self.start_date = start_date
+        self.end_date = end_date
+        self.certifying_organisation = certifying_organisation
+        self.template_certificate = template_certificate
+
+
+class DummyCertificate(object):
+    """Dummy object for preview certificate."""
+
+    def __init__(self, course, attendee, project):
+        self.certificateID = '{}-1'.format(project.name)
+        self.course = course
+        self.attendee = attendee
+
+
+def preview_certificate(request, **kwargs):
+    """Generate pdf for preview upon creating new course."""
+
+    response = HttpResponse(content_type='application/pdf')
+    response['Content-Disposition'] = 'filename="preview.pdf"'
+
+    project_slug = kwargs.pop('project_slug')
+    project = Project.objects.get(slug=project_slug)
+    organisation_slug = kwargs.pop('organisation_slug')
+
+    convener_id = request.POST.get('course_convener', None)
+    if convener_id is not None:
+        # Get all posted data.
+        course_convener = CourseConvener.objects.get(id=convener_id)
+        training_center_id = request.POST.get('training_center', None)
+        training_center = TrainingCenter.objects.get(id=training_center_id)
+        course_type_id = request.POST.get('course_type', None)
+        course_type = CourseType.objects.get(id=course_type_id)
+        start_date = request.POST.get('start_date', None)
+        course_start_date = datetime.datetime.strptime(start_date, '%Y-%m-%d')
+        end_date = request.POST.get('end_date', None)
+        course_end_date = datetime.datetime.strptime(end_date, '%Y-%m-%d')
+        certifying_organisation = \
+            CertifyingOrganisation.objects.get(slug=organisation_slug)
+        raw_image = request.POST.get('template_certificate', None)
+        if 'base64' in raw_image:
+            image_data = re.sub('^data:image/.+;base64,', '',
+                                raw_image).decode('base64')
+            template_certificate = Image.open(cStringIO.StringIO(image_data))
+        else:
+            template_certificate = None
+
+        # Create dummy objects
+        attendee = DummyAttendee()
+        course = \
+            DummyCourse(
+                course_convener, course_type, training_center,
+                course_start_date, course_end_date, certifying_organisation,
+                template_certificate)
+        certificate = DummyCertificate(course, attendee, project)
+
+        current_site = request.META['HTTP_HOST']
+
+        generate_pdf(
+            response, project, course, attendee, certificate, current_site)
+
+    else:
+        # When preview page is refreshed, the data is gone so user needs to
+        # go back to create page.
+
+        page = canvas.Canvas(response, pagesize=landscape(A4))
+        width, height = A4
+        center = height * 0.5
+        page.setFillColorRGB(0.1, 0.1, 0.1)
+        page.setFont('Times-Bold', 26)
+        page.drawCentredString(center, 480, 'Certificate of Completion')
+        page.setFont('Times-Roman', 16)
+        page.drawCentredString(
+            center, 360,
+            'To preview your certificate template, '
+            'please go to create new course page')
+        page.drawCentredString(
+            center, 335, 'and click on Preview Certificate button.')
+        page.showPage()
+        page.save()
+
+    return response
diff --git a/django_project/certification/views/course.py b/django_project/certification/views/course.py
--- a/django_project/certification/views/course.py
+++ b/django_project/certification/views/course.py
@@ -60,6 +60,8 @@ def get_context_data(self, **kwargs):
             CourseCreateView, self).get_context_data(**kwargs)
         context['courses'] = self.get_queryset() \
             .filter(certifying_organisation=self.certifying_organisation)
+        context['organisation_slug'] = self.kwargs.pop('organisation_slug')
+        context['project_slug'] = self.kwargs.pop('project_slug')
         return context
 
     def get_form_kwargs(self):
@@ -116,6 +118,8 @@ def get_context_data(self, **kwargs):
         context = super(CourseUpdateView, self).get_context_data(**kwargs)
         context['courses'] = self.get_queryset() \
             .filter(certifying_organisation=self.certifying_organisation)
+        context['organisation_slug'] = self.kwargs.pop('organisation_slug')
+        context['project_slug'] = self.kwargs.pop('project_slug')
         return context
 
     def get_queryset(self):


Test patch:
diff --git a/django_project/certification/tests/test_certificate_previews.py b/django_project/certification/tests/test_certificate_previews.py
new file mode 100644
--- /dev/null
+++ b/django_project/certification/tests/test_certificate_previews.py
@@ -0,0 +1,98 @@
+# coding=utf-8
+from django.core.urlresolvers import reverse
+from django.test import TestCase, override_settings
+from django.test.client import Client
+import logging
+from core.model_factories import UserF
+from base.tests.model_factories import ProjectF
+from certification.tests.model_factories import (
+    CertifyingOrganisationF,
+    CourseConvenerF,
+    TrainingCenterF,
+    CourseTypeF
+)
+
+
+class TestCertificatePreview(TestCase):
+    """Test that certificate views work."""
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def setUp(self):
+        """
+        Setup before each test
+        We force the locale to en otherwise it will use
+        the locale of the host running the tests and we
+        will get unpredictable results / 404s
+        """
+
+        self.client = Client()
+        self.client.post(
+            '/set_language/', data={'language': 'en'})
+        logging.disable(logging.CRITICAL)
+        self.user = UserF.create(**{
+            'username': 'anita',
+            'is_staff': True
+        })
+        # Something changed in the way factoryboy works with django 1.8
+        # I think - we need to explicitly set the users password
+        # because the core.model_factories.UserF._prepare method
+        # which sets the password is never called. Next two lines are
+        # a work around for that - sett #581
+        self.user.set_password('password')
+        self.user.save()
+
+        self.test_project = ProjectF.create()
+        self.test_certifying_organisation = CertifyingOrganisationF.create()
+        self.convener = CourseConvenerF.create()
+        self.training_center = TrainingCenterF.create()
+        self.course_type = CourseTypeF.create()
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def tearDown(self):
+        """
+        Teardown after each test.
+
+        :return:
+        """
+        self.test_project.delete()
+        self.convener.delete()
+        self.training_center.delete()
+        self.course_type.delete()
+        self.user.delete()
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def test_preview_certificate_no_data_posted_no_login(self):
+        client = Client()
+        response = client.get(reverse('preview-certificate', kwargs={
+            'project_slug': self.test_project.slug,
+            'organisation_slug': self.test_certifying_organisation.slug
+        }))
+        self.assertEqual(response.status_code, 200)
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def test_preview_certificate_no_data_posted(self):
+        client = Client()
+        client.login(username='anita', password='password')
+        response = client.get(reverse('preview-certificate', kwargs={
+            'project_slug': self.test_project.slug,
+            'organisation_slug': self.test_certifying_organisation.slug
+        }))
+        self.assertEqual(response.status_code, 200)
+
+    @override_settings(VALID_DOMAIN=['testserver', ])
+    def test_preview_certificate_with_posted_data(self):
+        client = Client(HTTP_HOST='testserver')
+        client.login(username='anita', password='password')
+        post_data = {
+            'course_convener': self.convener.pk,
+            'training_center': self.training_center.pk,
+            'course_type': self.course_type.pk,
+            'start_date': '2018-01-01',
+            'end_date': '2018-02-01',
+            'template_certificate': None
+        }
+        response = client.post(reverse('preview-certificate', kwargs={
+            'project_slug': self.test_project.slug,
+            'organisation_slug': self.test_certifying_organisation.slug
+        }), post_data)
+        self.assertEqual(response.status_code, 200)