diff --git a/smash/web/admin.py b/smash/web/admin.py
index 303aa56d09cf241230750c164e8abb6424dedea9..91037bb878f4658e2f3142ee72a6b1878ddff938 100644
--- a/smash/web/admin.py
+++ b/smash/web/admin.py
@@ -1,7 +1,7 @@
 from django.contrib import admin
 
-from models import StudySubject, Item, Room, AppointmentType, Language, Location, Worker, FlyingTeam, Availability, Holiday, \
-    Visit, Appointment
+from models import StudySubject, Item, Room, AppointmentType, Language, Location, Worker, FlyingTeam, Availability, \
+    Holiday, Visit, Appointment
 
 
 class LanguageAdmin(admin.ModelAdmin):
diff --git a/smash/web/api_urls.py b/smash/web/api_urls.py
index 93f32c2839d8ea0448c8cc88324cb2630ccb2aec..b6eb174006ab53e076b1e057e6deee497419c46c 100644
--- a/smash/web/api_urls.py
+++ b/smash/web/api_urls.py
@@ -34,6 +34,8 @@ urlpatterns = [
     url(r'^cities$', subject.cities, name='web.api.cities'),
     url(r'^referrals$', subject.referrals, name='web.api.referrals'),
     url(r'^subjects/(?P<type>[A-z]+)$', subject.subjects, name='web.api.subjects'),
+    url(r'^subjects:columns/(?P<subject_list_type>[A-z]+)$', subject.get_subject_columns,
+        name='web.api.subjects.columns'),
     url(r'^subject_types', subject.types, name='web.api.subject_types'),
 
     # locations
diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py
index 14a96d62d4423f0343255cc896301db479169dc1..331c331c50b6cecc4f17070235448222c07d7dda 100644
--- a/smash/web/api_views/appointment.py
+++ b/smash/web/api_views/appointment.py
@@ -91,7 +91,9 @@ def serialize_appointment(appointment):
         subject_string = study_subject.subject.last_name + " " + study_subject.subject.first_name
         nd_number = study_subject.nd_number
         screening_number = study_subject.screening_number
-        phone_numbers = ", ".join(filter(None, [study_subject.subject.phone_number, study_subject.subject.phone_number_2, study_subject.subject.phone_number_3]))
+        phone_numbers = ", ".join(filter(None,
+                                         [study_subject.subject.phone_number, study_subject.subject.phone_number_2,
+                                          study_subject.subject.phone_number_3]))
         appointment_types = ", ".join([unicode(type) for type in appointment.appointment_types.all()])
     else:
         title = appointment.comment
diff --git a/smash/web/api_views/daily_planning.py b/smash/web/api_views/daily_planning.py
index bd739c5c5569aabffc3ed9b05c9164e3cd9d5c07..cca2057d53c58f1e41114d90968e1026ac759a51 100644
--- a/smash/web/api_views/daily_planning.py
+++ b/smash/web/api_views/daily_planning.py
@@ -73,15 +73,13 @@ def remove_holidays(availability, worker, date):
     holidays_ending_today = Holiday.objects.filter(person=worker, datetime_end__lte=today_end,
                                                    datetime_end__gte=today_start)
     result = []
-    timestamps = []
-    timestamps.append({
+    timestamps = [{
         "time": availability.available_from,
         "type": "start"
-    })
-    timestamps.append({
+    }, {
         "time": availability.available_till,
         "type": "stop"
-    })
+    }]
 
     direction = -holidays_starting_before.count()
 
diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index c47bd91d42c9c2c5513db6ea31f31b6523072786..170367b0a5999c0cd91a42329efd0b83f08cf6ab 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -5,11 +5,12 @@ from django.db.models import Count, Case, When, Min
 from django.db.models import Q
 from django.http import JsonResponse
 
-from web.models import StudySubject, Visit, Appointment, Subject
-from web.models.constants import SUBJECT_TYPE_CHOICES
+from web.models import StudySubject, Visit, Appointment, Subject, SubjectColumns, StudyColumns, Study
+from web.models.constants import SUBJECT_TYPE_CHOICES, GLOBAL_STUDY_ID
+from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \
+    StudySubjectList
 from web.views import e500_error
 from web.views.notifications import get_subjects_with_no_visit, get_subjects_with_reminder, get_today_midnight_date
-from web.views.subject import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
 
 logger = logging.getLogger(__name__)
 
@@ -30,6 +31,59 @@ def referrals(request):
     })
 
 
+def add_column(result, name, field_name, column_list, param, columns_used_in_study=None):
+    add = True
+    if columns_used_in_study:
+        add = getattr(columns_used_in_study, field_name)
+    if add:
+        if column_list is None:
+            visible = True
+        else:
+            visible = getattr(column_list, field_name)
+        result.append({
+            "type": field_name,
+            "name": name,
+            "filter": param,
+            "visible": visible
+        })
+
+
+@login_required
+def get_subject_columns(request, subject_list_type):
+    study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+    study_subject_list = StudySubjectList.objects.filter(study=study, type=subject_list_type)
+    if len(study_subject_list) > 0:
+        subject_columns = study_subject_list[0].visible_subject_columns
+        study_subject_columns = study_subject_list[0].visible_subject_study_columns
+    else:
+        subject_columns = SubjectColumns()
+        study_subject_columns = StudyColumns()
+
+    result = []
+    add_column(result, "ND", "nd_number", study_subject_columns, "string_filter", study.columns)
+    add_column(result, "Screening", "screening_number", study_subject_columns, "string_filter", study.columns)
+    add_column(result, "First name", "first_name", subject_columns, "string_filter")
+    add_column(result, "Last name", "last_name", subject_columns, "string_filter")
+    add_column(result, "Date of birth", "date_born", subject_columns, None)
+    add_column(result, "Location", "default_location", study_subject_columns, "location_filter", study.columns)
+    add_column(result, "Deceased", "dead", subject_columns, "yes_no_filter")
+    add_column(result, "Resigned", "resigned", study_subject_columns, "yes_no_filter", study.columns)
+    add_column(result, "Postponed", "postponed", study_subject_columns, "yes_no_filter", study.columns)
+    add_column(result, "Info sent", "information_sent", study_subject_columns, "yes_no_filter", study.columns)
+    add_column(result, "Type", "type", study_subject_columns, "type_filter", study.columns)
+    add_column(result, "Edit", "edit", None, None)
+    add_column(result, "Visit 1", "visit_1", None, "visit_filter")
+    add_column(result, "Visit 2", "visit_2", None, "visit_filter")
+    add_column(result, "Visit 3", "visit_3", None, "visit_filter")
+    add_column(result, "Visit 4", "visit_4", None, "visit_filter")
+    add_column(result, "Visit 5", "visit_5", None, "visit_filter")
+    add_column(result, "Visit 6", "visit_6", None, "visit_filter")
+    add_column(result, "Visit 7", "visit_7", None, "visit_filter")
+    add_column(result, "Visit 8", "visit_8", None, "visit_filter")
+
+    return JsonResponse({"columns": result})
+
+
 @login_required
 def get_subjects(request, type):
     if type == SUBJECT_LIST_GENERIC:
diff --git a/smash/web/docx_helper.py b/smash/web/docx_helper.py
index fb78711c12cf62d7129610e3417785a2039ed796..49c2fc87105b7722a9ab341dd37f4b90fe67242b 100644
--- a/smash/web/docx_helper.py
+++ b/smash/web/docx_helper.py
@@ -1,5 +1,9 @@
+import logging
+
 from docx import Document
 
+logger = logging.getLogger(__name__)
+
 
 def process_file(path_to_docx, path_to_new_docx, changes_to_apply):
     """
diff --git a/smash/web/forms/__init__.py b/smash/web/forms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..41432b74e56c6ac250f4f182d9654a39f6f7f772
--- /dev/null
+++ b/smash/web/forms/__init__.py
@@ -0,0 +1,11 @@
+from forms import WorkerAddForm, \
+    WorkerEditForm, AppointmentDetailForm, AppointmentEditForm, AppointmentAddForm, VisitDetailForm, VisitAddForm, \
+    ContactAttemptForm, ContactAttemptEditForm, KitRequestForm, StatisticsForm, AvailabilityAddForm, \
+    AvailabilityEditForm, HolidayAddForm
+from study_subject_forms import StudySubjectAddForm, StudySubjectDetailForm, StudySubjectEditForm
+from subject_forms import SubjectAddForm, SubjectEditForm, SubjectDetailForm
+
+__all__ = [StudySubjectAddForm, StudySubjectDetailForm, StudySubjectEditForm, WorkerAddForm, WorkerEditForm,
+           AppointmentDetailForm, AppointmentEditForm, AppointmentAddForm, VisitDetailForm, VisitAddForm,
+           ContactAttemptForm, ContactAttemptEditForm, KitRequestForm, StatisticsForm, AvailabilityAddForm,
+           AvailabilityEditForm, HolidayAddForm, SubjectAddForm, SubjectEditForm, SubjectDetailForm]
diff --git a/smash/web/forms.py b/smash/web/forms/forms.py
similarity index 67%
rename from smash/web/forms.py
rename to smash/web/forms/forms.py
index ade762d5d0f7bb3bfdfd76840d90fb337bf1877c..b33eb52d2f19cde653eb450aa2732a97e241aba6 100644
--- a/smash/web/forms.py
+++ b/smash/web/forms/forms.py
@@ -6,10 +6,9 @@ from django import forms
 from django.forms import ModelForm, Form
 from django.utils.dates import MONTHS
 
-from web.models import Subject, StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, \
-    AppointmentTypeLink, \
+from web.models import StudySubject, Worker, Appointment, Visit, AppointmentType, ContactAttempt, AppointmentTypeLink, \
     Availability, Holiday
-from web.models.constants import SUBJECT_TYPE_CHOICES, SCREENING_NUMBER_PREFIXES_FOR_TYPE, COUNTRY_OTHER_ID
+from web.models.constants import SUBJECT_TYPE_CHOICES
 from web.views.notifications import get_filter_locations
 
 """
