diff --git a/smash/smash/settings.py b/smash/smash/settings.py
index c11fe16accedc21a7dcdf46f168e4305be7ec5ea..14f7a7ff96dc662eadb5b2216029619d0f16cc28 100644
--- a/smash/smash/settings.py
+++ b/smash/smash/settings.py
@@ -76,6 +76,7 @@ CRON_CLASSES = [
     'web.views.kit.KitRequestEmailSendJob',
     'web.redcap_connector.RedCapRefreshJob',
     'web.views.voucher.ExpireVouchersJob',
+    'web.importer.importer_cron_job.ImporterCronJob'
 ]
 
 # Password validation
diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index fb81d9624b40750c5f24315112d873516b2ff3ab..c31d13903ab43b26fa7db0fd1f9c570d28e5cb5e 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -70,6 +70,10 @@ def get_subject_columns(request, subject_list_type):
     add_column(result, "Resigned", "resigned", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Endpoint Reached", "endpoint_reached", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Postponed", "postponed", study_subject_columns, "yes_no_filter", study.columns)
+    add_column(result, "Next of keen", "next_of_keen_name", subject_columns, "string_filter")
+    add_column(result, "Next of keen phone", "next_of_keen_phone", subject_columns, "string_filter")
+    add_column(result, "Next of keen address", "next_of_keen_address", subject_columns, "string_filter")
+    add_column(result, "Brain donation agreement", "brain_donation_agreement", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Excluded", "excluded", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Info sent", "information_sent", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Type", "type", study_subject_columns, "type_filter", study.columns)
@@ -113,6 +117,12 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction, co
         result = subjects_to_be_ordered.order_by(order_direction + 'subject__last_name')
     elif order_column == "address":
         result = subjects_to_be_ordered.order_by(order_direction + 'subject__address')
+    elif order_column == "next_of_keen_name":
+        result = subjects_to_be_ordered.order_by(order_direction + 'subject__next_of_keen_name')
+    elif order_column == "next_of_keen_phone":
+        result = subjects_to_be_ordered.order_by(order_direction + 'subject__next_of_keen_phone')
+    elif order_column == "next_of_keen_address":
+        result = subjects_to_be_ordered.order_by(order_direction + 'subject__next_of_keen_address')
     elif order_column == "nd_number":
         result = subjects_to_be_ordered.order_by(order_direction + 'nd_number')
     elif order_column == "referral":
@@ -144,6 +154,8 @@ def get_subjects_order(subjects_to_be_ordered, order_column, order_direction, co
         result = subjects_to_be_ordered.order_by(order_direction + 'subject__social_security_number')
     elif order_column == "postponed":
         result = subjects_to_be_ordered.order_by(order_direction + 'postponed')
+    elif order_column == "brain_donation_agreement":
+        result = subjects_to_be_ordered.order_by(order_direction + 'brain_donation_agreement')
     elif order_column == "excluded":
         result = subjects_to_be_ordered.order_by(order_direction + 'excluded')
     elif order_column == "type":
@@ -242,6 +254,12 @@ def get_subjects_filtered(subjects_to_be_filtered, filters):
             result = result.filter(subject__last_name__icontains=value)
         elif column == "address":
             result = result.filter(subject__address__icontains=value)
+        elif column == "next_of_keen_name":
+            result = result.filter(subject__next_of_keen_name__icontains=value)
+        elif column == "next_of_keen_phone":
+            result = result.filter(subject__next_of_keen_phone__icontains=value)
+        elif column == "next_of_keen_address":
+            result = result.filter(subject__next_of_keen_address__icontains=value)
         elif column == "nd_number":
             result = result.filter(nd_number__icontains=value)
         elif column == "referral":
@@ -254,6 +272,8 @@ def get_subjects_filtered(subjects_to_be_filtered, filters):
             result = result.filter(resigned=(value == "true"))
         elif column == "endpoint_reached":
             result = result.filter(endpoint_reached=(value == "true"))
+        elif column == "brain_donation_agreement":
+            result = result.filter(brain_donation_agreement=(value == "true"))
         elif column == "postponed":
             result = result.filter(postponed=(value == "true"))
         elif column == "excluded":
@@ -402,6 +422,9 @@ def serialize_subject(study_subject):
         "first_name": study_subject.subject.first_name,
         "last_name": study_subject.subject.last_name,
         "address": study_subject.subject.pretty_address(),
+        "next_of_keen_name": study_subject.subject.next_of_keen_name,
+        "next_of_keen_phone": study_subject.subject.next_of_keen_phone,
+        "next_of_keen_address": study_subject.subject.next_of_keen_address,
         "date_born": study_subject.subject.date_born,
         "datetime_contact_reminder": contact_reminder,
         "last_contact_attempt": last_contact_attempt_string,
@@ -414,6 +437,7 @@ def serialize_subject(study_subject):
         "resigned": bool_to_yes_no(study_subject.resigned),
         "endpoint_reached": bool_to_yes_no(study_subject.endpoint_reached),
         "postponed": bool_to_yes_no(study_subject.postponed),
+        "brain_donation_agreement": bool_to_yes_no(study_subject.brain_donation_agreement),
         "excluded": bool_to_yes_no(study_subject.excluded),
         "information_sent": bool_to_yes_no(study_subject.information_sent),
         "health_partner_first_name": health_partner_first_name,
diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py
index ea2849fc6ae8ddce7b8f5ddc62d1bb7467908b8a..2bc880780226734cfa1653501960eb47bc8138b0 100644
--- a/smash/web/forms/study_subject_forms.py
+++ b/smash/web/forms/study_subject_forms.py
@@ -161,6 +161,7 @@ def prepare_study_subject_fields(fields, study):
     prepare_field(fields, study.columns, 'nd_number')
     prepare_field(fields, study.columns, 'datetime_contact_reminder')
     prepare_field(fields, study.columns, 'postponed')
+    prepare_field(fields, study.columns, 'brain_donation_agreement')
     prepare_field(fields, study.columns, 'flying_team')
     prepare_field(fields, study.columns, 'mpower_id')
     prepare_field(fields, study.columns, 'comments')
diff --git a/smash/web/importer/__init__.py b/smash/web/importer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..eda42f3accab2707cf3151273f71db973c51f267
--- /dev/null
+++ b/smash/web/importer/__init__.py
@@ -0,0 +1,8 @@
+from csv_subject_import_reader import CsvSubjectImportReader
+from exporter import Exporter
+from exporter_cron_job import ExporterCronJob
+from importer import Importer
+from importer_cron_job import ImporterCronJob
+from subject_import_reader import SubjectImportReader
+
+__all__ = [Importer, SubjectImportReader, CsvSubjectImportReader, ImporterCronJob, Exporter, ExporterCronJob]
diff --git a/smash/web/importer/csv_subject_import_reader.py b/smash/web/importer/csv_subject_import_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..187b59aba9d98798f8630054828256aa3b80b07f
--- /dev/null
+++ b/smash/web/importer/csv_subject_import_reader.py
@@ -0,0 +1,74 @@
+import csv
+import datetime
+import logging
+
+from subject_import_reader import SubjectImportReader
+from web.models import StudySubject, Subject, Study
+from web.models.constants import GLOBAL_STUDY_ID
+
+CSV_DATE_FORMAT = "%d-%m-%Y"
+
+logger = logging.getLogger(__name__)
+
+
+class CsvSubjectImportReader(SubjectImportReader):
+    def __init__(self):
+        self.study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+
+    def load_data(self, filename):
+        study_subjects = []
+        with open(filename) as csv_file:
+            reader = csv.reader(csv_file, delimiter=',')
+            headers = next(reader, None)
+            for row in reader:
+                subject = Subject()
+                study_subject = StudySubject()
+                study_subject.subject = subject
+                study_subject.study = self.study
+                for header, value in zip(headers, row):
+                    self.add_data(study_subject, header, value)
+                if study_subject.nd_number is None or study_subject.nd_number == "":
+                    study_subject.nd_number = study_subject.screening_number
+                study_subjects.append(study_subject)
+        return study_subjects
+
+    def add_data(self, study_subject, column_name, value):
+        # type: (StudySubject, str, str) ->  None
+        if column_name == "first_name":
+            study_subject.subject.first_name = self.get_new_value(study_subject.subject.first_name, column_name, value)
+        elif column_name == "last_name":
+            study_subject.subject.last_name = self.get_new_value(study_subject.subject.last_name, column_name, value)
+        elif column_name == "participant_id":
+            study_subject.screening_number = self.get_new_value(study_subject.screening_number, column_name, value)
+        elif column_name == "date_born":
+            study_subject.subject.date_born = self.get_new_date_value(study_subject.subject.date_born, column_name,
+                                                                      value)
+        else:
+            logger.warn("Don't know how to handle column " + column_name + " with data " + value)
+
+    def get_new_value(self, old_value, column_name, new_value):
+        # type: (unicode,unicode,unicode) ->  unicode
+        if old_value is None or old_value == "":
+            return new_value
+        if new_value is None or new_value == "":
+            return old_value
+        logger.warn(
+            "Contradicting entries in csv file for column: " + column_name + "(" + new_value + "," + old_value +
+            "). Latest value will be used")
+        return new_value
+
+    def get_new_date_value(self, old_value, column_name, new_value):
+        # type: (datetime,unicode,unicode) ->  datetime
+        if old_value is None or old_value == "":
+            try:
+                result = datetime.datetime.strptime(new_value, CSV_DATE_FORMAT)
+            except ValueError:
+                logger.warn("Invalid date: " + new_value)
+                result = old_value
+            return result
+        if new_value is None or new_value == "":
+            return old_value
+        logger.warn(
+            "Contradicting entries in csv file for column: " + column_name + "(" + new_value + "," + old_value +
+            "). Latest value will be used")
+        return datetime.datetime.strptime(new_value, CSV_DATE_FORMAT)
diff --git a/smash/web/importer/exporter.py b/smash/web/importer/exporter.py
new file mode 100644
index 0000000000000000000000000000000000000000..99b72d79a94443e3f04f4a7ad69f2c859da7b339
--- /dev/null
+++ b/smash/web/importer/exporter.py
@@ -0,0 +1,50 @@
+# coding=utf-8
+import csv
+import logging
+
+from warning_counter import MsgCounterHandler
+from web.models import StudySubject
+
+logger = logging.getLogger(__name__)
+
+
+class Exporter(object):
+    def __init__(self, filename):
+        # type: (str) -> None
+        self.filename = filename
+        self.exported_count = 0
+        self.warning_count = 0
+
+    def execute(self):
+        self.exported_count = 0
+        self.warning_count = 0
+
+        warning_counter = MsgCounterHandler()
+        logging.getLogger('').addHandler(warning_counter)
+
+        with open(self.filename, 'w') as csv_file:
+            data = self.get_subjects_as_array()
+            writer = csv.writer(csv_file, quotechar=str(u'"'), quoting=csv.QUOTE_ALL)
+            for row in data:
+                writer.writerow([s.encode("utf-8") for s in row])
+                self.exported_count += 1
+
+        if "WARNING" in warning_counter.level2count:
+            self.warning_count = warning_counter.level2count["WARNING"]
+        logging.getLogger('').removeHandler(warning_counter)
+
+    def get_summary(self):
+        result = "<p>Number of entries: <b>" + str(self.exported_count) + "</b></p>"
+        style = ''
+        if self.warning_count > 0:
+            style = ' color="brown" '
+        result += "<p><font " + style + ">Number of raised warnings: <b>" + str(self.warning_count) + "</b></font></p>"
+
+        return result
+
+    def get_subjects_as_array(self):
+        result = []
+        study_subjects = StudySubject.objects.filter(excluded=True)
+        for study_subject in study_subjects:
+            result.append([study_subject.nd_number])
+        return result
diff --git a/smash/web/importer/exporter_cron_job.py b/smash/web/importer/exporter_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..90ce95a604070e873332ec0c8610180808694ece
--- /dev/null
+++ b/smash/web/importer/exporter_cron_job.py
@@ -0,0 +1,46 @@
+# coding=utf-8
+import logging
+import traceback
+
+import timeout_decorator
+from django.conf import settings
+from django_cron import CronJobBase, Schedule
+
+from exporter import Exporter
+from web.models.constants import CRON_JOB_TIMEOUT
+from ..smash_email import EmailSender
+
+logger = logging.getLogger(__name__)
+
+
+class ExporterCronJob(CronJobBase):
+    RUN_EVERY_MINUTES = 60 * 24
+    schedule = Schedule(run_every_mins=RUN_EVERY_MINUTES)
+    code = 'web.import_daily_job'  # a unique code
+
+    @timeout_decorator.timeout(CRON_JOB_TIMEOUT)
+    def do(self):
+        email_title = "Daily export"
+        email_recipients = getattr(settings, "DEFAULT_FROM_EMAIL", None)
+
+        filename = getattr(settings, "DAILY_EXPORT_FILE", None)
+
+        if filename is None:
+            logger.info("Exporting subjects skipped. File not defined ")
+            return "export file not defined"
+        logger.info("Exporting subjects from file: " + filename)
+        try:
+            exporter = Exporter(settings.DAILY_EXPORT_FILE)
+            exporter.execute()
+            email_body = exporter.get_summary()
+            EmailSender().send_email(email_title,
+                                     "<h3>Data was successfully exported to file: " + filename + "</h3>" + email_body,
+                                     email_recipients)
+            return "export is successful"
+
+        except:
+            tb = traceback.format_exc()
+            EmailSender().send_email(email_title,
+                                     "<h3><font color='red'>There was a problem with exporting data to file: " + filename + "</font></h3><pre>" + tb + "</pre>",
+                                     email_recipients)
+            return "export crashed"
diff --git a/smash/web/importer/importer.py b/smash/web/importer/importer.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c7702f0de38fb9a29156e87b207b382f7a7b1ff
--- /dev/null
+++ b/smash/web/importer/importer.py
@@ -0,0 +1,161 @@
+# coding=utf-8
+import logging
+import sys
+import traceback
+
+from django.conf import settings
+from django.contrib.auth.models import User
+
+from subject_import_reader import SubjectImportReader
+from warning_counter import MsgCounterHandler
+from web.models import StudySubject, Subject, Provenance, Worker
+from web.models.constants import GLOBAL_STUDY_ID
+
+logger = logging.getLogger(__name__)
+
+
+class Importer(object):
+    def __init__(self, filename, reader):
+        # type: (str, SubjectImportReader) -> None
+        self.filename = filename
+        self.reader = reader
+        self.added_count = 0
+        self.problematic_count = 0
+        self.merged_count = 0
+        self.warning_count = 0
+        self.study_subjects = []
+
+        self.importer_user = None
+
+        importer_user_name = getattr(settings, "IMPORTER_USER", None)
+        if importer_user_name is not None:
+            user = User.objects.filter(username=importer_user_name)
+            if user is None:
+                logger.warn("User does not exist: " + importer_user_name)
+            else:
+                self.importer_user = Worker.objects.filter(user=user)
+
+    def execute(self):
+        self.added_count = 0
+        self.problematic_count = 0
+        self.merged_count = 0
+        self.warning_count = 0
+
+        warning_counter = MsgCounterHandler()
+        logging.getLogger('').addHandler(warning_counter)
+
+        self.study_subjects = self.reader.load_data(self.filename)
+        for study_subject in self.study_subjects:
+            try:
+                if study_subject.study is None:
+                    self.problematic_count += 1
+                    logger.warn("Empty study found. Ignoring")
+                    continue
+                elif study_subject.study.id != GLOBAL_STUDY_ID:
+                    self.problematic_count += 1
+                    logger.warn("Empty study found. Ignoring: " + study_subject.study.id)
+                    continue
+                else:
+                    self.import_study_subject(study_subject)
+            except:
+                self.problematic_count += 1
+                traceback.print_exc(file=sys.stdout)
+                logger.error("Problem with importing study subject: " + study_subject.screening_number)
+        if "WARNING" in warning_counter.level2count:
+            self.warning_count = warning_counter.level2count["WARNING"]
+        logging.getLogger('').removeHandler(warning_counter)
+
+    def import_study_subject(self, study_subject):
+        # type: (StudySubject) -> None
+        db_study_subjects = StudySubject.objects.filter(screening_number=study_subject.screening_number)
+        if db_study_subjects.count() > 0:
+            db_study_subject = db_study_subjects.first()
+            for field in Subject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject.subject, field.name)
+                    if new_value is not None and new_value != "":
+                        old_value = getattr(db_study_subject.subject, field.name)
+                        description = '{} changed from "{}" to "{}"'.format(field, old_value, new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=db_study_subject.subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value=old_value,
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        setattr(db_study_subject.subject, field.name, new_value)
+                        p.save()
+
+            db_study_subject.subject.save()
+
+            for field in StudySubject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject, field.name)
+                    if new_value is not None and new_value != "":
+                        old_value = getattr(db_study_subject, field.name)
+                        description = '{} changed from "{}" to "{}"'.format(field, old_value, new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=db_study_subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value=old_value,
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        setattr(db_study_subject, field.name, new_value)
+                        p.save()
+            db_study_subject.save()
+            self.merged_count += 1
+        else:
+            study_subject.subject.save()
+            study_subject.subject = Subject.objects.filter(id=study_subject.subject.id)[0]
+            study_subject.save()
+
+            for field in Subject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject.subject, field.name)
+                    if new_value is not None and new_value != "":
+                        description = '{} changed from "{}" to "{}"'.format(field, '', new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=study_subject.subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value='',
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        p.save()
+
+            for field in StudySubject._meta.get_fields():
+                if field.get_internal_type() == "CharField" or field.get_internal_type() == "DateField" or field.get_internal_type() is "BooleanField":
+                    new_value = getattr(study_subject, field.name)
+                    if new_value is not None and new_value != "":
+                        description = '{} changed from "{}" to "{}"'.format(field, '', new_value)
+                        p = Provenance(modified_table=Subject._meta.db_table,
+                                       modified_table_id=study_subject.id,
+                                       modification_author=self.importer_user,
+                                       previous_value='',
+                                       new_value=new_value,
+                                       modification_description=description,
+                                       modified_field=field,
+                                       )
+                        p.save()
+
+            self.added_count += 1
+
+    def get_summary(self):
+        result = "<p>Number of entries: <b>" + str(len(self.study_subjects)) + "</b></p>" + \
+                 "<p>Number of successfully added entries: <b>" + str(self.added_count) + "</b></p>" + \
+                 "<p>Number of successfully merged entries: <b>" + str(self.merged_count) + "</b></p>"
+        style = ''
+        if self.problematic_count > 0:
+            style = ' color="red" '
+        result += "<p><font " + style + ">Number of problematic entries: <b>" + str(
+            self.problematic_count) + "</b></font></p>"
+        style = ''
+        if self.warning_count > 0:
+            style = ' color="brown" '
+        result += "<p><font " + style + ">Number of raised warnings: <b>" + str(self.warning_count) + "</b></font></p>"
+
+        return result
diff --git a/smash/web/importer/importer_cron_job.py b/smash/web/importer/importer_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..bfb75d105c4d0db0daaea06034fa1a82946b182b
--- /dev/null
+++ b/smash/web/importer/importer_cron_job.py
@@ -0,0 +1,61 @@
+# coding=utf-8
+import datetime
+import logging
+import os
+import os.path
+import traceback
+
+import timeout_decorator
+from django.conf import settings
+from django_cron import CronJobBase, Schedule
+
+from csv_subject_import_reader import CsvSubjectImportReader
+from importer import Importer
+from web.models.constants import CRON_JOB_TIMEOUT
+from ..smash_email import EmailSender
+
+logger = logging.getLogger(__name__)
+
+
+class ImporterCronJob(CronJobBase):
+    RUN_EVERY_MINUTES = 60 * 24
+    schedule = Schedule(run_every_mins=RUN_EVERY_MINUTES)
+    code = 'web.import_daily_job'  # a unique code
+
+    @timeout_decorator.timeout(CRON_JOB_TIMEOUT)
+    def do(self):
+        email_title = "Daily import"
+        email_recipients = getattr(settings, "DEFAULT_FROM_EMAIL", None)
+
+        filename = getattr(settings, "DAILY_IMPORT_FILE", None)
+
+        if filename is None:
+            logger.info("Importing subjects skipped. File not defined ")
+            return "import file not defined"
+        logger.info("Importing subjects from file: " + filename)
+        if not os.path.isfile(filename):
+            EmailSender().send_email(email_title,
+                                     "<h3><font color='red'>File with imported data is not available in the system: " + filename + "</font></h3>",
+                                     email_recipients)
+            return "import file not found"
+        try:
+            importer = Importer(settings.DAILY_IMPORT_FILE, CsvSubjectImportReader())
+            importer.execute()
+            email_body = importer.get_summary()
+            EmailSender().send_email(email_title,
+                                     "<h3>Data was successfully imported from file: " + filename + "</h3>" + email_body,
+                                     email_recipients)
+            self.backup_file(filename)
+            return "import is successful"
+
+        except:
+            tb = traceback.format_exc()
+            EmailSender().send_email(email_title,
+                                     "<h3><font color='red'>There was a problem with importing data from file: " + filename + "</font></h3><pre>" + tb + "</pre>",
+                                     email_recipients)
+            return "import crashed"
+
+    def backup_file(self, filename):
+        new_file = filename + "-" + datetime.datetime.now().strftime("%Y-%m-%d-%H-%M") + ".bac"
+        os.rename(filename, new_file)
+        return
diff --git a/smash/web/importer/subject_import_reader.py b/smash/web/importer/subject_import_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..0bc7f898ea24a1af7e99b32bca1c8a095635a38d
--- /dev/null
+++ b/smash/web/importer/subject_import_reader.py
@@ -0,0 +1,7 @@
+from web.models.study_subject import StudySubject
+
+
+class SubjectImportReader:
+    def load_data(self, filename):
+        # type: (str) ->  List [StudySubject]
+        pass
diff --git a/smash/web/importer/warning_counter.py b/smash/web/importer/warning_counter.py
new file mode 100644
index 0000000000000000000000000000000000000000..8500d6a34303f5e2368f536ccd9c206069174eae
--- /dev/null
+++ b/smash/web/importer/warning_counter.py
@@ -0,0 +1,15 @@
+import logging
+
+class MsgCounterHandler(logging.Handler):
+    level2count = None
+
+    def __init__(self, *args, **kwargs):
+        super(MsgCounterHandler, self).__init__(*args, **kwargs)
+        self.level2count = {}
+
+    def emit(self, record):
+        l = record.levelname
+        if l not in self.level2count:
+            self.level2count[l] = 0
+        print(l)
+        self.level2count[l] += 1
diff --git a/smash/web/migrations/0148_auto_20200319_1301.py b/smash/web/migrations/0148_auto_20200319_1301.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b2abecbe955819c77d988944336b22ad28bd016
--- /dev/null
+++ b/smash/web/migrations/0148_auto_20200319_1301.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 13:01
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0147_auto_20200320_0931'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='appointmenttypelink',
+            options={'permissions': [('view_daily_planning', 'Can see daily planning')]},
+        ),
+        migrations.AlterField(
+            model_name='appointmenttype',
+            name='calendar_font_color',
+            field=models.CharField(default=b'#00000', max_length=2000, verbose_name=b'Calendar font color'),
+        ),
+    ]
diff --git a/smash/web/migrations/0149_auto_20200319_1415.py b/smash/web/migrations/0149_auto_20200319_1415.py
new file mode 100644
index 0000000000000000000000000000000000000000..c948e9780d3d5da309c62035ff5afb27f0d835fc
--- /dev/null
+++ b/smash/web/migrations/0149_auto_20200319_1415.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 14:15
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0148_auto_20200319_1301'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='appointment',
+            options={'permissions': [('send_sample_mail_for_appointments', 'Can send sample collection list')]},
+        ),
+    ]
diff --git a/smash/web/migrations/0150_auto_20200319_1446.py b/smash/web/migrations/0150_auto_20200319_1446.py
new file mode 100644
index 0000000000000000000000000000000000000000..0fcdea7e40cf414c94558b82d7befe42e7dbc275
--- /dev/null
+++ b/smash/web/migrations/0150_auto_20200319_1446.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 14:46
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0149_auto_20200319_1415'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='appointment',
+            options={'permissions': [('send_sample_mail_for_appointments', 'Can send sample collection list'), ('view_statistics', 'Can see statistics')]},
+        ),
+    ]
diff --git a/smash/web/migrations/0151_auto_20200319_1518.py b/smash/web/migrations/0151_auto_20200319_1518.py
new file mode 100644
index 0000000000000000000000000000000000000000..a034d7b50283e5a6011cbcf3e9bf24a465c0cfcb
--- /dev/null
+++ b/smash/web/migrations/0151_auto_20200319_1518.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 15:18
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0150_auto_20200319_1446'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='subject',
+            options={'permissions': [('send_sample_mail_for_appointments', 'Can send sample collection list'), ('export_subjects', 'Can export subject data to excel/csv')]},
+        ),
+    ]
diff --git a/smash/web/migrations/0152_add_permissions_to_existing_workers.py b/smash/web/migrations/0152_add_permissions_to_existing_workers.py
new file mode 100644
index 0000000000000000000000000000000000000000..67752e4690b59643da8f7ae2fe375db39a43f2a2
--- /dev/null
+++ b/smash/web/migrations/0152_add_permissions_to_existing_workers.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 13:01
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0151_auto_20200319_1518'),
+    ]
+
+    operations = [
+        migrations.RunSQL("insert into web_workerstudyrole_permissions(workerstudyrole_id, permission_id) "
+                          "select web_workerstudyrole.id, auth_permission.id from web_workerstudyrole,auth_permission "
+                          "where codename='view_daily_planning';"),
+        migrations.RunSQL("insert into web_workerstudyrole_permissions(workerstudyrole_id, permission_id) "
+                          "select web_workerstudyrole.id, auth_permission.id from web_workerstudyrole,auth_permission "
+                          "where codename='change_flyingteam';"),
+        migrations.RunSQL("insert into web_workerstudyrole_permissions(workerstudyrole_id, permission_id) "
+                          "select web_workerstudyrole.id, auth_permission.id from web_workerstudyrole,auth_permission "
+                          "where codename='export_subjects';"),
+
+    ]
diff --git a/smash/web/migrations/0153_auto_20200320_0932.py b/smash/web/migrations/0153_auto_20200320_0932.py
new file mode 100644
index 0000000000000000000000000000000000000000..06a7de106240cf856fceb9720477be0b9aa87526
--- /dev/null
+++ b/smash/web/migrations/0153_auto_20200320_0932.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-20 09:32
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0152_add_permissions_to_existing_workers'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='studycolumns',
+            name='brain_donation_agreement',
+            field=models.BooleanField(default=False, verbose_name=b'Brain donation agreement'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='brain_donation_agreement',
+            field=models.BooleanField(default=False, verbose_name=b'Brain donation agreement'),
+        ),
+        migrations.AddField(
+            model_name='subject',
+            name='next_of_keen_address',
+            field=models.TextField(blank=True, max_length=2000, verbose_name=b'Next of keen address'),
+        ),
+        migrations.AddField(
+            model_name='subject',
+            name='next_of_keen_name',
+            field=models.CharField(blank=True, max_length=50, verbose_name=b'Next of keen'),
+        ),
+        migrations.AddField(
+            model_name='subject',
+            name='next_of_keen_phone',
+            field=models.CharField(blank=True, max_length=50, verbose_name=b'Next of keen phone'),
+        ),
+        migrations.AddField(
+            model_name='subjectcolumns',
+            name='next_of_keen_address',
+            field=models.BooleanField(default=False, max_length=1, verbose_name=b'Next of keen address'),
+        ),
+        migrations.AddField(
+            model_name='subjectcolumns',
+            name='next_of_keen_name',
+            field=models.BooleanField(default=False, max_length=1, verbose_name=b'Next of keen'),
+        ),
+        migrations.AddField(
+            model_name='subjectcolumns',
+            name='next_of_keen_phone',
+            field=models.BooleanField(default=False, max_length=1, verbose_name=b'Next of keen phone'),
+        ),
+    ]
diff --git a/smash/web/migrations/0154_add_permission_to_existing_workers.py b/smash/web/migrations/0154_add_permission_to_existing_workers.py
new file mode 100644
index 0000000000000000000000000000000000000000..76d2ca30d1e461f10b09a110f1b3d66d036dff4f
--- /dev/null
+++ b/smash/web/migrations/0154_add_permission_to_existing_workers.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.5 on 2020-03-19 13:01
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0153_auto_20200320_0932'),
+    ]
+
+    operations = [
+        migrations.RunSQL("insert into web_workerstudyrole_permissions(workerstudyrole_id, permission_id) "
+                          "select web_workerstudyrole.id, auth_permission.id from web_workerstudyrole,auth_permission "
+                          "where codename='add_subject';"),
+
+    ]
diff --git a/smash/web/migrations/0150_auto_20200406_1144.py b/smash/web/migrations/0155_auto_20200406_1144.py
similarity index 86%
rename from smash/web/migrations/0150_auto_20200406_1144.py
rename to smash/web/migrations/0155_auto_20200406_1144.py
index c0c35fb793fd5fbd0d4ae261105f7f7c02e4620b..438315eef6d19f38f08b0f8e04255c59bd2ad612 100644
--- a/smash/web/migrations/0150_auto_20200406_1144.py
+++ b/smash/web/migrations/0155_auto_20200406_1144.py
@@ -8,7 +8,7 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0147_auto_20200320_0931'),
+        ('web', '0154_add_permission_to_existing_workers'),
     ]
 
     operations = [
diff --git a/smash/web/migrations/0151_auto_20200406_1207.py b/smash/web/migrations/0156_auto_20200406_1207.py
similarity index 90%
rename from smash/web/migrations/0151_auto_20200406_1207.py
rename to smash/web/migrations/0156_auto_20200406_1207.py
index 630fa020bc600198b67ec8b76aa5379222e6426a..dc0544dbc712148c6d38ad9e14c7eb29ba92541a 100644
--- a/smash/web/migrations/0151_auto_20200406_1207.py
+++ b/smash/web/migrations/0156_auto_20200406_1207.py
@@ -8,7 +8,7 @@ from django.db import migrations
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0150_auto_20200406_1144'),
+        ('web', '0155_auto_20200406_1144'),
     ]
 
     operations = [
diff --git a/smash/web/models/appointment.py b/smash/web/models/appointment.py
index 9b5b3944c623d47a8e090bccab83fbfed6eb245b..c7a52d416f21c313e7930088f85944c91f35cd50 100644
--- a/smash/web/models/appointment.py
+++ b/smash/web/models/appointment.py
@@ -11,6 +11,10 @@ from . import ConfigurationItem
 class Appointment(models.Model):
     class Meta:
         app_label = 'web'
+        permissions = [
+            ("send_sample_mail_for_appointments", "Can send sample collection list"),
+            ("view_statistics", "Can see statistics"),
+        ]
 
     APPOINTMENT_STATUS_SCHEDULED = 'SCHEDULED'
     APPOINTMENT_STATUS_FINISHED = 'FINISHED'
diff --git a/smash/web/models/appointment_type_link.py b/smash/web/models/appointment_type_link.py
index a48a55026880f9437ee66c390a19645dfe3a5671..c2de6050854dc57820b72b3d53b3f945f3d08aaf 100644
--- a/smash/web/models/appointment_type_link.py
+++ b/smash/web/models/appointment_type_link.py
@@ -2,6 +2,10 @@ from django.db import models
 
 
 class AppointmentTypeLink(models.Model):
+    class Meta:
+        permissions = [
+            ("view_daily_planning", "Can see daily planning"),
+        ]
     appointment = models.ForeignKey("web.Appointment", on_delete=models.CASCADE)
     appointment_type = models.ForeignKey("web.AppointmentType", on_delete=models.CASCADE)
     date_when = models.DateTimeField(null=True, default=None)
diff --git a/smash/web/models/study_columns.py b/smash/web/models/study_columns.py
index a1b345700db1db01741b050496d1e609524c30da..7d20b3913d40a71f72fc0b70327a20084e83a158 100644
--- a/smash/web/models/study_columns.py
+++ b/smash/web/models/study_columns.py
@@ -1,8 +1,6 @@
 # coding=utf-8
 from django.db import models
 
-from web.models.constants import BOOL_CHOICES
-
 
 class StudyColumns(models.Model):
     class Meta:
@@ -81,7 +79,7 @@ class StudyColumns(models.Model):
     excluded = models.BooleanField(default=False, verbose_name='Excluded')
 
     endpoint_reached = models.BooleanField(default=True, verbose_name='Endpoint reached')
-    
+
     resign_reason = models.BooleanField(default=True, verbose_name='Endpoint reached comments')
 
     referral_letter = models.BooleanField(
@@ -117,4 +115,9 @@ class StudyColumns(models.Model):
     vouchers = models.BooleanField(
                                    default=False,
                                    verbose_name='Vouchers',
-                                   )
\ No newline at end of file
+                                   )
+
+    brain_donation_agreement = models.BooleanField(
+                                                   default=False,
+                                                   verbose_name='Brain donation agreement',
+                                                   )
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index 0fe4b5c4129a7c3952aefca764dca658f3e2920f..22a5b0b8fe77518abb0092587531f0b715413633 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -167,6 +167,11 @@ class StudySubject(models.Model):
         verbose_name='PD in family',
         default=None,
     )
