diff --git a/CHANGELOG b/CHANGELOG
index 928f1108405ffc90934e65d54926f1692d54abbe..6707d1902f3d4402110a4e97fc6bcb1ce8e34e4f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,11 @@
+smasch (1.1.0~alpha.0-1) unstable; urgency=low
+
+  * improvement: user can modify/add Subject types with custom follow up schema
+    (#371)
+  * bug fix: privacy notice files were not removed when policy was removed
+
+ -- Piotr Gawron <piotr.gawron@uni.lu>  Thu, 25 Feb 2021 17:00:00 +0200
+
 smasch (1.0.0-1) stable; urgency=low
 
   * backward incompatible: smasch is using python3 (#337)
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..48e341a0954d5f8c2accf3a6731be28e5bb9c0de
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3 @@
+{
+  "lockfileVersion": 1
+}
diff --git a/smash/db_scripts/create_dummy_data.py b/smash/db_scripts/create_dummy_data.py
index dadf16b1f1093524e1d864ddfab5edfc9ffa274f..6bcfcb36fd8fe58f0485fa899216bc11c2b43e8c 100644
--- a/smash/db_scripts/create_dummy_data.py
+++ b/smash/db_scripts/create_dummy_data.py
@@ -1,10 +1,8 @@
 # coding=utf-8
-import os, sys
+import os
+import sys
+
 sys.path.append(sys.path.append(os.path.join(os.path.dirname(__file__), '..'))) #run script as it was on parent folder
-from django.conf import settings
-from django.core.files import File  # you need this somewhere
-import urllib.request, urllib.parse, urllib.error
-from django.core.files.uploadedfile import SimpleUploadedFile
 import django
 import datetime
 from django.utils import timezone
@@ -12,25 +10,24 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "smash.settings")
 django.setup()
 from django.contrib.auth.models import User
 # models (please add in both lines)
-from web.models import StudySubject, Availability, Visit, Appointment, AppointmentType, AppointmentTypeLink, Study, Subject, Worker, Location, Language, Country, WorkerStudyRole, Item, FlyingTeam, Room, MailTemplate
+from web.models import StudySubject, Availability, Visit, Appointment, AppointmentType, AppointmentTypeLink, Study, \
+    Subject, Worker, Location, Language, Country, WorkerStudyRole, Item, FlyingTeam, Room, MailTemplate, SubjectType
 from smash.local_settings import MEDIA_ROOT
-from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, REDCAP_BASE_URL_CONFIGURATION_TYPE, \
-    SEX_CHOICES_MALE, SEX_CHOICES_FEMALE, SUBJECT_TYPE_CHOICES_CONTROL, SUBJECT_TYPE_CHOICES_PATIENT, CONTACT_TYPES_PHONE, \
-    MONDAY_AS_DAY_OF_WEEK, COUNTRY_AFGHANISTAN_ID, VOUCHER_STATUS_NEW, GLOBAL_STUDY_ID, DEFAULT_LOCALE_NAME
+from web.models.constants import SEX_CHOICES_MALE, SEX_CHOICES_FEMALE, COUNTRY_AFGHANISTAN_ID, GLOBAL_STUDY_ID, \
+    DEFAULT_LOCALE_NAME
 from web.models.constants import MAIL_TEMPLATE_CONTEXT_APPOINTMENT, MAIL_TEMPLATE_CONTEXT_VISIT, \
     MAIL_TEMPLATE_CONTEXT_SUBJECT, MAIL_TEMPLATE_CONTEXT_VOUCHER
-from web.models.worker_study_role import ROLE_CHOICES_PROJECT_MANAGER, ROLE_CHOICES_SECRETARY, ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER, ROLE_CHOICES_TECHNICIAN, ROLE_CHOICES_PSYCHOLOGIST, ROLE_CHOICES_NURSE
+from web.models.worker_study_role import ROLE_CHOICES_PROJECT_MANAGER, ROLE_CHOICES_SECRETARY, ROLE_CHOICES_DOCTOR, \
+    ROLE_CHOICES_TECHNICIAN, ROLE_CHOICES_PSYCHOLOGIST, ROLE_CHOICES_NURSE
 
 from collections import defaultdict
 import logging
 logger = logging.getLogger(__name__)
 
 from web.utils import get_today_midnight_date
-from faker.providers import BaseProvider, color
+from faker.providers import BaseProvider
 from numpy.random import choice
 from faker import Faker
-import platform
-import tempfile
 from shutil import copyfile
 
 
@@ -420,8 +417,8 @@ class smashProvider(BaseProvider):
             study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
 
         if type is None:
-            type = choice([SUBJECT_TYPE_CHOICES_CONTROL,
-                           SUBJECT_TYPE_CHOICES_PATIENT], 1, p=[0.2, 0.8])
+            type = choice([SubjectType.objects.all().first(),
+                           SubjectType.objects.all().last()], 1, p=[0.2, 0.8])
             type = type[0]
 
         if default_location is None:
diff --git a/smash/db_scripts/import_file.py b/smash/db_scripts/import_file.py
index efae7af09d72f94171747796904a68456bf075b8..974ef9a3a253b4fec964c7bb2d2715f563d888e7 100644
--- a/smash/db_scripts/import_file.py
+++ b/smash/db_scripts/import_file.py
@@ -18,7 +18,7 @@ import re
 from operator import itemgetter
 from collections import OrderedDict
 from django.contrib.auth.models import User
-from web.models.constants import VOUCHER_STATUS_IN_USE, SUBJECT_TYPE_CHOICES_PATIENT, GLOBAL_STUDY_ID, SEX_CHOICES_MALE, SEX_CHOICES_FEMALE
+from web.models.constants import VOUCHER_STATUS_IN_USE, GLOBAL_STUDY_ID, SEX_CHOICES_MALE, SEX_CHOICES_FEMALE
 from web.algorithm import VerhoeffAlgorithm, LuhnAlgorithm
 from web.utils import is_valid_social_security_number
 
@@ -501,7 +501,7 @@ def parse_row(index, row, visit_columns, appointmentTypes, voucher_types, lcsb_w
         'nd_number': nd_number,
         'resigned': row['RESIGNED'],
         'resign_reason': row['REASON'],
-        'type': SUBJECT_TYPE_CHOICES_PATIENT,
+        'type': StudySubject.objects.all().first(),
         'excluded': row['EXCLUDED'],
         'exclude_reason': row['REASON.1'],
         'comments': row['COMMENT'],
diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py
index 6100440f9e7c0ca6ead923cf10a976780f42efe9..e7b8f17bc06df1be8041a9aa908949d3fc311d16 100644
--- a/smash/web/api_views/appointment.py
+++ b/smash/web/api_views/appointment.py
@@ -168,7 +168,7 @@ def appointments(request, appointment_type):
     })
 
 
-def serialize_appointment(appointment):
+def serialize_appointment(appointment: Appointment):
     subject_string = ""
     first_name = ""
     last_name = ""
@@ -183,7 +183,7 @@ def serialize_appointment(appointment):
         last_name = study_subject.subject.last_name
         nd_number = study_subject.nd_number
         screening_number = study_subject.screening_number