@@ -40,138 +39,14 @@ APPOINTMENT_TYPES_FIELD_POSITION = 1
 logger = logging.getLogger(__name__)
 
 
-def validate_subject_nd_number(self, cleaned_data):
-    if cleaned_data['nd_number'] != "":
-        subjects_from_db = StudySubject.objects.filter(nd_number=cleaned_data['nd_number'])
-        if subjects_from_db:
-            if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''):
-                self.add_error('nd_number', "ND number already in use")
-
-
-def validate_subject_country(self, cleaned_data):
-    if cleaned_data['country'].id == COUNTRY_OTHER_ID:
-        self.add_error('country', "Select valid country")
-
-
-def validate_subject_resign_reason(self, cleaned_data):
-    if cleaned_data['resigned'] and cleaned_data['resign_reason'] == '':
-        self.add_error('resign_reason', "Resign reason cannot be empty")
-
-
-def validate_subject_mpower_number(self, cleaned_data):
-    if cleaned_data['mpower_id'] != "":
-        subjects_from_db = StudySubject.objects.filter(mpower_id=cleaned_data['mpower_id'])
-        if subjects_from_db:
-            if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''):
-                self.add_error('mpower_id', "mPower number already in use")
-
-
-class StudySubjectAddForm(ModelForm):
-    datetime_contact_reminder = forms.DateTimeField(label="Contact on",
-                                                    widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS),
-                                                    required=False
-                                                    )
-
-    class Meta:
-        model = StudySubject
-        fields = '__all__'
-        exclude = ['resigned', 'resign_reason']
-
-    def __init__(self, *args, **kwargs):
-        user = kwargs.pop('user', None)
-        if user is None:
-            raise TypeError("User not defined")
-        self.user = Worker.get_by_user(user)
-        if self.user is None:
-            raise TypeError("Worker not defined for: " + user.username)
-
-        super(ModelForm, self).__init__(*args, **kwargs)
-        self.fields['screening_number'].required = False
-
-    def build_screening_number(self, cleaned_data):
-        screening_number = cleaned_data.get('screening_number', None)
-        if not screening_number:
-            prefix_screening_number = self.get_prefix_screening_number()
-            if prefix_screening_number is not None:
-                screening_number = get_new_screening_number(prefix_screening_number)
-        return screening_number
-
-    def clean(self):
-        cleaned_data = super(StudySubjectAddForm, self).clean()
-        screening_number = self.build_screening_number(cleaned_data)
-        if screening_number is not None:
-            cleaned_data['screening_number'] = screening_number
-            subjects_from_db = StudySubject.objects.filter(screening_number=screening_number)
-
-            if len(subjects_from_db) > 0:
-                self.add_error('screening_number', "Screening number already in use")
-        validate_subject_nd_number(self, cleaned_data)
-        validate_subject_mpower_number(self, cleaned_data)
-        return cleaned_data
-
-    def get_prefix_screening_number(self):
-        default_location = self.cleaned_data.get('default_location', None)
-        screening_number_prefix = None
-        if default_location is not None and default_location.prefix:
-            screening_number_prefix = default_location.prefix
-        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]
-        if screening_number_prefix is None:
-            return None
-        prefix_screening_number = screening_number_prefix + "-"
-        return prefix_screening_number
-
-
-def get_new_screening_number(screening_number_prefix):
-    result_number = 0
-    subjects = StudySubject.objects.filter(screening_number__contains=screening_number_prefix)
-    for subject in subjects:
-        screening_numbers = subject.screening_number.split(";")
-        for screening_number in screening_numbers:
-            screening_number = screening_number.strip()
-            if screening_number.startswith(screening_number_prefix):
-                number = screening_number[len(screening_number_prefix):]
-                try:
-                    result_number = max(result_number, int(number))
-                except ValueError:
-                    pass
-
-    return screening_number_prefix + str(result_number + 1).zfill(3)
-
-
-class StudySubjectDetailForm(ModelForm):
-    class Meta:
-        model = StudySubject
-        fields = '__all__'
-
-
-class StudySubjectEditForm(ModelForm):
-    datetime_contact_reminder = forms.DateTimeField(label="Contact on",
-                                                    widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS),
-                                                    required=False
-                                                    )
-
-    def __init__(self, *args, **kwargs):
-        was_resigned = kwargs.get('was_resigned', False)
-        if 'was_resigned' in kwargs:
-            kwargs.pop('was_resigned')
-        super(StudySubjectEditForm, self).__init__(*args, **kwargs)
-        instance = getattr(self, 'instance', None)
-        if instance and instance.id:
-            self.fields['screening_number'].widget.attrs['readonly'] = True
-        if was_resigned:
-            self.fields['resigned'].disabled = True
-
-    def clean(self):
-        validate_subject_nd_number(self, self.cleaned_data)
-        validate_subject_mpower_number(self, self.cleaned_data)
-        validate_subject_resign_reason(self, self.cleaned_data)
-
-    class Meta:
-        model = StudySubject
-        fields = '__all__'
+def get_worker_from_args(kwargs):
+    user = kwargs.pop('user', None)
+    if user is None:
+        raise TypeError("User not defined")
+    result = Worker.get_by_user(user)
+    if result is None:
+        raise TypeError("Worker not defined for: " + user.username)
+    return result
 
 
 class WorkerAddForm(ModelForm):
@@ -474,7 +349,7 @@ class AvailabilityEditForm(ModelForm):
         worker = Worker.objects.get(id=self.cleaned_data["person"].id)
         availabilities = worker.availability_set.all()
         for availability in availabilities:
-            if availability.id <> self.availability_id:
+            if availability.id != self.availability_id:
                 validate_availability_conflict(self, self.cleaned_data, availability)
 
 
@@ -483,9 +358,9 @@ def validate_availability_conflict(self, cleaned_data, availability):
     end_hour = self.cleaned_data.get("available_till", None)
     if availability.day_number == self.cleaned_data.get("day_number", None) and \
             ((start_hour <= availability.available_from < end_hour) or
-                 (start_hour < availability.available_till <= end_hour) or
-                 (availability.available_from <= start_hour < availability.available_till) or
-                 (availability.available_from < end_hour <= availability.available_till)):
+             (start_hour < availability.available_till <= end_hour) or
+             (availability.available_from <= start_hour < availability.available_till) or
+             (availability.available_from < end_hour <= availability.available_till)):
         error = "User has defined availability for this day that overlaps: " + availability.available_from.strftime(
             DATE_FORMAT_TIME) + ", " + availability.available_till.strftime(DATE_FORMAT_TIME)
         self.add_error('day_number', error)
@@ -510,48 +385,3 @@ class HolidayAddForm(ModelForm):
         availabilities = worker.availability_set.all()
         for availability in availabilities:
             validate_availability_conflict(self, self.cleaned_data, availability)
