From 5e5afa60f2cee324e9c8add24221b3d0e39a3b33 Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Mon, 11 Dec 2017 11:10:09 +0100
Subject: [PATCH] forms and views for creatng and editing vouchers

---
 smash/web/algorithm/__init__.py               |  4 +
 smash/web/algorithm/luhn_algorithm.py         | 21 +++++
 smash/web/algorithm/verhoeff_alogirthm.py     | 45 +++++++++++
 smash/web/forms/forms.py                      | 51 +++++++++++-
 smash/web/forms/subject_forms.py              | 66 +---------------
 smash/web/migrations/0092_voucher.py          | 31 ++++++++
 .../web/migrations/0093_auto_20171208_1508.py | 21 +++++
 .../web/migrations/0094_auto_20171208_1508.py | 21 +++++
 .../web/migrations/0095_auto_20171208_1509.py | 20 +++++
 .../web/migrations/0096_auto_20171208_1509.py | 20 +++++
 smash/web/models/__init__.py                  |  3 +-
 smash/web/models/voucher.py                   | 18 ++---
 smash/web/templates/sidebar.html              |  8 ++
 smash/web/templates/vouchers/add.html         |  9 +++
 smash/web/templates/vouchers/add_edit.html    | 77 +++++++++++++++++++
 smash/web/templates/vouchers/breadcrumb.html  |  2 +
 smash/web/templates/vouchers/edit.html        | 10 +++
 smash/web/templates/vouchers/list.html        | 74 ++++++++++++++++++
 smash/web/urls.py                             |  8 ++
 smash/web/views/__init__.py                   |  1 +
 smash/web/views/voucher.py                    | 55 +++++++++++++
 21 files changed, 487 insertions(+), 78 deletions(-)
 create mode 100644 smash/web/algorithm/__init__.py
 create mode 100644 smash/web/algorithm/luhn_algorithm.py
 create mode 100644 smash/web/algorithm/verhoeff_alogirthm.py
 create mode 100644 smash/web/migrations/0092_voucher.py
 create mode 100644 smash/web/migrations/0093_auto_20171208_1508.py
 create mode 100644 smash/web/migrations/0094_auto_20171208_1508.py
 create mode 100644 smash/web/migrations/0095_auto_20171208_1509.py
 create mode 100644 smash/web/migrations/0096_auto_20171208_1509.py
 create mode 100644 smash/web/templates/vouchers/add.html
 create mode 100644 smash/web/templates/vouchers/add_edit.html
 create mode 100644 smash/web/templates/vouchers/breadcrumb.html
 create mode 100644 smash/web/templates/vouchers/edit.html
 create mode 100644 smash/web/templates/vouchers/list.html
 create mode 100644 smash/web/views/voucher.py