+    brain_donation_agreement = models.BooleanField(
+                                                   default=False,
+                                                   verbose_name='Brain donation agreement',
+                                                   )
+
     resigned = models.BooleanField(
         verbose_name='Resigned',
         default=False,
diff --git a/smash/web/models/subject.py b/smash/web/models/subject.py
index 5a323cbe8a243f64182291dc01c657dd3a30847b..918d4645513b2a690bb163d79c8b448b741dc141 100644
--- a/smash/web/models/subject.py
+++ b/smash/web/models/subject.py
@@ -1,20 +1,25 @@
 # coding=utf-8
 import logging
+
 from django.db import models
+from django.db.models.signals import post_save
+from django.dispatch import receiver
 
 from constants import SEX_CHOICES, COUNTRY_OTHER_ID
 from web.models import Country, Visit, Appointment, Provenance
 from . import Language
-from django.db.models.signals import post_save
-from django.dispatch import receiver
 
 logger = logging.getLogger(__name__)
 
-class Subject(models.Model):
 
+class Subject(models.Model):
     class Meta:
         app_label = 'web'
-    
+        permissions = [
+            ("send_sample_mail_for_appointments", "Can send sample collection list"),
+            ("export_subjects", "Can export subject data to excel/csv"),
+        ]
+
     @property
     def provenances(self):
         return Provenance.objects.filter(modified_table=Subject._meta.db_table, modified_table_id=self.id).order_by('-modification_date')
@@ -100,6 +105,21 @@ class Subject(models.Model):
                                 verbose_name='Country'
                                 )
 