-
-
-class SubjectAddForm(ModelForm):
-    date_born = forms.DateField(label="Date of birth",
-                                widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"),
-                                required=False
-                                )
-
-    class Meta:
-        model = Subject
-        fields = '__all__'
-        exclude = ['dead']
-
-    def clean(self):
-        cleaned_data = super(SubjectAddForm, self).clean()
-        validate_subject_country(self, cleaned_data)
-        return cleaned_data
-
-
-class SubjectEditForm(ModelForm):
-    date_born = forms.DateField(label="Date of birth",
-                                widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"),
-                                required=False
-                                )
-
-    def __init__(self, *args, **kwargs):
-        was_dead = kwargs.get('was_dead', False)
-        if 'was_dead' in kwargs:
-            kwargs.pop('was_dead')
-        super(SubjectEditForm, self).__init__(*args, **kwargs)
-        if was_dead:
-            self.fields['dead'].disabled = True
-
-    def clean(self):
-        validate_subject_country(self, self.cleaned_data)
-
-    class Meta:
-        model = Subject
-        fields = '__all__'
-
-
-class SubjectDetailForm(ModelForm):
-    class Meta:
-        model = Subject
-        fields = '__all__'
diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..aacd999186c4d76bd08a7545369d488530456558
--- /dev/null
+++ b/smash/web/forms/study_subject_forms.py
@@ -0,0 +1,204 @@
+import logging
+import re
+
+from django import forms
+from django.forms import ModelForm
+
+from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, get_worker_from_args
+from web.models import StudySubject, Study, StudyColumns
+from web.models.constants import SCREENING_NUMBER_PREFIXES_FOR_TYPE
+
+logger = logging.getLogger(__name__)
+
+
+class StudySubjectAddForm(ModelForm):
+    datetime_contact_reminder = forms.DateTimeField(label="Contact on",
+                                                    widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS),
+                                                    required=False
+                                                    )
+
+    class Meta:
+        model = StudySubject
+        fields = '__all__'
+        exclude = ['resigned', 'resign_reason']
+
+    def __init__(self, *args, **kwargs):
+        self.user = get_worker_from_args(kwargs)
+        self.study = get_study_from_args(kwargs)
+
+        super(ModelForm, self).__init__(*args, **kwargs)
+        prepare_study_subject_fields(fields=self.fields, study=self.study)
+
+    def save(self, commit=True):
+        self.instance.study_id = self.study.id
+        return super(ModelForm, self).save(commit)
+
+    def build_screening_number(self, cleaned_data):
+        screening_number = cleaned_data.get('screening_number', None)
+        if not screening_number:
+            prefix_screening_number = self.get_prefix_screening_number()
+            if prefix_screening_number is not None:
+                screening_number = get_new_screening_number(prefix_screening_number)
+        return screening_number
+
+    def clean(self):
+        cleaned_data = super(StudySubjectAddForm, self).clean()
+        screening_number = self.build_screening_number(cleaned_data)
+        if screening_number is not None and self.study.columns.screening_number:
+            cleaned_data['screening_number'] = screening_number
+        validate_subject_screening_number(self, cleaned_data)
+        validate_subject_nd_number(self, cleaned_data)
+        validate_subject_mpower_number(self, cleaned_data)
+        return cleaned_data
+
+    def get_prefix_screening_number(self):
+        default_location = self.cleaned_data.get('default_location', None)
+        screening_number_prefix = None
+        if default_location is not None and default_location.prefix:
+            screening_number_prefix = default_location.prefix
+        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]
+        if screening_number_prefix is None:
+            return None
+        prefix_screening_number = screening_number_prefix + "-"
+        return prefix_screening_number
+
+
+def get_new_screening_number(screening_number_prefix):
+    result_number = 0
+    subjects = StudySubject.objects.filter(screening_number__contains=screening_number_prefix)
+    for subject in subjects:
+        screening_numbers = subject.screening_number.split(";")
+        for screening_number in screening_numbers:
+            screening_number = screening_number.strip()
+            if screening_number.startswith(screening_number_prefix):
+                number = screening_number[len(screening_number_prefix):]
+                try:
+                    result_number = max(result_number, int(number))
+                except ValueError:
+                    pass
+
+    return screening_number_prefix + str(result_number + 1).zfill(3)
+
+
+class StudySubjectDetailForm(ModelForm):
+    class Meta:
+        model = StudySubject
+        fields = '__all__'
+
+    def __init__(self, *args, **kwargs):
+        super(StudySubjectDetailForm, self).__init__(*args, **kwargs)
+        instance = getattr(self, 'instance', None)
+
+        self.study = get_study_from_study_subject_instance(instance)
+
+        prepare_study_subject_fields(fields=self.fields, study=self.study)
+
+
+def get_study_from_study_subject_instance(study_subject):
+    if study_subject and study_subject.study_id:
+        return Study.objects.filter(id=study_subject.study_id)[0]
+    else:
+        return Study(columns=StudyColumns())
+
+
+class StudySubjectEditForm(ModelForm):
+    datetime_contact_reminder = forms.DateTimeField(label="Contact on",
+                                                    widget=forms.DateTimeInput(DATETIMEPICKER_DATE_ATTRS),
+                                                    required=False
+                                                    )
+
+    def __init__(self, *args, **kwargs):
+        was_resigned = kwargs.get('was_resigned', False)
+        if 'was_resigned' in kwargs:
+            kwargs.pop('was_resigned')
+        super(StudySubjectEditForm, self).__init__(*args, **kwargs)
+        instance = getattr(self, 'instance', None)
+        if instance and instance.id:
+            self.fields['screening_number'].widget.attrs['readonly'] = True
+        self.study = get_study_from_study_subject_instance(instance)
+
+        if was_resigned:
+            self.fields['resigned'].disabled = True
+
+        prepare_study_subject_fields(fields=self.fields, study=self.study)
+
+    def clean(self):
+        validate_subject_nd_number(self, self.cleaned_data)
+        validate_subject_mpower_number(self, self.cleaned_data)
+        validate_subject_resign_reason(self, self.cleaned_data)
+
+    class Meta:
+        model = StudySubject
+        fields = '__all__'
+
+
+def get_study_from_args(kwargs):
+    study = kwargs.pop('study', None)
+    if study is None:
+        raise TypeError("Study not defined")
+    return study
+
+
+def prepare_field(fields, visible_columns, field_name, required=False):
+    if not getattr(visible_columns, field_name) and field_name in fields:
+        del fields[field_name]
+    elif required:
+        fields[field_name].required = True
+
+
+def prepare_study_subject_fields(fields, study):
+    prepare_field(fields, study.columns, 'default_location', required=True)
+    prepare_field(fields, study.columns, 'type', required=True)
+    prepare_field(fields, study.columns, 'screening_number')
+    prepare_field(fields, study.columns, 'nd_number')
+    prepare_field(fields, study.columns, 'datetime_contact_reminder')
+    prepare_field(fields, study.columns, 'postponed')
+    prepare_field(fields, study.columns, 'flying_team')
+    prepare_field(fields, study.columns, 'mpower_id')
+    prepare_field(fields, study.columns, 'comments')
+    prepare_field(fields, study.columns, 'referral')
+    prepare_field(fields, study.columns, 'diagnosis')
+    prepare_field(fields, study.columns, 'year_of_diagnosis')
+    prepare_field(fields, study.columns, 'information_sent')
+    prepare_field(fields, study.columns, 'pd_in_family')
+    prepare_field(fields, study.columns, 'resigned')
+    prepare_field(fields, study.columns, 'resign_reason')
+
+
+def validate_subject_screening_number(self, cleaned_data):
+    if self.study.columns.resign_reason:
+        subjects_from_db = StudySubject.objects.filter(screening_number=cleaned_data["screening_number"],
+                                                       study=self.study)
+        if len(subjects_from_db) > 0:
+            self.add_error('screening_number', "Screening number already in use")
+
+
+def validate_subject_nd_number(self, cleaned_data):
+    if self.study.columns.nd_number:
+        nd_number = cleaned_data['nd_number']
+        if nd_number != "":
+            if re.match('ND[0-9][0-9][0-9][0-9]', nd_number) is None:
+                self.add_error('nd_number', "Invalid ND number")
+            else:
+                subjects_from_db = StudySubject.objects.filter(nd_number=nd_number, study=self.study)
+                if subjects_from_db:
+                    if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''):
+                        self.add_error('nd_number', "ND number already in use")
+
+
+def validate_subject_resign_reason(self, cleaned_data):
+    if self.study.columns.resigned and self.study.columns.resign_reason:
+        if cleaned_data['resigned'] and cleaned_data['resign_reason'] == '':
+            self.add_error('resign_reason', "Resign reason cannot be empty")
+
+
+def validate_subject_mpower_number(self, cleaned_data):
+    if self.study.columns.mpower_id:
+        if cleaned_data['mpower_id'] != "":
+            subjects_from_db = StudySubject.objects.filter(mpower_id=cleaned_data['mpower_id'])
+            if subjects_from_db:
+                if subjects_from_db[0].screening_number != cleaned_data.get('screening_number', ''):
+                    self.add_error('mpower_id', "mPower number already in use")
diff --git a/smash/web/forms/subject_forms.py b/smash/web/forms/subject_forms.py
new file mode 100644
index 0000000000000000000000000000000000000000..008ab88238188f249a20755f215103846a57f11c
--- /dev/null
+++ b/smash/web/forms/subject_forms.py
@@ -0,0 +1,56 @@
+from django import forms
+from django.forms import ModelForm
+
+from web.models import Subject
+from web.models.constants import COUNTRY_OTHER_ID
+from web.forms.forms import DATEPICKER_DATE_ATTRS
+
+
+def validate_subject_country(self, cleaned_data):
+    if cleaned_data['country'].id == COUNTRY_OTHER_ID:
+        self.add_error('country', "Select valid country")
+
+
+class SubjectAddForm(ModelForm):
+    date_born = forms.DateField(label="Date of birth",
+                                widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"),
+                                required=False
+                                )
+
+    class Meta:
+        model = Subject
+        fields = '__all__'
+        exclude = ['dead']
+
+    def clean(self):
+        cleaned_data = super(SubjectAddForm, self).clean()
+        validate_subject_country(self, cleaned_data)
+        return cleaned_data
+
+
+class SubjectEditForm(ModelForm):
+    date_born = forms.DateField(label="Date of birth",
+                                widget=forms.DateInput(DATEPICKER_DATE_ATTRS, "%Y-%m-%d"),
+                                required=False
+                                )
+
+    def __init__(self, *args, **kwargs):
+        was_dead = kwargs.get('was_dead', False)
+        if 'was_dead' in kwargs:
+            kwargs.pop('was_dead')
+        super(SubjectEditForm, self).__init__(*args, **kwargs)
+        if was_dead:
+            self.fields['dead'].disabled = True
+
+    def clean(self):
+        validate_subject_country(self, self.cleaned_data)
+
+    class Meta:
+        model = Subject
+        fields = '__all__'
+
+
+class SubjectDetailForm(ModelForm):
+    class Meta:
+        model = Subject
+        fields = '__all__'
diff --git a/smash/web/migrations/0071_auto_20171130_1607.py b/smash/web/migrations/0071_auto_20171130_1607.py
new file mode 100644
index 0000000000000000000000000000000000000000..c757fdc14f19e05e1dc95a1518757140e59179db
--- /dev/null
+++ b/smash/web/migrations/0071_auto_20171130_1607.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-11-30 16:07
+from __future__ import unicode_literals
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+# noinspection PyUnusedLocal
+# noinspection PyPep8Naming
+def create_default_study(apps, schema_editor):
+    # We can't import the Study model directly as it may be a newer
+    # version than this migration expects. We use the historical version.
+    Study = apps.get_model("web", "Study")
+    study = Study.objects.create()
+    study.name = "New study"
+    study.save()
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0070_auto_20171128_1124'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Study',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255, verbose_name=b'Name')),
+            ],
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='subject',
+            field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.Subject',
+                                    verbose_name=b'Subject'),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='study',
+            field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE,
+                                    to='web.Study', verbose_name=b'Study'),
+        ),
+        migrations.RunPython(create_default_study),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='study',
+            field=models.ForeignKey(default=1, editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.Study', verbose_name=b'Study'),
+            preserve_default=False,
+        ),
+
+    ]
diff --git a/smash/web/migrations/0072_auto_20171201_1013.py b/smash/web/migrations/0072_auto_20171201_1013.py
new file mode 100644
index 0000000000000000000000000000000000000000..c90da294dd1ceb61ac58b09bbdc8b1eff9588641
--- /dev/null
+++ b/smash/web/migrations/0072_auto_20171201_1013.py
@@ -0,0 +1,55 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-01 10:13
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+# noinspection PyUnusedLocal
+# noinspection PyPep8Naming
+def create_default_study_columns(apps, schema_editor):
+    # We can't import the Study model directly as it may be a newer
+    # version than this migration expects. We use the historical version.
+    StudyColumns = apps.get_model("web", "StudyColumns")
+    study_columns = StudyColumns.objects.create()
+    study_columns.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0071_auto_20171130_1607'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='StudyColumns',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('postponed', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Postponed')),
+                ('datetime_contact_reminder', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Please make a contact on')),
+                ('type', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Type')),
+                ('default_location', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Default appointment location')),
+                ('flying_team', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Default flying team location (if applicable)')),
+                ('screening_number', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Screening number')),
+                ('nd_number', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'ND number')),
+                ('mpower_id', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'MPower ID')),
+                ('comments', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Comments')),
+                ('referral', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Referred by')),
+                ('diagnosis', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Diagnosis')),
+                ('year_of_diagnosis', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Year of diagnosis (YYYY)')),
+                ('information_sent', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Information sent')),
+                ('pd_in_family', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'PD in family')),
+                ('resigned', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Resigned')),
+                ('resign_reason', models.BooleanField(choices=[(True, b'Yes'), (False, b'No')], default=True, verbose_name=b'Resign reason')),
+            ],
+        ),
+        migrations.RunPython(create_default_study_columns),
+        migrations.AddField(
+            model_name='study',
+            name='columns',
+            field=models.OneToOneField(default=1, on_delete=django.db.models.deletion.CASCADE, to='web.StudyColumns'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/smash/web/migrations/0073_auto_20171201_1034.py b/smash/web/migrations/0073_auto_20171201_1034.py
new file mode 100644
index 0000000000000000000000000000000000000000..d23196db8c602c9824645a587468ef700dc7587d
--- /dev/null
+++ b/smash/web/migrations/0073_auto_20171201_1034.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-01 10:34
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0072_auto_20171201_1013'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='studysubject',
+            name='default_location',
+            field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Location', verbose_name=b'Default appointment location'),
+        ),
+    ]
diff --git a/smash/web/migrations/0074_auto_20171201_1038.py b/smash/web/migrations/0074_auto_20171201_1038.py
new file mode 100644
index 0000000000000000000000000000000000000000..55297a5b043aec9a5285464223c44444586d3edc
--- /dev/null
+++ b/smash/web/migrations/0074_auto_20171201_1038.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-01 10:38
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0073_auto_20171201_1034'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='studysubject',
+            name='default_location',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='web.Location', verbose_name=b'Default appointment location'),
+        ),
+    ]
diff --git a/smash/web/migrations/0075_auto_20171201_1252.py b/smash/web/migrations/0075_auto_20171201_1252.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d11beaa9167f3e0cd36d8688c1c2243e4a95d6c
--- /dev/null
+++ b/smash/web/migrations/0075_auto_20171201_1252.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-01 12:52
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0074_auto_20171201_1038'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='studysubject',
+            name='nd_number',
+            field=models.CharField(blank=True, max_length=25, verbose_name=b'ND number'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='screening_number',
+            field=models.CharField(blank=True, max_length=50, null=True, verbose_name=b'Screening number'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='type',
+            field=models.CharField(blank=True, choices=[(b'P', b'PATIENT'), (b'C', b'CONTROL')], max_length=1, null=True, verbose_name=b'Type'),
+        ),
+    ]
diff --git a/smash/web/migrations/0076_studysubjectlist.py b/smash/web/migrations/0076_studysubjectlist.py
new file mode 100644
index 0000000000000000000000000000000000000000..1da548038649494fb446aa0104f29a5bea51de9f
--- /dev/null
+++ b/smash/web/migrations/0076_studysubjectlist.py
@@ -0,0 +1,57 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-01 14:51
+from __future__ import unicode_literals
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+# noinspection PyUnusedLocal
+# noinspection PyPep8Naming
+def create_default_study_columns(apps, schema_editor):
+    # We can't import the Study model directly as it may be a newer
+    # version than this migration expects. We use the historical version.
+    StudyColumns = apps.get_model("web", "StudyColumns")
+    study_columns = StudyColumns.objects.create()
+    study_columns.postponed = False
+    study_columns.datetime_contact_reminder = False
+    study_columns.type = True
+    study_columns.default_location = True
+    study_columns.flying_team = False
+    study_columns.screening_number = True
+    study_columns.nd_number = True
+    study_columns.mpower_id = False
+    study_columns.comments = False
+    study_columns.referral = False
+    study_columns.diagnosis = False
+    study_columns.year_of_diagnosis = False
+    study_columns.information_sent = True
+    study_columns.pd_in_family = False
+    study_columns.resigned = True
+    study_columns.resign_reason = False
+    study_columns.save()
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0075_auto_20171201_1252'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='StudySubjectList',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('type', models.CharField(blank=True,
+                                          choices=[(b'GENERIC', b'Generic'), (b'NO_VISIT', b'Subjects without visit'),
+                                                   (b'REQUIRE_CONTACT', b'Subjects required contact')], max_length=50,
+                                          null=True, verbose_name=b'Type o list')),
+                ('study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Study')),
+                ('visible_columns',
+                 models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.StudyColumns')),
+            ],
+        ),
+        migrations.RunPython(create_default_study_columns),
+        migrations.RunSQL('insert into web_studysubjectlist (study_id, visible_columns_id, type) ' +
+                          "select 1, max(id), 'GENERIC' from web_studycolumns;"),
+    ]
diff --git a/smash/web/migrations/0077_subjectcolumns.py b/smash/web/migrations/0077_subjectcolumns.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f2757b04f8c8c6779b880e96fbacf37d94ec5af
--- /dev/null
+++ b/smash/web/migrations/0077_subjectcolumns.py
@@ -0,0 +1,59 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-12-01 15:21
+from __future__ import unicode_literals
+
+import django
+from django.db import migrations, models
+
+
+# noinspection PyUnusedLocal
+# noinspection PyPep8Naming
+def create_default_subject_columns(apps, schema_editor):
+    # We can't import the Study model directly as it may be a newer
+    # version than this migration expects. We use the historical version.
+    SubjectColumns = apps.get_model("web", "SubjectColumns")
+    subject_columns = SubjectColumns.objects.create()
+    subject_columns.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0076_studysubjectlist'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SubjectColumns',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('sex', models.BooleanField(default=False, max_length=1, verbose_name=b'Sex')),
+                ('first_name', models.BooleanField(default=True, max_length=1, verbose_name=b'First name')),
+                ('last_name', models.BooleanField(default=True, max_length=1, verbose_name=b'Last name')),
+                ('languages', models.BooleanField(default=False, max_length=1, verbose_name=b'Known languages')),
+                ('default_written_communication_language', models.BooleanField(default=False, max_length=1, verbose_name=b'Default language for document generation')),
+                ('phone_number', models.BooleanField(default=False, max_length=1, verbose_name=b'Phone number')),
+                ('phone_number_2', models.BooleanField(default=False, max_length=1, verbose_name=b'Phone number 2')),
+                ('phone_number_3', models.BooleanField(default=False, max_length=1, verbose_name=b'Phone number 3')),
+                ('email', models.BooleanField(default=False, max_length=1, verbose_name=b'E-mail')),
+                ('date_born', models.BooleanField(default=False, max_length=1, verbose_name=b'Date of birth')),
+                ('address', models.BooleanField(default=False, max_length=1, verbose_name=b'Address')),
+                ('postal_code', models.BooleanField(default=False, max_length=1, verbose_name=b'Postal code')),
+                ('city', models.BooleanField(default=False, max_length=1, verbose_name=b'City')),
+                ('country', models.BooleanField(default=False, max_length=1, verbose_name=b'Country')),
+                ('dead', models.BooleanField(default=True, max_length=1, verbose_name=b'Deceased')),
+            ],
+        ),
+        migrations.RunPython(create_default_subject_columns),
+        migrations.RenameField(
+            model_name='studysubjectlist',
+            old_name='visible_columns',
+            new_name='visible_subject_study_columns',
+        ),
+        migrations.AddField(
+            model_name='studysubjectlist',
+            name='visible_subject_columns',
+            field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='web.SubjectColumns'),
+            preserve_default=False,
+        ),
+    ]
diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py
index 2b7f7dba19788a6778d0430ed60436cc0c6c4e88..5226d98e619ba915c24b935149c896c839ea5d34 100644
--- a/smash/web/models/__init__.py
+++ b/smash/web/models/__init__.py
@@ -8,6 +8,9 @@ from flying_team import FlyingTeam
 from location import Location
 from appointment_type_link import AppointmentTypeLink
 from country import Country