-        subject_type = study_subject.get_type_display()
+        subject_type = study_subject.type.name
         phone_numbers = ", ".join([_f for _f in [study_subject.subject.phone_number, study_subject.subject.phone_number_2,
                                           study_subject.subject.phone_number_3] if _f])
         appointment_type_names = ", ".join(
diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index ec39cb03d6819fc5a4451afa559f394a574b5321..2a817787d77d5b057e00634952b2382da2f4ee9f 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -10,8 +10,8 @@ from django.urls import reverse
 from web.api_views.serialization_utils import str_to_yes_no_null, bool_to_yes_no, flying_team_to_str, location_to_str, \
     add_column, serialize_date, serialize_datetime, get_filters_for_data_table_request
 from web.models import ConfigurationItem, StudySubject, Visit, Appointment, Subject, SubjectColumns, StudyColumns, \
-    Study, ContactAttempt
-from web.models.constants import SUBJECT_TYPE_CHOICES, GLOBAL_STUDY_ID, VISIT_SHOW_VISIT_NUMBER_FROM_ZERO, \
+    Study, ContactAttempt, SubjectType
+from web.models.constants import GLOBAL_STUDY_ID, VISIT_SHOW_VISIT_NUMBER_FROM_ZERO, \
     CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, \
     CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_FILE
 from web.models.custom_data.custom_study_subject_field import get_study_subject_field_id, CustomStudySubjectField
@@ -92,7 +92,6 @@ def get_subject_columns(request, subject_list_type):
         virus_visit_numbers = list(range(1, 5 + 1))
         visit_numbers = list(range(1, study.visits_to_show_in_subject_list + 1))
 
-
     add_column(result, "Type", "type", study_subject_columns, "type_filter", study.columns)
     for custom_study_subject_field in study.customstudysubjectfield_set.all():
         visible = study_subject_columns.is_custom_field_visible(custom_study_subject_field)
@@ -236,7 +235,7 @@ def get_subjects_order(subjects_to_be_ordered: QuerySet, order_column, order_dir
     elif order_column == "excluded":
         result = subjects_to_be_ordered.order_by(order_direction + 'excluded')
     elif order_column == "type":
-        result = subjects_to_be_ordered.order_by(order_direction + 'type')
+        result = subjects_to_be_ordered.order_by(order_direction + 'type__name')
     elif order_column == "id":
         result = subjects_to_be_ordered.order_by(order_direction + 'id')
     elif order_column == "date_born":
@@ -374,7 +373,7 @@ def get_subjects_filtered(subjects_to_be_filtered: QuerySet, filters) -> QuerySe
         elif column == "flying_team":
             result = result.filter(flying_team=value)
         elif column == "type":
-            result = result.filter(type=value)
+            result = result.filter(type_id=value)
         elif str(column).startswith("visit_"):
             visit_number = get_visit_number_from_visit_x_string(column)
             result = filter_by_visit(result, visit_number, value)
@@ -462,14 +461,15 @@ def subjects(request, subject_list_type):
 
 # noinspection PyUnusedLocal
 def types(request):
-    data = [{"id": subject_type_id, "name": subject_type_name} for subject_type_id, subject_type_name in
-            list(SUBJECT_TYPE_CHOICES.items())]
+    data = []
+    for subject_type in SubjectType.objects.all():
+        data.append({"id": subject_type.id, "name": subject_type.name})
     return JsonResponse({
         "types": data
     })
 
 
-def serialize_subject(study_subject:StudySubject):
+def serialize_subject(study_subject: StudySubject):
     location = location_to_str(study_subject.default_location)
     flying_team = flying_team_to_str(study_subject.flying_team)
     visits = Visit.objects.filter(subject=study_subject).order_by('visit_number')
@@ -553,7 +553,7 @@ def serialize_subject(study_subject:StudySubject):
         "health_partner_first_name": health_partner_first_name,
         "health_partner_last_name": health_partner_last_name,
         "social_security_number": study_subject.subject.social_security_number,
-        "type": study_subject.get_type_display(),
+        "type": study_subject.type.name,
         "id": study_subject.id,
         "visits": serialized_visits,
     }
diff --git a/smash/web/forms/forms.py b/smash/web/forms/forms.py
index be349a491d3beb6a76ecd1c521639f16499cc7c2..bbcf28078f01e7f8742821649f51bdd85e00af22 100644
--- a/smash/web/forms/forms.py
+++ b/smash/web/forms/forms.py
@@ -1,15 +1,14 @@
 import datetime
 import logging
+from distutils.util import strtobool
 
 from django import forms
 from django.forms import ModelForm, Form
 from django.utils.dates import MONTHS
 
-from web.models import Appointment, AppointmentType, AppointmentTypeLink, \
-    Availability, ContactAttempt, FlyingTeam, Holiday, Item, \
-    StudySubject, Room, Worker, Visit, VoucherType, VoucherTypePrice, ConfigurationItem
-from web.models.constants import SUBJECT_TYPE_CHOICES, VISIT_SHOW_VISIT_NUMBER_FROM_ZERO
-from distutils.util import strtobool
+from web.models import AppointmentType, Availability, FlyingTeam, Holiday, Item, \
+    StudySubject, Room, Worker, Visit, ConfigurationItem, SubjectType
+from web.models.constants import VISIT_SHOW_VISIT_NUMBER_FROM_ZERO
 from web.templatetags.filters import display_visit_number
 
 """
@@ -65,11 +64,10 @@ class VisitDetailForm(ModelForm):
     def __init__(self, *args, **kwargs):
         super(VisitDetailForm, self).__init__(*args, **kwargs)
         instance = getattr(self, 'instance', None)
-        if instance.is_finished: #set form as readonly
+        if instance.is_finished:  # set form as readonly
             for key in list(self.fields.keys()):
                 self.fields[key].widget.attrs['readonly'] = True
 
-
     class Meta:
         model = Visit
         exclude = ['is_finished', 'visit_number']
@@ -127,10 +125,11 @@ class StatisticsForm(Form):
         self.fields['month'] = forms.ChoiceField(choices=list(MONTHS.items()), initial=month)
         self.fields['year'] = forms.ChoiceField(choices=year_choices, initial=year)
         choices = [(-1, "all")]
-        choices.extend(list(SUBJECT_TYPE_CHOICES.items()))
+        for subject_type in SubjectType.objects.all():
+            choices.append((subject_type.id, subject_type.name))
         self.fields['subject_type'] = forms.ChoiceField(choices=choices, initial="-1")
         visit_from_zero = ConfigurationItem.objects.get(type=VISIT_SHOW_VISIT_NUMBER_FROM_ZERO).value
-        #True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0.
+        # True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0.
         if strtobool(visit_from_zero):
             new_choices = []
             for value, label in visit_choices:
@@ -138,7 +137,7 @@ class StatisticsForm(Form):
                     label = display_visit_number(label)
                 new_choices.append((value, label))
             visit_choices = new_choices
-                    
+
         self.fields['visit'] = forms.ChoiceField(choices=visit_choices, initial="-1")
 
 
@@ -146,7 +145,7 @@ class AvailabilityAddForm(ModelForm):
     def __init__(self, *args, **kwargs):
         super(AvailabilityAddForm, self).__init__(*args, **kwargs)
         self.fields['person'].widget.attrs['readonly'] = True
-    
+
     available_from = forms.TimeField(label="Available from",
                                      widget=forms.TimeInput(TIMEPICKER_DATE_ATTRS),
                                      initial="8:00",
diff --git a/smash/web/forms/study_forms.py b/smash/web/forms/study_forms.py
index 4062b6e72a3d506864992866c96ed68a86892d37..6bb22821b5b34a644b2cae98d4f6f12b90f1694c 100644
--- a/smash/web/forms/study_forms.py
+++ b/smash/web/forms/study_forms.py
@@ -1,10 +1,8 @@
 import logging
 
-from django.forms import ModelForm, ValidationError
-from web.models import Study, StudyNotificationParameters, StudyColumns, StudySubject, StudyRedCapColumns
+from django.forms import ModelForm
 
-import datetime
-from dateutil.relativedelta import relativedelta
+from web.models import Study, StudyNotificationParameters, StudyColumns, StudySubject, StudyRedCapColumns
 
 logger = logging.getLogger(__name__)
 
@@ -17,31 +15,15 @@ class StudyEditForm(ModelForm):
     def clean(self):
         cleaned_data = super(StudyEditForm, self).clean()
 
-        #check regex
+        # check regex
         nd_number_study_subject_regex = cleaned_data.get('nd_number_study_subject_regex')
-        
+
         instance = getattr(self, 'instance', None)
 
-        if nd_number_study_subject_regex is None or StudySubject.check_nd_number_regex(nd_number_study_subject_regex, instance) == False:
+        if nd_number_study_subject_regex is None or not StudySubject.check_nd_number_regex(
+                nd_number_study_subject_regex, instance):
             self.add_error('nd_number_study_subject_regex', 'Please enter a valid nd_number_study_subject_regex regex.')
 
-        #check default_visit_duration_in_months
-        visit_duration_in_months = cleaned_data.get('default_visit_duration_in_months')
-        control_follow_up = cleaned_data.get('default_delta_time_for_control_follow_up')
-        patient_follow_up = cleaned_data.get('default_delta_time_for_patient_follow_up')
-        units = cleaned_data.get('default_delta_time_for_follow_up_units')
-
-        if None not in [visit_duration_in_months, control_follow_up, patient_follow_up, units]:
-            t = datetime.datetime.today()
-            visit_duration = relativedelta(months=int(visit_duration_in_months))
-            control_delta = relativedelta(**{units: control_follow_up})
-            patient_delta = relativedelta(**{units: patient_follow_up})
-        
-            #relative time delta has no __cmp__ method, so we add them to a datetime
-            min_delta_time = min((t + control_delta), (t + patient_delta))
-            if (t+visit_duration) > min_delta_time:
-                self.add_error('default_visit_duration_in_months', 'Please enter a valid "duration of the visits". It must be shorter than the time difference between patient and control visits.')
-
         return cleaned_data
 
     class Meta:
@@ -70,6 +52,7 @@ class StudyColumnsEditForm(ModelForm):
         model = StudyColumns
         fields = '__all__'
 
+
 class StudyRedCapColumnsEditForm(ModelForm):
 
     def __init__(self, *args, **kwargs):
@@ -77,4 +60,4 @@ class StudyRedCapColumnsEditForm(ModelForm):
 
     class Meta:
         model = StudyRedCapColumns
-        fields = '__all__'
\ No newline at end of file
+        fields = '__all__'
diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py
index c22662cd92ab569560c54da23d79d165d8b322ed..be2ba5dedb835fee7fc27126af0a3ecc9678f57d 100644
--- a/smash/web/forms/study_subject_forms.py
+++ b/smash/web/forms/study_subject_forms.py
@@ -7,7 +7,7 @@ from django.forms import ModelForm
 
 from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, get_worker_from_args, DATEPICKER_DATE_ATTRS
 from web.models import StudySubject, Study, StudyColumns, VoucherType, Worker
-from web.models.constants import SCREENING_NUMBER_PREFIXES_FOR_TYPE, CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, \
+from web.models.constants import CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, \
     CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, \
     CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_FILE
 from web.models.custom_data import CustomStudySubjectField, CustomStudySubjectValue
@@ -187,7 +187,7 @@ class StudySubjectAddForm(StudySubjectForm):
         else:
             subject_type = self.cleaned_data.get('type', None)
             if subject_type is not None:
-                screening_number_prefix = SCREENING_NUMBER_PREFIXES_FOR_TYPE[subject_type]
+                screening_number_prefix = subject_type.screening_number_prefix
         if screening_number_prefix is None:
             return None
         prefix_screening_number = screening_number_prefix + "-"
diff --git a/smash/web/forms/subject_type_forms.py b/smash/web/forms/subject_type_forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff193f48602436af8dc40647444b2f4ba2f3b4ba
--- /dev/null
+++ b/smash/web/forms/subject_type_forms.py
@@ -0,0 +1,40 @@
+from django.forms import ModelForm
+
+from web.models import SubjectType, Study
+
+
+class SubjectTypeForm(ModelForm):
+    class Meta:
+        model = SubjectType
+        fields = "__all__"
+
+    def save(self, commit=True) -> SubjectType:
+        self.instance.study_id = self.study.id
+        return super().save(commit)
+
+
+class SubjectTypeAddForm(SubjectTypeForm):
+    def __init__(self, *args, **kwargs):
+        self.study = get_study_from_args(kwargs)
+        super().__init__(*args, **kwargs)
+
+
+class SubjectTypeEditForm(SubjectTypeForm):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        instance = getattr(self, 'instance', None)
+        self.study = get_study_from_instance(instance)
+
+
+def get_study_from_args(kwargs) -> Study:
+    study = kwargs.pop('study', None)
+    if study is None:
+        raise TypeError("Study not defined")
+    return study
+
+
+def get_study_from_instance(subject_type: SubjectType) -> Study:
+    if subject_type is not None:
+        return subject_type.study
+    else:
+        raise TypeError("SubjectType not defined")
diff --git a/smash/web/importer/csv_subject_import_reader.py b/smash/web/importer/csv_subject_import_reader.py
index 2e73d303b5328da7b71fae415ca3c80c83b2222c..0411fcaece59a89eba220aa3a2dde63c1e87331f 100644
--- a/smash/web/importer/csv_subject_import_reader.py
+++ b/smash/web/importer/csv_subject_import_reader.py
@@ -5,7 +5,7 @@ from typing import List, Type, Tuple
 from django.db import models
 from django.db.models import Field
 
-from web.models import StudySubject, Subject, SubjectImportData, Language
+from web.models import StudySubject, Subject, SubjectImportData, Language, SubjectType
 from .etl_common import EtlCommon
 from .subject_import_reader import SubjectImportReader
 
@@ -56,16 +56,16 @@ class CsvSubjectImportReader(SubjectImportReader):
             value = self.get_value_for_foreign_field(field, value)
 
         if table == Subject:
-            old_val = getattr(study_subject.subject, field.name)
+            old_val = self.get_field_value(study_subject.subject, field)
             setattr(study_subject.subject, field.name, self.get_new_value(old_val, value))
         elif table == StudySubject:
-            old_val = getattr(study_subject, field.name)
+            old_val = self.get_field_value(study_subject, field)
             setattr(study_subject, field.name, self.get_new_value(old_val, value))
         else:
             logger.warning("Don't know how to handle column " + column_name + " with data " + value)
 
     @staticmethod
-    def get_value_for_foreign_field(field, value):
+    def get_value_for_foreign_field(field: Field, value: str):
         if field.related_model == Language:
             if value == "":
                 return None
@@ -74,10 +74,25 @@ class CsvSubjectImportReader(SubjectImportReader):
                 if language is None:
                     language = Language.objects.create(name=value)
                 return language
+        elif field.related_model == SubjectType:
+            subject_type = SubjectType.objects.filter(name=value).first()
+            if subject_type is None:
+                subject_type = SubjectType.objects.all().first()
+                logger.warning(
+                    "Subject type does not exist: '" + str(value) + "'. Changing to: '" + subject_type.name + "'")
+            return subject_type
         else:
             logger.warning("Don't know how to handle type " + str(field.related_model))
             return None
 
+    @staticmethod
+    def get_field_value(model_object: models.Model, field: Field):
+        # for foreign keys we need to check if the key id is not none, otherwise for not nullable fields exception
+        # would be raised
+        if field.get_internal_type() == "ForeignKey" and getattr(model_object, field.name + "_id") is None:
+            return None
+        return getattr(model_object, field.name)
+
     def get_table_and_field(self, column_name: str) -> Tuple[Type[models.Model], Field]:
         return self.mappings.get(column_name, (None, None))
 
@@ -86,7 +101,7 @@ class CsvSubjectImportReader(SubjectImportReader):
             if field.get_internal_type() == "CharField" or \
                     field.get_internal_type() == "DateField" or \
                     field.get_internal_type() == "TextField" or \
-                    (field.get_internal_type() == "ForeignKey" and field.related_model in (Language,)):
+                    (field.get_internal_type() == "ForeignKey" and field.related_model in (Language, SubjectType)):
                 found = False
                 for mapping in self.import_data.column_mappings.all():
                     if mapping.table_name == object_type._meta.db_table and field.name == mapping.column_name:
diff --git a/smash/web/importer/csv_visit_import_reader.py b/smash/web/importer/csv_visit_import_reader.py
index ad8fd2b62f43a66bcfaeba5fde56699c95c99a89..f6d90a004fe20b5cf0f80735fe368029c8a4c410 100644
--- a/smash/web/importer/csv_visit_import_reader.py
+++ b/smash/web/importer/csv_visit_import_reader.py
@@ -7,7 +7,7 @@ import traceback
 
 import pytz
 
-from web.models import StudySubject, Visit, Appointment, Location, AppointmentTypeLink, Subject
+from web.models import StudySubject, Visit, Appointment, Location, AppointmentTypeLink, Subject, SubjectType
 from web.models.etl.visit_import import VisitImportData
 from .etl_common import EtlCommon, EtlException
 from .warning_counter import MsgCounterHandler
@@ -135,7 +135,8 @@ class CsvVisitImportReader(EtlCommon):
             study_subject = StudySubject.objects.create(subject=subject,
                                                         study=self.import_data.study,
                                                         nd_number=nd_number,
-                                                        screening_number=nd_number)
+                                                        screening_number=nd_number,
+                                                        type=SubjectType.objects.all().first())
         else:
             study_subject = study_subjects[0]
         return study_subject
diff --git a/smash/web/importer/importer.py b/smash/web/importer/importer.py
index 9cb1b2995a21425a0c822fc5a473eb3d53582429..8c5966dddc1da0bac5105e876b36a640b48d1846 100644
--- a/smash/web/importer/importer.py
+++ b/smash/web/importer/importer.py
@@ -3,7 +3,7 @@ import logging
 import sys
 import traceback
 
-from web.models import StudySubject, Subject, Language
+from web.models import StudySubject, Subject, Language, SubjectType
 from .etl_common import EtlCommon
 from .subject_import_reader import SubjectImportReader
 from .warning_counter import MsgCounterHandler
@@ -76,6 +76,8 @@ class Importer(EtlCommon):
 
             self.merged_count += 1
         else:
+            if study_subject.type_id is None:
+                study_subject.type = SubjectType.objects.all().first()
             study_subject.subject.save()
             study_subject.subject = Subject.objects.get(pk=study_subject.subject.id)
             study_subject.save()
diff --git a/smash/web/migrations/0193_subjecttype.py b/smash/web/migrations/0193_subjecttype.py
new file mode 100644
index 0000000000000000000000000000000000000000..e40c3859edf8ad2d6d917621d3c9be75d7f8ed1f
--- /dev/null
+++ b/smash/web/migrations/0193_subjecttype.py
@@ -0,0 +1,34 @@
+# Generated by Django 3.1.4 on 2021-02-25 15:13
+
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0192_auto_20210224_1703'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SubjectType',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=50, verbose_name='Name')),
+                ('screening_number_prefix', models.CharField(max_length=5)),
+                ('follow_up_delta_time', models.IntegerField(default=1, help_text='Time difference between visits used to automatically create follow up visits', null=True, validators=[django.core.validators.MinValueValidator(1)], verbose_name='Time difference between subject visits')),
+                ('follow_up_delta_units', models.CharField(choices=[('years', 'Years'), ('days', 'Days')], default='years', help_text='Units for the number of days between visits', max_length=10, verbose_name='Units for the follow up time difference')),
+                ('study', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.study')),
+                ('auto_create_follow_up', models.BooleanField(default=True, verbose_name='Auto create follow up visit')),
+            ],
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='new_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE,
+                                    to='web.subjecttype', verbose_name='Type',
+                                    blank=False, null=True)
+        ),
+    ]
diff --git a/smash/web/migrations/0194_migrate_subject_type_to_new_structure.py b/smash/web/migrations/0194_migrate_subject_type_to_new_structure.py
new file mode 100644
index 0000000000000000000000000000000000000000..69f4678923bb631aac676b0b03c61281b8c77eee
--- /dev/null
+++ b/smash/web/migrations/0194_migrate_subject_type_to_new_structure.py
@@ -0,0 +1,85 @@
+# Generated by Django 3.1.3 on 2020-12-01 07:55
+
+from django.db import migrations
+
+from web.models.constants import GLOBAL_STUDY_ID
+from web.models.study import FOLLOW_UP_INCREMENT_IN_YEARS
+from ..migration_functions import is_sqlite_db
+
+patient_prefix = 'P'
+patient_delta_time = str(1)
+patient_delta_units = FOLLOW_UP_INCREMENT_IN_YEARS
+patient_type_id = 1
+
+control_prefix = 'L'
+control_delta_time = str(4)
+control_delta_units = FOLLOW_UP_INCREMENT_IN_YEARS
+control_type_id = 2
+
+auto_create_follow_up = str(True)
+
+
+def fetch_subject_type_data(apps, schema_editor) -> None:
+    # noinspection PyPep8Naming
+    Study = apps.get_model("web", "Study")
+    study = Study.objects.get(pk=GLOBAL_STUDY_ID)
+    global patient_delta_time, patient_delta_units, control_delta_time, control_delta_units, auto_create_follow_up
+
+    patient_delta_time = str(study.default_delta_time_for_patient_follow_up)
+    patient_delta_units = study.default_delta_time_for_follow_up_units
+
+    control_delta_time = str(study.default_delta_time_for_control_follow_up)
+    control_delta_units = study.default_delta_time_for_follow_up_units
+
+    auto_create_follow_up = str(study.auto_create_follow_up)
+
+    if is_sqlite_db():
+        if auto_create_follow_up.lower() == "false":
+            auto_create_follow_up = "0"
+        else:
+            auto_create_follow_up = "1"
+    pass
+
+
+def fetch_subject_type_id_data(apps, schema_editor) -> None:
+    # noinspection PyPep8Naming
+    SubjectType = apps.get_model("web", "SubjectType")
+    global patient_type_id, control_type_id
+    patient_type_id = SubjectType.objects.get(name='PATIENT').id
+    control_type_id = SubjectType.objects.get(name='CONTROL').id
+    pass
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0193_subjecttype'),
+    ]
+
+    operations = [
+        migrations.RunPython(fetch_subject_type_data),
+        migrations.RunSQL("insert into web_subjecttype " +
+                          "(name, screening_number_prefix, follow_up_delta_time, follow_up_delta_units, "
+                          "auto_create_follow_up, study_id) " +
+                          "values(" +
+                          "'PATIENT','" +
+                          patient_prefix + "', " +
+                          patient_delta_time + ",'" +
+                          patient_delta_units + "'," +
+                          auto_create_follow_up + "," +
+                          str(GLOBAL_STUDY_ID) + ")"),
+        migrations.RunSQL("insert into web_subjecttype " +
+                          "(name, screening_number_prefix, follow_up_delta_time, follow_up_delta_units, "
+                          "auto_create_follow_up, study_id) " +
+                          "values(" +
+                          "'CONTROL','" +
+                          control_prefix + "', " +
+                          control_delta_time + ",'" +
+                          control_delta_units + "'," +
+                          auto_create_follow_up + "," +
+                          str(GLOBAL_STUDY_ID) + ")"),
+        migrations.RunPython(fetch_subject_type_id_data),
+        migrations.RunSQL("update web_studysubject " +
+                          " set new_type_id = " + str(patient_type_id) + " where type = 'P'"),
+        migrations.RunSQL("update web_studysubject " +
+                          " set new_type_id = " + str(control_type_id) + " where type = 'C'"),
+    ]
diff --git a/smash/web/migrations/0195_migrate_subject_type_to_new_structure.py b/smash/web/migrations/0195_migrate_subject_type_to_new_structure.py
new file mode 100644
index 0000000000000000000000000000000000000000..5f8a33285606d21646e395dc16d38d41d1f7ca03
--- /dev/null
+++ b/smash/web/migrations/0195_migrate_subject_type_to_new_structure.py
@@ -0,0 +1,43 @@
+# Generated by Django 3.1.3 on 2020-12-01 07:55
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0194_migrate_subject_type_to_new_structure'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='type',
+        ),
+        migrations.RenameField(
+            model_name='studysubject',
+            old_name='new_type',
+            new_name='type',
+        ),
+        migrations.RemoveField(
+            model_name='study',
+            name='default_delta_time_for_control_follow_up',
+        ),
+        migrations.RemoveField(
+            model_name='study',
+            name='default_delta_time_for_follow_up_units',
+        ),
+        migrations.RemoveField(
+            model_name='study',
+            name='default_delta_time_for_patient_follow_up',
+        ),
+        migrations.RemoveField(
+            model_name='study',
+            name='auto_create_follow_up',
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.subjecttype', verbose_name='Type', null=False),
+        ),
+    ]
diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py
index 56d7ea62f4e53372ea16b6423f6e43ff5549f19b..7894830a82fdbc79901582fea1534d4fe3fcc4f2 100644
--- a/smash/web/models/__init__.py
+++ b/smash/web/models/__init__.py
@@ -39,6 +39,7 @@ from .mail_template import MailTemplate
 from .missing_subject import MissingSubject
 from .inconsistent_subject import InconsistentSubject, InconsistentField
 from .privacy_notice import PrivacyNotice
+from .subject_type import SubjectType
 
 from .etl import VisitImportData, SubjectImportData, EtlColumnMapping
 from .custom_data import CustomStudySubjectVisibility
@@ -48,4 +49,4 @@ __all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holida
            AppointmentList, AppointmentColumns, Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate,
            AppointmentTypeLink, VoucherType, VoucherTypePrice, Voucher, WorkerStudyRole,
            MissingSubject, InconsistentSubject, InconsistentField, Country, StudyColumns, StudyRedCapColumns,
-           VisitColumns, StudyVisitList]
+           VisitColumns, StudyVisitList, SubjectType]
diff --git a/smash/web/models/constants.py b/smash/web/models/constants.py
index 8ba0ed97539910bee1f76152d0b9feeef164d199..290ca6cadb5d17291c663ab749ed369e53883c8b 100644
--- a/smash/web/models/constants.py
+++ b/smash/web/models/constants.py
@@ -24,18 +24,6 @@ VALUE_TYPE_CHOICES = (
     (VALUE_TYPE_TEXT, 'Text'),
 )
 
-
-SUBJECT_TYPE_CHOICES_CONTROL = 'C'
-SUBJECT_TYPE_CHOICES_PATIENT = 'P'
-SUBJECT_TYPE_CHOICES = {
-    SUBJECT_TYPE_CHOICES_CONTROL: 'CONTROL',
-    SUBJECT_TYPE_CHOICES_PATIENT: 'PATIENT',
-}
-SCREENING_NUMBER_PREFIXES_FOR_TYPE = {
-    SUBJECT_TYPE_CHOICES_CONTROL: "L",
-    SUBJECT_TYPE_CHOICES_PATIENT: "P",
-}
-
 APPOINTMENT_TYPE_DEFAULT_COLOR = '#cfc600'
 APPOINTMENT_TYPE_DEFAULT_FONT_COLOR = '#00000'
 
diff --git a/smash/web/models/mail_template.py b/smash/web/models/mail_template.py
index 633fbf4dd9c07175b3040ad9db6e54831baef33a..417405e99937b776a34038b7c44347ad9c685909 100644
--- a/smash/web/models/mail_template.py
+++ b/smash/web/models/mail_template.py
@@ -327,7 +327,7 @@ class MailTemplate(models.Model):
         return {}
 
     @staticmethod
-    def get_subject_replacements(study_subject):
+    def get_subject_replacements(study_subject: StudySubject):
         result = {}
         if study_subject is not None:
             date_born = date_to_str(study_subject.subject.date_born, DATE_FORMAT_SHORT)
@@ -351,7 +351,7 @@ class MailTemplate(models.Model):
                 "##S_PHONE_NUMBER_3##": str(study_subject.subject.phone_number_3),
                 "##S_POST_CODE##": study_subject.subject.postal_code,
                 "##S_SCREENING_NUMBER##": study_subject.screening_number,
-                "##S_TYPE##": study_subject.get_type_display(),
+                "##S_TYPE##": study_subject.type.name,
                 '##S_MAIL_LANGUAGE##': str(study_subject.subject.default_written_communication_language),
                 '##S_KNOWN_LANGUAGES##': ", ".join([l.name for l in study_subject.subject.languages.all()])
             }
diff --git a/smash/web/models/privacy_notice.py b/smash/web/models/privacy_notice.py
index 12c0696145f12c5b569a0595993c69e290532a98..a143ddd16392e57f3d82ffb21ec6063d07ac45ee 100644
--- a/smash/web/models/privacy_notice.py
+++ b/smash/web/models/privacy_notice.py
@@ -1,15 +1,18 @@
 # coding=utf-8
-import datetime
+import os
 
 from django.db import models
+from django.dispatch import receiver
+
 from web.templatetags.filters import basename
 
+
 class PrivacyNotice(models.Model):
     name = models.CharField(max_length=255, verbose_name='Name')
     created_at = models.DateTimeField(auto_now_add=True, verbose_name='Created at')
     updated_at = models.DateTimeField(auto_now=True, verbose_name='Updated at')
     summary = models.CharField(max_length=255, verbose_name='Summary', blank=False, null=False)
-    document   = models.FileField(upload_to='privacy_notices/', 
+    document = models.FileField(upload_to='privacy_notices/',
                                 verbose_name='Study Privacy Notice file',
                                 null=False, editable=True)
 
@@ -19,3 +22,36 @@ class PrivacyNotice(models.Model):
     @property
     def all_studies(self):
         return self.studies.all()
+
+
+# These two auto-delete files from filesystem when they are unneeded:
+
+@receiver(models.signals.post_delete, sender=PrivacyNotice)
+def auto_delete_file_on_delete(sender, instance: PrivacyNotice, **kwargs):
+    """
+    Deletes file from filesystem
+    when corresponding `MediaFile` object is deleted.
+    """
+    if instance.document:
+        if os.path.isfile(instance.document.path):
+            os.remove(instance.document.path)
+
+
+@receiver(models.signals.pre_save, sender=PrivacyNotice)
+def auto_delete_file_on_change(sender, instance: PrivacyNotice, **kwargs):
+    """
+    Deletes old file from filesystem
+    when corresponding `PrivacyNotice` object is updated
+    with new file.
+    """
+    if not instance.pk:
+        return False
+    try:
+        old_file = PrivacyNotice.objects.get(pk=instance.pk).document
+    except PrivacyNotice.DoesNotExist:
+        return False
+
+    new_file = instance.document
+    if not old_file == new_file:
+        if os.path.isfile(old_file.path):
+            os.remove(old_file.path)
diff --git a/smash/web/models/study.py b/smash/web/models/study.py
index 83f438ebaa452e7a5ca9ae442b5ccb45b8d65b3e..b6bff539799ccb02af908cded89987d202a8caa4 100644
--- a/smash/web/models/study.py
+++ b/smash/web/models/study.py
@@ -38,11 +38,6 @@ class Study(models.Model):
         on_delete=models.CASCADE,
     )
 
-    auto_create_follow_up = models.BooleanField(
-        default=True,
-        verbose_name="Auto create follow up visit"
-    )
-
     redcap_first_visit_number = models.IntegerField(
         default=1,
         verbose_name="Number of the first visit in redcap system"
@@ -72,28 +67,6 @@ class Study(models.Model):
         validators=[MinValueValidator(1)]
     )
 
-    default_delta_time_for_patient_follow_up = models.IntegerField(
-        verbose_name='Time difference between patient visits',
-        help_text='Time difference between visits used to automatically create follow up visits',
-        default=1,
-        validators=[MinValueValidator(1)]
-    )
-
-    default_delta_time_for_control_follow_up = models.IntegerField(
-        verbose_name='Time difference between control visits',
-        help_text='Time difference between visits used to automatically create follow up visits',
-        default=4,
-        validators=[MinValueValidator(1)]
-    )
-
-    default_delta_time_for_follow_up_units = models.CharField(max_length=10,
-                                                              choices=list(FOLLOW_UP_INCREMENT_UNIT_CHOICE.items()),
-                                                              verbose_name='Units for the follow up incrementals',
-                                                              help_text='Units for the number of days between visits for both patients and controls',
-                                                              default=FOLLOW_UP_INCREMENT_IN_YEARS,
-                                                              blank=False
-                                                              )
-
     study_privacy_notice = models.ForeignKey("web.PrivacyNotice",
                               verbose_name='Study Privacy Note',
                               editable=True, blank=True,
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index b7b1ac009f3720589ca67a61b9aa02eb2aa4fea3..4f224593d30efd58a9a9ba995882e20ab0daad2d 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -8,11 +8,9 @@ from django.db.models.signals import post_save
 from django.dispatch import receiver
 
 from web.models import VoucherType, Appointment, Location, Visit, Provenance
-from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES, FILE_STORAGE
+from web.models.constants import BOOL_CHOICES, FILE_STORAGE
 from web.models.custom_data import CustomStudySubjectValue, CustomStudySubjectField
 
-VIRUS_CHOICES = ((None, 'N/A'), ('Inconclusive', 'Inconclusive'), ('Positive', 'Positive'), ('Negative', 'Negative'))
-
 logger = logging.getLogger(__name__)
 
 
@@ -74,12 +72,13 @@ class StudySubject(models.Model):
         blank=True,
         verbose_name='Please make a contact on',
     )
-    type = models.CharField(max_length=1,
-                            choices=list(SUBJECT_TYPE_CHOICES.items()),
-                            verbose_name='Type',
-                            null=True,
-                            blank=True
-                            )
+
+    type = models.ForeignKey("web.SubjectType",
+                             null=False,
+                             blank=False,
+                             on_delete=models.CASCADE,
+                             verbose_name='Type'
+                             )
 
     default_location = models.ForeignKey(Location,
                                          verbose_name='Default appointment location',
diff --git a/smash/web/models/subject_type.py b/smash/web/models/subject_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..37a422d23e14b91af37cbed3c9009547d8edd0e4
--- /dev/null
+++ b/smash/web/models/subject_type.py
@@ -0,0 +1,52 @@
+# coding=utf-8
+
+from django.core.validators import MinValueValidator
+from django.db import models
+
+from web.models.study import FOLLOW_UP_INCREMENT_UNIT_CHOICE, FOLLOW_UP_INCREMENT_IN_YEARS
+from . import Study
+
+
+class SubjectType(models.Model):
+    class Meta:
+        app_label = 'web'
+
+    name = models.CharField(
+        max_length=50,
+        verbose_name='Name',
+        blank=False,
+        null=False
+    )
+
+    screening_number_prefix = models.CharField(max_length=5)
+
+    follow_up_delta_time = models.IntegerField(
+        verbose_name='Time difference between subject visits',
+        help_text='Time difference between visits used to automatically create follow up visits',
+        default=1,
+        null=True,
+        validators=[MinValueValidator(1)]
+    )
+
+    follow_up_delta_units = models.CharField(max_length=10,
+                                             choices=list(FOLLOW_UP_INCREMENT_UNIT_CHOICE.items()),
+                                             verbose_name='Units for the follow up time difference',
+                                             help_text='Units for the number of days between visits',
+                                             default=FOLLOW_UP_INCREMENT_IN_YEARS,
+                                             blank=False
+                                             )
+
+    study = models.ForeignKey(
+        Study,
+        null=False,
+        editable=False,
+        on_delete=models.CASCADE,
+    )
+
+    auto_create_follow_up = models.BooleanField(
+        default=True,
+        verbose_name="Auto create follow up visit"
+    )
+
+    def __str__(self):
+        return "%s" % self.name
diff --git a/smash/web/models/visit.py b/smash/web/models/visit.py
index c335ccebef2b1ed6e98cf325c342bbe19f44205f..8bd3ac4a7aff3faf9345fca0473acf97e6940624 100644
--- a/smash/web/models/visit.py
+++ b/smash/web/models/visit.py
@@ -1,18 +1,17 @@
 # coding=utf-8
-import datetime
-from dateutil.relativedelta import relativedelta
+import logging
 
+from dateutil.relativedelta import relativedelta
 from django.db import models
+from django.db import transaction
 from django.db.models.signals import post_save
 from django.dispatch import receiver
-from django.db import transaction
 
-from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_CHOICES_CONTROL
-from web.models import Study
+from web.models.constants import BOOL_CHOICES
 
-import logging
 logger = logging.getLogger(__name__)
 
+
 class Visit(models.Model):
 
     class Meta:
@@ -74,7 +73,7 @@ class Visit(models.Model):
             create_follow_up = False
         elif self.subject.endpoint_reached:
             create_follow_up = False
-        elif not self.subject.study.auto_create_follow_up:
+        elif not self.subject.type.auto_create_follow_up:
             create_follow_up = False
 
         if create_follow_up:
@@ -85,11 +84,8 @@ class Visit(models.Model):
 
             study = self.subject.study
 
-            if self.subject.type == SUBJECT_TYPE_CHOICES_CONTROL:
-                args = {study.default_delta_time_for_follow_up_units: study.default_delta_time_for_control_follow_up}
-            else:
-                args = {study.default_delta_time_for_follow_up_units: study.default_delta_time_for_patient_follow_up}
-            
+            args = {self.subject.type.follow_up_delta_units: self.subject.type.follow_up_delta_time}
+
             time_to_next_visit = relativedelta(**args) * (follow_up_number - 1) #calculated from first visit
 
             logger.warning('new visit: {} {} {}'.format(args, relativedelta(**args), time_to_next_visit))
diff --git a/smash/web/statistics.py b/smash/web/statistics.py
index 7d9d944ba76e083c5dece5b414b0bc883acde317..fb921c1ff5589ddbe4e97312c438a640bfd3801d 100644
--- a/smash/web/statistics.py
+++ b/smash/web/statistics.py
@@ -6,8 +6,9 @@ from operator import attrgetter
 
 from django.db import connection
 from django.db.models import Q, Count
+
 from web.migration_functions import is_sqlite_db
-from .models import AppointmentType, Appointment, Visit
+from .models import AppointmentType, Appointment, Visit, SubjectType
 
 __author__ = 'Valentin Grouès'
 
@@ -82,7 +83,7 @@ class StatisticsManager(object):
                                 self.statuses_list]
         self.visits_ranks = self._get_visits_ranks()
 
-    def get_statistics_for_month(self, month, year, subject_type=None, visit=None):
+    def get_statistics_for_month(self, month, year, subject_type: SubjectType = None, visit=None):
         """
         Build dict with statistics for a given month of a given year.
         Statistics include:
@@ -154,7 +155,7 @@ class StatisticsManager(object):
             query = QUERY_APPOINTMENTS
             subject_type_clause = ""
             if subject_type is not None:
-                subject_type_clause = " AND web_studysubject.type = '{}'".format(subject_type)
+                subject_type_clause = " AND web_studysubject.type_id = '{}'".format(subject_type.id)
             query = query.format(subject_type_clause)
             with connection.cursor() as cursor:
                 cursor.execute(query, [visit, month, year])
@@ -174,10 +175,10 @@ class StatisticsManager(object):
         return results_appointments
 
     @staticmethod
-    def _get_count_from_filters_or_sql(model, filters, query, visit, month, year, subject_type):
+    def _get_count_from_filters_or_sql(model, filters, query, visit, month, year, subject_type: SubjectType):
         if visit:
             if subject_type is not None:
-                query += " AND web_studysubject.type = '{}'".format(subject_type)
+                query += " AND web_studysubject.type_id = '{}'".format(subject_type.id)
             with connection.cursor() as cursor:
                 cursor.execute(
                     query,
@@ -188,15 +189,17 @@ class StatisticsManager(object):
             count = model.objects.filter(filters).count()
         return count
 
-    def _get_number_visits_started(self, filters_month_year_visits_started, visit, month, year, subject_type=None):
+    def _get_number_visits_started(self, filters_month_year_visits_started, visit, month, year,
+                                   subject_type: SubjectType = None):
         return self._get_count_from_filters_or_sql(Visit, filters_month_year_visits_started, QUERY_VISITS_STARTED_COUNT,
                                                    visit, month, year, subject_type)
 
-    def _get_number_visits_ended(self, filters_month_year_visits_ended, visit, month, year, subject_type=None):
+    def _get_number_visits_ended(self, filters_month_year_visits_ended, visit, month, year,
+                                 subject_type: SubjectType = None):
         return self._get_count_from_filters_or_sql(Visit, filters_month_year_visits_ended, QUERY_VISITS_ENDED_COUNT,
                                                    visit, month, year, subject_type)
 
-    def _get_number_of_appointments(self, filters, visit, month, year, subject_type=None):
+    def _get_number_of_appointments(self, filters, visit, month, year, subject_type: SubjectType = None):
         return self._get_count_from_filters_or_sql(Appointment, filters, QUERY_APPOINTMENTS_COUNT, visit, month, year,
                                                    subject_type)
 
@@ -223,15 +226,10 @@ class StatisticsManager(object):
         return filters_month_year_appointments, filters_month_year_visits_ended, filters_month_year_visits_started
 
     @staticmethod
-    def _get_visits_ranks(subject_type=None):
+    def _get_visits_ranks():
         query = QUERY_VISITS_RANKS
-        if subject_type is not None:
-            query += " LEFT JOIN web_studysubject ON web_studysubject.id = web_visit.subject_id WHERE web_studysubject.type = '{}'".format(
-                subject_type)
         with connection.cursor() as cursor:
-            cursor.execute(
-                query,
-                [])
+            cursor.execute(query, [])
             rows = cursor.fetchall()
 
         return [r[0] for r in rows]
diff --git a/smash/web/templates/_base.html b/smash/web/templates/_base.html
index f12f45aca6e598e4f8fe99c57dbf6ce6a5ca165a..3484515d3962c72a9a9e1212e20db25be5f8a7a9 100644
--- a/smash/web/templates/_base.html
+++ b/smash/web/templates/_base.html
@@ -256,7 +256,7 @@ desired effect
         {% block footer %}
             <!-- To the right -->
             <div class="pull-right hidden-xs">
-                Version: <strong>1.0.0</strong>
+                Version: <strong>1.1.0~alpha.0</strong>
             </div>
 
             <!-- Default to the left -->
diff --git a/smash/web/templates/sidebar.html b/smash/web/templates/sidebar.html
index 1ca1ba464789c55b5d8eaafc299d28346ebabe78..7d3818cb6d2de1b1933623409172e989a59f0563 100644
--- a/smash/web/templates/sidebar.html
+++ b/smash/web/templates/sidebar.html
@@ -144,6 +144,9 @@
                 {% if equipment_perms and "change_room" in permissions %}
                     <li data-desc="rooms"><a href="{% url 'web.views.equipment_and_rooms.rooms' %}">Rooms</a></li>
                 {% endif %}
+                {% if "change_subjecttype" in permissions %}
+                    <li data-desc="rooms"><a href="{% url 'web.views.subject_types' study_id %}">Subject types</a></li>
+                {% endif %}
             </ul>
         </li>
 
diff --git a/smash/web/templates/subject_types/add.html b/smash/web/templates/subject_types/add.html
new file mode 100644
index 0000000000000000000000000000000000000000..80788e1f7ff5d1c0ac337de1d493303b1b590f84
--- /dev/null
+++ b/smash/web/templates/subject_types/add.html
@@ -0,0 +1,9 @@
+{% extends "subject_types/add_edit.html" %}
+
+{% block page_header %}New subject type{% endblock page_header %}
+
+{% block title %}{{ block.super }} - Add new subject type{% endblock %}
+
+{% block form-title %}Enter subject type §details{% endblock %}
+
+{% block save-button %}Add{% endblock %}
diff --git a/smash/web/templates/subject_types/add_edit.html b/smash/web/templates/subject_types/add_edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..e33ee0782c6e5935656af570f8bec36a4fdd6ee4
--- /dev/null
+++ b/smash/web/templates/subject_types/add_edit.html
@@ -0,0 +1,77 @@
+{% extends "_base.html" %}
+{% load static %}
+{% load filters %}
+
+{% block styles %}
+    {{ block.super }}
+    <link rel="stylesheet" href="{% static 'npm/awesomplete/awesomplete.css' %}"/>
+
+{% endblock styles %}
+
+{% block ui_active_tab %}'configuration'{% endblock ui_active_tab %}
+{% block page_description %}{% endblock page_description %}
+
+{% block breadcrumb %}
+    {% include "subject_types/breadcrumb.html" %}
+{% endblock breadcrumb %}
+
+{% block maincontent %}
+
+    {% block content %}
+        <div class="row">
+            <div class="col-md-12">
+                <div class="box box-success">
+                    <div class="box-header with-border">
+                        <h3 class="box-title">{% block form-title %}Enter subject type details{% endblock %}</h3>
+                    </div>
+
+
+                    <form method="post" action="" class="form-horizontal" enctype="multipart/form-data">
+                        {% csrf_token %}
+
+                        <div class="box-body">
+                            {% for field in form %}
+                                <div class="form-group {% if field.errors %}has-error{% endif %}">
+                                    <label class="col-sm-4  col-lg-offset-1 col-lg-2 control-label">
+                                        {{ field.label }}
+                                    </label>
+
+                                    <div class="col-sm-8 col-lg-4">
+                                        {{ field|add_class:'form-control' }}
+                                        {% if field.errors %}
+                                            <span class="help-block">{{ field.errors }}</span>
+                                        {% endif %}
+                                    </div>
+
+
+                                </div>
+                            {% endfor %}
+                        </div><!-- /.box-body -->
+                        <div class="box-footer">
+                            <div class="col-sm-6">
+                                <button type="submit" class="btn btn-block btn-success">{% block save-button %}
+                                    Add{% endblock %}
+                                </button>
+                            </div>
+                            <div class="col-sm-6">
+                                <a href="{% url 'web.views.subject_types' study_id %}"
+                                   class="btn btn-block btn-default">Cancel</a>
+                            </div>
+                        </div><!-- /.box-footer -->
+                    </form>
+                </div>
+
+            </div>
+        </div>
+
+    {% endblock %}
+
+
+{% endblock maincontent %}
+
+{% block scripts %}
+    {{ block.super }}
+
+    <script src="{% static 'npm/awesomplete/awesomplete.min.js' %}"></script>
+
+{% endblock scripts %}
\ No newline at end of file
diff --git a/smash/web/templates/subject_types/breadcrumb.html b/smash/web/templates/subject_types/breadcrumb.html
new file mode 100644
index 0000000000000000000000000000000000000000..6b613249a304a72ce18da26f388194e375ca6a71
--- /dev/null
+++ b/smash/web/templates/subject_types/breadcrumb.html
@@ -0,0 +1,5 @@
+<li><a href="{% url 'web.views.appointments' %}"><i class="fa fa-dashboard"></i> Dashboard</a></li>
+<li>Configuration</li>
+<li class="active">
+	<a href="{% url 'web.views.subject_types' study_id %}">Subject types</a>
+</li>
\ No newline at end of file
diff --git a/smash/web/templates/subject_types/confirm_delete.html b/smash/web/templates/subject_types/confirm_delete.html
new file mode 100644
index 0000000000000000000000000000000000000000..4e72a5b1938de016be9116bccbd2aa6fbf69621a
--- /dev/null
+++ b/smash/web/templates/subject_types/confirm_delete.html
@@ -0,0 +1,62 @@
+{% extends "_base.html" %}
+{% load static %}
+{% load filters %}
+
+{% block styles %}
+    {{ block.super }}
+    <link rel="stylesheet" href="{% static 'npm/awesomplete/awesomplete.css' %}"/>
+
+{% endblock styles %}
+
+{% block ui_active_tab %}'configuration'{% endblock ui_active_tab %}
+{% block page_header %}Delete subject type{% endblock page_header %}
+{% block page_description %}{% endblock page_description %}
+
+{% block title %}{{ block.super }} - Delete subject type{% endblock %}
+
+{% block breadcrumb %}
+    {% include "subject_types/breadcrumb.html" %}
+{% endblock breadcrumb %}
+
+{% block maincontent %}
+
+    {% block content %}
+        <div class="row">
+            <div class="col-md-12">
+                <div class="box box-success">
+                    <div class="box-header with-border">
+                        <h3 class="box-title">Confirm deletion</h3>
+                    </div>
+
+                    <form action="" method="post" class="form-horizontal">{% csrf_token %}
+                        <div class="box-body">
+                            <p>Are you sure you want to delete subject type "{{ object.name }}"?</p>
+                        </div><!-- /.box-body -->
+                        <div class="box-footer">
+                            <div class="col-sm-6">
+                                <button type="submit" class="btn btn-block btn-danger">Delete</button>
+                            </div>
+                            <div class="col-sm-6">
+                                <a href="{% url 'web.views.languages' %}"
+                                   class="btn btn-block btn-default">Cancel</a>
+                            </div>
+                        </div><!-- /.box-footer -->
+                    </form>
+                </div>
+
+            </div>
+        </div>
+
+    {% endblock %}
+
+
+{% endblock maincontent %}
+
+{% block scripts %}
+    {{ block.super }}
+
+    <script src="{% static 'npm/awesomplete/awesomplete.min.js' %}"></script>
+
+{% endblock scripts %}
+
+
diff --git a/smash/web/templates/subject_types/edit.html b/smash/web/templates/subject_types/edit.html
new file mode 100644
index 0000000000000000000000000000000000000000..7efe1900538fd35e9947f1142d8153c4a2d16dc0
--- /dev/null
+++ b/smash/web/templates/subject_types/edit.html
@@ -0,0 +1,10 @@
+{% extends "subject_types/add_edit.html" %}
+
+{% block page_header %}Edit subject type "{{ language.name }}"{% endblock page_header %}
+
+{% block title %}{{ block.super }} - Edit subject type "{{ language.name }}"{% endblock %}
+
+{% block form-title %}Enter subject type details{% endblock %}
+
+{% block save-button %}Save{% endblock %}
+
diff --git a/smash/web/templates/subject_types/list.html b/smash/web/templates/subject_types/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..1ffb7385a590d20e3dfe3f9c001c7a53cba11f29
--- /dev/null
+++ b/smash/web/templates/subject_types/list.html
@@ -0,0 +1,74 @@
+{% extends "_base.html" %}
+{% load static %}
+
+{% block styles %}
+    {{ block.super }}
+    <!-- DataTables -->
+    <link rel="stylesheet" href="{% static 'npm/datatables.net-bs/css/dataTables.bootstrap.css' %}">
+{% endblock styles %}
+
+{% block ui_active_tab %}'subject types'{% endblock ui_active_tab %}
+{% block page_header %}Subject types{% endblock page_header %}
+{% block page_description %}{% endblock page_description %}
+
+{% block breadcrumb %}
+    {% include "subject_types/breadcrumb.html" %}
+{% endblock breadcrumb %}
+
+{% block maincontent %}
+
+    <div>
+        <a class="btn btn-app" href="{% url 'web.views.subject_type_add' study_id %}">
+            <i class="fa fa-plus"></i> Add new subject type
+        </a>
+    </div>
+
+    <div class="box-body">
+        <table id="table" class="table table-bordered table-striped">
+            <thead>
+            <tr>
+                <th>Id</th>
+                <th>Name</th>
+                <th>Edit</th>
+                <th>Delete</th>
+            </tr>
+            </thead>
+            <tbody>
+            {% for subject_type in subject_type_list %}
+                <tr>
+                    <td>{{ subject_type.id }}</td>
+                    <td>{{ subject_type.name }}</td>
+                    <td><a href="{% url 'web.views.subject_type_edit' study_id subject_type.id %}"><i
+                            class="fa fa-edit"></i></a></td>
+                    <td>{% if subject_type.subject_count == 0 %}<a
+                            href="{% url 'web.views.subject_type_delete' study_id subject_type.id %}"><i
+                            class="fa fa-trash text-danger"></i></a>
+                    {% else %} There are subjects with the type
+                    {% endif %}
+                    </td>
+                </tr>
+            {% endfor %}
+            </tbody>
+        </table>
+    </div>
+{% endblock maincontent %}
+
+{% block scripts %}
+    {{ block.super }}
+
+    <script src="{% static 'npm/datatables.net/js/jquery.dataTables.min.js' %}"></script>
+    <script src="{% static 'npm/datatables.net-bs/js/dataTables.bootstrap.min.js' %}"></script>
+
+    <script>
+        $(function () {
+            $('#table').DataTable({
+                "paging": true,
+                "lengthChange": false,
+                "searching": true,
+                "ordering": true,
+                "info": true,
+                "autoWidth": false
+            });
+        });
+    </script>
+{% endblock scripts %}
diff --git a/smash/web/tests/__init__.py b/smash/web/tests/__init__.py
index bf24a02c5d39827dcfe705b1ce4f57980458e89b..7fa27a7d1e2e11d28a4f15d6f2a92003bf292884 100644
--- a/smash/web/tests/__init__.py
+++ b/smash/web/tests/__init__.py
@@ -23,13 +23,17 @@ logger = logging.getLogger(__name__)
 
 class LoggedInTestCase(TestCase):
     def setUp(self):
-        self.password = 'abcd1234'
-        #superuser
-        self.super_worker = create_worker(user=User.objects.create_superuser(username='super', password=self.password, email='a@mail.com'))
-        #admin
+        self.password = 'passwd1234'
+
+        # superuser
+        self.super_worker = create_worker(
+            user=User.objects.create_superuser(username='super', password=self.password, email='a@mail.com'))
+
+        # admin
         self.admin_worker = Worker.get_by_user(create_user(username='admin', password=self.password))
         add_permissions_to_worker(self.admin_worker, PermissionDecorator.codenames)
-        #staff
+
+        # staff
         self.staff_worker = Worker.get_by_user(create_user(username='staff', password=self.password))
 
         self.client = Client()
diff --git a/smash/web/tests/api_views/test_subject.py b/smash/web/tests/api_views/test_subject.py
index caccfdc9827ce01817eea6b7733cd40475e2a32f..1d6a915ae922ee9343a4aa57e5ea5e5b1b57aa92 100644
--- a/smash/web/tests/api_views/test_subject.py
+++ b/smash/web/tests/api_views/test_subject.py
@@ -4,15 +4,15 @@ import json
 import logging
 
 from django.urls import reverse
-from parameterized import parameterized
 from django.utils import timezone
+from parameterized import parameterized
 from six import ensure_str
 
 from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject, get_subject_columns
 from web.importer.warning_counter import MsgCounterHandler
-from web.models import StudySubject, Appointment, Study, Worker, SubjectColumns, StudyColumns
-from web.models.constants import GLOBAL_STUDY_ID, SUBJECT_TYPE_CHOICES_PATIENT, SUBJECT_TYPE_CHOICES_CONTROL, \
-    CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, \
+from web.models import StudySubject, Appointment, Study, Worker, SubjectColumns, StudyColumns, SubjectType
+from web.models.constants import GLOBAL_STUDY_ID, CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, \
+    CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, \
     CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_FILE
 from web.models.custom_data import CustomStudySubjectField
 from web.models.custom_data.custom_study_subject_field import get_study_subject_field_id
@@ -21,7 +21,7 @@ from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_
 from web.tests import LoggedInWithWorkerTestCase
 from web.tests.functions import create_study_subject, create_get_suffix, create_visit, \
     create_appointment, create_empty_study_columns, create_contact_attempt, create_flying_team, create_worker, \
-    get_test_study
+    get_test_study, get_patient_subject_type, get_control_subject_type
 from web.views.notifications import get_today_midnight_date
 
 logger = logging.getLogger(__name__)
@@ -60,6 +60,15 @@ class TestSubjectApi(LoggedInWithWorkerTestCase):
 
         self.assertTrue(city_name in cities)
 
+    def test_subject_types(self):
+        response = self.client.get(reverse('web.api.subject_types'))
+        self.assertEqual(response.status_code, 200)
+
+        types = json.loads(response.content)['types']
+
+        for subject_type in types:
+            self.assertIsNotNone(SubjectType.objects.get(pk=subject_type['id']))
+
     def test_get_columns(self):
         response = self.client.get(
             reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC}))