+    next_of_keen_name = models.CharField(max_length=50,
+                                         blank=True,
+                                         verbose_name='Next of keen'
+                                         )
+
+    next_of_keen_phone = models.CharField(max_length=50,
+                                          blank=True,
+                                          verbose_name='Next of keen phone'
+                                          )
+
+    next_of_keen_address = models.TextField(max_length=2000,
+                                            blank=True,
+                                            verbose_name='Next of keen address'
+                                            )
+
     dead = models.BooleanField(
         verbose_name='Deceased',
         default=False,
@@ -107,7 +127,7 @@ class Subject(models.Model):
     )
 
     def pretty_address(self):
-      return u'{} ({}), {}. {}'.format(self.address, self.postal_code, self.city, self.country)
+        return u'{} ({}), {}. {}'.format(self.address, self.postal_code, self.city, self.country)
 
     def mark_as_dead(self):
         self.dead = True
@@ -134,7 +154,7 @@ class Subject(models.Model):
         return "%s %s" % (self.first_name, self.last_name)
 
 
-#SIGNALS
+# SIGNALS
 @receiver(post_save, sender=Subject)
 def set_as_deceased(sender, instance, **kwargs):
     if instance.dead:
diff --git a/smash/web/models/subject_columns.py b/smash/web/models/subject_columns.py
index d83847c7d6589aa98adbcd1740137fcb1a80526b..04a1869b0787f66eb61e8c76bd858419db16798f 100644
--- a/smash/web/models/subject_columns.py
+++ b/smash/web/models/subject_columns.py
@@ -83,3 +83,18 @@ class SubjectColumns(models.Model):
                                default=True,
                                verbose_name='Deceased',
                                )