+from subject_columns import SubjectColumns
+from study_columns import StudyColumns
+from study import Study
 from room import Room
 from visit import Visit
 from worker import Worker
@@ -19,12 +22,13 @@ from item import Item
 from language import Language
 from subject import Subject
 from study_subject import StudySubject
+from study_subject_list import StudySubjectList
 from contact_attempt import ContactAttempt
 from mail_template import MailTemplate
 from missing_subject import MissingSubject
 from inconsistent_subject import InconsistentSubject, InconsistentField
 
-
-__all__ = [FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room, Subject, StudySubject,
+__all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room,
+           Subject, StudySubject, StudySubjectList, SubjectColumns,
            Visit, Worker, ContactAttempt, ConfigurationItem, MailTemplate, AppointmentTypeLink, MissingSubject,
-           InconsistentSubject, InconsistentField, Country]
+           InconsistentSubject, InconsistentField, Country, StudyColumns]
diff --git a/smash/web/models/configuration_item.py b/smash/web/models/configuration_item.py
index 37db0488bb25d9b370e567f3acc5445d7ebe2a8e..cdaa6fbbabdabbd8c082e1ed41f3eaeb6c736ce8 100644
--- a/smash/web/models/configuration_item.py
+++ b/smash/web/models/configuration_item.py
@@ -8,6 +8,9 @@ from web.models.constants import CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE,
 
 
 class ConfigurationItem(models.Model):