@@ -436,11 +445,11 @@ class TestSubjectApi(LoggedInWithWorkerTestCase):
 
     def test_subjects_filter_type(self):
         subject = self.study_subject
-        subject.type = SUBJECT_TYPE_CHOICES_PATIENT
+        subject.type = get_patient_subject_type()
         subject.save()
 
-        self.check_subject_filtered([["type", SUBJECT_TYPE_CHOICES_PATIENT]], [subject])
-        self.check_subject_filtered([["type", SUBJECT_TYPE_CHOICES_CONTROL]], [])
+        self.check_subject_filtered([["type", get_patient_subject_type().id]], [subject])
+        self.check_subject_filtered([["type", get_control_subject_type().id]], [])
 
     def test_subjects_filter_unknown(self):
         subject = self.study_subject
@@ -618,10 +627,10 @@ class TestSubjectApi(LoggedInWithWorkerTestCase):
 
     def test_subjects_ordered_by_type(self):
         subject = self.study_subject
-        subject.type = SUBJECT_TYPE_CHOICES_CONTROL
+        subject.type = get_control_subject_type()
         subject.save()
         subject2 = create_study_subject(2)
-        subject2.type = SUBJECT_TYPE_CHOICES_PATIENT
+        subject2.type = get_patient_subject_type()
         subject2.save()
 
         self.check_subject_ordered("type", [subject, subject2])