+
+    next_of_keen_name = models.BooleanField(max_length=1,
+                                            default=False,
+                                            verbose_name='Next of keen',
+                                            )
+
+    next_of_keen_phone = models.BooleanField(max_length=1,
+                                             default=False,
+                                             verbose_name='Next of keen phone',
+                                             )
+
+    next_of_keen_address = models.BooleanField(max_length=1,
+                                               default=False,
+                                               verbose_name='Next of keen address',
+                                               )
diff --git a/smash/web/templates/_base.html b/smash/web/templates/_base.html
index da7b7ddab5d8ed1c554095a1dff663a440158df7..ecc810b2bced77a1747bfe65c65e711ff1933541 100644
--- a/smash/web/templates/_base.html
+++ b/smash/web/templates/_base.html
@@ -260,11 +260,11 @@ desired effect
         {% block footer %}
             <!-- To the right -->
             <div class="pull-right hidden-xs">
-                Version: <strong>0.13.1</strong> (9 Apr 2019)
+                Version: <strong>0.14.0</strong> (1 Apr 2020)
             </div>
 
             <!-- Default to the left -->
-            2019, Parkinson Research Clinic <!--(eg. <small>
+            2020, Parkinson Research Clinic <!--(eg. <small>
 
 		<strong>Copyright &copy; 2016 <a href="#">Company</a>.</strong> All rights reserved.
   </small>)-->
diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index a91be3d75937d828cecab8216c779c3b557271a7..a575319957dc7bab0a8d2cf03b4204c9796d53b3 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -16,12 +16,14 @@
         </a>
     </li>
 
-    <li data-desc="daily_planning">
-        <a href="{% url 'web.views.daily_planning' %}">
-            <i class="fa fa-clock-o"></i>
-            <span>Daily Planning</span>
-        </a>
-    </li>
+    {% if "view_daily_planning" in permissions %}
+        <li data-desc="daily_planning">
+            <a href="{% url 'web.views.daily_planning' %}">
+                <i class="fa fa-clock-o"></i>
+                <span>Daily Planning</span>
+            </a>
+        </li>
+    {% endif %}
 
     {% if "change_worker" in permissions %}
     <li data-desc="workers">
@@ -32,44 +34,63 @@
     </li>
     {% endif %}
 
-    <li data-desc="equipment_and_rooms" class="treeview">
-        <a href="{% url 'web.views.equipment_and_rooms' %}">
-            <i class="fa fa-building-o"></i> <span>Equipment &amp; Rooms</span>
-            <span class="pull-right-container">
+    {% if equipment_perms %}
+        <li data-desc="equipment_and_rooms" class="treeview">
+            <a href="{% url 'web.views.equipment_and_rooms' %}">
+                <i class="fa fa-building-o"></i> <span>Equipment &amp; Rooms</span>
+                <span class="pull-right-container">
               <i class="fa fa-angle-left pull-right"></i>
             </span>
-        </a>
-        <ul class="treeview-menu">
-                <li data-desc="equipment_items"><a href="{% url 'web.views.equipment' %}">Equipment items</a></li>
-            {% if "change_appointmenttype" in permissions %}
-                <li data-desc="appointment_types"><a href="{% url 'web.views.appointment_types' %}">Appointment Types</a></li>
-            {% endif %}
-                <li data-desc="flying_teams"><a href="{% url 'web.views.equipment_and_rooms.flying_teams' %}">Flying teams</a></li>
-                <li data-desc="kit_requests"><a href="{% url 'web.views.kit_requests' %}">Kit requests</a></li>
-                <li data-desc="rooms"><a href="{% url 'web.views.equipment_and_rooms.rooms' %}">Rooms</a></li>
-        </ul>
-    </li>
+            </a>
+            <ul class="treeview-menu">
+                {% if "change_item" in permissions %}
+                    <li data-desc="equipment_items"><a href="{% url 'web.views.equipment' %}">Equipment items</a></li>
+                {% endif %}
+                {% if "change_appointmenttype" in permissions %}
+                    <li data-desc="appointment_types"><a href="{% url 'web.views.appointment_types' %}">Appointment
+                        Types</a></li>
+                {% endif %}
+                {% if "change_flyingteam" in permissions %}
+                    <li data-desc="flying_teams"><a href="{% url 'web.views.equipment_and_rooms.flying_teams' %}">Flying
+                        teams</a></li>
+                {% endif %}
+                {% if "send_sample_mail_for_appointments" in permissions %}
+                    <li data-desc="kit_requests"><a href="{% url 'web.views.kit_requests' %}">Kit requests</a></li>
+                {% endif %}
+                {% if "change_room" in permissions %}
+                    <li data-desc="rooms"><a href="{% url 'web.views.equipment_and_rooms.rooms' %}">Rooms</a></li>
+                {% endif %}
+            </ul>
+        </li>
+    {% endif %}
 
-    <li data-desc="statistics">
-        <a href="{% url 'web.views.statistics' %}">
-            <i class="fa fa-bar-chart" aria-hidden="true"></i>
-            <span>Statistics</span>
-        </a>
-    </li>
+    {% if  "view_statistics" in permissions %}
+        <li data-desc="statistics">
+            <a href="{% url 'web.views.statistics' %}">
+                <i class="fa fa-bar-chart" aria-hidden="true"></i>
+                <span>Statistics</span>
+            </a>
+        </li>
+    {% endif %}
 
-    <li data-desc="mail_templates">
-        <a href="{% url 'web.views.mail_templates' %}">
-            <i class="fa fa-envelope-o"></i>
-            <span>Mail templates</span>
-        </a>
-    </li>
 
-    <li data-desc="export">
-        <a href="{% url 'web.views.export' %}">
-            <i class="fa fa-file-excel-o"></i>
-            <span>Export</span>
-        </a>
-    </li>
+    {% if  "change_mailtemplate" in permissions %}
+        <li data-desc="mail_templates">
+            <a href="{% url 'web.views.mail_templates' %}">
+                <i class="fa fa-envelope-o"></i>
+                <span>Mail templates</span>
+            </a>
+        </li>
+    {% endif %}
+
+    {% if  "export_subjects" in permissions %}
+        <li data-desc="export">
+            <a href="{% url 'web.views.export' %}">
+                <i class="fa fa-file-excel-o"></i>
+                <span>Export</span>
+            </a>
+        </li>
+    {% endif %}
 
     {% if study.has_vouchers and "change_voucher" in permissions%}
     <li data-desc="vouchers">
diff --git a/smash/web/templates/subjects/edit.html b/smash/web/templates/subjects/edit.html
index 836dfaf324d6af64c22227985ca7f0ea856668df..eb668516c1bce8ed011ffda5715dff29112b8cbf 100644
--- a/smash/web/templates/subjects/edit.html
+++ b/smash/web/templates/subjects/edit.html
@@ -223,6 +223,11 @@
                     $("#confirm-dead-resigned-mark-dialog").modal("show");
                     return false;
                 }