+    class Meta:
+        app_label = 'web'
+
     type = models.CharField(max_length=50,
                             verbose_name='Type',
                             editable=False
diff --git a/smash/web/models/constants.py b/smash/web/models/constants.py
index d2defa4d3d1f8a52f4f40fe60fc0dcf71f0963be..fe6ade3b9b0c61fb9df46206f7c427d0735157f9 100644
--- a/smash/web/models/constants.py
+++ b/smash/web/models/constants.py
@@ -76,4 +76,8 @@ REDCAP_TOKEN_CONFIGURATION_TYPE = "REDCAP_TOKEN_CONFIGURATION_TYPE"
 REDCAP_BASE_URL_CONFIGURATION_TYPE = "REDCAP_BASE_URL_CONFIGURATION_TYPE"
 
 COUNTRY_OTHER_ID = 1
-COUNTRY_AFGHANISTAN_ID = 2
\ No newline at end of file
+COUNTRY_AFGHANISTAN_ID = 2
+
+# id of the singleton Study,
+# TODO remove after allowing many studies per Smasch instance
+GLOBAL_STUDY_ID = 1
diff --git a/smash/web/models/study.py b/smash/web/models/study.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3b4090d33f589d100ce6c723f5af783597903bf
--- /dev/null
+++ b/smash/web/models/study.py
@@ -0,0 +1,22 @@
+# coding=utf-8
+from django.db import models
+
+from web.models.study_columns import StudyColumns
+
+
+class Study(models.Model):
+    class Meta:
+        app_label = 'web'
+
+    name = models.CharField(max_length=255, verbose_name='Name')
+
+    columns = models.OneToOneField(
+        StudyColumns,
+        on_delete=models.CASCADE,
+    )
+
+    def __str__(self):
+        return "%s" % self.name
+
+    def __unicode__(self):
+        return "%s" % self.name
diff --git a/smash/web/models/study_columns.py b/smash/web/models/study_columns.py
new file mode 100644
index 0000000000000000000000000000000000000000..15142bd56913678159e4d92ef53928d0f7440ad0
--- /dev/null
+++ b/smash/web/models/study_columns.py
@@ -0,0 +1,79 @@
+# coding=utf-8
+from django.db import models
+
+from web.models.constants import BOOL_CHOICES
+
+
+class StudyColumns(models.Model):
+    class Meta:
+        app_label = 'web'
+
+    postponed = models.BooleanField(choices=BOOL_CHOICES,
+                                    verbose_name='Postponed',
+                                    default=True
+                                    )
+
+    datetime_contact_reminder = models.BooleanField(choices=BOOL_CHOICES,
+                                                    default=True,
+                                                    verbose_name='Please make a contact on'
+                                                    )
+    type = models.BooleanField(choices=BOOL_CHOICES,
+                               default=True,
+                               verbose_name='Type'
+                               )
+
+    default_location = models.BooleanField(choices=BOOL_CHOICES,
+                                           default=True,
+                                           verbose_name='Default appointment location',
+                                           )
+
+    flying_team = models.BooleanField(choices=BOOL_CHOICES,
+                                      default=True,
+                                      verbose_name='Default flying team location (if applicable)',
+                                      )
+
+    screening_number = models.BooleanField(choices=BOOL_CHOICES,
+                                           default=True,
+                                           verbose_name='Screening number',
+                                           )
+    nd_number = models.BooleanField(choices=BOOL_CHOICES,
+                                    default=True,
+                                    verbose_name='ND number',
+                                    )
+    mpower_id = models.BooleanField(choices=BOOL_CHOICES,
+                                    default=True,
+                                    verbose_name='MPower ID'
+                                    )
+    comments = models.BooleanField(choices=BOOL_CHOICES,
+                                   default=True,
+                                   verbose_name='Comments'
+                                   )
+    referral = models.BooleanField(choices=BOOL_CHOICES,
+                                   default=True,
+                                   verbose_name='Referred by'
+                                   )
+    diagnosis = models.BooleanField(choices=BOOL_CHOICES,
+                                    default=True,
+                                    verbose_name='Diagnosis'
+                                    )
+    year_of_diagnosis = models.BooleanField(choices=BOOL_CHOICES,
+                                            default=True,
+                                            verbose_name='Year of diagnosis (YYYY)'
+                                            )
+
+    information_sent = models.BooleanField(choices=BOOL_CHOICES,
+                                           default=True,
+                                           verbose_name='Information sent',
+                                           )
+    pd_in_family = models.BooleanField(choices=BOOL_CHOICES,
+                                       default=True,
+                                       verbose_name='PD in family',
+                                       )
+    resigned = models.BooleanField(choices=BOOL_CHOICES,
+                                   default=True,
+                                   verbose_name='Resigned',
+                                   )
+    resign_reason = models.BooleanField(choices=BOOL_CHOICES,
+                                        default=True,
+                                        verbose_name='Resign reason'
+                                        )
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index 7fdc2eaca43b9bfffe90bca16f298dc88056d1ec..8bbeae76e223005167fc857d97465eff1c92af4f 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -33,6 +33,12 @@ class StudySubject(models.Model):
                                 null=False,
                                 )
 