diff --git a/smash/web/tests/data/import_type.csv b/smash/web/tests/data/import_type.csv
new file mode 100644
index 0000000000000000000000000000000000000000..49808d887f40c23e7ef7ee75250c81034da591d5
--- /dev/null
+++ b/smash/web/tests/data/import_type.csv
@@ -0,0 +1,5 @@
+first_name,last_name,participant_id,type
+Piotr,Gawron,Cov-000001,PATIENT
+Piotr,Gawron,Cov-000002,CONTROL
+Piotr,Gawron,Cov-000003,
+Piotr,Gawron,Cov-000004,UNKNOWN
diff --git a/smash/web/tests/forms/test_StudySubjectAddForm.py b/smash/web/tests/forms/test_StudySubjectAddForm.py
index 58cecf19c55a5ae1a6e96c0323bbc4cbe07c143e..cd810037c59ed9e55a9c08a397aaf2537c00b449 100644
--- a/smash/web/tests/forms/test_StudySubjectAddForm.py
+++ b/smash/web/tests/forms/test_StudySubjectAddForm.py
@@ -1,17 +1,15 @@
-import datetime
 import logging
 
-from django.core.files.uploadedfile import SimpleUploadedFile
 from parameterized import parameterized
 
 from web.forms import StudySubjectAddForm
 from web.forms.study_subject_forms import get_new_screening_number, get_study_subject_field_id