diff --git a/smash/web/algorithm/__init__.py b/smash/web/algorithm/__init__.py
new file mode 100644
index 00000000..defa4efb
--- /dev/null
+++ b/smash/web/algorithm/__init__.py
@@ -0,0 +1,4 @@
+from luhn_algorithm import LuhnAlgorithm
+from verhoeff_alogirthm import VerhoeffAlgorithm
+
+__all__ = [VerhoeffAlgorithm, LuhnAlgorithm]
diff --git a/smash/web/algorithm/luhn_algorithm.py b/smash/web/algorithm/luhn_algorithm.py
new file mode 100644
index 00000000..c752bc33
--- /dev/null
+++ b/smash/web/algorithm/luhn_algorithm.py
@@ -0,0 +1,21 @@
+class LuhnAlgorithm(object):
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def luhn_checksum(card_number):
+        def digits_of(n):
+            return [int(d) for d in str(n)]
+
+        digits = digits_of(card_number)
+        odd_digits = digits[-1::-2]
+        even_digits = digits[-2::-2]
+        checksum = 0
+        checksum += sum(odd_digits)
+        for d in even_digits:
+            checksum += sum(digits_of(d * 2))
+        return checksum % 10
+
+    @staticmethod
+    def is_luhn_valid(card_number):
+        return LuhnAlgorithm.luhn_checksum(card_number) == 0
diff --git a/smash/web/algorithm/verhoeff_alogirthm.py b/smash/web/algorithm/verhoeff_alogirthm.py
new file mode 100644
index 00000000..0b9dc9ff
--- /dev/null
+++ b/smash/web/algorithm/verhoeff_alogirthm.py
@@ -0,0 +1,45 @@
+verhoeff_multiplication_table = (
+    (0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
+    (1, 2, 3, 4, 0, 6, 7, 8, 9, 5),
+    (2, 3, 4, 0, 1, 7, 8, 9, 5, 6),
+    (3, 4, 0, 1, 2, 8, 9, 5, 6, 7),
+    (4, 0, 1, 2, 3, 9, 5, 6, 7, 8),
+    (5, 9, 8, 7, 6, 0, 4, 3, 2, 1),
+    (6, 5, 9, 8, 7, 1, 0, 4, 3, 2),
+    (7, 6, 5, 9, 8, 2, 1, 0, 4, 3),
+    (8, 7, 6, 5, 9, 3, 2, 1, 0, 4),
+    (9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
+verhoeff_permutation_table = (
+    (0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
+    (1, 5, 7, 6, 2, 8, 3, 0, 9, 4),
+    (5, 8, 0, 3, 7, 9, 6, 1, 4, 2),
+    (8, 9, 1, 6, 0, 4, 3, 5, 2, 7),
+    (9, 4, 5, 3, 1, 2, 6, 8, 7, 0),
+    (4, 2, 8, 6, 5, 7, 3, 9, 0, 1),
+    (2, 7, 9, 3, 8, 0, 6, 4, 1, 5),
+    (7, 0, 4, 6, 9, 1, 3, 2, 5, 8))
+
+
+class VerhoeffAlgorithm(object):
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def verhoeff_checksum(number):
+        """Calculate the Verhoeff checksum over the provided number. The checksum
+        is returned as an int. Valid numbers should have a checksum of 0."""
+        # transform number list
+        number = tuple(int(n) for n in reversed(str(number)))
+        # calculate checksum
+        check = 0
+        for i, n in enumerate(number):
+            check = verhoeff_multiplication_table[check][verhoeff_permutation_table[i % 8][n]]
+        return check
+
+    @staticmethod
+    def is_valid_verhoeff(number):
+        return VerhoeffAlgorithm.verhoeff_checksum(number) == 0
+
+    @staticmethod
+    def calculate_verhoeff_check_sum(number):
+        return str(verhoeff_multiplication_table[VerhoeffAlgorithm.verhoeff_checksum(str(number) + '0')].index(0))
diff --git a/smash/web/forms/forms.py b/smash/web/forms/forms.py
index 570db433..d1f90b47 100644
--- a/smash/web/forms/forms.py
+++ b/smash/web/forms/forms.py
@@ -4,11 +4,13 @@ from collections import OrderedDict
 
 from django import forms
 from django.forms import ModelForm, Form
+from django.utils import timezone
 from django.utils.dates import MONTHS
 
+from web.algorithm import VerhoeffAlgorithm
 from web.models import StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, AppointmentTypeLink, \
-    Availability, Holiday, VoucherType, VoucherTypePrice
-from web.models.constants import SUBJECT_TYPE_CHOICES
+    Availability, Holiday, VoucherType, VoucherTypePrice, Voucher
+from web.models.constants import SUBJECT_TYPE_CHOICES, VOUCHER_STATUS_NEW, VOUCHER_STATUS_USED
 from web.views.notifications import get_filter_locations
 
 """
@@ -404,3 +406,48 @@ class VoucherTypePriceForm(ModelForm):
     class Meta:
         model = VoucherTypePrice
         exclude = ['voucher_type']
+
+
+class VoucherForm(ModelForm):
+    class Meta:
+        model = Voucher
+        fields = '__all__'
+
+    def __init__(self, *args, **kwargs):
+        super(VoucherForm, self).__init__(*args, **kwargs)
+        self.fields['number'].widget.attrs['readonly'] = True
+        self.fields['number'].required = False
+
+        self.fields['issue_date'].widget.attrs['readonly'] = True
+        self.fields['issue_date'].required = False
+        self.fields['expiry_date'].widget.attrs['readonly'] = True
+        self.fields['expiry_date'].required = False
+        self.fields['use_date'].widget.attrs['readonly'] = True
+        instance = getattr(self, 'instance', None)
+        if instance and instance.pk:
+            self.fields['voucher_type'].widget.attrs['readonly'] = True
+            if instance.status != VOUCHER_STATUS_NEW:
+                self.fields['status'].widget.attrs['readonly'] = True
+                self.fields['feedback'].widget.attrs['readonly'] = True
+                self.fields['usage_partner'].widget.attrs['readonly'] = True
+
+    def clean(self):
+        if self.cleaned_data["status"] == VOUCHER_STATUS_USED and not self.cleaned_data["usage_partner"]:
+            self.add_error('usage_partner', "Partner must be defined for used voucher")
+        if self.cleaned_data["status"] != VOUCHER_STATUS_USED and self.cleaned_data["usage_partner"]:
+            self.add_error('status', "Status must be used for voucher with defined partner")
+
+    def save(self, commit=True):
+        instance = super(VoucherForm, self).save(commit=False)
+        if not instance.id:
+            instance.issue_date = timezone.now()
+            instance.expiry_date = instance.issue_date + datetime.timedelta(days=92)
+            max_id = str(0).zfill(5)
+            if Voucher.objects.latest('id'):
+                max_id = str(Voucher.objects.latest('id').id).zfill(5)
+            instance.number = max_id + VerhoeffAlgorithm.calculate_verhoeff_check_sum(max_id)
+        if instance.status == VOUCHER_STATUS_USED and not instance.use_date:
+            instance.use_date = timezone.now()
+
+        if commit:
+            instance.save()
diff --git a/smash/web/forms/subject_forms.py b/smash/web/forms/subject_forms.py
index cc717842..05fe533e 100644
--- a/smash/web/forms/subject_forms.py
+++ b/smash/web/forms/subject_forms.py
@@ -3,6 +3,7 @@ import logging
 from django import forms
 from django.forms import ModelForm
 
+from web.algorithm import VerhoeffAlgorithm, LuhnAlgorithm
 from web.forms.forms import DATEPICKER_DATE_ATTRS
 from web.models import Subject
 from web.models.constants import COUNTRY_OTHER_ID
@@ -26,75 +27,14 @@ def is_valid_social_security_number(number):
             return False
         if not number.isdigit():
             return False
-        if not is_luhn_valid(number[:12]):
+        if not LuhnAlgorithm.is_luhn_valid(number[:12]):
             return False
-        if not is_valid_verhoeff(number[:11] + number[12]):
+        if not VerhoeffAlgorithm.is_valid_verhoeff(number[:11] + number[12]):
             return False
 
     return True
 
 
-def luhn_checksum(card_number):
-    def digits_of(n):
-        return [int(d) for d in str(n)]
-
-    digits = digits_of(card_number)
-    odd_digits = digits[-1::-2]
-    even_digits = digits[-2::-2]
-    checksum = 0
-    checksum += sum(odd_digits)
-    for d in even_digits:
-        checksum += sum(digits_of(d * 2))
-    return checksum % 10
-
-
-def is_luhn_valid(card_number):
-    return luhn_checksum(card_number) == 0
-
-
-verhoeff_multiplication_table = (
-    (0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
-    (1, 2, 3, 4, 0, 6, 7, 8, 9, 5),
-    (2, 3, 4, 0, 1, 7, 8, 9, 5, 6),
-    (3, 4, 0, 1, 2, 8, 9, 5, 6, 7),
-    (4, 0, 1, 2, 3, 9, 5, 6, 7, 8),
-    (5, 9, 8, 7, 6, 0, 4, 3, 2, 1),
-    (6, 5, 9, 8, 7, 1, 0, 4, 3, 2),
-    (7, 6, 5, 9, 8, 2, 1, 0, 4, 3),
-    (8, 7, 6, 5, 9, 3, 2, 1, 0, 4),
-    (9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
-
-verhoeff_permutation_table = (
-    (0, 1, 2, 3, 4, 5, 6, 7, 8, 9),
-    (1, 5, 7, 6, 2, 8, 3, 0, 9, 4),
-    (5, 8, 0, 3, 7, 9, 6, 1, 4, 2),
-    (8, 9, 1, 6, 0, 4, 3, 5, 2, 7),
-    (9, 4, 5, 3, 1, 2, 6, 8, 7, 0),
-    (4, 2, 8, 6, 5, 7, 3, 9, 0, 1),
-    (2, 7, 9, 3, 8, 0, 6, 4, 1, 5),
-    (7, 0, 4, 6, 9, 1, 3, 2, 5, 8))
-
-
-def verhoeff_checksum(number):
-    """Calculate the Verhoeff checksum over the provided number. The checksum
-    is returned as an int. Valid numbers should have a checksum of 0."""
-    # transform number list
-    number = tuple(int(n) for n in reversed(str(number)))
-    # calculate checksum
-    check = 0
-    for i, n in enumerate(number):
-        check = verhoeff_multiplication_table[check][verhoeff_permutation_table[i % 8][n]]
-    return check
-
-
-def is_valid_verhoeff(number):
-    return verhoeff_checksum(number) == 0
-
-
-def calculate_verhoeff_check_sum(number):
-    return str(verhoeff_multiplication_table[verhoeff_checksum(str(number) + '0')].index(0))
-
-
 FIELD_ORDER = ["first_name", "last_name", "sex", "date_born", "social_security_number",
                "default_written_communication_language", "languages", "phone_number", "phone_number_2",
                "phone_number_3", "address", "city", "postal_code", "country"]
diff --git a/smash/web/migrations/0092_voucher.py b/smash/web/migrations/0092_voucher.py
new file mode 100644
index 00000000..963120ee
--- /dev/null
+++ b/smash/web/migrations/0092_voucher.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-08 15:00
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0091_auto_20171208_1312'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Voucher',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('number', models.CharField(max_length=10, unique=True, verbose_name=b'Number')),
+                ('issue_date', models.DateField(verbose_name=b'Issue date')),
+                ('expiry_date', models.DateField(verbose_name=b'Expiry date')),
+                ('use_date', models.DateField(verbose_name=b'Use date')),
+                ('status', models.CharField(choices=[(b'NEW', b'New'), (b'USED', b'Used'), (b'EXPIRED', b'Expired')], default=b'NEW', max_length=20, verbose_name=b'Status')),
+                ('feedback', models.TextField(blank=True, max_length=2000, verbose_name=b'Feedback')),
+                ('study_subject', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.StudySubject')),
+                ('usage_partner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Worker')),
+                ('voucher_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.VoucherType')),
+            ],
+        ),
+    ]
diff --git a/smash/web/migrations/0093_auto_20171208_1508.py b/smash/web/migrations/0093_auto_20171208_1508.py
new file mode 100644
index 00000000..839c2936
--- /dev/null
+++ b/smash/web/migrations/0093_auto_20171208_1508.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-08 15:08
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0092_voucher'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='voucher',
+            name='usage_partner',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Worker'),
+        ),
+    ]
diff --git a/smash/web/migrations/0094_auto_20171208_1508.py b/smash/web/migrations/0094_auto_20171208_1508.py
new file mode 100644
index 00000000..5ca26364
--- /dev/null
+++ b/smash/web/migrations/0094_auto_20171208_1508.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-08 15:08
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0093_auto_20171208_1508'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='voucher',
+            name='usage_partner',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Worker'),
+        ),
+    ]
diff --git a/smash/web/migrations/0095_auto_20171208_1509.py b/smash/web/migrations/0095_auto_20171208_1509.py
new file mode 100644
index 00000000..37b50905
--- /dev/null
+++ b/smash/web/migrations/0095_auto_20171208_1509.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-08 15:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0094_auto_20171208_1508'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='voucher',
+            name='use_date',
+            field=models.DateField(null=True, verbose_name=b'Use date'),
+        ),
+    ]
diff --git a/smash/web/migrations/0096_auto_20171208_1509.py b/smash/web/migrations/0096_auto_20171208_1509.py
new file mode 100644
index 00000000..b076d5c3
--- /dev/null
+++ b/smash/web/migrations/0096_auto_20171208_1509.py
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-08 15:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0095_auto_20171208_1509'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='voucher',
+            name='use_date',
+            field=models.DateField(blank=True, null=True, verbose_name=b'Use date'),
+        ),
+    ]
diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py
index 6b9eac7e..77a30db0 100644
--- a/smash/web/models/__init__.py
+++ b/smash/web/models/__init__.py
@@ -27,6 +27,7 @@ from item import Item
 from language import Language
 from subject import Subject
 from study_subject import StudySubject
+from voucher import Voucher
 from study_subject_list import StudySubjectList
 from study_visit_list import StudyVisitList
 from appointment_list import AppointmentList
@@ -38,5 +39,5 @@ from inconsistent_subject import InconsistentSubject, InconsistentField
 __all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room,
            Subject, StudySubject, StudySubjectList, SubjectColumns, StudyNotificationParameters,
            AppointmentList, AppointmentColumns, Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate,
-           AppointmentTypeLink, VoucherType, VoucherTypePrice,
+           AppointmentTypeLink, VoucherType, VoucherTypePrice, Voucher,
            MissingSubject, InconsistentSubject, InconsistentField, Country, StudyColumns, VisitColumns, StudyVisitList]
diff --git a/smash/web/models/voucher.py b/smash/web/models/voucher.py
index e9e59787..f7cc8f15 100644
--- a/smash/web/models/voucher.py
+++ b/smash/web/models/voucher.py
@@ -1,11 +1,9 @@
 # coding=utf-8
-import datetime
-from time import timezone
 
 from django.db import models
 
-from models import VoucherType, StudySubject, Worker
-from models.constants import VOUCHER_STATUS_CHOICES, VOUCHER_STATUS_NEW
+from web.models import VoucherType, StudySubject, Worker
+from web.models.constants import VOUCHER_STATUS_CHOICES, VOUCHER_STATUS_NEW
 
 
 class Voucher(models.Model):
@@ -22,7 +20,7 @@ class Voucher(models.Model):
 
     issue_date = models.DateField(verbose_name='Issue date', null=False)
     expiry_date = models.DateField(verbose_name='Expiry date', null=False)
-    use_date = models.DateField(verbose_name='Use date')
+    use_date = models.DateField(verbose_name='Use date', null=True, blank=True)
     voucher_type = models.ForeignKey(
         VoucherType,
         on_delete=models.CASCADE,
@@ -36,7 +34,7 @@ class Voucher(models.Model):
         editable=False
     )
 
-    status = models.CharField(max_length=20, choices=VOUCHER_STATUS_CHOICES.items(),
+    status = models.CharField(max_length=20, choices=VOUCHER_STATUS_CHOICES,
                               verbose_name='Status',
                               default=VOUCHER_STATUS_NEW
                               )
@@ -49,14 +47,10 @@ class Voucher(models.Model):
     usage_partner = models.ForeignKey(
         Worker,
         on_delete=models.CASCADE,
+        null=True,
+        blank=True
     )
 
-    def save(self, *args, **kwargs):
-        if not self.id:
-            self.issue_date = timezone.now()
-            self.expiry_date = self.issue_date + datetime.timedelta(days=92)
-        return super(Voucher, self).save(*args, **kwargs)
-
     def __str__(self):
         return "%s - %s %s" % (self.number, self.study_subject.subject.first_name, self.study_subject.subject.last_name)
 
diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index a8dd1d75..71c325a1 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -57,6 +57,14 @@
             <span>Export</span>
         </a>
     </li>
+
+    <li data-desc="vouchers">
+        <a href="{% url 'web.views.vouchers' %}">
+            <i class="fa fa-user-md"></i>
+            <span>Vouchers</span>
+        </a>
+    </li>
+
     <li data-desc="configuration" class="treeview">
         <a href="#">
             <i class="fa fa-wrench"></i> <span>Configuration</span>
diff --git a/smash/web/templates/vouchers/add.html b/smash/web/templates/vouchers/add.html
new file mode 100644
index 00000000..994ab3cb
--- /dev/null
+++ b/smash/web/templates/vouchers/add.html
@@ -0,0 +1,9 @@
+{% extends "vouchers/add_edit.html" %}
+
+{% block page_header %}New voucher{% endblock page_header %}
+
+{% block title %}{{ block.super }} - Add voucher{% endblock %}
+
+{% block form-title %}Enter voucher details{% endblock %}
+
+{% block save-button %}Add{% endblock %}
diff --git a/smash/web/templates/vouchers/add_edit.html b/smash/web/templates/vouchers/add_edit.html
new file mode 100644
index 00000000..8e5e5dfb
--- /dev/null
+++ b/smash/web/templates/vouchers/add_edit.html
@@ -0,0 +1,77 @@
+{% extends "_base.html" %}
+{% load static %}
+{% load filters %}
+
+{% block styles %}
+    {{ block.super }}
+    <link rel="stylesheet" href="{% static 'AdminLTE/plugins/awesomplete/awesomplete.css' %}"/>
+
+{% endblock styles %}
+
+{% block ui_active_tab %}'configuration'{% endblock ui_active_tab %}
+{% block page_description %}{% endblock page_description %}
+
+{% block breadcrumb %}
+    {% include "vouchers/breadcrumb.html" %}
+{% endblock breadcrumb %}
+
+{% block maincontent %}
+
+    {% block content %}
+        <div class="row">
+            <div class="col-md-12">
+                <div class="box box-success">
+                    <div class="box-header with-border">
+                        <h3 class="box-title">{% block form-title %}Enter voucher details{% endblock %}</h3>
+                    </div>
+
+
+                    <form method="post" action="" class="form-horizontal" enctype="multipart/form-data">
+                        {% csrf_token %}
+
+                        <div class="box-body">
+                            {% for field in form %}
+                                <div class="form-group {% if field.errors %}has-error{% endif %}">
+                                    <label class="col-sm-4  col-lg-offset-1 col-lg-2 control-label">
+                                        {{ field.label }}
+                                    </label>
+
+                                    <div class="col-sm-8 col-lg-4">
+                                        {{ field|add_class:'form-control' }}
+                                        {% if field.errors %}
+                                            <span class="help-block">{{ field.errors }}</span>
+                                        {% endif %}
+                                    </div>
+
+
+                                </div>
+                            {% endfor %}
+                        </div><!-- /.box-body -->
+                        <div class="box-footer">
+                            <div class="col-sm-6">
+                                <button type="submit" class="btn btn-block btn-success">{% block save-button %}
+                                    Add{% endblock %}
+                                </button>
+                            </div>
+                            <div class="col-sm-6">
+                                <a href="{% url 'web.views.vouchers' %}"
+                                   class="btn btn-block btn-default">Cancel</a>
+                            </div>
+                        </div><!-- /.box-footer -->
+                    </form>
+                </div>
+
+            </div>
+        </div>
+
+    {% endblock %}
+
+
+{% endblock maincontent %}
+
+{% block scripts %}
+    {{ block.super }}
+
+    <script src="{% static 'AdminLTE/plugins/awesomplete/awesomplete.min.js' %}"></script>
+
+{% endblock scripts %}
\ No newline at end of file
diff --git a/smash/web/templates/vouchers/breadcrumb.html b/smash/web/templates/vouchers/breadcrumb.html
new file mode 100644
index 00000000..f71acc80
--- /dev/null
+++ b/smash/web/templates/vouchers/breadcrumb.html
@@ -0,0 +1,2 @@
+<li><a href="{% url 'web.views.appointments' %}"><i class="fa fa-dashboard"></i> Dashboard</a></li>
+<li class="active"><a href="{% url 'web.views.vouchers' %}">Vouchers</a></li>
\ No newline at end of file
diff --git a/smash/web/templates/vouchers/edit.html b/smash/web/templates/vouchers/edit.html
new file mode 100644
index 00000000..20e1bea6
--- /dev/null
+++ b/smash/web/templates/vouchers/edit.html
@@ -0,0 +1,10 @@
+{% extends "vouchers/add_edit.html" %}
+
+{% block page_header %}Edit voucher "{{ voucher.number }}"{% endblock page_header %}
+
+{% block title %}{{ block.super }} - Edit voucher "{{ voucher.number }}"{% endblock %}
+
+{% block form-title %}Enter voucher details{% endblock %}
+
+{% block save-button %}Save{% endblock %}
+
diff --git a/smash/web/templates/vouchers/list.html b/smash/web/templates/vouchers/list.html
new file mode 100644
index 00000000..7a29b16f
--- /dev/null
+++ b/smash/web/templates/vouchers/list.html
@@ -0,0 +1,74 @@
+{% extends "_base.html" %}
+{% load static %}
+
+{% block styles %}
+    {{ block.super }}
+    <!-- DataTables -->
+    <link rel="stylesheet" href="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.css' %}">
+{% endblock styles %}
+
+{% block ui_active_tab %}'configuration'{% endblock ui_active_tab %}
+{% block page_header %}Vouchers{% endblock page_header %}
+{% block page_description %}{% endblock page_description %}
+
+{% block breadcrumb %}
+    {% include "vouchers/breadcrumb.html" %}
+{% endblock breadcrumb %}
+
+{% block maincontent %}
+
+    <div class="box-body">
+        <table id="table" class="table table-bordered table-striped">
+            <thead>
+            <tr>
+                <th>Number</th>
+                <th>First name</th>
+                <th>Last name</th>
+                <th>Issue date</th>
+                <th>Expiry date</th>
+                <th>Status</th>
+                <th>Use date</th>
+                <th>Partner</th>
+                <th>Feedback</th>
+                <th>Edit</th>
+            </tr>
+            </thead>
+            <tbody>
+            {% for voucher in vouchers %}
+                <tr>
+                    <td>{{ voucher.number }}</td>
+                    <td>{{ voucher.study_subject.subject.first_name }}</td>
+                    <td>{{ voucher.study_subject.subject.last_name }}</td>
+                    <td>{{ voucher.issue_date }}</td>
+                    <td>{{ voucher.expiry_date }}</td>
+                    <td>{{ voucher.status }}</td>
+                    <td>{{ voucher.use_date }}</td>
+                    <td>{{ voucher.usage_partner.first_name }} {{ voucher.usage_partner.last_name }}</td>
+                    <td>{{ voucher.feedback }}</td>
+                    <td><a href="{% url 'web.views.voucher_edit' voucher.id %}"><i class="fa fa-edit"></i></a></td>
+                </tr>
+            {% endfor %}
+            </tbody>
+        </table>
+    </div>
+{% endblock maincontent %}
+
+{% block scripts %}
+    {{ block.super }}
+
+    <script src="{% static 'AdminLTE/plugins/datatables/jquery.dataTables.min.js' %}"></script>
+    <script src="{% static 'AdminLTE/plugins/datatables/dataTables.bootstrap.min.js' %}"></script>
+
+    <script>
+        $(function () {
+            $('#table').DataTable({
+                "paging": true,
+                "lengthChange": false,
+                "searching": true,
+                "ordering": true,
+                "info": true,
+                "autoWidth": false
+            });
+        });
+    </script>
+{% endblock scripts %}
diff --git a/smash/web/urls.py b/smash/web/urls.py
index c2bc3d3b..1ee0abb3 100644
--- a/smash/web/urls.py
+++ b/smash/web/urls.py
@@ -178,6 +178,14 @@ urlpatterns = [
     url(r'^voucher_types/(?P<voucher_type_id>\d+)/prices/(?P<pk>\d+)/edit$',
         views.voucher_type_price.VoucherTypePriceEditView.as_view(), name='web.views.voucher_type_price_edit'),
 
+    ####################
+    #     VOUCHERS     #
+    ####################
+
+    url(r'^vouchers$', views.voucher.VoucherListView.as_view(), name='web.views.vouchers'),
+    url(r'^vouchers/add$', views.voucher.VoucherCreateView.as_view(), name='web.views.voucher_add'),
+    url(r'^vouchers/(?P<pk>\d+)/edit$', views.voucher.VoucherEditView.as_view(), name='web.views.voucher_edit'),
+
     ####################
     #    STATISTICS    #
     ####################
diff --git a/smash/web/views/__init__.py b/smash/web/views/__init__.py
index 01bdb13f..9a8ed660 100644
--- a/smash/web/views/__init__.py
+++ b/smash/web/views/__init__.py
@@ -72,6 +72,7 @@ import export
 import contact_attempt
 import configuration_item
 import language
+import voucher
 import voucher_type
 import voucher_type_price
 import redcap
diff --git a/smash/web/views/voucher.py b/smash/web/views/voucher.py
new file mode 100644
index 00000000..a054d9d0
--- /dev/null
+++ b/smash/web/views/voucher.py
@@ -0,0 +1,55 @@
+# coding=utf-8
+import logging
+
+from django.contrib.messages.views import SuccessMessageMixin
+from django.urls import reverse_lazy
+from django.views.generic import CreateView
+from django.views.generic import ListView
+from django.views.generic import UpdateView
+
+from web.forms.forms import VoucherForm
+from web.models import Voucher
+from web.models.constants import GLOBAL_STUDY_ID
+from . import WrappedView
+
+logger = logging.getLogger(__name__)
+
+
+class VoucherListView(ListView, WrappedView):
+    model = Voucher
+    context_object_name = "vouchers"
+    template_name = 'vouchers/list.html'
+
+
+class VoucherCreateView(CreateView, WrappedView):
+    form_class = VoucherForm
+    model = Voucher
+
+    template_name = "vouchers/add.html"
+    success_url = reverse_lazy('web.views.vouchers')
+    success_message = "Voucher type created"
+
+    def form_valid(self, form):
+        form.instance.study_id = GLOBAL_STUDY_ID
+        # noinspection PyUnresolvedReferences
+        form.instance.study_subject_id = self.request.GET.get("study_subject_id", -1)
+        return super(VoucherCreateView, self).form_valid(form)
+
+    def get_success_url(self, **kwargs):
+        # noinspection PyUnresolvedReferences
+        return reverse_lazy('web.views.subject_edit', kwargs={'id': self.request.GET.get("study_subject_id", -1)})
+
+
+class VoucherEditView(SuccessMessageMixin, UpdateView, WrappedView):
+    form_class = VoucherForm
+    model = Voucher
+
+    success_url = reverse_lazy('web.views.vouchers')
+    success_message = "Voucher saved successfully"
+    template_name = "vouchers/edit.html"
+    context_object_name = "voucher_type"
+
+    def get_success_url(self, **kwargs):
+        # noinspection PyUnresolvedReferences
+        study_subject_id = Voucher.objects.get(id=self.kwargs['pk']).study_subject.id
+        return reverse_lazy('web.views.subject_edit', kwargs={'id': study_subject_id})
-- 
GitLab