+    study = models.ForeignKey("web.Study",
+                              verbose_name='Study',
+                              editable=False,
+                              null=False,
+                              )
+
     postponed = models.BooleanField(choices=BOOL_CHOICES,
                                     verbose_name='Postponed',
                                     default=False
@@ -44,28 +50,31 @@ class StudySubject(models.Model):
     )
     type = models.CharField(max_length=1,
                             choices=SUBJECT_TYPE_CHOICES.items(),
-                            verbose_name='Type'
+                            verbose_name='Type',
+                            null=True,
+                            blank=True
                             )
 
     default_location = models.ForeignKey(Location,
                                          verbose_name='Default appointment location',
+                                         null=True,
+                                         blank=True
                                          )
 
     flying_team = models.ForeignKey("web.FlyingTeam",
                                     verbose_name='Default flying team location (if applicable)',
-                                    null=True, blank=True
+                                    null=True,
+                                    blank=True
                                     )
 
     screening_number = models.CharField(max_length=50,
-                                        unique=True,
-                                        verbose_name='Screening number', blank=False, null=False
+                                        verbose_name='Screening number',
+                                        blank=True,
+                                        null=True
                                         )
     nd_number = models.CharField(max_length=25,
                                  blank=True,
                                  verbose_name='ND number',
-                                 validators=[
-                                     RegexValidator('^(ND[0-9]{4}|)$',
-                                                    message="ND number should look as follows: NDxxxx")]
                                  )
     mpower_id = models.CharField(max_length=20,
                                  blank=True,
diff --git a/smash/web/models/study_subject_list.py b/smash/web/models/study_subject_list.py
new file mode 100644
index 0000000000000000000000000000000000000000..fae16c14fb03cecdcae5d75adaffcf746c217513
--- /dev/null
+++ b/smash/web/models/study_subject_list.py
@@ -0,0 +1,44 @@
+# coding=utf-8
+from django.db import models
+
+from web.models import Study, SubjectColumns, StudyColumns
+
+SUBJECT_LIST_GENERIC = "GENERIC"
+SUBJECT_LIST_NO_VISIT = "NO_VISIT"
+SUBJECT_LIST_REQUIRE_CONTACT = "REQUIRE_CONTACT"
+
+SUBJECT_LIST_CHOICES = {
+    SUBJECT_LIST_GENERIC: 'Generic',
+    SUBJECT_LIST_NO_VISIT: 'Subjects without visit',
+    SUBJECT_LIST_REQUIRE_CONTACT: 'Subjects required contact',
+}
+
+
+class StudySubjectList(models.Model):
+    class Meta:
+        app_label = 'web'
+
+    study = models.ForeignKey(
+        Study,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+
+    visible_subject_study_columns = models.ForeignKey(
+        StudyColumns,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+
+    visible_subject_columns = models.ForeignKey(
+        SubjectColumns,
+        on_delete=models.CASCADE,
+        null=False,
+    )
+
+    type = models.CharField(max_length=50,
+                            choices=SUBJECT_LIST_CHOICES.items(),
+                            verbose_name='Type o list',
+                            null=True,
+                            blank=True
+                            )
diff --git a/smash/web/models/subject_columns.py b/smash/web/models/subject_columns.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad1779b9302c403efedaa7aece05176080290857
--- /dev/null
+++ b/smash/web/models/subject_columns.py
@@ -0,0 +1,81 @@
+# coding=utf-8
+from django.db import models
+
+
+class SubjectColumns(models.Model):
+    class Meta:
+        app_label = 'web'
+
+    sex = models.BooleanField(max_length=1,
+                              default=False,
+                              verbose_name='Sex',
+                              )
+
+    first_name = models.BooleanField(max_length=1,
+                                     default=True,
+                                     verbose_name='First name'
+                                     )
+
+    last_name = models.BooleanField(max_length=1,
+                                    default=True,
+                                    verbose_name='Last name'
+                                    )
+
+    languages = models.BooleanField(max_length=1,
+                                    default=False,
+                                    verbose_name='Known languages'
+                                    )
+
+    default_written_communication_language = models.BooleanField(max_length=1,
+                                                                 default=False,
+                                                                 verbose_name='Default language for document generation'
+                                                                 )
+    phone_number = models.BooleanField(max_length=1,
+                                       default=False,
+                                       verbose_name='Phone number'
+                                       )
+
+    phone_number_2 = models.BooleanField(max_length=1,
+                                         default=False,
+                                         verbose_name='Phone number 2'
+                                         )
+
+    phone_number_3 = models.BooleanField(max_length=1,
+                                         default=False,
+                                         verbose_name='Phone number 3'
+                                         )
+
+    email = models.BooleanField(max_length=1,
+                                default=False,
+                                verbose_name='E-mail'
+                                )
+
+    date_born = models.BooleanField(max_length=1,
+                                    default=False,
+                                    verbose_name='Date of birth'
+                                    )
+
+    address = models.BooleanField(max_length=1,
+                                  default=False,
+                                  verbose_name='Address'
+                                  )
+
+    postal_code = models.BooleanField(max_length=1,
+                                      default=False,
+                                      verbose_name='Postal code'
+                                      )
+
+    city = models.BooleanField(max_length=1,
+                               default=False,
+                               verbose_name='City'
+                               )
+
+    country = models.BooleanField(max_length=1,
+                                  default=False,
+                                  verbose_name='Country'
+                                  )
+
+    dead = models.BooleanField(max_length=1,
+                               default=True,
+                               verbose_name='Deceased',
+                               )
diff --git a/smash/web/redcap_connector.py b/smash/web/redcap_connector.py
index 8a2828583aee2e92ff795c73ae3182fc505d5e9b..ff09df743be6cd71ce5826ed375330df967e4df6 100644
--- a/smash/web/redcap_connector.py
+++ b/smash/web/redcap_connector.py
@@ -21,14 +21,18 @@ RED_CAP_LANGUAGE_2_FIELD = 'dm_language_2'
 
 RED_CAP_LANGUAGE_1_FIELD = 'dm_language_1'
 
+# noinspection SpellCheckingInspection
 RED_CAP_MPOWER_ID_FIELD = 'dm_mpowerid'
 
 RED_CAP_DEAD_FIELD = 'dm_death'
 
+# noinspection SpellCheckingInspection
 RED_CAP_SEX_FIELD = 'cdisc_dm_sex'
 
+# noinspection SpellCheckingInspection
 RED_CAP_DATE_BORN_FIELD = 'cdisc_dm_brthdtc'
 
+# noinspection SpellCheckingInspection
 RED_CAP_ND_NUMBER_FIELD = 'cdisc_dm_usubjd'
 
 logger = logging.getLogger(__name__)
diff --git a/smash/web/static/js/subject.js b/smash/web/static/js/subject.js
index 1cc539c908db2fbfbef26a52cb0113663ec24bab..d6f5ac20e5bb8c737c667e3a333594a69fde9303 100644
--- a/smash/web/static/js/subject.js
+++ b/smash/web/static/js/subject.js
@@ -1,3 +1,10 @@
+if (!String.prototype.startsWith) {
+    String.prototype.startsWith = function (searchString, position) {
+        position = position || 0;
+        return this.indexOf(searchString, position) === position;
+    };
+}
+
 function createColumn(dataType, name, filter, visible, renderFunction) {
     if (renderFunction === undefined) {
         renderFunction = function (data, type, row, meta) {
@@ -13,39 +20,31 @@ function createColumn(dataType, name, filter, visible, renderFunction) {
     };
 }
 
-function getColumns(type, getSubjectEditUrl) {
+function getColumns(columns, getSubjectEditUrl) {
     var result = [];
-    // don't confuse end user
-    // result.push(createColumn("id", "Id", null, false));
-    result.push(createColumn("nd_number", "ND", "string_filter", true));
-    result.push(createColumn("screening_number", "Screening", "string_filter", true));
-    result.push(createColumn("first_name", "First name", "string_filter", true));
-    result.push(createColumn("last_name", "Last name", "string_filter", true));
-    result.push(createColumn("date_born", "Date of birth", null, false));
-    result.push(createColumn("default_location", "Location", "location_filter", true));
-    result.push(createColumn("dead", "Deceased", "yes_no_filter", true));
-    result.push(createColumn("resigned", "Resigned", "yes_no_filter", true));
-    result.push(createColumn("postponed", "Postponed", "yes_no_filter", true));
-    result.push(createColumn("information_sent", "Info sent", "yes_no_filter", true));
-    result.push(createColumn("type", "Type", "type_filter", true));
-    result.push(createColumn("id", "Edit", null, true, function (data, type, row, meta) {
-        var url = getSubjectEditUrl(row.id.toString());
-
-        return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>';
-    }));
-    for (var i = 1; i <= 8; i++) {
-        var renderFunction = (function () {
-            var x = i;
-            return function (data, type, row, meta) {
-                return create_visit_row(row.visits[x - 1]);
-            };
-        })();
-
-        result.push(createColumn("visit_" + i, "Visit " + i, "visit_filter", true, renderFunction));
-
+    for (var i = 0; i < columns.length; i++) {
+        var columnRow = columns[i];
+        if (columnRow.type === "edit") {
+            result.push(createColumn("id", columnRow.name, columnRow.filter, columnRow.visible, function (data, type, row) {
+                var url = getSubjectEditUrl(row.id.toString());
+
+                return '<a href="' + url + '" type="button" class="btn btn-block btn-default">Edit</a>';
+            }));
+        } else if (columnRow.type.startsWith("visit")) {
+            var renderFunction = (function () {
+                var x = i;
+                return function (data, type, row) {
+                    return create_visit_row(row.visits[x - 1]);
+                };
+            })();
+
+            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible, renderFunction));
+
+        } else {
+            result.push(createColumn(columnRow.type, columnRow.name, columnRow.filter, columnRow.visible));
+        }
     }
     return result;
-
 }
 
 function createHeader(columnsDefinition) {
diff --git a/smash/web/templates/subjects/index.html b/smash/web/templates/subjects/index.html
index 7f97eb8619ff275cbe4f294b43ee2449b76a966e..2c39cc33e2518e07eb385778dd682e48d11b94b0 100644
--- a/smash/web/templates/subjects/index.html
+++ b/smash/web/templates/subjects/index.html
@@ -52,16 +52,18 @@
             worker_locations.push({id: location.id, name: location.name});
         {% endfor %}
 
-        createSubjectsTable({
-            worker_locations: worker_locations,
-            subject_types_url: "{% url 'web.api.subject_types' %}",
-            locations_url: "{% url 'web.api.locations' %}",
-            subjects_url: "{% url 'web.api.subjects' list_type %}",
-            tableElement: document.getElementById("table"),
-            columns: getColumns("{{ list_type }}", getSubjectEditUrl),
-            checkboxesElement: document.getElementById("visible-column-checkboxes")
-        })
-        ;
+        $.get("{% url 'web.api.subjects.columns' list_type %}", function (data) {
+            createSubjectsTable({
+                worker_locations: worker_locations,
+                subject_types_url: "{% url 'web.api.subject_types' %}",
+                locations_url: "{% url 'web.api.locations' %}",
+                subjects_url: "{% url 'web.api.subjects' list_type %}",
+                tableElement: document.getElementById("table"),
+                columns: getColumns(data.columns, getSubjectEditUrl),
+                checkboxesElement: document.getElementById("visible-column-checkboxes")
+            })
+        });
+
     </script>
 
 {% endblock scripts %}
diff --git a/smash/web/tests/api_views/test_daily_planning.py b/smash/web/tests/api_views/test_daily_planning.py
index abaea166149c9dbf02967115040209f5fcaa9cb8..0d48c8d15ed527d6704f168a77cdd0b47f94dfad 100644
--- a/smash/web/tests/api_views/test_daily_planning.py
+++ b/smash/web/tests/api_views/test_daily_planning.py
@@ -10,8 +10,8 @@ from django.urls import reverse
 from web.api_views.daily_planning import get_workers_for_daily_planning, get_generic_appointment_events
 from web.models import Worker, Availability, Holiday, AppointmentTypeLink
 from web.models.constants import TUESDAY_AS_DAY_OF_WEEK
-from web.tests.functions import create_worker, create_study_subject, create_appointment, create_flying_team, create_visit, \
-    create_appointment_type, get_test_location
+from web.tests.functions import create_worker, create_study_subject, create_appointment, create_flying_team, \
+    create_visit, create_appointment_type, get_test_location
 
 
 class TestApi(TestCase):
diff --git a/smash/web/tests/api_views/test_subject.py b/smash/web/tests/api_views/test_subject.py
index 672266db6f3a6f4e0ba6792081b2329d73f332c7..7dc320a195db39709542773cd26735e09dfc0cdd 100644
--- a/smash/web/tests/api_views/test_subject.py
+++ b/smash/web/tests/api_views/test_subject.py
@@ -8,10 +8,13 @@ from django.test import Client
 from django.test import TestCase
 from django.urls import reverse
 
-from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject, SUBJECT_LIST_GENERIC, \
-    SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
-from web.models import StudySubject, Appointment
-from web.tests.functions import create_study_subject, create_worker, create_get_suffix, create_visit, create_appointment
+from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject
+from web.models import StudySubject, Appointment, Study
+from web.models.constants import GLOBAL_STUDY_ID
+from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \
+    StudySubjectList
+from web.tests.functions import create_study_subject, create_worker, create_get_suffix, create_visit, \
+    create_appointment, create_empty_study_columns
 from web.views.notifications import get_today_midnight_date
 
 logger = logging.getLogger(__name__)
@@ -46,6 +49,35 @@ class TestApi(TestCase):
 
         self.assertTrue(city_name in cities)
 
+    def test_get_columns(self):
+        response = self.client.get(
+            reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC}))
+        self.assertEqual(response.status_code, 200)
+
+        columns = json.loads(response.content)['columns']
+        self.assertTrue(len(columns) >= 20)
+
+    def test_get_columns_when_no_list_is_available(self):
+        StudySubjectList.objects.all().delete()
+        response = self.client.get(
+            reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC}))
+        self.assertEqual(response.status_code, 200)
+
+        columns = json.loads(response.content)['columns']
+        self.assertTrue(len(columns) > 0)
+
+    def test_get_columns_when_study_has_no_data_columns(self):
+        study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
+        study.columns = create_empty_study_columns()
+        study.save()
+
+        response = self.client.get(
+            reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC}))
+        self.assertEqual(response.status_code, 200)
+
+        columns = json.loads(response.content)['columns']
+        self.assertTrue(len(columns) < 20)
+
     def test_referrals(self):
         referral_name = "some referral"
 