-from web.models.constants import SUBJECT_TYPE_CHOICES_CONTROL, CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, \
-    CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, \
-    CUSTOM_FIELD_TYPE_FILE
+from web.models.constants import CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, \
+    CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST
 from web.models.custom_data import CustomStudySubjectField, CustomStudySubjectValue
 from web.tests import LoggedInWithWorkerTestCase
-from web.tests.functions import create_study_subject, create_subject, get_test_study, create_empty_study
+from web.tests.functions import create_study_subject, create_subject, get_test_study, create_empty_study, \
+    get_control_subject_type
 
 logger = logging.getLogger(__name__)
 
@@ -20,11 +18,11 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
     def setUp(self):
         super().setUp()
 
-        location = self.worker.locations.all()[0]
+        location = self.worker.locations.all().first()
         self.subject = create_subject()
         self.study = get_test_study()
         self.sample_data = {
-            'type': SUBJECT_TYPE_CHOICES_CONTROL,
+            'type': get_control_subject_type().id,
             'default_location': location.id,
             'screening_number': "123",
             'subject': self.subject.id,
diff --git a/smash/web/tests/forms/test_subject_forms.py b/smash/web/tests/forms/test_subject_forms.py
index 9b45ee08278d984c84868700a1ac987e9e14084a..721a7787225c93dfd7a589b60841261a9c0cbb44 100644
--- a/smash/web/tests/forms/test_subject_forms.py
+++ b/smash/web/tests/forms/test_subject_forms.py
@@ -7,7 +7,7 @@ from web.tests.functions import create_subject
 logger = logging.getLogger(__name__)
 
 
-class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
+class SubjectFormsTests(LoggedInWithWorkerTestCase):
     def setUp(self):
         super().setUp()
         self.subject = create_subject()
diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py
index a4831422fe449fcf1ae7f111f790c096bd42f64f..998e99ed21ca5df45ffdee51c27bd1fec400f00a 100644
--- a/smash/web/tests/functions.py
+++ b/smash/web/tests/functions.py
@@ -9,9 +9,9 @@ from django.utils.timezone import make_aware, is_aware
 from web.models import Location, AppointmentType, StudySubject, Worker, Visit, Appointment, ConfigurationItem, \
     Language, ContactAttempt, FlyingTeam, Availability, Subject, Study, StudyColumns, StudyNotificationParameters, \
     VoucherType, VoucherTypePrice, Voucher, Room, Item, WorkerStudyRole, StudyRedCapColumns, EtlColumnMapping, \
-    SubjectImportData
+    SubjectImportData, SubjectType
 from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, REDCAP_BASE_URL_CONFIGURATION_TYPE, \
-    SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL, CONTACT_TYPES_PHONE, \
+    SEX_CHOICES_MALE, CONTACT_TYPES_PHONE, \
     MONDAY_AS_DAY_OF_WEEK, COUNTRY_AFGHANISTAN_ID, VOUCHER_STATUS_NEW, GLOBAL_STUDY_ID, DEFAULT_LOCALE_NAME
 from web.models.worker_study_role import ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER
 from web.redcap_connector import RedcapSubject
@@ -197,7 +197,7 @@ def create_study_subject(subject_id: int = 1, subject: Subject = None, nd_number
         subject = create_subject()
     study_subject = StudySubject.objects.create(
         default_location=get_test_location(),
-        type=SUBJECT_TYPE_CHOICES_CONTROL,
+        type=get_control_subject_type(),
         screening_number="piotr's number" + str(subject_id),
         study=study,
         subject=subject
@@ -209,6 +209,14 @@ def create_study_subject(subject_id: int = 1, subject: Subject = None, nd_number
     return study_subject
 
 
+def get_control_subject_type() -> SubjectType:
+    return SubjectType.objects.filter(name='CONTROL').first()
+
+
+def get_patient_subject_type() -> SubjectType:
+    return SubjectType.objects.filter(name='PATIENT').first()
+
+
 def create_study_subject_with_multiple_screening_numbers(subject_id=1, subject=None, screening_number=None):
     if subject is None:
         subject = create_subject()
@@ -217,7 +225,7 @@ def create_study_subject_with_multiple_screening_numbers(subject_id=1, subject=N
         screening_number = 'E-00{}; L-00{}'.format(subject_id, subject_id)
     return StudySubject.objects.create(
         default_location=get_test_location(),
-        type=SUBJECT_TYPE_CHOICES_CONTROL,
+        type=get_control_subject_type(),
         screening_number=screening_number,
         study=get_test_study(),
         subject=subject
@@ -299,7 +307,7 @@ def create_availability(worker=None, available_from=None, available_till=None, d
     return availability
 
 
-def create_visit(subject=None, datetime_begin=None, datetime_end=None):
+def create_visit(subject:StudySubject = None, datetime_begin=None, datetime_end=None) -> Visit:
     if subject is None:
         subject = create_study_subject()
     if datetime_begin is None:
diff --git a/smash/web/tests/importer/test_csv_subject_import_reader.py b/smash/web/tests/importer/test_csv_subject_import_reader.py
index 792331b8dba45373a941a6680113ad8ec1e4824d..8f524595ea65211eedc0936599e6bf07fa4a12c2 100644
--- a/smash/web/tests/importer/test_csv_subject_import_reader.py
+++ b/smash/web/tests/importer/test_csv_subject_import_reader.py
@@ -7,7 +7,8 @@ from django.test import TestCase
 from web.importer import CsvSubjectImportReader, MsgCounterHandler
 from web.models import SubjectImportData, EtlColumnMapping, StudySubject, Country
 from web.models.constants import COUNTRY_AFGHANISTAN_ID
-from web.tests.functions import get_resource_path, get_test_study, create_tns_column_mapping, create_location
+from web.tests.functions import get_resource_path, get_test_study, create_tns_column_mapping, create_location, \
+    get_control_subject_type, get_patient_subject_type
 
 logger = logging.getLogger(__name__)
 
@@ -54,15 +55,22 @@ class TestCsvReader(TestCase):
     def test_load_language(self):
         self.subject_import_data.filename = get_resource_path('import_language.csv')
         study_subjects = CsvSubjectImportReader(self.subject_import_data).load_data()
-        for study_subject in study_subjects:
-            study_subject.subject.save()
-            study_subject.save()
 
         self.assertEqual(3, len(study_subjects))
         self.assertIsNotNone(study_subjects[0].subject.default_written_communication_language)
         self.assertIsNotNone(study_subjects[1].subject.default_written_communication_language)
         self.assertIsNone(study_subjects[2].subject.default_written_communication_language)
 
+    def test_load_type(self):
+        self.subject_import_data.filename = get_resource_path('import_type.csv')
+        study_subjects = CsvSubjectImportReader(self.subject_import_data).load_data()
+
+        self.assertEqual(4, len(study_subjects))
+        self.assertEqual(get_patient_subject_type(), study_subjects[0].type)
+        self.assertEqual(get_control_subject_type(), study_subjects[1].type)
+        self.assertIsNotNone(study_subjects[2].type)
+        self.assertIsNotNone(study_subjects[3].type)
+
     def test_load_data_for_tns(self):
         self.subject_import_data = SubjectImportData.objects.create(study=get_test_study(),
                                                                     date_format="%d/%m/%Y",
diff --git a/smash/web/tests/models/test_mail_template.py b/smash/web/tests/models/test_mail_template.py
index e42ca32e9582a2444bd5b51197981e77b7449d35..906e32757e7d2cb8c4ce567e5c05332363702af7 100644
--- a/smash/web/tests/models/test_mail_template.py
+++ b/smash/web/tests/models/test_mail_template.py
@@ -111,7 +111,7 @@ class MailTemplateModelTests(TestCase):
         worker_name = str(self.user.worker)
 
         self.check_doc_contains(doc, [worker_name, str(subject), str(subject.subject.country), subject.nd_number,
-                                      subject.get_type_display()])
+                                      subject.type.name])
 
     def test_apply_voucher(self):
         template_name_french = "test_without_language"
@@ -227,7 +227,7 @@ class MailTemplateModelTests(TestCase):
 
         self.check_doc_contains(doc, [worker_name, str(visit.subject), str(visit.subject.subject.country),
                                       visit.subject.nd_number,
-                                      visit.subject.get_type_display(),
+                                      visit.subject.type.name,
                                       visit.datetime_begin.strftime(DATE_FORMAT_SHORT),
                                       visit.datetime_end.strftime(DATE_FORMAT_SHORT)])
 
diff --git a/smash/web/tests/models/test_visit.py b/smash/web/tests/models/test_visit.py
index e806bc6df7f8be39061b3d30ca2162a4ecd9921e..63fa0c6be53aad9b4d16b390a515ca3c4720f26b 100644
--- a/smash/web/tests/models/test_visit.py
+++ b/smash/web/tests/models/test_visit.py
@@ -5,12 +5,13 @@ from dateutil.relativedelta import relativedelta
 from django.test import TestCase
 
 from web.models import Visit, Study
-from web.models.constants import SUBJECT_TYPE_CHOICES_PATIENT, GLOBAL_STUDY_ID
-from web.tests.functions import create_study_subject, create_visit
+from web.models.constants import GLOBAL_STUDY_ID
+from web.tests.functions import create_study_subject, create_visit, get_patient_subject_type
 from web.utils import get_today_midnight_date
 
 logger = logging.getLogger(__name__)
 
+
 class VisitModelTests(TestCase):
     def test_so_called_no_concurrency(self):
         subject = create_study_subject()
@@ -194,13 +195,13 @@ class VisitModelTests(TestCase):
 
     def test_mark_as_finished_for_follow_up_visit(self):
         subject = create_study_subject()
-        subject.type = SUBJECT_TYPE_CHOICES_PATIENT
+        subject.type = get_patient_subject_type()
         subject.save()
         visit = create_visit(subject)
 
         visit.mark_as_finished()
 
-        visit_number=2
+        visit_number = 2
         follow_up_visit = Visit.objects.filter(subject=subject).filter(visit_number=visit_number)[0]
         follow_up_visit.datetime_begin = visit.datetime_begin + datetime.timedelta(days=133)
         follow_up_visit.datetime_end = visit.datetime_begin + datetime.timedelta(days=170)
@@ -211,16 +212,17 @@ class VisitModelTests(TestCase):
         visit_count = Visit.objects.filter(subject=subject).count()
         self.assertEqual(3, visit_count)
 
-        visit_number=3
+        visit_number = 3
         new_follow_up = Visit.objects.filter(subject=subject).filter(visit_number=visit_number)[0]
 
         # check if follow up date is based on the first visit date
-        study = visit.subject.study
-        args = {study.default_delta_time_for_follow_up_units: study.default_delta_time_for_patient_follow_up} #patient
+        subject_type = get_patient_subject_type()
+        args = {subject_type.follow_up_delta_units: subject_type.follow_up_delta_time}  # patient
 
-        time_to_next_visit = relativedelta(**args) * (visit_number - 1) #calculated from first visit
+        time_to_next_visit = relativedelta(**args) * (visit_number - 1)  # calculated from first visit
 
-        self.assertTrue(visit.datetime_begin + time_to_next_visit - datetime.timedelta(days=1) < new_follow_up.datetime_begin)
+        self.assertTrue(
+            visit.datetime_begin + time_to_next_visit - datetime.timedelta(days=1) < new_follow_up.datetime_begin)
 
     def test_visit_to_string(self):
         visit = create_visit(create_study_subject())
diff --git a/smash/web/tests/test_statistics.py b/smash/web/tests/test_statistics.py
index 3c36dcf7fdbd483492f8adacaa51c6e3bbd3e22b..f48ead719afde2fe3eba98391c5e17c1101be3d6 100644
--- a/smash/web/tests/test_statistics.py
+++ b/smash/web/tests/test_statistics.py
@@ -5,7 +5,8 @@ from django.test import TestCase
 
 from web.models import Visit, AppointmentTypeLink
 from web.statistics import get_previous_year_and_month_for_date, StatisticsManager
-from web.tests.functions import create_appointment, create_appointment_type
+from web.tests.functions import create_appointment, create_appointment_type, get_control_subject_type, \
+    get_patient_subject_type
 from web.views.notifications import get_today_midnight_date
 
 __author__ = 'Valentin Grouès'
@@ -51,18 +52,22 @@ class TestStatistics(TestCase):
         self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ['Scheduled'])
 
     def test_get_statistics_for_month_one_appointment_subject_type(self):
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, subject_type="C")
+        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
+                                                                      subject_type=get_control_subject_type())
         self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ['Scheduled'])
 
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, subject_type="P")
+        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
+                                                                      subject_type=get_patient_subject_type())
         self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ['Scheduled'])
 
     def test_get_statistics_for_month_one_appointment_subject_type_and_visit(self):
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, subject_type="C",
+        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
+                                                                      subject_type=get_control_subject_type(),
                                                                       visit='1')
         self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ['Scheduled'])
 
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, subject_type="P",
+        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
+                                                                      subject_type=get_patient_subject_type(),
                                                                       visit='1')
         self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ['Scheduled'])
 