+                var brainDonation = $("#id_study_subject-brain_donation_agreement").is(":checked");
+                if (brainDonation && ($("#id_subject-next_of_keen_phone").val() === '' || $("#id_subject-next_of_keen_address").val() === '' || $("#id_subject-next_of_keen_name").val() === '')) {
+                    alert("Next of keen data must be entered when brain donation agreement is in place");
+                    return false;
+                }
             });
             $("#confirm-save").click(function () {
                 confirmed = true;
diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html
index cfe954bcc1251c81a64dffee7c12a18b62893bda..29a4d2308145826a49e5d49091eb949a3157b524 100644
--- a/smash/web/templates/subjects/index.html
+++ b/smash/web/templates/subjects/index.html
@@ -31,7 +31,7 @@
             padding-left: 2px;
         }
         .visit_row > span > a{
-            color: inherit; 
+            color: inherit;
         }
         .appointment_type_list{
             margin-top: 10px;
@@ -57,7 +57,11 @@
 {% block maincontent %}
 
     <div>
-        <a href="{% url 'web.views.subject_add' %}" class="btn btn-app">
+        <a href="{% url 'web.views.subject_add' %}" class="btn btn-app"
+                {% if not "add_subject" in permissions %}
+           disabled
+                {% endif %}
+        >
             <i class="fa fa-plus"></i>
             Add new subject
         </a>
diff --git a/smash/web/tests/data/import.csv b/smash/web/tests/data/import.csv
new file mode 100644
index 0000000000000000000000000000000000000000..b13e437a14950691eb4f1daaa7b9cc2a609f8843
--- /dev/null
+++ b/smash/web/tests/data/import.csv
@@ -0,0 +1,2 @@
+first_name,last_name,participant_id,date_born
+Piotr,Gawron,Cov-000001,01-02-2020
\ No newline at end of file
diff --git a/smash/web/tests/data/import_date_of_birth.csv b/smash/web/tests/data/import_date_of_birth.csv
new file mode 100644
index 0000000000000000000000000000000000000000..43d9adb8ac452c865ffcbdb96446f22847873c61
--- /dev/null
+++ b/smash/web/tests/data/import_date_of_birth.csv
@@ -0,0 +1,4 @@
+first_name,last_name,participant_id,date_born
+Piotr,Gawron,Cov-000001,
+Piotr,Gawron,Cov-000002,invalid
+Piotr,Gawron,Cov-000003,2222-20-20
\ No newline at end of file
diff --git a/smash/web/tests/importer/__init__.py b/smash/web/tests/importer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/smash/web/tests/importer/mock_reader.py b/smash/web/tests/importer/mock_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5aedb09dd658512fcbcfbe8a80913e6f144026f
--- /dev/null
+++ b/smash/web/tests/importer/mock_reader.py
@@ -0,0 +1,13 @@
+import logging
+
+from web.importer.subject_import_reader import SubjectImportReader
+
+logger = logging.getLogger(__name__)
+
+
+class MockReader(SubjectImportReader):
+    def __init__(self, study_subjects):
+        self.study_subjects = study_subjects
+
+    def load_data(self, filename):
+        return self.study_subjects
diff --git a/smash/web/tests/importer/test_csv_subject_import_reader.py b/smash/web/tests/importer/test_csv_subject_import_reader.py
new file mode 100644
index 0000000000000000000000000000000000000000..a80f323469577c13837ee6bf83c6ed1baebd004c
--- /dev/null
+++ b/smash/web/tests/importer/test_csv_subject_import_reader.py
@@ -0,0 +1,37 @@
+# coding=utf-8
+
+import logging
+
+from django.test import TestCase
+
+from web.importer import CsvSubjectImportReader
+from web.tests.functions import get_resource_path
+
+logger = logging.getLogger(__name__)
+
+
+class TestCsvReader(TestCase):
+
+    def test_load_data(self):
+        filename = get_resource_path('import.csv')
+        study_subjects = CsvSubjectImportReader().load_data(filename)
+        self.assertEqual(1, len(study_subjects))
+        study_subject = study_subjects[0]
+        self.assertEqual("Piotr", study_subject.subject.first_name)
+        self.assertEqual("Gawron", study_subject.subject.last_name)
+        self.assertEqual("Cov-000001", study_subject.screening_number)
+        self.assertEqual("Cov-000001", study_subject.nd_number)
+
+        self.assertEqual(1, study_subject.subject.date_born.day)
+        self.assertEqual(2, study_subject.subject.date_born.month)
+        self.assertEqual(2020, study_subject.subject.date_born.year)
+
+        self.assertIsNotNone(study_subject.study)
+
+    def test_load_problematic_dates(self):
+        filename = get_resource_path('import_date_of_birth.csv')
+        study_subjects = CsvSubjectImportReader().load_data(filename)
+        self.assertEqual(3, len(study_subjects))
+        self.assertIsNone(study_subjects[0].subject.date_born)
+        self.assertIsNone(study_subjects[1].subject.date_born)
+        self.assertIsNone(study_subjects[2].subject.date_born)
diff --git a/smash/web/tests/importer/test_exporter.py b/smash/web/tests/importer/test_exporter.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a552443b9ba73818783f213a5af0ced1b50a890
--- /dev/null
+++ b/smash/web/tests/importer/test_exporter.py
@@ -0,0 +1,40 @@
+# coding=utf-8
+
+import datetime
+import logging
+
+from django.test import TestCase
+
+from web.tests.functions import create_study_subject
+from web.importer import Exporter
+from web.models import Subject, StudySubject, Study, Provenance
+from web.models.constants import GLOBAL_STUDY_ID
+
+logger = logging.getLogger(__name__)
+
+
+class TestExporter(TestCase):
+
+    def setUp(self):
+        self.study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+
+    def test_export_not_excluded(self):
+        create_study_subject()
+
+        exporter = Exporter(filename="empty.csv")
+        exporter.execute()
+
+        self.assertEqual(0, exporter.exported_count)
+        self.assertEqual(0, exporter.warning_count)
+
+    def test_export_excluded(self):
+        subject = create_study_subject()
+        subject.excluded=True
+        subject.save()
+
+        exporter = Exporter(filename="empty.csv")
+        exporter.execute()
+
+        self.assertEqual(1, exporter.exported_count)
+        self.assertEqual(0, exporter.warning_count)
+
diff --git a/smash/web/tests/importer/test_exporter_cron_job.py b/smash/web/tests/importer/test_exporter_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..d1b44a37fbb2eec72ddceeef88d43d2928f689c4
--- /dev/null
+++ b/smash/web/tests/importer/test_exporter_cron_job.py
@@ -0,0 +1,45 @@
+# coding=utf-8
+
+import logging
+import tempfile
+
+from django.conf import settings
+from django.test import TestCase
+
+from web.importer import ExporterCronJob
+
+logger = logging.getLogger(__name__)
+from django.core import mail
+from django_cron.models import CronJobLog
+
+
+class TestCronJobExporter(TestCase):
+
+    def setUp(self):
+        setattr(settings, "DAILY_EXPORT_FILE", None)
+
+    def tearDown(self):
+        setattr(settings, "DAILY_EXPORT_FILE", None)
+
+    def test_import_without_configuration(self):
+        CronJobLog.objects.all().delete()
+
+        job = ExporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("export file not defined", status)
+        self.assertEqual(0, len(mail.outbox))
+
+    def test_import(self):
+        new_file, tmp = tempfile.mkstemp()
+
+        setattr(settings, "DAILY_EXPORT_FILE", tmp)
+        CronJobLog.objects.all().delete()
+
+        job = ExporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("export is successful", status)
+        self.assertEqual(1, len(mail.outbox))
diff --git a/smash/web/tests/importer/test_importer.py b/smash/web/tests/importer/test_importer.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5e95a1ea900a4a1742abece15e00cfea13160d2
--- /dev/null
+++ b/smash/web/tests/importer/test_importer.py
@@ -0,0 +1,110 @@
+# coding=utf-8
+
+import datetime
+import logging
+
+from django.test import TestCase
+
+from mock_reader import MockReader
+from web.tests.functions import create_study_subject
+from web.importer import Importer
+from web.models import Subject, StudySubject, Study, Provenance
+from web.models.constants import GLOBAL_STUDY_ID
+
+logger = logging.getLogger(__name__)
+
+
+class TestImporter(TestCase):
+
+    def setUp(self):
+        self.study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+
+    def test_import_new_subject(self):
+        study_subjects = []
+        subject = Subject()
+        subject.first_name = "Piotr"
+        subject.last_name = "Gawron"
+        study_subject = StudySubject()
+        study_subject.screening_number = "Cov-123456"
+        study_subject.subject = subject
+        study_subject.study = self.study
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        subject_counter = Subject.objects.count()
+        study_subject_counter = StudySubject.objects.count()
+        provenance_counter = Provenance.objects.count()
+        importer.execute()
+
+        self.assertEqual(subject_counter + 1, Subject.objects.count())
+        self.assertEqual(study_subject_counter + 1, StudySubject.objects.count())
+        self.assertNotEqual(provenance_counter, Provenance.objects.count())
+
+        self.assertEqual(1, importer.added_count)
+        self.assertEqual(0, importer.problematic_count)
+        self.assertEqual(0, importer.warning_count)
+
+    def test_import_invalid(self):
+        study_subjects = []
+        study_subject = StudySubject()
+        study_subject.screening_number = "Cov-123456"
+        study_subject.study = self.study
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        study_subject_counter = StudySubject.objects.count()
+        importer.execute()
+
+        self.assertEqual(study_subject_counter, StudySubject.objects.count())
+
+        self.assertEqual(0, importer.added_count)
+        self.assertEqual(1, importer.problematic_count)
+
+    def test_import_no_study(self):
+        study_subjects = []
+        study_subject = StudySubject()
+        study_subject.screening_number = "Cov-123456"
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        study_subject_counter = StudySubject.objects.count()
+        importer.execute()
+
+        self.assertEqual(study_subject_counter, StudySubject.objects.count())
+
+        self.assertEqual(0, importer.added_count)
+        self.assertEqual(1, importer.problematic_count)
+
+    def test_import_merge_subject(self):
+        existing_study_subject = create_study_subject()
+        study_subjects = []
+        subject = Subject()
+        subject.first_name = "XYZ"
+        subject.last_name = "AAA"
+        subject.date_born = datetime.datetime.now()
+        study_subject = StudySubject()
+        study_subject.screening_number = existing_study_subject.screening_number
+        study_subject.subject = subject
+        study_subject.study = self.study
+        study_subjects.append(study_subject)
+
+        importer = Importer(filename="empty.csv", reader=MockReader(study_subjects))
+        provenance_counter = Provenance.objects.count()
+        subject_counter = Subject.objects.count()
+        study_subject_counter = StudySubject.objects.count()
+        importer.execute()
+
+        self.assertEqual(subject_counter, Subject.objects.count())
+        self.assertEqual(study_subject_counter, StudySubject.objects.count())
+        self.assertNotEqual(provenance_counter, Provenance.objects.count())
+
+        self.assertEqual(0, importer.added_count)
+        self.assertEqual(1, importer.merged_count)
+        self.assertEqual(0, importer.problematic_count)
+        self.assertEqual(0, importer.warning_count)
+
+        existing_study_subject = StudySubject.objects.filter(id=existing_study_subject.id)[0]
+        self.assertEquals(existing_study_subject.subject.first_name, subject.first_name)
+        self.assertEquals(existing_study_subject.subject.last_name, subject.last_name)
+        self.assertEquals(existing_study_subject.subject.date_born.strftime("%Y-%m-%d"),
+                          subject.date_born.strftime("%Y-%m-%d"))
diff --git a/smash/web/tests/importer/test_importer_cron_job.py b/smash/web/tests/importer/test_importer_cron_job.py
new file mode 100644
index 0000000000000000000000000000000000000000..0eec9f6acb0969b11f703639ee961f665b71ad13
--- /dev/null
+++ b/smash/web/tests/importer/test_importer_cron_job.py
@@ -0,0 +1,46 @@
+# coding=utf-8
+
+import logging
+import tempfile
+from shutil import copyfile
+
+from django.conf import settings
+from django.test import TestCase
+
+from web.importer import ImporterCronJob
+from web.tests.functions import get_resource_path
+
+logger = logging.getLogger(__name__)
+from django.core import mail
+from django_cron.models import CronJobLog
+
+
+class TestCronJobImporter(TestCase):
+
+    def tearDown(self):
+        setattr(settings, "DAILY_IMPORT_FILE", None)
+
+    def test_import_without_configuration(self):
+        CronJobLog.objects.all().delete()
+
+        job = ImporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("import file not defined", status)
+        self.assertEqual(0, len(mail.outbox))
+
+    def test_import(self):
+        filename = get_resource_path('import.csv')
+        new_file, tmp = tempfile.mkstemp()
+        copyfile(filename, tmp)
+
+        setattr(settings, "DAILY_IMPORT_FILE", tmp)
+        CronJobLog.objects.all().delete()
+
+        job = ImporterCronJob()
+
+        status = job.do()
+
+        self.assertEqual("import is successful", status)
+        self.assertEqual(1, len(mail.outbox))
diff --git a/smash/web/tests/view/test_daily_planning.py b/smash/web/tests/view/test_daily_planning.py
new file mode 100644
index 0000000000000000000000000000000000000000..096cc9200a770d92d5665aa33ff5cb94ac08635e
--- /dev/null
+++ b/smash/web/tests/view/test_daily_planning.py
@@ -0,0 +1,20 @@
+import logging
+
+from django.urls import reverse
+
+from web.tests import LoggedInTestCase
+
+logger = logging.getLogger(__name__)
+
+
+class DailyPlanningViewTests(LoggedInTestCase):
+    def test_visit_details_request(self):
+        self.login_as_admin()
+        response = self.client.get(reverse('web.views.daily_planning'))
+
+        self.assertEqual(response.status_code, 200)
+
+    def test_visit_details_request_without_permissions(self):
+        self.login_as_staff()
+        response = self.client.get(reverse('web.views.daily_planning'))
+        self.assertEqual(response.status_code, 302)
diff --git a/smash/web/tests/view/test_equipments.py b/smash/web/tests/view/test_equipments.py
index 99e312a8c85d747a66ca3a90396f1c9ece48b9f5..34cba24a89c5595b000dbec28d2f36e967c8f223 100644
--- a/smash/web/tests/view/test_equipments.py
+++ b/smash/web/tests/view/test_equipments.py
@@ -10,7 +10,14 @@ logger = logging.getLogger(__name__)
 
 
 class EquipmentTests(LoggedInTestCase):
+
+    def test_list_without_permissions(self):
+        self.login_as_staff()
+        response = self.client.get(reverse('web.views.equipment'))
+        self.assertEqual(response.status_code, 302)
+
     def test_equipment_requests(self):
+        self.login_as_admin()
         pages = [
             'web.views.equipment',
             'web.views.equipment_add',
@@ -21,6 +28,7 @@ class EquipmentTests(LoggedInTestCase):
             self.assertEqual(response.status_code, 200)
 
     def test_equipment_edit_request(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_edit',
                        kwargs={'equipment_id': str(item.id)})
@@ -28,6 +36,7 @@ class EquipmentTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 200)
 
     def test_equipment_delete_request(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_delete',
                        kwargs={'equipment_id': str(item.id)})
@@ -35,6 +44,7 @@ class EquipmentTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 302)
 
     def test_equipment_add(self):
+        self.login_as_admin()
         page = reverse('web.views.equipment_add')
         data = {
             'name': 'The mysterious potion',
@@ -48,6 +58,7 @@ class EquipmentTests(LoggedInTestCase):
         self.assertEqual(len(freshly_created), 1)
 
     def test_equipment_edit(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_edit',
                        kwargs={'equipment_id': str(item.id)})
@@ -64,6 +75,7 @@ class EquipmentTests(LoggedInTestCase):
             self.assertEqual(getattr(freshly_edited, key, ''), data[key])
 
     def test_equipment_delete(self):
+        self.login_as_admin()
         item = create_item()
         page = reverse('web.views.equipment_delete',
                        kwargs={'equipment_id': str(item.id)})
diff --git a/smash/web/tests/view/test_export.py b/smash/web/tests/view/test_export.py
index d6938476f7fd4ea6e3d3b36f2441e448fb02bfad..3f6a5c99b7fc12060eb0aad8ca95a92460d424de 100644
--- a/smash/web/tests/view/test_export.py
+++ b/smash/web/tests/view/test_export.py
@@ -9,26 +9,47 @@ from web.views.export import subject_to_row_for_fields, DROP_OUT_FIELD
 
 class TestExportView(LoggedInTestCase):
     def test_export_subjects_to_csv(self):
+        self.login_as_admin()
         create_study_subject()
         response = self.client.get(reverse('web.views.export_to_csv', kwargs={'data_type': "subjects"}))
         self.assertEqual(response.status_code, 200)
 
+    def test_export_subjects_to_csv_without_permission(self):
+        response = self.client.get(reverse("web.views.mail_templates"))
+        create_study_subject()
+        response = self.client.get(reverse('web.views.export_to_csv', kwargs={'data_type': "subjects"}))
+        self.assertEqual(response.status_code, 302)
+
     def test_render_export(self):
+        self.login_as_admin()
         create_study_subject()
         response = self.client.get(reverse('web.views.export'))
         self.assertEqual(response.status_code, 200)
 
+    def test_render_export_without_permission(self):
+        create_study_subject()
+        response = self.client.get(reverse('web.views.export'))
+        self.assertEqual(response.status_code, 302)
+
     def test_export_appointments_to_csv(self):
+        self.login_as_admin()
         create_appointment()
         response = self.client.get(reverse('web.views.export_to_csv', kwargs={'data_type': "appointments"}))
         self.assertEqual(response.status_code, 200)
 
     def test_export_subjects_to_excel(self):
+        self.login_as_admin()
         create_study_subject()
         response = self.client.get(reverse('web.views.export_to_excel', kwargs={'data_type': "subjects"}))
         self.assertEqual(response.status_code, 200)
 
+    def test_export_subjects_to_excel_without_permission(self):
+        create_study_subject()
+        response = self.client.get(reverse('web.views.export_to_excel', kwargs={'data_type': "subjects"}))
+        self.assertEqual(response.status_code, 302)
+
     def test_export_appointments_to_excel(self):
+        self.login_as_admin()
         appointment = create_appointment()
         appointment.visit = None
         appointment.save()
diff --git a/smash/web/tests/view/test_flying_teams.py b/smash/web/tests/view/test_flying_teams.py
index 32cfe22ff9d0ac7cbd073f66dd3691ff051a06b9..e282c3e9cc14987d9c4a4a7ca5b90ca26af1c907 100644
--- a/smash/web/tests/view/test_flying_teams.py
+++ b/smash/web/tests/view/test_flying_teams.py
@@ -17,6 +17,7 @@ class FlyingTeamTests(LoggedInTestCase):
         return 'Random' + ''.join(random.choice(letters) for x in range(15))
 
     def test_flying_team_requests(self):
+        self.login_as_admin()
         pages = [
             'web.views.equipment_and_rooms.flying_teams',
             'web.views.equipment_and_rooms.flying_teams_add',
@@ -26,7 +27,18 @@ class FlyingTeamTests(LoggedInTestCase):
             response = self.client.get(reverse(page))
             self.assertEqual(response.status_code, 200)
 
+    def test_flying_team_requests_without_permission(self):
+        pages = [
+            'web.views.equipment_and_rooms.flying_teams',
+            'web.views.equipment_and_rooms.flying_teams_add',
+        ]
+
+        for page in pages:
+            response = self.client.get(reverse(page))
+            self.assertEqual(response.status_code, 302)
+
     def test_flying_team_add(self):
+        self.login_as_admin()
         page = reverse('web.views.equipment_and_rooms.flying_teams_add')
         data = {
             'place': self.generate_more_or_less_random_name()
@@ -38,6 +50,7 @@ class FlyingTeamTests(LoggedInTestCase):
         self.assertEqual(len(freshly_created), 1)
 
     def test_flying_team_edit(self):
+        self.login_as_admin()
         flying_team = create_flying_team()
         page = reverse('web.views.equipment_and_rooms.flying_teams_edit',
                        kwargs={'flying_team_id': str(flying_team.id)})
@@ -51,6 +64,7 @@ class FlyingTeamTests(LoggedInTestCase):
         self.assertEqual(freshly_edited.place, data["place"])
 
     def test_flying_team_edit_request(self):
+        self.login_as_admin()
         flying_team = create_flying_team()
         page = reverse('web.views.equipment_and_rooms.flying_teams_edit',
                        kwargs={'flying_team_id': str(flying_team.id)})
diff --git a/smash/web/tests/view/test_kit_request.py b/smash/web/tests/view/test_kit_request.py
index 387001f3bd804c87fef7db3dccac4de4897dd97f..72222f5df0f4de62743ed4d3edbf388f8232b008 100644
--- a/smash/web/tests/view/test_kit_request.py
+++ b/smash/web/tests/view/test_kit_request.py
@@ -5,7 +5,8 @@ from django.urls import reverse
 
 from web.models import Item, Appointment, AppointmentTypeLink
 from web.tests import LoggedInTestCase
-from web.tests.functions import create_appointment_type, create_appointment, create_visit, create_appointment_without_visit
+from web.tests.functions import create_appointment_type, create_appointment, create_visit, \
+    create_appointment_without_visit
 from web.views.kit import get_kit_requests
 from web.views.notifications import get_today_midnight_date
 
@@ -13,10 +14,16 @@ from web.views.notifications import get_today_midnight_date
 class ViewFunctionsTests(LoggedInTestCase):
 
     def test_kit_requests(self):
+        self.login_as_admin()
         response = self.client.get(reverse('web.views.kit_requests'))
         self.assertEqual(response.status_code, 200)
 
+    def test_kit_requests_without_permission(self):
+        response = self.client.get(reverse('web.views.kit_requests'))
+        self.assertEqual(response.status_code, 302)
+
     def test_kit_requests_2(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -35,6 +42,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertTrue(item_name in response.content)
 
     def test_kit_requests_4(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -54,6 +62,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertFalse(item_name in response.content)
 
     def test_kit_requests_3(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -72,6 +81,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertTrue(item_name in response.content)
 
     def test_kit_requests_order(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -104,6 +114,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertEqual(appointment2, result['appointments'][2])
 
     def test_kit_requests_for_appointment_with_two_types(self):
+        self.login_as_admin()
         item = Item.objects.create(disposable=True, name="item 1")
         appointment_type = create_appointment_type()
         appointment_type.required_equipment.add(item)
@@ -129,6 +140,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertEqual(1, len(result["appointments"]))
 
     def test_kit_requests_send_email(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
@@ -150,6 +162,7 @@ class ViewFunctionsTests(LoggedInTestCase):
         self.assertEqual(1, len(mail.outbox))
 
     def test_kit_request_send_mail_with_general_appointment(self):
+        self.login_as_admin()
         item_name = "Test item to be ordered"
         item = Item.objects.create(disposable=True, name=item_name)
         appointment_type = create_appointment_type()
diff --git a/smash/web/tests/view/test_mail.py b/smash/web/tests/view/test_mail.py
index 900c84a7bcadf38a37c420308d638eb4f2c74d14..1f11340a796aedc4e9c0eebf7662ddd50d3cce33 100644
--- a/smash/web/tests/view/test_mail.py
+++ b/smash/web/tests/view/test_mail.py
@@ -4,8 +4,8 @@ from django.urls import reverse
 
 from web.models import MailTemplate
 from web.models.constants import MAIL_TEMPLATE_CONTEXT_VOUCHER
-from web.tests.functions import create_voucher, get_resource_path
 from web.tests import LoggedInTestCase
+from web.tests.functions import create_voucher, get_resource_path
 
 logger = logging.getLogger(__name__)
 
@@ -20,3 +20,12 @@ class MailTests(LoggedInTestCase):
         page = reverse('web.views.mail_template_generate_for_vouchers') + "?voucher_id=" + str(voucher.id)
         response = self.client.get(page)
         self.assertEqual(response.status_code, 200)
+
+    def test_list_mail_templates(self):
+        self.login_as_admin()
+        response = self.client.get(reverse("web.views.mail_templates"))
+        self.assertEqual(response.status_code, 200)
+
+    def test_list_mail_templates_without_permission(self):
+        response = self.client.get(reverse("web.views.mail_templates"))
+        self.assertEqual(response.status_code, 302)
diff --git a/smash/web/tests/view/test_rooms.py b/smash/web/tests/view/test_rooms.py
index ecf1161677c0a104d9861bd356c2c96416b81bf0..b607e8105b025ac6e5492ff5b53f6ec0e9aba7ea 100644
--- a/smash/web/tests/view/test_rooms.py
+++ b/smash/web/tests/view/test_rooms.py
@@ -2,15 +2,16 @@ import logging
 
 from django.urls import reverse
 
-from web.tests.functions import create_room, create_item
-from web.models import Item, Room
+from web.models import Room
 from web.tests import LoggedInTestCase
+from web.tests.functions import create_room, create_item
 
 logger = logging.getLogger(__name__)
 
 
 class RoomsTests(LoggedInTestCase):
     def test_rooms_requests(self):
+        self.login_as_admin()
         pages = [
             'web.views.equipment_and_rooms.rooms',
             'web.views.equipment_and_rooms.rooms_add',
@@ -20,7 +21,18 @@ class RoomsTests(LoggedInTestCase):
             response = self.client.get(reverse(page))
             self.assertEqual(response.status_code, 200)
 
+    def test_rooms_requests_without_permission(self):
+        pages = [
+            'web.views.equipment_and_rooms.rooms',
+            'web.views.equipment_and_rooms.rooms_add',
+        ]
+
+        for page in pages:
+            response = self.client.get(reverse(page))
+            self.assertEqual(response.status_code, 302)
+
     def test_rooms_edit_request(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_edit',
                        kwargs={'room_id': str(room.id)})
@@ -28,6 +40,7 @@ class RoomsTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 200)
 
     def test_rooms_delete_request(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_delete',
                        kwargs={'room_id': str(room.id)})
@@ -35,6 +48,7 @@ class RoomsTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 302)
 
     def test_rooms_add(self):
+        self.login_as_admin()
         page = reverse('web.views.equipment_and_rooms.rooms_add')
         item = create_item()
         data = {
@@ -53,6 +67,7 @@ class RoomsTests(LoggedInTestCase):
         self.assertEqual(len(freshly_created), 1)
 
     def test_rooms_edit(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_edit',
                        kwargs={'room_id': str(room.id)})
@@ -72,6 +87,7 @@ class RoomsTests(LoggedInTestCase):
             self.assertEqual(getattr(freshly_edited, key, ''), data[key])
 
     def test_rooms_delete(self):
+        self.login_as_admin()
         room = create_room()
         page = reverse('web.views.equipment_and_rooms.rooms_delete',
                        kwargs={'room_id': str(room.id)})
diff --git a/smash/web/tests/view/test_statistics.py b/smash/web/tests/view/test_statistics.py
index 737371076825bc63c5cbbe510da8a156ae5d17a5..aa7fee20b0dcffbe402185e823e885a598ec0850 100644
--- a/smash/web/tests/view/test_statistics.py
+++ b/smash/web/tests/view/test_statistics.py
@@ -10,6 +10,7 @@ __author__ = 'Valentin Grouès'
 
 class TestStatisticsView(LoggedInTestCase):
     def test_statistics_request(self):
+        self.login_as_admin()
         url = reverse('web.views.statistics')
         response = self.client.get(url)
         self.assertEqual(response.status_code, 200)
@@ -19,3 +20,8 @@ class TestStatisticsView(LoggedInTestCase):
         response = self.client.get(url, {"month": 10, "year": 2017, "subject_type": -1, "visit": -1})
         content = response.content
         self.assertIn('<option value="10" selected>October', content)
+
+    def test_statistics_request_without_permission(self):
+        url = reverse('web.views.statistics')
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 302)
diff --git a/smash/web/tests/view/test_study.py b/smash/web/tests/view/test_study.py
index 6b7f18b6e90f2190f03ea9ef233a390807c668af..40df2f29ea6df924021cb5f854c40e6731562b1d 100644
--- a/smash/web/tests/view/test_study.py
+++ b/smash/web/tests/view/test_study.py
@@ -10,9 +10,9 @@ from web.tests.functions import get_test_study, format_form_field
 logger = logging.getLogger(__name__)
 
 
-class SubjectsViewTests(LoggedInWithWorkerTestCase):
+class StudyViewTests(LoggedInWithWorkerTestCase):
     def setUp(self):
-        super(SubjectsViewTests, self).setUp()
+        super(StudyViewTests, self).setUp()
         self.study = get_test_study()
 
     def test_render_study_edit(self):
diff --git a/smash/web/tests/view/test_subjects.py b/smash/web/tests/view/test_subjects.py
index c1a99e76530f7aa12f79e6edb0edd5b08d00ef4a..71f0d3e773fa9cfba8d2cd6c5c3d5988c9bf6eaf 100644
--- a/smash/web/tests/view/test_subjects.py
+++ b/smash/web/tests/view/test_subjects.py
@@ -1,6 +1,7 @@
 import datetime
 import logging
 
+from django.contrib.auth.models import Permission
 from django.core.files.uploadedfile import SimpleUploadedFile
 from django.urls import reverse
 
@@ -23,6 +24,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         self.study = get_test_study()
 
     def test_render_subjects_add(self):
+        self.worker.roles.all()[0].permissions.add(Permission.objects.get(codename="add_subject"))
         self.worker.save()
 
         response = self.client.get(reverse('web.views.subject_add'))
@@ -161,6 +163,8 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         return form_data
 
     def test_subjects_add_2(self):
+        self.worker.roles.all()[0].permissions.add(Permission.objects.get(codename="add_subject"))
+        self.worker.save()
         form_data = self.create_add_form_data_for_study_subject()
 
         form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL
@@ -176,6 +180,8 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
                          " as default location prefix is not defined and subject type is control")
 
     def test_subjects_add_with_referral_letter_file(self):
+        self.worker.roles.all()[0].permissions.add(Permission.objects.get(codename="add_subject"))
+        self.worker.save()
         StudyColumns.objects.all().update(referral_letter=True)
 
         form_data = self.create_add_form_data_for_study_subject()
@@ -213,6 +219,8 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         form_data["study_subject-last_name"] = "Doe"
 
     def test_subjects_add_patient(self):
+        self.worker.roles.all()[0].permissions.add(Permission.objects.get(codename="add_subject"))
+        self.worker.save()
         form_data = self.create_add_form_data_for_study_subject()
 
         form_data["study_subject-default_location"] = get_test_location().id
@@ -227,6 +235,8 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
                          " as default location prefix is not defined and subject type is patient")
 
     def test_subjects_add_invalid(self):
+        self.worker.roles.all()[0].permissions.add(Permission.objects.get(codename="add_subject"))
+        self.worker.save()
         form_data = self.create_add_form_data_for_study_subject()
         form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL
         form_data["study_subject-default_location"] = get_test_location().id
@@ -237,6 +247,8 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         self.assertTrue("Invalid data" in response.content)
 
     def test_subjects_add_with_prefixed_location(self):
+        self.worker.roles.all()[0].permissions.add(Permission.objects.get(codename="add_subject"))
+        self.worker.save()
         form_data = self.create_add_form_data_for_study_subject()
         form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL
 
diff --git a/smash/web/views/__init__.py b/smash/web/views/__init__.py
index 04dd18e6f48f979fb473a0626544776129c86777..66c3b6339e4afecdf3332e273a6f03d4e1c43705 100644
--- a/smash/web/views/__init__.py
+++ b/smash/web/views/__init__.py
@@ -52,7 +52,7 @@ def extend_context(params, request):
     else:
         #use full name if available, username otherwise
         if len(request.user.get_full_name()) > 1:
-            person = request.user.get_full_name()  
+            person = request.user.get_full_name()
         else:
             person = request.user.get_username()
         role   = '<No worker information>'
@@ -61,6 +61,7 @@ def extend_context(params, request):
     final_params.update({
         'permissions' : permissions,
         'conf_perms'  : permissions & PermissionDecorator.codename_groups['configuration'],
+        'equipment_perms'  : permissions & PermissionDecorator.codename_groups['equipment'],
         'person': person,
         'role': role,
         'notifications': notifications,
diff --git a/smash/web/views/appointment_type.py b/smash/web/views/appointment_type.py
index 9aa924d1af11b85b9aa54d6a0594866325898277..2637c26ade7f9f8809b8a7ca031368f29142b579 100644
--- a/smash/web/views/appointment_type.py
+++ b/smash/web/views/appointment_type.py
@@ -11,7 +11,7 @@ class AppointmentTypeListView(ListView, WrappedView):
     template_name = 'appointment_types/index.html'
     context_object_name = "appointment_types"
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeListView, self).dispatch(*args, **kwargs)
 
@@ -22,7 +22,7 @@ class AppointmentTypeCreateView(CreateView, WrappedView):
     success_url = reverse_lazy('web.views.appointment_types')
     success_message = "Appointment type created"
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeCreateView, self).dispatch(*args, **kwargs)
 
@@ -34,7 +34,7 @@ class AppointmentTypeEditView(UpdateView, WrappedView):
     template_name = "appointment_types/edit.html"
     context_object_name = "appointment_types"
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeEditView, self).dispatch(*args, **kwargs)
 
@@ -47,6 +47,6 @@ class AppointmentTypeDeleteView(DeleteView, WrappedView):
         messages.success(request, "Appointment Type deleted")
         return super(AppointmentTypeDeleteView, self).delete(request, *args, **kwargs)
 
-    @PermissionDecorator('change_appointmenttype', 'configuration')
+    @PermissionDecorator('change_appointmenttype', 'equipment')
     def dispatch(self, *args, **kwargs):
         return super(AppointmentTypeDeleteView, self).dispatch(*args, **kwargs)
\ No newline at end of file
diff --git a/smash/web/views/daily_planning.py b/smash/web/views/daily_planning.py
index 40ab776c754d5bc3c8b89e8d9e1ec70701669f53..d720816f0672bb455260b5fc3f4a9caa9fc4c2db 100644
--- a/smash/web/views/daily_planning.py
+++ b/smash/web/views/daily_planning.py
@@ -1,12 +1,15 @@
 # coding=utf-8
-import logging
 
 from django.views.generic import TemplateView
-from . import wrap_response
+
+from web.decorators import PermissionDecorator
 from web.models.worker_study_role import STUDY_ROLE_CHOICES
+from . import wrap_response
+
 
 class TemplateDailyPlannerView(TemplateView):
+    @PermissionDecorator('view_daily_planning', 'daily_planning')
     def get(self, request, *args, **kwargs):
         context = self.get_context_data(**kwargs)
         context['worker_study_roles'] = STUDY_ROLE_CHOICES
-        return wrap_response(request, 'daily_planning.html', context)
\ No newline at end of file
+        return wrap_response(request, 'daily_planning.html', context)
diff --git a/smash/web/views/equipment.py b/smash/web/views/equipment.py
index ff498134cab372d0f026c0316f89295d60b83191..4d006e41d3db59e1d8785f6e0d35ddab089cf3d1 100644
--- a/smash/web/views/equipment.py
+++ b/smash/web/views/equipment.py
@@ -1,11 +1,13 @@
 # coding=utf-8
 from django.shortcuts import redirect, get_object_or_404
 
+from web.decorators import PermissionDecorator
 from . import wrap_response
-from ..models import Item
 from ..forms.forms import ItemForm
+from ..models import Item
 
 
+@PermissionDecorator('change_item', 'equipment')
 def equipment(request):
     equipment_list = Item.objects.order_by('-name')
     context = {
@@ -15,6 +17,7 @@ def equipment(request):
     return wrap_response(request, "equipment_and_rooms/equipment/index.html", context)
 
 
+@PermissionDecorator('change_item', 'equipment')
 def equipment_add(request):
     if request.method == 'POST':
         form = ItemForm(request.POST)
@@ -27,6 +30,7 @@ def equipment_add(request):
     return wrap_response(request, 'equipment_and_rooms/equipment/add.html', {'form': form})
 
 
+@PermissionDecorator('change_item', 'equipment')
 def equipment_edit(request, equipment_id):
     the_item = get_object_or_404(Item, id=equipment_id)
     if request.method == 'POST':
@@ -40,6 +44,7 @@ def equipment_edit(request, equipment_id):
     return wrap_response(request, 'equipment_and_rooms/equipment/edit.html', {'form': form})
 
 
+@PermissionDecorator('change_item', 'equipment')
 def equipment_delete(request, equipment_id):
     the_item = get_object_or_404(Item, id=equipment_id)
     the_item.delete()
diff --git a/smash/web/views/export.py b/smash/web/views/export.py
index 02fc1a82c1cb8a61341f3f1c0af9469209da0a0c..1cb6271207a31855a0199c62945cd654e336849a 100644
--- a/smash/web/views/export.py
+++ b/smash/web/views/export.py
@@ -5,10 +5,12 @@ import django_excel as excel
 from django.http import HttpResponse
 
 from notifications import get_today_midnight_date
+from web.decorators import PermissionDecorator
 from . import e500_error, wrap_response
 from ..models import Subject, StudySubject, Appointment
 
 
+@PermissionDecorator('export_subjects', 'subject')
 def export_to_csv(request, data_type="subjects"):
     # Create the HttpResponse object with the appropriate CSV header.
     selected_fields = request.GET.get('fields', None)
@@ -29,6 +31,7 @@ def export_to_csv(request, data_type="subjects"):
     return response
 
 
+@PermissionDecorator('export_subjects', 'subject')
 def export_to_excel(request, data_type="subjects"):
     selected_fields = request.GET.get('fields', None)
     filename = data_type + '-' + get_today_midnight_date().strftime("%Y-%m-%d") + ".xls"
@@ -53,26 +56,27 @@ class CustomField:
 
 DROP_OUT_FIELD = CustomField({'verbose_name': "DROP OUT", 'name': "custom-drop-out"})
 APPOINTMENT_TYPE_FIELD = CustomField({
-        'name': 'appointment_types',
-        'verbose_name': 'Appointment Types'
-    })
+    'name': 'appointment_types',
+    'verbose_name': 'Appointment Types'
+})
 STUDY_SUBJECT_FIELDS = [CustomField({
-        'name': 'nd_number',
-        'verbose_name' : 'ND number'
-    })]
+    'name': 'nd_number',
+    'verbose_name': 'ND number'
+})]
 
 SUBJECT_FIELDS = [CustomField({
-        'name': 'last_name',
-        'verbose_name': 'Family name'
-    }),
+    'name': 'last_name',
+    'verbose_name': 'Family name'
+}),
     CustomField({
         'name': 'first_name',
         'verbose_name': 'Name'
     })]
 VISIT_FIELDS = [CustomField({
-        'name': 'visit_number',
-        'verbose_name': 'Visit'
-    })]
+    'name': 'visit_number',
+    'verbose_name': 'Visit'
+})]
+
 
 def filter_fields_from_selected_fields(fields, selected_fields):
     if selected_fields is None:
@@ -80,6 +84,7 @@ def filter_fields_from_selected_fields(fields, selected_fields):
     selected_fields = set(selected_fields.split(','))
     return [field for field in fields if field.name in selected_fields]
 
+
 def get_default_subject_fields():
     subject_fields = []
     for field in Subject._meta.fields:
@@ -91,12 +96,13 @@ def get_default_subject_fields():
     subject_fields.append(DROP_OUT_FIELD)
     return subject_fields
 
+
 def get_subjects_as_array(selected_fields=None):
     result = []
-    subject_fields = get_default_subject_fields() 
+    subject_fields = get_default_subject_fields()
     subject_fields = filter_fields_from_selected_fields(subject_fields, selected_fields)
 
-    field_names = [field.verbose_name for field in subject_fields] #faster than loop
+    field_names = [field.verbose_name for field in subject_fields]  # faster than loop
     result.append(field_names)
 
     subjects = StudySubject.objects.order_by('-subject__last_name')
@@ -105,6 +111,7 @@ def get_subjects_as_array(selected_fields=None):
         result.append([unicode(s).replace("\n", ";").replace("\r", ";") for s in row])
     return result
 
+
 def subject_to_row_for_fields(study_subject, subject_fields):
     row = []
     for field in subject_fields:
@@ -128,31 +135,33 @@ def subject_to_row_for_fields(study_subject, subject_fields):
         row.append(cell)
     return row
 