diff --git a/smash/web/tests/forms/test_StudySubjectAddForm.py b/smash/web/tests/forms/test_StudySubjectAddForm.py
index a417a5bb3f11f562dbf2121b02c4074d3f5f45ff..29b4a7b7715b80db243e0a3b30053f1a7f3ba7b7 100644
--- a/smash/web/tests/forms/test_StudySubjectAddForm.py
+++ b/smash/web/tests/forms/test_StudySubjectAddForm.py
@@ -1,9 +1,10 @@
 import logging
 
-from web.forms import StudySubjectAddForm, get_new_screening_number
+from web.forms.study_subject_forms import get_new_screening_number
+from web.forms import StudySubjectAddForm
 from web.models.constants import SUBJECT_TYPE_CHOICES_CONTROL
 from web.tests import LoggedInWithWorkerTestCase
-from web.tests.functions import create_study_subject, create_subject
+from web.tests.functions import create_study_subject, create_subject, get_test_study, create_empty_study
 
 logger = logging.getLogger(__name__)
 
@@ -14,6 +15,7 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
 
         location = self.worker.locations.all()[0]
         self.subject = create_subject()
+        self.study = get_test_study()
         self.sample_data = {
             'type': SUBJECT_TYPE_CHOICES_CONTROL,
             'default_location': location.id,
@@ -22,22 +24,32 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
         }
 
     def test_validation(self):
-        form = StudySubjectAddForm(data=self.sample_data, user=self.user)
+        form = StudySubjectAddForm(data=self.sample_data, user=self.user, study=self.study)
         form.is_valid()
         self.assertTrue(form.is_valid())
 
+    def test_validation_for_study_without_columns(self):
+        form = StudySubjectAddForm(data=self.sample_data, user=self.user, study=create_empty_study())
+        self.assertTrue(form.is_valid())
+
+    def test_validate_nd_number(self):
+        self.sample_data['nd_number'] = 'invalid nd number'
+        form = StudySubjectAddForm(data=self.sample_data, user=self.user, study=self.study)
+        self.assertFalse(form.is_valid())
+        self.assertTrue("nd_number" in form.errors)
+
     def test_invalid(self):
         form_data = self.sample_data
         form_data['screening_number'] = "123"
 
-        form = StudySubjectAddForm(data=form_data, user=self.user)
+        form = StudySubjectAddForm(data=form_data, user=self.user, study=self.study)
         form.is_valid()
         form.instance.subject_id = self.subject.id
         self.assertTrue(form.is_valid())
         self.assertIsNone(form.fields['year_of_diagnosis'].initial)
         form.save()
 
-        form2 = StudySubjectAddForm(data=form_data, user=self.user)
+        form2 = StudySubjectAddForm(data=form_data, user=self.user, study=self.study)
         validation_status = form2.is_valid()
         self.assertFalse(validation_status)
         self.assertTrue("screening_number" in form2.errors)
@@ -46,14 +58,14 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
         form_data = self.sample_data
         form_data['nd_number'] = "ND0123"
 
-        form = StudySubjectAddForm(data=form_data, user=self.user)
+        form = StudySubjectAddForm(data=form_data, user=self.user, study=self.study)
         form.is_valid()
         self.assertTrue(form.is_valid())
         form.instance.subject_id = self.subject.id
         form.save()
 
         form_data['screening_number'] = "2"
-        form2 = StudySubjectAddForm(data=form_data, user=self.user)
+        form2 = StudySubjectAddForm(data=form_data, user=self.user, study=self.study)
         validation_status = form2.is_valid()
         self.assertFalse(validation_status)
         self.assertTrue("nd_number" in form2.errors)
@@ -62,14 +74,14 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
         form_data = self.sample_data
         form_data['mpower_id'] = "123"
 
-        form = StudySubjectAddForm(data=form_data, user=self.user)
+        form = StudySubjectAddForm(data=form_data, user=self.user, study=self.study)
         form.is_valid()
         self.assertTrue(form.is_valid())
         form.instance.subject_id = self.subject.id
         form.save()
 
         form_data['screening_number'] = "2"
-        form2 = StudySubjectAddForm(data=form_data, user=self.user)
+        form2 = StudySubjectAddForm(data=form_data, user=self.user, study=self.study)
         validation_status = form2.is_valid()
         self.assertFalse(validation_status)
         self.assertTrue("mpower_id" in form2.errors)
diff --git a/smash/web/tests/forms/test_StudySubjectEditForm.py b/smash/web/tests/forms/test_StudySubjectEditForm.py
index 5f838cff2724de1c31ae353a5e828d426d3bb002..1e7a5c0919c5b3ed775b23e1cf4bb1b3ab538f59 100644
--- a/smash/web/tests/forms/test_StudySubjectEditForm.py
+++ b/smash/web/tests/forms/test_StudySubjectEditForm.py
@@ -3,7 +3,7 @@ import logging
 from web.forms import StudySubjectEditForm
 from web.models import StudySubject
 from web.tests import LoggedInWithWorkerTestCase
-from web.tests.functions import create_study_subject
+from web.tests.functions import create_study_subject, create_empty_study
 
 logger = logging.getLogger(__name__)
 
@@ -31,6 +31,14 @@ class StudySubjectEditFormTests(LoggedInWithWorkerTestCase):
         save_status = edit_form.is_valid()
         self.assertTrue(save_status)
 
+    def test_validation_with_empty_study(self):
+        self.study_subject.study = create_empty_study()
+        self.study_subject.save()
+
+        edit_form = StudySubjectEditForm(self.sample_data)
+        save_status = edit_form.is_valid()
+        self.assertTrue(save_status)
+
     def test_invalid_nd_number_edit(self):
         study_subject2 = create_study_subject(124)
         study_subject2.nd_number = "ND0124"
@@ -38,7 +46,7 @@ class StudySubjectEditFormTests(LoggedInWithWorkerTestCase):
         study_subject2.save()
 
         self.sample_data['nd_number'] = "ND0124"
-        edit_form = StudySubjectEditForm(self.sample_data)
+        edit_form = StudySubjectEditForm(self.sample_data, instance=self.study_subject)
 
         save_status = edit_form.is_valid()
         self.assertTrue("nd_number" in edit_form.errors)
diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py
index adb1eaf8511656ec1b92688405d4979d893ccb53..6994935f76045a59b011f429d58b408386c7152d 100644
--- a/smash/web/tests/functions.py
+++ b/smash/web/tests/functions.py
@@ -3,8 +3,8 @@ import os
 
 from django.contrib.auth.models import User
 
-from web.models import Location, AppointmentType, StudySubject, Worker, Visit, Appointment, ConfigurationItem, Language, \
-    ContactAttempt, FlyingTeam, Availability, Subject
+from web.models import Location, AppointmentType, StudySubject, Worker, Visit, Appointment, ConfigurationItem, \
+    Language, ContactAttempt, FlyingTeam, Availability, Subject, Study, StudyColumns
 from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, REDCAP_BASE_URL_CONFIGURATION_TYPE, \
     SEX_CHOICES_MALE, SUBJECT_TYPE_CHOICES_CONTROL, CONTACT_TYPES_PHONE, \
     MONDAY_AS_DAY_OF_WEEK, COUNTRY_AFGHANISTAN_ID
@@ -23,6 +23,56 @@ def create_location(name="test"):
     return Location.objects.create(name=name)
 
 