diff --git a/smash/web/tests/view/test_privacy_notice.py b/smash/web/tests/view/test_privacy_notice.py
index 609d46b55efc863445b80abf758a195bb08642aa..abd22b6f76691f000b371de4a42ff8f1c3b21cd8 100644
--- a/smash/web/tests/view/test_privacy_notice.py
+++ b/smash/web/tests/view/test_privacy_notice.py
@@ -1,19 +1,23 @@
-from web.tests.functions import get_resource_path, get_test_study
-from web.models import PrivacyNotice, Study, Worker
-from web.forms import PrivacyNoticeForm, WorkerAcceptPrivacyNoticeForm
-from web.tests import LoggedInTestCase
-from django.urls import reverse
-from django.core.files.uploadedfile import SimpleUploadedFile
+import os
+
 from django.contrib.messages import get_messages
+from django.core.files.uploadedfile import SimpleUploadedFile
+from django.urls import reverse
+
+from web.forms import PrivacyNoticeForm, WorkerAcceptPrivacyNoticeForm
+from web.models import PrivacyNotice, Study, Worker
 from web.models.constants import GLOBAL_STUDY_ID
+from web.tests import LoggedInTestCase
+
 
 class PrivacyNoticeTests(LoggedInTestCase):
-    def test_add_privacy_notice(self):
+    def setUp(self):
+        super().setUp()
         self.assertEqual(0, PrivacyNotice.objects.count())
         self.login_as_admin()
 
         form_data = dict(
-            name='example', 
+            name='example',
             summary='example summary'
         )
 