+
 def get_appointment_fields():
     appointments_fields = []
     for field in Appointment._meta.fields:
         if field.name.upper() != "VISIT" and field.name.upper() != "ID" and \
-                        field.name.upper() != "WORKER_ASSIGNED" and field.name.upper() != "APPOINTMENT_TYPES" and \
-                        field.name.upper() != "ROOM" and field.name.upper() != "FLYING_TEAM":
+                field.name.upper() != "WORKER_ASSIGNED" and field.name.upper() != "APPOINTMENT_TYPES" and \
+                field.name.upper() != "ROOM" and field.name.upper() != "FLYING_TEAM":
             appointments_fields.append(field)
 
     all_fields = STUDY_SUBJECT_FIELDS + SUBJECT_FIELDS + VISIT_FIELDS + appointments_fields + [APPOINTMENT_TYPE_FIELD]
 
     return all_fields, appointments_fields
 
+
 def get_appointments_as_array(selected_fields=None):
     result = []
     all_fields, appointments_fields = get_appointment_fields()
     all_fields = filter_fields_from_selected_fields(all_fields, selected_fields)
     appointments_fields = filter_fields_from_selected_fields(appointments_fields, selected_fields)
 
-    field_names = [field.verbose_name for field in all_fields] #faster than loop
+    field_names = [field.verbose_name for field in all_fields]  # faster than loop
     result.append(field_names)
 
     appointments = Appointment.objects.order_by('-datetime_when')
 
     for appointment in appointments:
-        #add field_names ['ND number', 'Family name', 'Name', 'Visit'] first
+        # add field_names ['ND number', 'Family name', 'Name', 'Visit'] first
         row = []
         for field in STUDY_SUBJECT_FIELDS:
             if field.verbose_name in field_names:
@@ -175,15 +184,16 @@ def get_appointments_as_array(selected_fields=None):
         for field in appointments_fields:
             row.append(getattr(appointment, field.name))
         if APPOINTMENT_TYPE_FIELD.verbose_name in field_names:
-            #avoid last comma in the list of appointment types
+            # avoid last comma in the list of appointment types
             type_string = ','.join([appointment_type.code for appointment_type in appointment.appointment_types.all()])
             row.append(type_string)
             result.append([unicode(s).replace("\n", ";").replace("\r", ";") for s in row])
     return result
 
 
+@PermissionDecorator('export_subjects', 'subject')
 def export(request):
     return wrap_response(request, 'export/index.html', {
         'subject_fields': get_default_subject_fields(),
         'appointment_fields': get_appointment_fields()[0]
-    })
\ No newline at end of file
+    })
diff --git a/smash/web/views/flying_teams.py b/smash/web/views/flying_teams.py
index dc6c4752045cef881778084f6ece845aa259ef2f..b0f5c7b01f0403daf1bea1a4f5c9f69c52b4f03e 100644
--- a/smash/web/views/flying_teams.py
+++ b/smash/web/views/flying_teams.py
@@ -1,11 +1,13 @@
 # coding=utf-8
 from django.shortcuts import redirect, get_object_or_404
 
+from web.decorators import PermissionDecorator
 from . import wrap_response
-from ..models import FlyingTeam
 from ..forms.forms import FlyingTeamAddForm, FlyingTeamEditForm
+from ..models import FlyingTeam
 
 
+@PermissionDecorator('change_flyingteam', 'equipment')
 def flying_teams(request):
     flying_team_list = FlyingTeam.objects.order_by('-place')
     context = {
@@ -16,6 +18,8 @@ def flying_teams(request):
                          "equipment_and_rooms/flying_teams/index.html",
                          context)
 
+
+@PermissionDecorator('change_flyingteam', 'equipment')
 def flying_teams_add(request):
     if request.method == 'POST':
         form = FlyingTeamAddForm(request.POST)
@@ -28,6 +32,7 @@ def flying_teams_add(request):
     return wrap_response(request, 'equipment_and_rooms/flying_teams/add.html', {'form': form})
 
 
+@PermissionDecorator('change_flyingteam', 'equipment')
 def flying_teams_edit(request, flying_team_id):
     the_flying_team = get_object_or_404(FlyingTeam, id=flying_team_id)
     if request.method == 'POST':
diff --git a/smash/web/views/kit.py b/smash/web/views/kit.py
index 79c42ef5fb89430026ceaa05c20e766f7175e7ca..c099d903f2716b474cadbcc60be70ce2f1d376db 100644
--- a/smash/web/views/kit.py
+++ b/smash/web/views/kit.py
@@ -13,6 +13,7 @@ from django_cron import CronJobBase, Schedule
 from django_cron.models import CronJobLog
 
 from notifications import get_filter_locations, get_today_midnight_date
+from web.decorators import PermissionDecorator
 from web.models import ConfigurationItem, Language, Worker
 from web.models.constants import KIT_EMAIL_HOUR_CONFIGURATION_TYPE, \
     KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT
@@ -60,6 +61,7 @@ def get_kit_requests(user, start_date=None, end_date=None):
     return result
 
 
+@PermissionDecorator('send_sample_mail_for_appointments', 'equipment')
 def get_kit_requests_data(request, start_date=None, end_date=None):
     form = KitRequestForm()
     if request.method == 'POST':
@@ -76,6 +78,7 @@ def get_kit_requests_data(request, start_date=None, end_date=None):
     return params
 
 
+@PermissionDecorator('send_sample_mail_for_appointments', 'equipment')
 def kit_requests(request):
     return wrap_response(request, 'equipment_and_rooms/kit_requests/kit_requests.html', get_kit_requests_data(request))
 
@@ -85,7 +88,7 @@ def send_mail(data):
     if data["end_date"] is not None:
         end_date_str = data["end_date"].strftime('%Y-%m-%d')
     title = "Samples between " + \
-        data["start_date"].strftime('%Y-%m-%d') + " and " + end_date_str
+            data["start_date"].strftime('%Y-%m-%d') + " and " + end_date_str
 
     cell_style = "padding: 8px; line-height: 1.42857143; vertical-align: top; " \
                  "font-size: 14px; font-family: 'Source Sans Pro','Helvetica Neue',Helvetica,Arial,sans-serif;"
@@ -109,10 +112,10 @@ def send_mail(data):
             row_style = ' background-color: #f9f9f9;'
         email_body += "<tr style='" + row_style + "'>"
         email_body += "<td style='" + cell_style + "'>" + \
-            appointment.datetime_when.strftime('%Y-%m-%d %H:%M') + "</td>"
+                      appointment.datetime_when.strftime('%Y-%m-%d %H:%M') + "</td>"
         if appointment.visit is not None and appointment.visit.subject is not None:
             email_body += "<td style='" + cell_style + "'>" + \
-                appointment.visit.subject.nd_number + "</td>"
+                          appointment.visit.subject.nd_number + "</td>"
         else:
             email_body += "<td style='" + cell_style + "'>" + '-' + "</td>"
         email_body += "<td style='" + cell_style + "'>"
@@ -126,7 +129,7 @@ def send_mail(data):
             location += " (" + unicode(appointment.flying_team) + ")"
         email_body += "<td style='" + cell_style + "'>" + location + "</td>"
         email_body += "<td style='" + cell_style + "'>" + \
-            unicode(appointment.worker_assigned) + "</td>"
+                      unicode(appointment.worker_assigned) + "</td>"
         email_body += "</tr>"
     email_body += "</tbody></table>"
     recipients = ConfigurationItem.objects.get(
@@ -136,6 +139,7 @@ def send_mail(data):
     EmailSender().send_email(title, email_body, recipients, cc_recipients)
 
 
+@PermissionDecorator('send_sample_mail_for_appointments', 'equipment')
 def kit_requests_send_mail(request, start_date, end_date=None):
     data = get_kit_requests_data(request, start_date, end_date)
     try:
diff --git a/smash/web/views/mails.py b/smash/web/views/mails.py
index ef4cf0b3bb6ce05c8ff4f35b0246c0f830ea5171..7b49409c99a1f1fafb6c4600bf16fbd9af6897d2 100644
--- a/smash/web/views/mails.py
+++ b/smash/web/views/mails.py
@@ -9,6 +9,7 @@ from django.urls import reverse_lazy
 from django.views.generic import DeleteView
 from django.views.generic import ListView
 
+from web.decorators import PermissionDecorator
 from web.docx_helper import merge_files
 from . import WrappedView
 from . import wrap_response
@@ -32,7 +33,11 @@ class MailTemplatesListView(ListView, WrappedView):
     context_object_name = "mail_templates"
     template_name = 'mail_templates/list.html'
 
-    def get_context_data(self, **kwargs):
+    @PermissionDecorator('change_mailtemplate', 'mailtemplate')
+    def dispatch(self, *args, **kwargs):
+        return super(MailTemplatesListView, self).dispatch(*args, **kwargs)
+
+    def get_context_data(self, *args, **kwargs):
         context = super(MailTemplatesListView, self).get_context_data()
         context['explanations'] = {"generic": MailTemplate.MAILS_TEMPLATE_GENERIC_TAGS,
                                    "subject": MailTemplate.MAILS_TEMPLATE_SUBJECT_TAGS,
@@ -43,6 +48,7 @@ class MailTemplatesListView(ListView, WrappedView):
         return context
 
 
+@PermissionDecorator('change_mailtemplate', 'mailtemplate')
 def mail_template_add(request):
     if request.method == 'POST':
         form = MailTemplateForm(request.POST, request.FILES)
@@ -59,6 +65,7 @@ def mail_template_add(request):
     return wrap_response(request, 'mail_templates/add.html', {'form': form})
 
 
+@PermissionDecorator('change_mailtemplate', 'mailtemplate')
 def mail_template_edit(request, pk):
     template = get_object_or_404(MailTemplate, pk=pk)
     if request.method == 'POST':
@@ -82,6 +89,7 @@ class MailTemplatesDeleteView(DeleteView, WrappedView):
     success_url = reverse_lazy('web.views.mail_templates')
     template_name = 'mail_templates/confirm_delete.html'
 
+    @PermissionDecorator('change_mailtemplate', 'mailtemplate')
     def delete(self, request, *args, **kwargs):
         messages.success(request, "Template deleted")
         try:
diff --git a/smash/web/views/rooms.py b/smash/web/views/rooms.py
index 2ff626e95a9259a0769bcb7b8e840a40908952bc..a5ca91933a09431edc200fb0febb51cd43fe3134 100644
--- a/smash/web/views/rooms.py
+++ b/smash/web/views/rooms.py
@@ -1,11 +1,13 @@
 # coding=utf-8
 from django.shortcuts import redirect, get_object_or_404
 
+from web.decorators import PermissionDecorator
 from . import wrap_response
 from ..forms.forms import RoomForm
 from ..models import Room
 
 
+@PermissionDecorator('change_room', 'equipment')
 def rooms(request):
     rooms_list = Room.objects.order_by('-city')
     context = {
@@ -17,6 +19,7 @@ def rooms(request):
                          context)
 
 
+@PermissionDecorator('change_room', 'equipment')
 def rooms_add(request):
     if request.method == 'POST':
         form = RoomForm(request.POST)
@@ -29,6 +32,7 @@ def rooms_add(request):
     return wrap_response(request, 'equipment_and_rooms/rooms/add.html', {'form': form})
 
 
+@PermissionDecorator('change_room', 'equipment')
 def rooms_edit(request, room_id):
     the_room = get_object_or_404(Room, id=room_id)
     if request.method == 'POST':
@@ -42,6 +46,7 @@ def rooms_edit(request, room_id):
     return wrap_response(request, 'equipment_and_rooms/rooms/edit.html', {'form': form})
 
 
+@PermissionDecorator('change_room', 'equipment')
 def rooms_delete(request, room_id):
     the_room = get_object_or_404(Room, id=room_id)
     the_room.delete()
diff --git a/smash/web/views/statistics.py b/smash/web/views/statistics.py
index 67bdb79c8784b427bba9526b546661f266eaf10f..b8cd84cc9cb8316a434b6c8dfe438a038a00f126 100644
--- a/smash/web/views/statistics.py
+++ b/smash/web/views/statistics.py
@@ -1,9 +1,11 @@
 # coding=utf-8
+from web.decorators import PermissionDecorator
 from . import wrap_response
 from ..forms import StatisticsForm
 from ..statistics import StatisticsManager, get_previous_year_and_month
 
 
+@PermissionDecorator('view_statistics', 'appointment')
 def statistics(request):
     statistics_manager = StatisticsManager()
     visit_choices = [("-1", "all")]
diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py
index 668346607186f1f5eca9a3e4d9547acc3381a183..ef65d5f486e0aa018a762fa8deb0091a09fdf72a 100644
--- a/smash/web/views/subject.py
+++ b/smash/web/views/subject.py
@@ -4,6 +4,7 @@ import logging
 from django.contrib import messages
 from django.shortcuts import redirect, get_object_or_404
 
+from web.decorators import PermissionDecorator
 from . import wrap_response
 from ..forms import VisitDetailForm, SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm
 from ..models import StudySubject, MailTemplate, Worker, Study, Provenance, Subject
@@ -27,6 +28,7 @@ def subjects(request):
     return subject_list(request, SUBJECT_LIST_GENERIC)
 
 
+@PermissionDecorator('add_subject', 'subject')
 def subject_add(request):
     study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
     if request.method == 'POST':
@@ -100,7 +102,7 @@ def subject_edit(request, id):
                 p = Provenance(modified_table = Subject._meta.db_table,
                                 modified_table_id = study_subject.subject.id,
                                 modification_author = worker,
-                                previous_value = study_subject.subject.dead,
+                                previous_value = was_dead,
                                 new_value = True,
                                 modification_description = 'Worker "{}" marks subject "{}" as dead'.format(worker, study_subject.subject),
                                 modified_field = 'dead',
@@ -112,7 +114,7 @@ def subject_edit(request, id):
                 p = Provenance(modified_table = StudySubject._meta.db_table,
                                 modified_table_id = study_subject.id,
                                 modification_author = worker,
-                                previous_value = study_subject.resigned,
+                                previous_value = was_resigned,
                                 new_value = True,
                                 modification_description = 'Worker "{}" marks study subject "{}" as resigned from study "{}"'.format(worker, study_subject.nd_number, study_subject.study),
                                 modified_field = 'resigned',