+def create_empty_study_columns():
+    study_columns = StudyColumns.objects.create(
+        postponed=False,
+        datetime_contact_reminder=False,
+        type=False,
+        default_location=False,
+        flying_team=False,
+        screening_number=False,
+        nd_number=False,
+        mpower_id=False,
+        comments=False,
+        referral=False,
+        diagnosis=False,
+        year_of_diagnosis=False,
+        information_sent=False,
+        pd_in_family=False,
+        resigned=False,
+        resign_reason=False,
+    )
+
+    return study_columns
+
+
+def create_study(name="test"):
+    study_columns = StudyColumns.objects.create()
+    return Study.objects.create(name=name, columns=study_columns)
+
+
+def create_empty_study(name="test"):
+    study_columns = StudyColumns.objects.create(
+        postponed=False,
+        datetime_contact_reminder=False,
+        type=False,
+        default_location=False,
+        flying_team=False,
+        screening_number=False,
+        nd_number=False,
+        mpower_id=False,
+        comments=False,
+        referral=False,
+        diagnosis=False,
+        year_of_diagnosis=False,
+        information_sent=False,
+        pd_in_family=False,
+        resigned=False,
+        resign_reason=False
+    )
+    return Study.objects.create(name=name, columns=study_columns)
+
+
 def get_test_location():
     locations = Location.objects.filter(name="test")
     if len(locations) > 0:
@@ -31,6 +81,14 @@ def get_test_location():
         return create_location()
 
 
+def get_test_study():
+    studies = Study.objects.filter(name="test-study")
+    if len(studies) > 0:
+        return studies[0]
+    else:
+        return create_study("test-study")
+
+
 def create_appointment_type():
     return AppointmentType.objects.create(
         code="C",
@@ -70,6 +128,7 @@ def create_study_subject(subject_id=1, subject=None):
         default_location=get_test_location(),
         type=SUBJECT_TYPE_CHOICES_CONTROL,
         screening_number="piotr's number" + str(subject_id),
+        study=get_test_study(),
         subject=subject
     )
 
diff --git a/smash/web/tests/models/test_study.py b/smash/web/tests/models/test_study.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c9e02f77c08015bd59a4ef19149945caa5a0e6b
--- /dev/null
+++ b/smash/web/tests/models/test_study.py
@@ -0,0 +1,14 @@
+import logging
+
+from django.test import TestCase
+
+from web.tests.functions import create_study
+
+logger = logging.getLogger(__name__)
+
+
+class StudyTests(TestCase):
+    def test_image_img(self):
+        study = create_study()
+
+        self.assertTrue(study.name in str(study))
diff --git a/smash/web/tests/view/test_appointments.py b/smash/web/tests/view/test_appointments.py
index 97f26eb5c7abae165eb7f89d2766c299f9245e35..c187af91ed8d55d33f861d6f956cb998e3663946 100644
--- a/smash/web/tests/view/test_appointments.py
+++ b/smash/web/tests/view/test_appointments.py
@@ -3,12 +3,11 @@ import logging
 
 from django.urls import reverse
 
-from web.forms import AppointmentEditForm, StudySubjectEditForm, SubjectEditForm
+from web.forms import AppointmentEditForm, SubjectEditForm, StudySubjectEditForm
 from web.models import Appointment, StudySubject
 from web.tests import LoggedInTestCase
 from web.tests.functions import create_study_subject, create_visit, create_appointment, create_worker, \
-    create_flying_team, \
-    format_form_field
+    create_flying_team, format_form_field
 from web.views.notifications import get_today_midnight_date
 
 logger = logging.getLogger(__name__)
diff --git a/smash/web/tests/view/test_subjects.py b/smash/web/tests/view/test_subjects.py
index 83820e0109d406de796320d2cdabb668df8c145a..cf9d49777f4ebd58988db0c48c3e8df2d6eb509e 100644
--- a/smash/web/tests/view/test_subjects.py
+++ b/smash/web/tests/view/test_subjects.py
@@ -3,13 +3,13 @@ import logging
 
 from django.urls import reverse
 
-from web.forms import StudySubjectAddForm, StudySubjectEditForm, SubjectEditForm, SubjectAddForm
+from web.forms import SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm
 from web.models import MailTemplate, StudySubject
 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
 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
+    create_language, get_resource_path, get_test_study
 from web.views.notifications import get_today_midnight_date
 
 logger = logging.getLogger(__name__)
@@ -19,6 +19,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
     def setUp(self):
         super(SubjectsViewTests, self).setUp()
         self.study_subject = create_study_subject()
+        self.study = get_test_study()
 
     def test_render_subjects_add(self):
         self.worker.save()
@@ -105,7 +106,7 @@ class SubjectsViewTests(LoggedInWithWorkerTestCase):
         return form_data
 
     def create_add_form_data_for_study_subject(self):
-        form_study_subject = StudySubjectAddForm(prefix="study_subject", user=self.user)
+        form_study_subject = StudySubjectAddForm(prefix="study_subject", user=self.user, study=self.study)
         form_subject = SubjectAddForm(prefix="subject")
         form_data = {}
         for key, value in form_study_subject.initial.items():
diff --git a/smash/web/views/appointment.py b/smash/web/views/appointment.py
index e7f08ea311fe1581c2c1363606c0842f11f572a2..938e713058cbd8ce402bf708122e81d489bfd728 100644
--- a/smash/web/views/appointment.py
+++ b/smash/web/views/appointment.py
@@ -7,8 +7,8 @@ from django.core.exceptions import ValidationError
 from django.shortcuts import get_object_or_404, redirect
 
 from . import wrap_response
-from ..forms import AppointmentDetailForm, AppointmentAddForm, AppointmentEditForm, StudySubjectEditForm, \
-    SubjectEditForm
+from ..forms import AppointmentDetailForm, AppointmentAddForm, AppointmentEditForm, SubjectEditForm, \
+    StudySubjectEditForm
 from ..models import Appointment, StudySubject, MailTemplate
 
 APPOINTMENT_LIST_GENERIC = "GENERIC"
diff --git a/smash/web/views/subject.py b/smash/web/views/subject.py
index 53ceafe61a78afb0d1e260df5cfb4e8248e547b5..5825db2be3767e1075badcd3096e6580cfa20cec 100644
--- a/smash/web/views/subject.py
+++ b/smash/web/views/subject.py
@@ -4,13 +4,11 @@ import logging
 from django.contrib import messages
 from django.shortcuts import redirect, get_object_or_404
 
+from ..models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT
 from . import wrap_response
-from ..forms import StudySubjectAddForm, StudySubjectEditForm, VisitDetailForm, SubjectEditForm, SubjectAddForm
-from ..models import StudySubject, MailTemplate, Worker
-
-SUBJECT_LIST_GENERIC = "GENERIC"
-SUBJECT_LIST_NO_VISIT = "NO_VISIT"
-SUBJECT_LIST_REQUIRE_CONTACT = "REQUIRE_CONTACT"
+from ..forms import VisitDetailForm, SubjectAddForm, SubjectEditForm, StudySubjectAddForm, StudySubjectEditForm
+from ..models import StudySubject, MailTemplate, Worker, Study
+from ..models.constants import GLOBAL_STUDY_ID
 
 logger = logging.getLogger(__name__)
 
@@ -24,8 +22,10 @@ def subjects(request):
 
 
 def subject_add(request):
+    study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
     if request.method == 'POST':
-        study_subject_form = StudySubjectAddForm(request.POST, request.FILES, prefix="study_subject", user=request.user)
+        study_subject_form = StudySubjectAddForm(request.POST, request.FILES, prefix="study_subject", user=request.user,
+                                                 study=study)
 
         subject_form = SubjectAddForm(request.POST, request.FILES, prefix="subject")
         if study_subject_form.is_valid() and subject_form.is_valid():
@@ -38,7 +38,7 @@ def subject_add(request):
             messages.add_message(request, messages.ERROR, 'Invalid data. Please fix data and try again.')
 
     else:
-        study_subject_form = StudySubjectAddForm(user=request.user, prefix="study_subject")
+        study_subject_form = StudySubjectAddForm(user=request.user, prefix="study_subject", study=study)
         subject_form = SubjectAddForm(prefix="subject")
 
     return wrap_response(request, 'subjects/add.html',
@@ -77,7 +77,8 @@ def subject_edit(request, id):
             # check if subject was marked as dead or resigned
             if subject_form.cleaned_data['dead'] and not was_dead:
                 study_subject.subject.mark_as_dead()
-            if study_subject_form.cleaned_data['resigned'] and not was_resigned:
+            if study_subject.study.columns.resigned \
+                    and study_subject_form.cleaned_data['resigned'] and not was_resigned:
                 study_subject.mark_as_resigned()
             messages.success(request, "Modifications saved")
             if '_continue' in request.POST:
diff --git a/smash/web/views/visit.py b/smash/web/views/visit.py
index 253270a4afe020cb5efaaccbc9654a4c1882f4b6..0dd67c3b3f67cabb631f9b08ee00359081b1694a 100644
--- a/smash/web/views/visit.py
+++ b/smash/web/views/visit.py
@@ -7,7 +7,7 @@ from notifications import get_active_visits_with_missing_appointments, get_unfin
     get_approaching_visits_without_appointments, get_approaching_visits_for_mail_contact, get_exceeded_visits, \
     waiting_for_appointment
 from . import wrap_response
-from ..forms import VisitDetailForm, StudySubjectDetailForm, VisitAddForm, SubjectDetailForm
+from ..forms import VisitDetailForm, VisitAddForm, SubjectDetailForm, StudySubjectDetailForm
 from ..models import Visit, Appointment, StudySubject, MailTemplate
 
 logger = logging.getLogger(__name__)