@@ -29,8 +33,13 @@ class PrivacyNoticeTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 302)
         self.assertEqual(1, PrivacyNotice.objects.count())
 
+    def tearDown(self):
+        for privacy_notice in PrivacyNotice.objects.all():
+            path = privacy_notice.document.path
+            privacy_notice.delete()
+            self.assertFalse(os.path.isfile(path))
+
     def test_edit_privacy_notice(self):
-        self.test_add_privacy_notice()
         self.assertEqual(1, PrivacyNotice.objects.count())
         pn = PrivacyNotice.objects.all()[0]
         form_data = dict(
@@ -52,19 +61,16 @@ class PrivacyNoticeTests(LoggedInTestCase):
         self.assertEqual(pn.name, 'example2')
 
     def test_delete_privacy_notice(self):
-        self.test_add_privacy_notice()
         self.assertEqual(1, PrivacyNotice.objects.count())
         pn = PrivacyNotice.objects.all()[0]
         page = reverse('web.views.privacy_notice_delete', kwargs={'pk': pn.id})
         response = self.client.post(page)
         self.assertEqual(response.status_code, 302)
         self.assertEqual(0, PrivacyNotice.objects.count())
-        
+
     def test_privacy_notice_middleware_superuser(self):
-        self.test_add_privacy_notice()
-        
         self.login_as_admin()
-        #assign privacy notice
+        # assign privacy notice
         pn = PrivacyNotice.objects.all()[0]
         study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
         study.acceptance_of_study_privacy_notice_required = True
@@ -82,12 +88,10 @@ class PrivacyNoticeTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 200)
         messages = list(get_messages(response.wsgi_request))
         self.assertEqual(len(messages), 0)
-    
+
     def test_privacy_notice_middleware(self):
-        self.test_add_privacy_notice()
-        
         self.login_as_admin()
-        #assign privacy notice
+        # assign privacy notice
         pn = PrivacyNotice.objects.all()[0]
         study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
         study.acceptance_of_study_privacy_notice_required = True
@@ -106,7 +110,7 @@ class PrivacyNoticeTests(LoggedInTestCase):
         messages = list(get_messages(response.wsgi_request))
         self.assertEqual(len(messages), 1)
         self.assertEqual(str(messages[0]), "You can't use the system until you accept the privacy notice.")
-        #accept privacy notice
+        # accept privacy notice
         form_data = dict(privacy_notice_accepted=True)
         form = WorkerAcceptPrivacyNoticeForm(form_data)
         self.assertTrue(form.is_valid())
@@ -115,7 +119,7 @@ class PrivacyNoticeTests(LoggedInTestCase):
         self.assertEqual(response.status_code, 302)
         messages = [m.message for m in get_messages(response.wsgi_request)]
         self.assertIn("Privacy notice accepted", messages)
-        #check acceptance
+        # check acceptance
         worker = Worker.objects.filter(id=self.staff_worker.id).first()
         self.assertEqual(worker.privacy_notice_accepted, True)
         page = reverse('web.views.appointments')
diff --git a/smash/web/tests/view/test_subject_types.py b/smash/web/tests/view/test_subject_types.py
new file mode 100644
index 0000000000000000000000000000000000000000..615fb4ee690e24fc96b8c77a228d3966cd083207
--- /dev/null
+++ b/smash/web/tests/view/test_subject_types.py
@@ -0,0 +1,96 @@
+import logging
+
+from django.db import models
+from django.forms import ModelForm
+from django.urls import reverse
+
+from web.forms.subject_type_forms import SubjectTypeAddForm, SubjectTypeEditForm
+from web.models import SubjectType, Study
+from web.models.constants import GLOBAL_STUDY_ID
+from web.models.study import FOLLOW_UP_INCREMENT_IN_YEARS
+from web.tests import LoggedInTestCase
+from web.tests.functions import format_form_field, get_patient_subject_type
+
+logger = logging.getLogger(__name__)
+
+
+class SubjectTypeTests(LoggedInTestCase):
+    def test_subject_type_requests(self):
+        self.login_as_admin()
+        pages = [
+            'web.views.subject_types',
+            'web.views.subject_type_add',
+        ]
+
+        for page in pages:
+            response = self.client.get(get_url(page))
+            self.assertEqual(response.status_code, 200)
+
+    def test_subject_type_requests_without_permission(self):
+        pages = [
+            'web.views.subject_types',
+            'web.views.subject_type_add',
+        ]
+
+        for page in pages:
+            response = self.client.get(get_url(page))
+            self.assertEqual(response.status_code, 302)
+
+    def test_subject_type_add(self):
+        self.login_as_admin()
+        page = get_url('web.views.subject_type_add')
+
+        form_data = create_form_with_init_data(SubjectTypeAddForm)
+        form_data['name'] = 'bla'
+
+        response = self.client.post(page, form_data)
+        self.assertEqual(response.status_code, 302)
+
+        freshly_created = SubjectType.objects.filter(name=form_data['name'])
+        self.assertEqual(len(freshly_created), 1)
+
+    def test_subject_type_edit(self):
+        self.login_as_admin()
+        patient_type = get_patient_subject_type()
+        page = get_url('web.views.subject_type_edit', patient_type)
+        form_data = create_form_with_init_data(SubjectTypeEditForm, patient_type)
+        form_data['name'] = 'bla'
+
+        response = self.client.post(page, form_data)
+        self.assertEqual(response.status_code, 302)
+
+        freshly_edited = SubjectType.objects.get(id=patient_type.id)
+        self.assertEqual(freshly_edited.name, form_data["name"])
+
+    def test_subject_type_edit_request(self):
+        self.login_as_admin()
+        page = get_url('web.views.subject_type_edit', get_patient_subject_type())
+        response = self.client.get(page)
+        self.assertEqual(response.status_code, 200)
+
+
+def get_url(view_name: str, subject_type: SubjectType = None):
+    if subject_type is None:
+        return reverse(view_name, kwargs={
+            'study_id': str(GLOBAL_STUDY_ID),
+        })
+    else:
+        return reverse(view_name, kwargs={
+            'subject_type_id': str(subject_type.id),
+            'study_id': str(subject_type.study.id),
+        })
+
+
+def create_form_with_init_data(form_type: [ModelForm], instance: models.Model = None):
+    if instance is None:
+        form = form_type(instance=instance, study=Study.objects.get(pk=GLOBAL_STUDY_ID))
+    else:
+        form = form_type(instance=instance)
+    form_data = {'name': 'abc',
+                 'screening_number_prefix': 'a',
+                 'follow_up_delta_time': '1',
+                 'follow_up_delta_units': FOLLOW_UP_INCREMENT_IN_YEARS
+                 }
+    for key, value in list(form.initial.items()):
+        form_data[key] = format_form_field(value)
+    return form_data
diff --git a/smash/web/tests/view/test_subjects.py b/smash/web/tests/view/test_subjects.py
index c7cf50653eb0743465fdb64b200cb2737594a94f..3575faaf1571a7dbc39e23d735c1b93ff25e16db 100644
--- a/smash/web/tests/view/test_subjects.py
+++ b/smash/web/tests/view/test_subjects.py
@@ -7,13 +7,14 @@ from django.urls import reverse
 
 from web.forms import SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm
 from web.models import MailTemplate, StudySubject, StudyColumns, Visit, Provenance, Subject
-from web.models.constants import SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL, SUBJECT_TYPE_CHOICES_PATIENT, \
-    COUNTRY_AFGHANISTAN_ID, COUNTRY_OTHER_ID, MAIL_TEMPLATE_CONTEXT_SUBJECT, CUSTOM_FIELD_TYPE_FILE
+from web.models.constants import SEX_CHOICES_MALE, COUNTRY_AFGHANISTAN_ID, COUNTRY_OTHER_ID, \
+    MAIL_TEMPLATE_CONTEXT_SUBJECT, CUSTOM_FIELD_TYPE_FILE
 from web.models.custom_data import CustomStudySubjectField
 from web.models.custom_data.custom_study_subject_field import get_study_subject_field_id
 from web.tests import LoggedInWithWorkerTestCase
 from web.tests.functions import create_study_subject, create_visit, create_appointment, get_test_location, \
-    create_language, get_resource_path, get_test_study, format_form_field
+    create_language, get_resource_path, get_test_study, format_form_field, get_patient_subject_type, \
+    get_control_subject_type
 from web.views.notifications import get_today_midnight_date
 
 logger = logging.getLogger(__name__)
@@ -202,7 +203,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         print(form.errors)
         self.assertTrue(form.is_valid())
 
-        form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL
+        form_data["study_subject-type"] = get_control_subject_type().id
         response = self.client.post(reverse('web.views.subject_add', kwargs={'study_id': self.study.id}),
                                     data=form_data)
         self.assertEqual(response.status_code, 302)
@@ -221,7 +222,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
 
         form_data = self.create_add_form_data_for_study_subject()
 
-        form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_CONTROL
+        form_data["study_subject-type"] = get_control_subject_type().id
         form_data["study_subject-referral_letter"] = SimpleUploadedFile("file.txt", b"file_content")
 
         form = SubjectAddForm(data=form_data, prefix="subject")
@@ -252,7 +253,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         form_data["subject-first_name"] = "John"
         form_data["subject-last_name"] = "Doe"
         form_data["subject-sex"] = SEX_CHOICES_MALE
-        form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_PATIENT
+        form_data["study_subject-type"] = get_patient_subject_type().id
         form_data["study_subject-subject"] = self.study_subject.id
         form_data["study_subject-postponed"] = False
 
@@ -297,7 +298,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         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-type"] = get_control_subject_type().id
 
         form_data["subject-country"] = COUNTRY_OTHER_ID
         response = self.client.post(reverse('web.views.subject_add', kwargs={'study_id': self.study.id}),
@@ -309,7 +310,6 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         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
 
         self.add_valid_form_data_for_subject_add(form_data)
         location = get_test_location()
@@ -348,7 +348,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         form_data = self.create_edit_form_data_for_study_subject(self.study_subject)
 
         count = Provenance.objects.all().count()
-        form_data["study_subject-type"] = SUBJECT_TYPE_CHOICES_PATIENT
+        form_data["study_subject-type"] = get_patient_subject_type().id
         response = self.client.post(
             reverse('web.views.subject_edit', kwargs={'id': self.study_subject.id}), data=form_data)
 
diff --git a/smash/web/tests/view/test_visit.py b/smash/web/tests/view/test_visit.py
index db6c99eeb861bea4d2df12660b9c74d08aa99043..2ac2e39529d29c4cf29d662cddf40db3eb71c8ca 100644
--- a/smash/web/tests/view/test_visit.py
+++ b/smash/web/tests/view/test_visit.py
@@ -210,9 +210,8 @@ class VisitViewTests(LoggedInTestCase):
 
     def test_mark_as_finished_with_study_no_follow_up_rule(self):
         visit = create_visit()
-        study = visit.subject.study
-        study.auto_create_follow_up = False
-        study.save()
+        visit.subject.type.auto_create_follow_up = False
+        visit.subject.type.save()
 
         self.assertFalse(visit.is_finished)
 
diff --git a/smash/web/urls.py b/smash/web/urls.py
index 79dbc6f4feefb3d7535a7584845fe7ed5b39cc4d..504354c8e5ca9e83c915895a615451f05f6011ed 100644
--- a/smash/web/urls.py
+++ b/smash/web/urls.py
@@ -20,6 +20,7 @@ from django.contrib.auth.views import LogoutView
 from django.views.defaults import page_not_found
 
 from web import views
+from web.views import subject_types
 from web.views.daily_planning import TemplateDailyPlannerView
 
 urlpatterns = [
@@ -169,6 +170,16 @@ urlpatterns = [
     url(r'^equipment_and_rooms/rooms/delete/(?P<room_id>\d+)$', views.rooms.rooms_delete,
         name='web.views.equipment_and_rooms.rooms_delete'),
 
+    url(r'^study/(?P<study_id>\d+)/subject_types/(?P<subject_type_id>\d+)$', subject_types.subject_type_edit,
+        name='web.views.subject_type_edit'),
+    url(r'^study/(?P<study_id>\d+)/subject_types/add$', subject_types.subject_type_add,
+        name='web.views.subject_type_add'),
+    url(r'^study/(?P<study_id>\d+)/subject_types$', subject_types.subject_types,
+        name='web.views.subject_types'),
+    url(r'^study/(?P<study_id>\d+)/subject_types/(?P<subject_type_id>\d+)/delete$', subject_types.subject_type_delete,
+        name='web.views.subject_type_delete'),
+
+
     ####################
     #  PRIVACY NOTICE  #
     ####################
diff --git a/smash/web/views/privacy_notice.py b/smash/web/views/privacy_notice.py
index dc44ade1a75b40d399528b4d13bedff39e38125c..6174e8384ab906170a6389ff9db4c5841ce3a83d 100644
--- a/smash/web/views/privacy_notice.py
+++ b/smash/web/views/privacy_notice.py
@@ -1,5 +1,6 @@
 # coding=utf-8
 import io
+import logging
 from wsgiref.util import FileWrapper
 
 from django.contrib import messages
@@ -16,6 +17,7 @@ from ..forms.privacy_notice import PrivacyNoticeForm
 from ..forms.worker_form import WorkerAcceptPrivacyNoticeForm
 from ..models import PrivacyNotice, Worker
 
+logger = logging.getLogger(__name__)
 
 class PrivacyNoticeListView(ListView, WrappedView):
     model = PrivacyNotice
@@ -34,7 +36,8 @@ def privacy_notice_add(request):
         if form.is_valid():
             try:
                 form.save()
-            except:
+            except Exception as e:
+                logger.error('Error at %s', 'division', exc_info=e)
                 messages.add_message(request, messages.ERROR, 'There was a problem when saving privacy notice. '
                                                               'Contact system administrator.')
             return redirect('web.views.privacy_notices')
@@ -53,8 +56,9 @@ def privacy_notice_edit(request, pk):
             try:
                 form.save()
                 return redirect('web.views.privacy_notices')
-            except:
-                messages.add_message(request, messages.ERROR, 'There was a problem when updating the privacy notice.'
+            except Exception as e:
+                logger.error('Error at %s', 'division', exc_info=e)
+                messages.add_message(request, messages.ERROR, 'There was a problem when updating the privacy notice. '
                                                               'Contact system administrator.')
                 return wrap_response(request, 'privacy_notice/edit.html',
                                      {'form': form, 'privacy_notice': privacy_notice})
@@ -96,7 +100,8 @@ def privacy_notice_accept(request, pk):
                     return redirect('web.views.appointments')
                 else:
                     return redirect('logout')
-            except BaseException:
+            except BaseException as e:
+                logger.error('Error at %s', 'division', exc_info=e)
                 messages.add_message(request, messages.ERROR, 'There was a problem when updating the privacy notice.'
                                                               'Contact system administrator.')
                 return wrap_response(request, 'privacy_notice/acceptance_study_privacy_notice.html',
diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py
index 8151f21cab8a581af671f62ddb2a48c9a52e99cc..c18427277f16e5d140fc72c558159917ffdf4e38 100644
--- a/smash/web/views/subject.py
+++ b/smash/web/views/subject.py
@@ -14,7 +14,7 @@ from . import WrappedView
 from django.urls import reverse_lazy
 from ..forms import VisitDetailForm, SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm
 from ..models import StudySubject, MailTemplate, Worker, Study, Provenance, Subject
-from ..models.constants import GLOBAL_STUDY_ID, SUBJECT_TYPE_CHOICES, FILE_STORAGE
+from ..models.constants import GLOBAL_STUDY_ID, FILE_STORAGE
 from ..models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \
     SUBJECT_LIST_VOUCHER_EXPIRY, SUBJECT_LIST_CHOICES
 
@@ -59,7 +59,8 @@ def subject_add(request, study_id):
     return wrap_response(request, 'subjects/add.html',
                          {'study_subject_form': study_subject_form, 'subject_form': subject_form})
 
-#delete subject (from all studies!)
+
+# delete subject (from all studies!)
 class SubjectDeleteView(DeleteView, WrappedView):
     model = Subject
     success_url = reverse_lazy('web.views.subjects')
@@ -83,7 +84,8 @@ class SubjectDeleteView(DeleteView, WrappedView):
                                                           'Contact system administrator.')
         return redirect('web.views.subjects')
 
-#delete subject from study
+
+# delete subject from study
 class StudySubjectDeleteView(DeleteView, WrappedView):
     model = StudySubject
     success_url = reverse_lazy('web.views.subjects')
@@ -107,6 +109,7 @@ class StudySubjectDeleteView(DeleteView, WrappedView):
                                                           'Contact system administrator.')
         return redirect('web.views.subjects')
 
+
 def subject_no_visits(request):
     return subject_list(request, SUBJECT_LIST_NO_VISIT)
 
@@ -140,49 +143,55 @@ def subject_edit(request, id):
 
             persist_custom_file_fields(request, study_subject)
 
-            # check if subject was marked as dead or resigned
             if 'type' in study_subject_form.changed_data and old_type != study_subject_form.cleaned_data['type']:
                 worker = Worker.get_by_user(request.user)
-                old_value = SUBJECT_TYPE_CHOICES.get(old_type, old_type)
-                new_value = SUBJECT_TYPE_CHOICES.get(study_subject_form.cleaned_data['type'], study_subject_form.cleaned_data['type'])
-                p = Provenance(modified_table = StudySubject._meta.db_table,
-                                modified_table_id = study_subject.id,
-                                modification_author = worker,
-                                previous_value = old_type,
-                                new_value = study_subject_form.cleaned_data['type'],
-                                modification_description = 'Worker "{}" changed study subject "{}" from "{}" to "{}"'.format(worker, 
-                                                study_subject.subject, old_value, new_value),
-                                modified_field = 'type',
-                                request_path=request.path,
-                                request_ip_addr=ip
-                                )
+                old_value = old_type.name
+                new_value = None
+                if study_subject_form.cleaned_data['type'] is not None:
+                    new_value = study_subject_form.cleaned_data['type'].name
+                p = Provenance(modified_table=StudySubject._meta.db_table,
+                               modified_table_id=study_subject.id,
+                               modification_author=worker,
+                               previous_value=old_type,
+                               new_value=study_subject_form.cleaned_data['type'],
+                               modification_description='Worker "{}" changed study subject "{}" from "{}" to "{}"'
+                               .format(
+                                   worker,
+                                   study_subject.subject, old_value, new_value),
+                               modified_field='type',
+                               request_path=request.path,
+                               request_ip_addr=ip
+                               )
                 p.save()
+            # check if subject was marked as dead or resigned
             if subject_form.cleaned_data['dead'] and not was_dead:
                 worker = Worker.get_by_user(request.user)
-                p = Provenance(modified_table = Subject._meta.db_table,
-                                modified_table_id = study_subject.subject.id,
-                                modification_author = worker,
-                                previous_value = was_dead,
-                                new_value = True,
-                                modification_description = 'Worker "{}" marks subject "{}" as dead'.format(worker, study_subject.subject),
-                                modified_field = 'dead',
-                                request_path=request.path,
-                                request_ip_addr=ip
-                                )
+                p = Provenance(modified_table=Subject._meta.db_table,
+                               modified_table_id=study_subject.subject.id,
+                               modification_author=worker,
+                               previous_value=was_dead,
+                               new_value=True,
+                               modification_description='Worker "{}" marks subject "{}" as dead'.format(worker,
+                                                                                                        study_subject.subject),
+                               modified_field='dead',
+                               request_path=request.path,
+                               request_ip_addr=ip
+                               )
                 study_subject.subject.mark_as_dead()
                 p.save()
             if study_subject_form.cleaned_data['resigned'] and not was_resigned:
                 worker = Worker.get_by_user(request.user)
-                p = Provenance(modified_table = StudySubject._meta.db_table,
-                                modified_table_id = study_subject.id,
-                                modification_author = worker,
-                                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',
-                                request_path=request.path,
-                                request_ip_addr=ip
-                                )
+                p = Provenance(modified_table=StudySubject._meta.db_table,
+                               modified_table_id=study_subject.id,
+                               modification_author=worker,
+                               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',
+                               request_path=request.path,
+                               request_ip_addr=ip
+                               )
                 study_subject.mark_as_resigned()
                 p.save()
             messages.success(request, "Modifications saved")
diff --git a/smash/web/views/subject_types.py b/smash/web/views/subject_types.py
new file mode 100644
index 0000000000000000000000000000000000000000..fe018aed612f107de3e053cec8d119fc835f3c93
--- /dev/null
+++ b/smash/web/views/subject_types.py
@@ -0,0 +1,63 @@
+# coding=utf-8
+from django.contrib import messages
+from django.http import HttpRequest
+from django.shortcuts import redirect, get_object_or_404
+
+from web.decorators import PermissionDecorator
+from . import wrap_response
+from ..forms.subject_type_forms import SubjectTypeAddForm, SubjectTypeEditForm
+from ..models import SubjectType, Study, StudySubject
+
+
+@PermissionDecorator('change_subjecttype')
+def subject_types(request: HttpRequest, study_id: int):
+    subject_type_list = SubjectType.objects.filter(study_id=study_id).order_by('-name')
+    data = []
+    for subject_type in subject_type_list:
+        data.append({"id": subject_type.id, "name": subject_type.name,
+                     "subject_count": StudySubject.objects.filter(type=subject_type).count()})
+
+    context = {
+        'subject_type_list': data
+    }
+
+    return wrap_response(request, "subject_types/list.html", context)
+
+
+@PermissionDecorator('change_subjecttype')
+def subject_type_add(request: HttpRequest, study_id: int):
+    study = get_object_or_404(Study, id=study_id)
+    if request.method == 'POST':
+        form = SubjectTypeAddForm(request.POST, study=study)
+        if form.is_valid():
+            form.save()
+            return redirect('web.views.subject_types', study_id=study_id)
+    else:
+        form = SubjectTypeAddForm(study=study)
+
+    return wrap_response(request, 'subject_types/add.html', {'form': form})
+
+
+@PermissionDecorator('change_subjecttype')
+def subject_type_edit(request: HttpRequest, study_id: int, subject_type_id: int):
+    subject_type = get_object_or_404(SubjectType, id=subject_type_id, study_id=study_id)
+    if request.method == 'POST':
+        form = SubjectTypeEditForm(request.POST, instance=subject_type)
+        if form.is_valid():
+            form.save()
+            return redirect('web.views.subject_types', study_id=study_id)
+    else:
+        form = SubjectTypeEditForm(instance=subject_type)
+
+    return wrap_response(request, 'subject_types/edit.html', {'form': form})
+
+
+@PermissionDecorator('change_subjecttype')
+def subject_type_delete(request: HttpRequest, study_id: int, subject_type_id: int):
+    subject_type = get_object_or_404(SubjectType, id=subject_type_id, study_id=study_id)
+    if StudySubject.objects.filter(type=subject_type).count() > 0:
+        messages.add_message(request, messages.ERROR,
+                             'SubjectType cannot be removed - there are subjects with the type defined')
+    else:
+        subject_type.delete()
+    return redirect('web.views.subject_types', study_id=study_id)