diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index aa60a1b1fc7004324d05274788d79bf77321ef40..b69183fefa265a33dae2b211e318922abc662c73 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -64,6 +64,14 @@ test_python_latest:
          - coverage run --source web manage.py test -v3
          - coverage report -m --omit="*/test*,*/migrations*,*debug_utils*"
 
+test_create_dummy_script:
+    <<: *test_definition
+    script:
+         - cp "local_settings_ci_sqlite.py" "smash/smash/local_settings.py"
+         - cd smash
+         - python manage.py makemigrations web && python manage.py migrate
+         - python db_scripts/create_dummy_data.py
+
 build_debian:
   image: debian
   stage: build
diff --git a/CHANGELOG b/CHANGELOG
index 1838d9e64722131af63a626dd13e9bf74cfd5932..0d1b7957322c24e147f4675df9f056e136d09c01 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,10 +1,14 @@
 smasch (1.0.0~alpha.1-0) unstable; urgency=low
 
+  * backward incompatible: all ncer/pdp related fields are moved to custom
+    fields and removed from default setup (#345)
   * improvement: added views to delete StudySubject and Subject (#354)
   * improvement: study subject can be configured to contain custom fields
     (#339)
   * improvement: daily automatic import is available in the study configuration
     panel (#334)
+  * improvement: possibility to edit default visible columns in different
+    subject lists (#348)
   * small improvement: "next of keen" renamed to "next of kin" (#362)
   * small improvement: django command for creating admin in application (#347)
   * small improvement: django admin panel contains usable data tables (#346)
diff --git a/smash/db_scripts/create_dummy_data.py b/smash/db_scripts/create_dummy_data.py
index aef885576c0f122d9f64dab0fa4408b6c2b705ed..dadf16b1f1093524e1d864ddfab5edfc9ffa274f 100644
--- a/smash/db_scripts/create_dummy_data.py
+++ b/smash/db_scripts/create_dummy_data.py
@@ -431,15 +431,16 @@ class smashProvider(BaseProvider):
             screening_number = self.createSmashScreeningNumber(
                 default_location.prefix)
 
-        virus_test_1 = choice([None, True, False], 1, p=[0.2, 0.3, 0.5])[0]
+        virus_test_1_result = choice([None, "Positive", "Negative", "Inconclusive"], 1, p=[0.2, 0.2, 0.3, 0.3])[0]
         virus_iga_1 = choice(["Borderline", "Positive", "Negative"], 1, p=[0.02, 0.03, 0.95])[0]
         virus_igg_1 = choice(["Borderline", "Positive", "Negative"], 1, p=[0.02, 0.03, 0.95])[0]
-        if virus_test_1 is None:
+        if virus_test_1_result is None:
+            virus_test_1_collection_date = ''
+            virus_test_1_updated = fake.date_between(start_date='-30d', end_date='-4d')
+        elif virus_test_1_result == "Inconclusive":
             virus_test_1_updated = fake.date_between(start_date='-30d', end_date='-4d')
-            #inconclusive results have a date
-            virus_test_1_updated = choice([None, virus_test_1_updated], 1, p=[0.7, 0.3])[0]
             virus_test_1_collection_date = choice([None, virus_test_1_updated], 1, p=[0.7, 0.3])[0]
-        elif virus_test_1 == True:
+        elif virus_test_1_result == "Positive":
             virus_test_1_updated = fake.date_between(start_date='-30d', end_date='-4d')
             virus_test_1_collection_date = fake.date_between(start_date='-30d', end_date='-4d')
         else:
@@ -447,13 +448,13 @@ class smashProvider(BaseProvider):
             virus_test_1_collection_date = fake.date_between(start_date='-30d', end_date='-4d')
 
         study_subject, _ = StudySubject.objects.update_or_create(nd_number=nd_number, subject=subject,
-                                                                 virus_test_1=virus_test_1, virus_test_1_updated=virus_test_1_updated,
-                                                                 virus_test_1_collection_date=virus_test_1_collection_date,
-                                                                 virus_test_1_iga_status=virus_iga_1,
-                                                                 virus_test_1_igg_status=virus_igg_1,
                                                                  defaults={'default_location': default_location, 'type': type,
                                                                            'screening_number': screening_number, 'study': study})
-
+        study_subject.set_custom_field_value('Visit 0 RT-PCR update date', virus_test_1_updated)
+        study_subject.set_custom_field_value('Virus 0 RT-PCR', virus_test_1_result)
+        study_subject.set_custom_field_value('Visit 0 RT-PCR collection date', virus_test_1_collection_date)
+        study_subject.set_custom_field_value('Visit 0 IgA Status', virus_iga_1)
+        study_subject.set_custom_field_value('Visit 0 IgG Status', virus_igg_1)
         self.alreadyCreatedStudySubjects.append(study_subject)
 
         return study_subject
@@ -607,7 +608,7 @@ class smashProvider(BaseProvider):
         defaults = {'first_name': first_name, 'last_name': last_name,
                     'email': email, 'unit': unit, 'specialization': specialization,
                     'phone_number': phone_number, 'user': user}
-        
+
         worker, _ = Worker.objects.update_or_create(first_name=first_name,
                                                     last_name=last_name, defaults=defaults)
 
diff --git a/smash/db_scripts/import_file.py b/smash/db_scripts/import_file.py
index d8d8d18406cdf77f3120eef893e44fb2cf266907..efae7af09d72f94171747796904a68456bf075b8 100644
--- a/smash/db_scripts/import_file.py
+++ b/smash/db_scripts/import_file.py
@@ -504,7 +504,6 @@ def parse_row(index, row, visit_columns, appointmentTypes, voucher_types, lcsb_w
         'type': SUBJECT_TYPE_CHOICES_PATIENT,
         'excluded': row['EXCLUDED'],
         'exclude_reason': row['REASON.1'],
-        'previously_in_study': row['PDP 1.0'],
         'comments': row['COMMENT'],
         'default_location': location,
         'flying_team': ft,
diff --git a/smash/web/api_views/appointment.py b/smash/web/api_views/appointment.py
index d3dcef780df268db9b5fdc4bf3a74f91e2f6a429..6100440f9e7c0ca6ead923cf10a976780f42efe9 100644
--- a/smash/web/api_views/appointment.py
+++ b/smash/web/api_views/appointment.py
@@ -33,7 +33,7 @@ def get_appointment_columns(request, appointment_list_type):
     result = []
     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, "ND number", "nd_number", subject_study_columns, "string_filter")
+    add_column(result, "Subject number", "nd_number", subject_study_columns, "string_filter")
     add_column(result, "Type", "type", subject_study_columns, "type_filter")
     add_column(result, "Info sent", "post_mail_sent", appointment_columns, "yes_no_filter")
     add_column(result, "Date", "datetime_when", appointment_columns, None)
diff --git a/smash/web/api_views/serialization_utils.py b/smash/web/api_views/serialization_utils.py
index 0994996d0d71d93e41f7ad2361454fdc85708c09..652079a60ee5d8c944c7231c88d6b4587ec6aa52 100644
--- a/smash/web/api_views/serialization_utils.py
+++ b/smash/web/api_views/serialization_utils.py
@@ -34,17 +34,6 @@ def str_to_yes_no_null(val: str):
         return "NO"
 
 
-def virus_test_to_str(test, date):
-    if test is None and date is not None:
-        return "Inconclusive"
-    if test is None:
-        return "N/A"
-    if test:
-        return "Positive"
-    else:
-        return "Negative"
-
-
 def flying_team_to_str(flying_team):
     result = ""
     if flying_team is not None:
diff --git a/smash/web/api_views/subject.py b/smash/web/api_views/subject.py
index 8a3b3b30fb56e6afc7f9c747bd3545c8944b405a..ec39cb03d6819fc5a4451afa559f394a574b5321 100644
--- a/smash/web/api_views/subject.py
+++ b/smash/web/api_views/subject.py
@@ -7,8 +7,8 @@ from django.db.models import Q
 from django.http import JsonResponse
 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, virus_test_to_str, str_to_yes_no
+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, \
@@ -54,7 +54,7 @@ def get_subject_columns(request, subject_list_type):
         study_subject_columns = StudyColumns()
 
     result = []
-    add_column(result, "ND", "nd_number", study_subject_columns, "string_filter", study.columns)
+    add_column(result, "Subject number", "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")
@@ -80,8 +80,6 @@ def get_subject_columns(request, subject_list_type):
     add_column(result, "Next of kin", "next_of_kin_name", subject_columns, "string_filter")
     add_column(result, "Next of kin phone", "next_of_kin_phone", subject_columns, "string_filter")
     add_column(result, "Next of kin address", "next_of_kin_address", subject_columns, "string_filter")
-    add_column(result, "Brain donation agreement", "brain_donation_agreement", study_subject_columns, "yes_no_filter",
-               study.columns)
     add_column(result, "Excluded", "excluded", study_subject_columns, "yes_no_filter", study.columns)
     add_column(result, "Info sent", "information_sent", study_subject_columns, "yes_no_filter", study.columns)
 
@@ -94,49 +92,24 @@ 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))
 
-    for one_based_idx, virus_visit_number in enumerate(virus_visit_numbers, 1):
-        add_column(result,
-                   'Virus {} RT-PCR'.format(virus_visit_number),
-                   'virus_test_{}'.format(one_based_idx),  # always starts in 1
-                   study_subject_columns,
-                   'yes_no_null_inconclusive_filter', study.columns)
-        add_column(result,
-                   "Visit {} RT-PCR update date".format(virus_visit_number),
-                   "virus_test_{}_updated".format(one_based_idx),
-                   study_subject_columns,
-                   "virus_test_date", study.columns)
-        add_column(result,
-                   "Visit {} RT-PCR collection date".format(virus_visit_number),
-                   "virus_test_{}_collection_date".format(one_based_idx),
-                   study_subject_columns,
-                   "virus_test_date", study.columns)
-        add_column(result,
-                   "Visit {} IgA Status".format(virus_visit_number),
-                   "virus_test_{}_iga_status".format(one_based_idx),
-                   study_subject_columns,
-                   'serology_filter', study.columns)
-        add_column(result,
-                   "Visit {} IgG Status".format(virus_visit_number),
-                   "virus_test_{}_igg_status".format(one_based_idx),
-                   study_subject_columns,
-                   'serology_filter', study.columns)
 
     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)
         if custom_study_subject_field.type == CUSTOM_FIELD_TYPE_TEXT:
             add_column(result,
                        custom_study_subject_field.name,
                        get_study_subject_field_id(custom_study_subject_field),
                        study_subject_columns,
                        "string_filter",
-                       visible_param=False)
+                       visible_param=visible)
         elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_BOOLEAN:
             add_column(result,
                        custom_study_subject_field.name,
                        get_study_subject_field_id(custom_study_subject_field),
                        study_subject_columns,
                        "yes_no_filter",
-                       visible_param=False)
+                       visible_param=visible)
         elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_INTEGER:
             add_column(result,
                        custom_study_subject_field.name,
@@ -144,7 +117,7 @@ def get_subject_columns(request, subject_list_type):
                        study_subject_columns,
                        None,
                        sortable=False,
-                       visible_param=False)
+                       visible_param=visible)
         elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_DOUBLE:
             add_column(result,
                        custom_study_subject_field.name,
@@ -152,28 +125,28 @@ def get_subject_columns(request, subject_list_type):
                        study_subject_columns,
                        None,
                        sortable=False,
-                       visible_param=False)
+                       visible_param=visible)
         elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_DATE:
             add_column(result,
                        custom_study_subject_field.name,
                        get_study_subject_field_id(custom_study_subject_field),
                        study_subject_columns,
                        None,
-                       visible_param=False)
+                       visible_param=visible)
         elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_SELECT_LIST:
             add_column(result,
                        custom_study_subject_field.name,
                        get_study_subject_field_id(custom_study_subject_field),
                        study_subject_columns,
                        'select_filter:' + custom_study_subject_field.possible_values,
-                       visible_param=False)
+                       visible_param=visible)
         elif custom_study_subject_field.type == CUSTOM_FIELD_TYPE_FILE:
             add_column(result,
                        custom_study_subject_field.name,
                        get_study_subject_field_id(custom_study_subject_field),
                        study_subject_columns,
                        'select_filter:N/A;Available',
-                       visible_param=False)
+                       visible_param=visible)
         else:
             raise NotImplementedError
 
@@ -260,8 +233,6 @@ def get_subjects_order(subjects_to_be_ordered: QuerySet, order_column, order_dir
         result = subjects_to_be_ordered.order_by(order_direction + 'subject__social_security_number')
     elif order_column == "postponed":
         result = subjects_to_be_ordered.order_by(order_direction + 'postponed')
-    elif order_column == "brain_donation_agreement":
-        result = subjects_to_be_ordered.order_by(order_direction + 'brain_donation_agreement')
     elif order_column == "excluded":
         result = subjects_to_be_ordered.order_by(order_direction + 'excluded')
     elif order_column == "type":
@@ -279,16 +250,6 @@ def get_subjects_order(subjects_to_be_ordered: QuerySet, order_column, order_dir
     elif str(order_column).startswith("visit_"):
         visit_number = get_visit_number_from_visit_x_string(order_column)
         result = order_by_visit(subjects_to_be_ordered, order_direction, visit_number)
-    elif re.search(r'^virus_test_[1-5]$', order_column):
-        result = subjects_to_be_ordered.order_by(order_direction + order_column)
-    elif re.search(r'^virus_test_[1-5]_updated$', order_column):
-        result = subjects_to_be_ordered.order_by(order_direction + order_column)
-    elif re.search(r'^virus_test_[1-5]_collection_date', order_column):
-        result = subjects_to_be_ordered.order_by(order_direction + order_column)
-    elif re.search(r'^virus_test_[1-5]_iga_status', order_column):
-        result = subjects_to_be_ordered.order_by(order_direction + order_column)
-    elif re.search(r'^virus_test_[1-5]_igg_status', order_column):
-        result = subjects_to_be_ordered.order_by(order_direction + order_column)
     elif re.search(r'^custom_field-[0-9]+$', order_column):
         field_id = int(order_column.replace("custom_field-", ""))
         result = subjects_to_be_ordered.annotate(
@@ -396,27 +357,6 @@ def get_subjects_filtered(subjects_to_be_filtered: QuerySet, filters) -> QuerySe
             result = result.filter(resigned=(value == "true"))
         elif column == "endpoint_reached":
             result = result.filter(endpoint_reached=(value == "true"))
-        elif re.search(r'^virus_test_[1-5]$', column):
-            visit_number = column.replace("virus_test_", "")
-            virus_test__isnull_param = "virus_test_{}__isnull".format(visit_number)
-            virus_test__updated__isnull_param = "virus_test_{}_updated__isnull".format(visit_number)
-            visit_test_param = "virus_test_{}".format(visit_number)
-            if value == "null":
-                result = result.filter(**{virus_test__isnull_param: True, virus_test__updated__isnull_param: True})
-            elif value == "inconclusive":
-                result = result.filter(**{virus_test__isnull_param: True, virus_test__updated__isnull_param: False})
-            else:
-                result = result.filter(**{visit_test_param: (value == "true")})
-        elif re.search(r'^virus_test_[1-5]_iga_status$', column):
-            visit_number = column.replace("virus_test_", "").replace("_iga_status", "")
-            virus_test_iga_status_param = "virus_test_{}_iga_status".format(visit_number)
-            result = result.filter(**{virus_test_iga_status_param: value})
-        elif re.search(r'^virus_test_[1-5]_igg_status$', column):
-            visit_number = column.replace("virus_test_", "").replace("_igg_status", "")
-            virus_test_igg_status_param = "virus_test_{}_igg_status".format(visit_number)
-            result = result.filter(**{virus_test_igg_status_param: value})
-        elif column == "brain_donation_agreement":
-            result = result.filter(brain_donation_agreement=(value == "true"))
         elif column == "postponed":
             result = result.filter(postponed=(value == "true"))
         elif column == "excluded":
@@ -529,7 +469,7 @@ def types(request):
     })
 
 
-def serialize_subject(study_subject):
+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')
@@ -608,7 +548,6 @@ def serialize_subject(study_subject):
         "resigned": bool_to_yes_no(study_subject.resigned),
         "endpoint_reached": bool_to_yes_no(study_subject.endpoint_reached),
         "postponed": bool_to_yes_no(study_subject.postponed),
-        "brain_donation_agreement": bool_to_yes_no(study_subject.brain_donation_agreement),
         "excluded": bool_to_yes_no(study_subject.excluded),
         "information_sent": bool_to_yes_no(study_subject.information_sent),
         "health_partner_first_name": health_partner_first_name,
@@ -618,14 +557,6 @@ def serialize_subject(study_subject):
         "id": study_subject.id,
         "visits": serialized_visits,
     }
-    for i in range(1, 6):
-        result['virus_test_{}'.format(i)] = virus_test_to_str(getattr(study_subject, "virus_test_{}".format(i)),
-                                                              getattr(study_subject, "virus_test_{}_updated".format(i)))
-        result["virus_test_{}_updated".format(i)] = getattr(study_subject, "virus_test_{}_updated".format(i))
-        result["virus_test_{}_collection_date".format(i)] = getattr(study_subject,
-                                                                    "virus_test_{}_collection_date".format(i))
-        result["virus_test_{}_iga_status".format(i)] = getattr(study_subject, "virus_test_{}_iga_status".format(i))
-        result["virus_test_{}_igg_status".format(i)] = getattr(study_subject, "virus_test_{}_igg_status".format(i))
 
     for field_value in study_subject.custom_data_values:
         if field_value.study_subject_field.type == CUSTOM_FIELD_TYPE_TEXT \
diff --git a/smash/web/api_views/visit.py b/smash/web/api_views/visit.py
index 276006fc18646ec03ca6052c62c0872918e2eb2a..b13b7c224f8c99c09c6bfc38ac0aef6b52a77326 100644
--- a/smash/web/api_views/visit.py
+++ b/smash/web/api_views/visit.py
@@ -39,7 +39,7 @@ def get_visit_columns(request, visit_list_type):
     result = []
     add_column(result, "First name", "first_name", visit_subject_columns, "string_filter")
     add_column(result, "Last name", "last_name", visit_subject_columns, "string_filter")
-    add_column(result, "ND number", "nd_number", visit_subject_study_columns, "string_filter", study.columns)
+    add_column(result, "Subject number", "nd_number", visit_subject_study_columns, "string_filter", study.columns)
     add_column(result, "Location", "default_location", visit_subject_study_columns, "location_filter", study.columns)
     add_column(result, "Flying team location", "flying_team", visit_subject_study_columns, "flying_team_filter",
                study.columns)
diff --git a/smash/web/forms/study_subject_forms.py b/smash/web/forms/study_subject_forms.py
index 6211544bd6817c549a35561bb9ce1e60786a3b86..c22662cd92ab569560c54da23d79d165d8b322ed 100644
--- a/smash/web/forms/study_subject_forms.py
+++ b/smash/web/forms/study_subject_forms.py
@@ -1,15 +1,14 @@
 import datetime
 import logging
 import re
-from distutils.util import strtobool
 
 from django import forms
 from django.forms import ModelForm
 
 from web.forms.forms import DATETIMEPICKER_DATE_ATTRS, get_worker_from_args, DATEPICKER_DATE_ATTRS
-from web.models import ConfigurationItem, StudySubject, Study, StudyColumns, VoucherType, Worker
-from web.models.constants import SCREENING_NUMBER_PREFIXES_FOR_TYPE, VISIT_SHOW_VISIT_NUMBER_FROM_ZERO, \
-    CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_DOUBLE, \
+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, \
+    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
 from web.models.custom_data.custom_study_subject_field import get_study_subject_field_id
@@ -81,6 +80,7 @@ def create_field_for_custom_study_subject_field(study_subject_field: CustomStudy
 
                 def __unicode__(self):
                     return "%s" % self.url
+
             initial = CustomFileField()
             initial.url = val
 
@@ -119,41 +119,6 @@ class StudySubjectForm(ModelForm):
         self.fields['health_partner'].queryset = Worker.get_workers_by_worker_type(
             WORKER_HEALTH_PARTNER)
 
-        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.
-        if strtobool(visit_from_zero):
-            virus_visit_numbers = list(range(0, 5))
-            for one_based_idx, virus_visit_number in enumerate(virus_visit_numbers, 1):
-                field = 'virus_test_{}'.format(one_based_idx)
-                self.fields[field].label = 'Visit {} RT-PCR'.format(virus_visit_number)
-
-                date_field = 'virus_test_{}_updated'.format(one_based_idx)
-                self.fields[date_field].label = 'Visit {} RT-PCR result date'.format(virus_visit_number)
-
-                collection_date_field = 'virus_test_{}_collection_date'.format(one_based_idx)
-                self.fields[collection_date_field].label = 'Visit {} RT-PCR collection date'.format(virus_visit_number)
-
-                iga_field = 'virus_test_{}_iga_status'.format(one_based_idx)
-                igg_field = 'virus_test_{}_igg_status'.format(one_based_idx)
-                self.fields[iga_field].label = 'Visit {} IgA status'.format(virus_visit_number)
-                self.fields[igg_field].label = 'Visit {} IgG status'.format(virus_visit_number)
-
-        for visit_number in range(1, 6):
-            disable_virus_test_field(self, visit_number)
-            self.update_virus_inconclusive_data(visit_number)
-
-    def update_virus_inconclusive_data(self, visit_number):
-        test_result_column_name = 'virus_test_{}'.format(visit_number)
-        test_date_column_name = 'virus_test_{}_updated'.format(visit_number)
-
-        self.fields[test_result_column_name].widget.choices.append(("Inc", 'Inconclusive'))
-        instance = getattr(self, 'instance', None)
-        if instance and instance.id:
-            test_result = getattr(instance, test_result_column_name)
-            test_date = getattr(instance, test_date_column_name)
-            if test_result is None and test_date is not None:
-                self.initial[test_result_column_name] = "Inc"
-
     def clean(self):
         cleaned_data = super(StudySubjectForm, self).clean()
         subject_id = -1
@@ -185,8 +150,6 @@ class StudySubjectAddForm(StudySubjectForm):
         self.study = get_study_from_args(kwargs)
 
         super(StudySubjectAddForm, self).__init__(*args, **kwargs)
-        for visit_number in range(1, 6):
-            disable_virus_test_field(self, visit_number)
 
         prepare_study_subject_fields(fields=self.fields, study=self.study)
 
@@ -214,7 +177,6 @@ class StudySubjectAddForm(StudySubjectForm):
             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):
@@ -322,7 +284,6 @@ class StudySubjectEditForm(StudySubjectForm):
     def clean(self):
         cleaned_data = super(StudySubjectEditForm, self).clean()
         validate_subject_nd_number(self, cleaned_data)
-        validate_subject_mpower_number(self, cleaned_data)
         validate_subject_resign_reason(self, cleaned_data)
         return cleaned_data
 
@@ -337,20 +298,6 @@ class StudySubjectEditForm(StudySubjectForm):
         fields = '__all__'
 
 
-def disable_virus_test_field(self, visit_number):
-    test_result_column_name = 'virus_test_{}'.format(visit_number)
-    test_result_date_column_name = 'virus_test_{}_updated'.format(visit_number)
-    test_collection_date_column_name = 'virus_test_{}_collection_date'.format(visit_number)
-    test_iga_status_column_name = 'virus_test_{}_iga_status'.format(visit_number)
-    test_igg_status_column_name = 'virus_test_{}_igg_status'.format(visit_number)
-    self.fields[test_result_column_name].widget.attrs['readonly'] = True
-    self.fields[test_result_column_name].widget.attrs['disabled'] = True
-    self.fields[test_result_date_column_name].widget.attrs['readonly'] = True
-    self.fields[test_collection_date_column_name].widget.attrs['readonly'] = True
-    self.fields[test_iga_status_column_name].widget.attrs['readonly'] = True
-    self.fields[test_igg_status_column_name].widget.attrs['readonly'] = True
-
-
 def get_study_from_args(kwargs):
     study = kwargs.pop('study', None)
     if study is None:
@@ -372,15 +319,10 @@ def prepare_study_subject_fields(fields, study):
     prepare_field(fields, study.columns, 'nd_number')
     prepare_field(fields, study.columns, 'datetime_contact_reminder')
     prepare_field(fields, study.columns, 'postponed')
-    prepare_field(fields, study.columns, 'brain_donation_agreement')
     prepare_field(fields, study.columns, 'flying_team')
-    prepare_field(fields, study.columns, 'mpower_id')
     prepare_field(fields, study.columns, 'comments')
     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, 'endpoint_reached')
     prepare_field(fields, study.columns, 'excluded')
     prepare_field(fields, study.columns, 'resigned')
@@ -388,36 +330,8 @@ def prepare_study_subject_fields(fields, study):
     prepare_field(fields, study.columns, 'referral_letter')
     prepare_field(fields, study.columns, 'health_partner')
     prepare_field(fields, study.columns, 'health_partner_feedback_agreement')
-    prepare_field(fields, study.columns, 'screening')
-    prepare_field(fields, study.columns, 'previously_in_study')
     prepare_field(fields, study.columns, 'voucher_types')
 
-    prepare_field(fields, study.columns, 'virus_test_1')
-    prepare_field(fields, study.columns, 'virus_test_2')
-    prepare_field(fields, study.columns, 'virus_test_3')
-    prepare_field(fields, study.columns, 'virus_test_4')
-    prepare_field(fields, study.columns, 'virus_test_5')
-    prepare_field(fields, study.columns, 'virus_test_1_updated')
-    prepare_field(fields, study.columns, 'virus_test_2_updated')
-    prepare_field(fields, study.columns, 'virus_test_3_updated')
-    prepare_field(fields, study.columns, 'virus_test_4_updated')
-    prepare_field(fields, study.columns, 'virus_test_5_updated')
-    prepare_field(fields, study.columns, 'virus_test_1_collection_date')
-    prepare_field(fields, study.columns, 'virus_test_2_collection_date')
-    prepare_field(fields, study.columns, 'virus_test_3_collection_date')
-    prepare_field(fields, study.columns, 'virus_test_4_collection_date')
-    prepare_field(fields, study.columns, 'virus_test_5_collection_date')
-    prepare_field(fields, study.columns, 'virus_test_1_iga_status')
-    prepare_field(fields, study.columns, 'virus_test_1_igg_status')
-    prepare_field(fields, study.columns, 'virus_test_2_iga_status')
-    prepare_field(fields, study.columns, 'virus_test_2_igg_status')
-    prepare_field(fields, study.columns, 'virus_test_3_iga_status')
-    prepare_field(fields, study.columns, 'virus_test_3_igg_status')
-    prepare_field(fields, study.columns, 'virus_test_4_iga_status')
-    prepare_field(fields, study.columns, 'virus_test_4_igg_status')
-    prepare_field(fields, study.columns, 'virus_test_5_iga_status')
-    prepare_field(fields, study.columns, 'virus_test_5_igg_status')
-
 
 def validate_subject_screening_number(self, cleaned_data):
     if self.study.columns.resign_reason:
@@ -440,7 +354,7 @@ def validate_subject_nd_number(self, cleaned_data):
                     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")
+                        self.add_error('nd_number', "Subject number already in use")
         # else: #empty nd_number is valid
 
 
@@ -448,13 +362,3 @@ 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/study_subject_list_form.py b/smash/web/forms/study_subject_list_form.py
index 134306d364211a81989b67072de78ad0cc170064..545b53deb3c2683b186f74b2dc19b6767d56777d 100644
--- a/smash/web/forms/study_subject_list_form.py
+++ b/smash/web/forms/study_subject_list_form.py
@@ -1,6 +1,8 @@
+from django import forms
 from django.forms import ModelForm
 
 from web.models import StudySubjectList, SubjectColumns, StudyColumns
+from web.models.custom_data.custom_study_subject_field import get_study_subject_field_id
 
 
 class StudySubjectListEditForm(ModelForm):
@@ -30,3 +32,17 @@ class StudySubjectColumnsEditForm(ModelForm):
 
     def __init__(self, *args, **kwargs):
         super().__init__(*args, **kwargs)
+        instance: StudyColumns = kwargs.get('instance')
+        for visibility in instance.custom_fields_visibility:
+            field = visibility.study_subject_field
+            field_id = get_study_subject_field_id(field)
+            self.fields[field_id] = forms.BooleanField(label=field.name, initial=visibility.visible, required=False)
+
+    def save(self, commit=True) -> StudyColumns:
+        instance = super().save(commit)
+        # we can add custom values only after object exists in the database
+        for visibility in instance.custom_fields_visibility:
+            field = self[get_study_subject_field_id(visibility.study_subject_field)]
+            visibility.visible = str(field.value()).lower() == "true"
+            visibility.save()
+        return instance
diff --git a/smash/web/migrations/0187_auto_20201130_1224.py b/smash/web/migrations/0187_auto_20201130_1224.py
new file mode 100644
index 0000000000000000000000000000000000000000..44d54c081f27749d1966a997b44f0149cb3117ab
--- /dev/null
+++ b/smash/web/migrations/0187_auto_20201130_1224.py
@@ -0,0 +1,44 @@
+# Generated by Django 3.1.3 on 2020-11-30 12:24
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0186_auto_20201127_1003'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='studysubjectlist',
+            name='study',
+            field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.study'),
+        ),
+        migrations.AlterField(
+            model_name='studysubjectlist',
+            name='type',
+            field=models.CharField(blank=True, choices=[('GENERIC', 'Generic subject list'), ('NO_VISIT', 'Subjects without visit'), ('REQUIRE_CONTACT', 'Subjects required contact'), ('VOUCHER_EXPIRY', 'Subject with vouchers to be expired soon')], editable=False, max_length=50, null=True, verbose_name='Type of list'),
+        ),
+        migrations.AlterField(
+            model_name='studysubjectlist',
+            name='visible_subject_columns',
+            field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.subjectcolumns'),
+        ),
+        migrations.AlterField(
+            model_name='studysubjectlist',
+            name='visible_subject_study_columns',
+            field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.studycolumns'),
+        ),
+        migrations.CreateModel(
+            name='CustomStudySubjectVisibility',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('visible', models.BooleanField(default=False)),
+                ('study_subject_field', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.customstudysubjectfield', verbose_name='Custom Field')),
+                ('visible_subject_study_columns', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, to='web.studycolumns', verbose_name='List of visible columns')),
+            ],
+        ),
+
+    ]
diff --git a/smash/web/migrations/0188_studysubject_virus_test_1_result.py b/smash/web/migrations/0188_studysubject_virus_test_1_result.py
new file mode 100644
index 0000000000000000000000000000000000000000..5a7403bbaf2b91320cedfc11cf87dd4d662007db
--- /dev/null
+++ b/smash/web/migrations/0188_studysubject_virus_test_1_result.py
@@ -0,0 +1,98 @@
+# Generated by Django 3.1.3 on 2020-12-01 07:55
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0187_auto_20201130_1224'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_1_result',
+            field=models.CharField(max_length=20, null=True),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_2_result',
+            field=models.CharField(max_length=20, null=True),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_3_result',
+            field=models.CharField(max_length=20, null=True),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_4_result',
+            field=models.CharField(max_length=20, null=True),
+        ),
+        migrations.AddField(
+            model_name='studysubject',
+            name='virus_test_5_result',
+            field=models.CharField(max_length=20, null=True),
+        ),
+        migrations.RunSQL("update web_studysubject set virus_test_1_result='Positive' "
+                          "where virus_test_1 is not null and virus_test_1"),
+        migrations.RunSQL("update web_studysubject set virus_test_1_result='Negative' "
+                          "where virus_test_1 is not null and not virus_test_1"),
+        migrations.RunSQL("update web_studysubject set virus_test_1_result='Inconclusive' "
+                          "where virus_test_1 is null and virus_test_1_updated is not null"),
+
+        migrations.RunSQL("update web_studysubject set virus_test_2_result='Positive' "
+                          "where virus_test_2 is not null and virus_test_2"),
+        migrations.RunSQL("update web_studysubject set virus_test_2_result='Negative' "
+                          "where virus_test_2 is not null and not virus_test_2"),
+        migrations.RunSQL("update web_studysubject set virus_test_2_result='Inconclusive' "
+                          "where virus_test_2 is null and virus_test_2_updated is not null"),
+
+        migrations.RunSQL("update web_studysubject set virus_test_3_result='Positive' "
+                          "where virus_test_3 is not null and virus_test_3"),
+        migrations.RunSQL("update web_studysubject set virus_test_3_result='Negative' "
+                          "where virus_test_3 is not null and not virus_test_3"),
+        migrations.RunSQL("update web_studysubject set virus_test_3_result='Inconclusive' "
+                          "where virus_test_3 is null and virus_test_3_updated is not null"),
+
+        migrations.RunSQL("update web_studysubject set virus_test_4_result='Positive' "
+                          "where virus_test_4 is not null and virus_test_4"),
+        migrations.RunSQL("update web_studysubject set virus_test_4_result='Negative' "
+                          "where virus_test_4 is not null and not virus_test_4"),
+        migrations.RunSQL("update web_studysubject set virus_test_4_result='Inconclusive' "
+                          "where virus_test_4 is null and virus_test_4_updated is not null"),
+
+        migrations.RunSQL("update web_studysubject set virus_test_5_result='Positive' "
+                          "where virus_test_5 is not null and virus_test_5"),
+        migrations.RunSQL("update web_studysubject set virus_test_5_result='Negative' "
+                          "where virus_test_5 is not null and not virus_test_5"),
+        migrations.RunSQL("update web_studysubject set virus_test_5_result='Inconclusive' "
+                          "where virus_test_5 is null and virus_test_5_updated is not null"),
+        migrations.RenameField(
+            model_name='studycolumns',
+            old_name='virus_test_1',
+            new_name='virus_test_1_result',
+        ),
+        migrations.RenameField(
+            model_name='studycolumns',
+            old_name='virus_test_2',
+            new_name='virus_test_2_result',
+        ),
+        migrations.RenameField(
+            model_name='studycolumns',
+            old_name='virus_test_3',
+            new_name='virus_test_3_result',
+        ),
+        migrations.RenameField(
+            model_name='studycolumns',
+            old_name='virus_test_4',
+            new_name='virus_test_4_result',
+        ),
+        migrations.RenameField(
+            model_name='studycolumns',
+            old_name='virus_test_5',
+            new_name='virus_test_5_result',
+        ),
+
+    ]
diff --git a/smash/web/migrations/0189_remove_studysubject_virus_test_1.py b/smash/web/migrations/0189_remove_studysubject_virus_test_1.py
new file mode 100644
index 0000000000000000000000000000000000000000..e18f54cb8db7ba0a3d6dd133526dfac6e058e906
--- /dev/null
+++ b/smash/web/migrations/0189_remove_studysubject_virus_test_1.py
@@ -0,0 +1,33 @@
+# Generated by Django 3.1.3 on 2020-12-01 08:03
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0188_studysubject_virus_test_1_result'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_1',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_2',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_3',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_4',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_5',
+        ),
+    ]
diff --git a/smash/web/migrations/0190_remove_study_related_fields.py b/smash/web/migrations/0190_remove_study_related_fields.py
new file mode 100644
index 0000000000000000000000000000000000000000..c95ddbfc084cd43f46ee672c19e653349e56c27e
--- /dev/null
+++ b/smash/web/migrations/0190_remove_study_related_fields.py
@@ -0,0 +1,267 @@
+# Generated by Django 3.1.3 on 2020-12-01 08:03
+from django.db import migrations
+
+# noinspection PyUnusedLocal
+from web.models.constants import CUSTOM_FIELD_TYPE_TEXT, GLOBAL_STUDY_ID, CUSTOM_FIELD_TYPE_BOOLEAN, \
+    CUSTOM_FIELD_TYPE_INTEGER, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_DATE
+
+
+def create_custom_fields(apps, schema_editor):
+    # noinspection PyPep8Naming
+    StudySubject = apps.get_model("web", "StudySubject")
+    if StudySubject.objects.all().count() > 0:
+        # noinspection PyPep8Naming
+        CustomStudySubjectField = apps.get_model("web", "CustomStudySubjectField")
+        # noinspection PyPep8Naming
+        CustomStudySubjectValue = apps.get_model("web", "CustomStudySubjectValue")
+
+        mpower_field = CustomStudySubjectField.objects.create(name="MPower ID", type=CUSTOM_FIELD_TYPE_TEXT,
+                                                              study_id=GLOBAL_STUDY_ID, unique=True)
+        screening_field = CustomStudySubjectField.objects.create(name="Screening", type=CUSTOM_FIELD_TYPE_TEXT,
+                                                                 study_id=GLOBAL_STUDY_ID)
+        diagnosis_field = CustomStudySubjectField.objects.create(name="Diagnosis", type=CUSTOM_FIELD_TYPE_TEXT,
+                                                                 study_id=GLOBAL_STUDY_ID)
+        previously_in_study_field = CustomStudySubjectField.objects.create(name="Previously in PDP study",
+                                                                           type=CUSTOM_FIELD_TYPE_BOOLEAN,
+                                                                           study_id=GLOBAL_STUDY_ID)
+        pd_in_family_field = CustomStudySubjectField.objects.create(name="PD in family",
+                                                                    type=CUSTOM_FIELD_TYPE_BOOLEAN,
+                                                                    study_id=GLOBAL_STUDY_ID)
+        brain_donation_agreement_field = CustomStudySubjectField.objects.create(name="Brain donation agreement",
+                                                                                type=CUSTOM_FIELD_TYPE_BOOLEAN,
+                                                                                study_id=GLOBAL_STUDY_ID)
+        year_of_diagnosis_field = CustomStudySubjectField.objects.create(name="Year of diagnosis",
+                                                                         type=CUSTOM_FIELD_TYPE_INTEGER,
+                                                                         study_id=GLOBAL_STUDY_ID)
+
+        virus_test_1_result_field = CustomStudySubjectField.objects.create(name="Virus 0 RT-PCR",
+                                                                           type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+                                                                           study_id=GLOBAL_STUDY_ID,
+                                                                           possible_values="N/A;Positive;Negative;Inconclusive",
+                                                                           default="N/A",
+                                                                           readonly=True)
+        virus_test_2_result_field = CustomStudySubjectField.objects.create(name="Virus 1 RT-PCR",
+                                                                           type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+                                                                           study_id=GLOBAL_STUDY_ID,
+                                                                           possible_values="N/A;Positive;Negative;Inconclusive",
+                                                                           default="N/A",
+                                                                           readonly=True)
+        virus_test_3_result_field = CustomStudySubjectField.objects.create(name="Virus 2 RT-PCR",
+                                                                           type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+                                                                           study_id=GLOBAL_STUDY_ID,
+                                                                           possible_values="N/A;Positive;Negative;Inconclusive",
+                                                                           default="N/A",
+                                                                           readonly=True)
+        virus_test_4_result_field = CustomStudySubjectField.objects.create(name="Virus 3 RT-PCR",
+                                                                           type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+                                                                           study_id=GLOBAL_STUDY_ID,
+                                                                           possible_values="N/A;Positive;Negative;Inconclusive",
+                                                                           default="N/A",
+                                                                           readonly=True)
+        virus_test_5_result_field = CustomStudySubjectField.objects.create(name="Virus 4 RT-PCR",
+                                                                           type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+                                                                           study_id=GLOBAL_STUDY_ID,
+                                                                           possible_values="N/A;Positive;Negative;Inconclusive",
+                                                                           default="N/A",
+                                                                           readonly=True)
+        virus_test_1_updated_field = CustomStudySubjectField.objects.create(name="Visit 0 RT-PCR update date",
+                                                                            type=CUSTOM_FIELD_TYPE_DATE,
+                                                                            study_id=GLOBAL_STUDY_ID,
+                                                                            default="",
+                                                                            readonly=True)
+        virus_test_2_updated_field = CustomStudySubjectField.objects.create(name="Visit 1 RT-PCR update date",
+                                                                            type=CUSTOM_FIELD_TYPE_DATE,
+                                                                            study_id=GLOBAL_STUDY_ID,
+                                                                            default="",
+                                                                            readonly=True)
+        virus_test_3_updated_field = CustomStudySubjectField.objects.create(name="Visit 2 RT-PCR update date",
+                                                                            type=CUSTOM_FIELD_TYPE_DATE,
+                                                                            study_id=GLOBAL_STUDY_ID,
+                                                                            default="",
+                                                                            readonly=True)
+        virus_test_4_updated_field = CustomStudySubjectField.objects.create(name="Visit 3 RT-PCR update date",
+                                                                            type=CUSTOM_FIELD_TYPE_DATE,
+                                                                            study_id=GLOBAL_STUDY_ID,
+                                                                            default="",
+                                                                            readonly=True)
+        virus_test_5_updated_field = CustomStudySubjectField.objects.create(name="Visit 4 RT-PCR update date",
+                                                                            type=CUSTOM_FIELD_TYPE_DATE,
+                                                                            study_id=GLOBAL_STUDY_ID,
+                                                                            default="",
+                                                                            readonly=True)
+        virus_test_1_collection_date_field = CustomStudySubjectField.objects.create(
+            name="Visit 0 RT-PCR collection date",
+            type=CUSTOM_FIELD_TYPE_DATE,
+            study_id=GLOBAL_STUDY_ID,
+            default="",
+            readonly=True)
+        virus_test_2_collection_date_field = CustomStudySubjectField.objects.create(
+            name="Visit 1 RT-PCR collection date",
+            type=CUSTOM_FIELD_TYPE_DATE,
+            study_id=GLOBAL_STUDY_ID,
+            default="",
+            readonly=True)
+        virus_test_3_collection_date_field = CustomStudySubjectField.objects.create(
+            name="Visit 2 RT-PCR collection date",
+            type=CUSTOM_FIELD_TYPE_DATE,
+            study_id=GLOBAL_STUDY_ID,
+            default="",
+            readonly=True)
+        virus_test_4_collection_date_field = CustomStudySubjectField.objects.create(
+            name="Visit 3 RT-PCR collection date",
+            type=CUSTOM_FIELD_TYPE_DATE,
+            study_id=GLOBAL_STUDY_ID,
+            default="",
+            readonly=True)
+        virus_test_5_collection_date_field = CustomStudySubjectField.objects.create(
+            name="Visit 4 RT-PCR collection date",
+            type=CUSTOM_FIELD_TYPE_DATE,
+            study_id=GLOBAL_STUDY_ID,
+            default="",
+            readonly=True)
+        virus_test_1_iga_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 0 IgA Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_2_iga_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 1 IgA Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_3_iga_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 2 IgA Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_4_iga_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 3 IgA Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_5_iga_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 4 IgA Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_1_igg_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 0 IgG Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_2_igg_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 1 IgG Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_3_igg_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 2 IgG Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_4_igg_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 3 IgG Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+        virus_test_5_igg_status_field = CustomStudySubjectField.objects.create(
+            name="Visit 4 IgG Status",
+            type=CUSTOM_FIELD_TYPE_SELECT_LIST,
+            study_id=GLOBAL_STUDY_ID,
+            possible_values="N/A;Positive;Negative;Borderline",
+            default="N/A",
+            readonly=True)
+
+        for subject in StudySubject.objects.all():
+            CustomStudySubjectValue.objects.create(study_subject_field=mpower_field, value=subject.mpower_id,
+                                                   study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=screening_field, value=subject.screening,
+                                                   study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=diagnosis_field, value=subject.diagnosis,
+                                                   study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=previously_in_study_field,
+                                                   value=subject.previously_in_study, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=brain_donation_agreement_field,
+                                                   value=subject.brain_donation_agreement, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=pd_in_family_field,
+                                                   value=subject.pd_in_family, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=year_of_diagnosis_field,
+                                                   value=subject.year_of_diagnosis, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_1_result_field,
+                                                   value=subject.virus_test_1_result, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_2_result_field,
+                                                   value=subject.virus_test_2_result, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_3_result_field,
+                                                   value=subject.virus_test_3_result, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_4_result_field,
+                                                   value=subject.virus_test_4_result, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_5_result_field,
+                                                   value=subject.virus_test_5_result, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_1_updated_field,
+                                                   value=subject.virus_test_1_updated, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_2_updated_field,
+                                                   value=subject.virus_test_2_updated, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_3_updated_field,
+                                                   value=subject.virus_test_3_updated, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_4_updated_field,
+                                                   value=subject.virus_test_4_updated, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_5_updated_field,
+                                                   value=subject.virus_test_5_updated, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_1_collection_date_field,
+                                                   value=subject.virus_test_1_collection_date, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_2_collection_date_field,
+                                                   value=subject.virus_test_2_collection_date, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_3_collection_date_field,
+                                                   value=subject.virus_test_3_collection_date, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_4_collection_date_field,
+                                                   value=subject.virus_test_4_collection_date, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_5_collection_date_field,
+                                                   value=subject.virus_test_5_collection_date, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_1_iga_status_field,
+                                                   value=subject.virus_test_1_iga_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_2_iga_status_field,
+                                                   value=subject.virus_test_2_iga_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_3_iga_status_field,
+                                                   value=subject.virus_test_3_iga_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_4_iga_status_field,
+                                                   value=subject.virus_test_4_iga_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_5_iga_status_field,
+                                                   value=subject.virus_test_5_iga_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_1_igg_status_field,
+                                                   value=subject.virus_test_1_igg_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_2_igg_status_field,
+                                                   value=subject.virus_test_2_igg_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_3_igg_status_field,
+                                                   value=subject.virus_test_3_igg_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_4_igg_status_field,
+                                                   value=subject.virus_test_4_igg_status, study_subject=subject)
+            CustomStudySubjectValue.objects.create(study_subject_field=virus_test_5_igg_status_field,
+                                                   value=subject.virus_test_5_igg_status, study_subject=subject)
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ('web', '0189_remove_studysubject_virus_test_1'),
+    ]
+
+    operations = [
+        migrations.RunPython(create_custom_fields),
+    ]
diff --git a/smash/web/migrations/0191_auto_20201201_1033.py b/smash/web/migrations/0191_auto_20201201_1033.py
new file mode 100644
index 0000000000000000000000000000000000000000..33d690caf0acb662ac1272ba8afa2b20272f0057
--- /dev/null
+++ b/smash/web/migrations/0191_auto_20201201_1033.py
@@ -0,0 +1,282 @@
+# Generated by Django 3.1.3 on 2020-12-01 10:33
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0190_remove_study_related_fields'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='mpower_id',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='diagnosis',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='screening',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='brain_donation_agreement',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='pd_in_family',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='previously_in_study',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='year_of_diagnosis',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_1_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_1_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_1_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_1_result',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_1_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_2_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_2_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_2_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_2_result',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_2_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_3_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_3_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_3_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_3_result',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_3_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_4_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_4_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_4_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_4_result',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_4_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_5_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_5_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_5_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_5_result',
+        ),
+        migrations.RemoveField(
+            model_name='studycolumns',
+            name='virus_test_5_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_1_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_1_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_1_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_1_result',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_1_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_2_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_2_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_2_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_2_result',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_2_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_3_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_3_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_3_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_3_result',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_3_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_4_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_4_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_4_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_4_result',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_4_updated',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_5_collection_date',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_5_iga_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_5_igg_status',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_5_result',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='virus_test_5_updated',
+
+        ),
+
+
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='mpower_id',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='diagnosis',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='screening',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='brain_donation_agreement',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='pd_in_family',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='previously_in_study',
+        ),
+        migrations.RemoveField(
+            model_name='studysubject',
+            name='year_of_diagnosis',
+        ),
+        migrations.AlterField(
+            model_name='studycolumns',
+            name='nd_number',
+            field=models.BooleanField(default=True, verbose_name='Subject number'),
+        ),
+        migrations.AlterField(
+            model_name='studysubject',
+            name='nd_number',
+            field=models.CharField(blank=True, max_length=25, verbose_name='Subject number'),
+        ),
+    ]
diff --git a/smash/web/migrations/0192_auto_20201207_0956.py b/smash/web/migrations/0192_auto_20201207_0956.py
new file mode 100644
index 0000000000000000000000000000000000000000..63b5cd45d2fdd440f26f0dc43208c9ad7037c015
--- /dev/null
+++ b/smash/web/migrations/0192_auto_20201207_0956.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.3 on 2020-12-07 09:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0191_auto_20201201_1033'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='customstudysubjectfield',
+            name='name',
+            field=models.CharField(max_length=64),
+        ),
+    ]
diff --git a/smash/web/models/__init__.py b/smash/web/models/__init__.py
index d6a3586ca9f80e5582d53892af72e7f282765454..56d7ea62f4e53372ea16b6423f6e43ff5549f19b 100644
--- a/smash/web/models/__init__.py
+++ b/smash/web/models/__init__.py
@@ -41,6 +41,7 @@ from .inconsistent_subject import InconsistentSubject, InconsistentField
 from .privacy_notice import PrivacyNotice
 
 from .etl import VisitImportData, SubjectImportData, EtlColumnMapping
+from .custom_data import CustomStudySubjectVisibility
 
 __all__ = [Study, FlyingTeam, Appointment, AppointmentType, Availability, Holiday, Item, Language, Location, Room,
            Subject, StudySubject, StudySubjectList, SubjectColumns, StudyNotificationParameters,
diff --git a/smash/web/models/custom_data/__init__.py b/smash/web/models/custom_data/__init__.py
index e9587ebe1ac196cca3e76610cce3e02bcaff672c..19012a57213da153c11126ed619e784546322728 100644
--- a/smash/web/models/custom_data/__init__.py
+++ b/smash/web/models/custom_data/__init__.py
@@ -1,4 +1,5 @@
 from .custom_study_subject_field import CustomStudySubjectField
 from .custom_study_subject_value import CustomStudySubjectValue
+from .custom_study_subject_visibility import CustomStudySubjectVisibility
 
-__all__ = [CustomStudySubjectField, CustomStudySubjectValue]
+__all__ = [CustomStudySubjectField, CustomStudySubjectValue, CustomStudySubjectVisibility]
diff --git a/smash/web/models/custom_data/custom_study_subject_field.py b/smash/web/models/custom_data/custom_study_subject_field.py
index 5e096da35e40804ca12975a303bfe153cc7e15e2..dd2d183603701a2d13765bbd07d3173d6176977e 100644
--- a/smash/web/models/custom_data/custom_study_subject_field.py
+++ b/smash/web/models/custom_data/custom_study_subject_field.py
@@ -6,7 +6,7 @@ from web.models.constants import CUSTOM_FIELD_TYPE
 
 
 class CustomStudySubjectField(models.Model):
-    name = models.CharField(max_length=20, null=False, blank=False)
+    name = models.CharField(max_length=64, null=False, blank=False)
     type = models.CharField(max_length=20, choices=CUSTOM_FIELD_TYPE, null=False, blank=False)
 
     possible_values = models.CharField(max_length=1024, null=True, blank=True, default='')
diff --git a/smash/web/models/custom_data/custom_study_subject_visibility.py b/smash/web/models/custom_data/custom_study_subject_visibility.py
new file mode 100644
index 0000000000000000000000000000000000000000..16aa2f8f6eaebdbbafbe68e3f28d6a49964f21bb
--- /dev/null
+++ b/smash/web/models/custom_data/custom_study_subject_visibility.py
@@ -0,0 +1,20 @@
+# coding=utf-8
+
+from django.db import models
+
+
+class CustomStudySubjectVisibility(models.Model):
+    visible = models.BooleanField(default=False, null=False)
+
+    study_subject_field = models.ForeignKey("web.CustomStudySubjectField",
+                                            verbose_name='Custom Field',
+                                            editable=False,
+                                            null=False,
+                                            on_delete=models.CASCADE
+                                            )
+    visible_subject_study_columns = models.ForeignKey("web.StudyColumns",
+                                                      verbose_name='List of visible columns',
+                                                      editable=False,
+                                                      null=False,
+                                                      on_delete=models.CASCADE
+                                                      )
diff --git a/smash/web/models/mail_template.py b/smash/web/models/mail_template.py
index bf101409a62f905e7c1c9f658bc262460517e4a9..633fbf4dd9c07175b3040ad9db6e54831baef33a 100644
--- a/smash/web/models/mail_template.py
+++ b/smash/web/models/mail_template.py
@@ -277,11 +277,11 @@ class MailTemplate(models.Model):
         if appointment.datetime_when is not None:
             # TODO: 2to3
             # Was:
-            #appointment_date_full = appointment.datetime_when.strftime(DATETIME_FORMAT).decode(
+            # appointment_date_full = appointment.datetime_when.strftime(DATETIME_FORMAT).decode(
             #    date_format_encoding())
-            #appointment_date_short = appointment.datetime_when.strftime(DATE_FORMAT_SHORT).decode(
+            # appointment_date_short = appointment.datetime_when.strftime(DATE_FORMAT_SHORT).decode(
             #    date_format_encoding())
-            #appointment_date_time = appointment.datetime_when.strftime(DATE_FORMAT_TIME).decode(
+            # appointment_date_time = appointment.datetime_when.strftime(DATE_FORMAT_TIME).decode(
             #    date_format_encoding())
             # Is:
             appointment_date_full = appointment.datetime_when.strftime(DATETIME_FORMAT)
@@ -315,8 +315,8 @@ class MailTemplate(models.Model):
                 # "##V_DATE_START_FULL##": visit.datetime_begin.strftime(DATETIME_FORMAT).decode(date_format_encoding()),
                 # "##V_DATE_START_SHORT##": visit.datetime_begin.strftime(DATE_FORMAT_SHORT).decode(
                 #    date_format_encoding()),
-                #"##V_DATE_ENDS_FULL##": visit.datetime_end.strftime(DATETIME_FORMAT).decode(date_format_encoding()),
-                #"##V_DATE_ENDS_SHORT##": visit.datetime_end.strftime(DATE_FORMAT_SHORT).decode(date_format_encoding()),
+                # "##V_DATE_ENDS_FULL##": visit.datetime_end.strftime(DATETIME_FORMAT).decode(date_format_encoding()),
+                # "##V_DATE_ENDS_SHORT##": visit.datetime_end.strftime(DATE_FORMAT_SHORT).decode(date_format_encoding()),
                 # Is:
                 "##V_DATE_START_FULL##": visit.datetime_begin.strftime(DATETIME_FORMAT),
                 "##V_DATE_START_SHORT##": visit.datetime_begin.strftime(DATE_FORMAT_SHORT),
@@ -338,13 +338,13 @@ class MailTemplate(models.Model):
                 "##S_ADDRESS##": study_subject.subject.address,
                 "##S_CITY##": study_subject.subject.city,
                 "##S_COUNTRY##": str(study_subject.subject.country),
-                "##S_DIAGNOSIS_YEAR##": str(study_subject.year_of_diagnosis),
+                "##S_DIAGNOSIS_YEAR##": study_subject.get_custom_field_value('Year of diagnosis'),
                 "##S_DATE_ADDED##": date_to_str(study_subject.date_added, DATE_FORMAT_SHORT),
                 "##S_DATE_BORN##": date_born,
-                "##S_DIAGNOSIS##": str(study_subject.diagnosis),
+                "##S_DIAGNOSIS##": study_subject.get_custom_field_value('Diagnosis'),
                 "##S_EMAIL##": str(study_subject.subject.email),
                 "##S_SEX##": study_subject.subject.get_sex_display(),
-                "##S_MPOWER_ID##": study_subject.mpower_id,
+                "##S_MPOWER_ID##": study_subject.get_custom_field_value('MPower ID'),
                 "##S_ND_NUMBER##": study_subject.nd_number,
                 "##S_PHONE_NUMBER##": str(study_subject.subject.phone_number),
                 "##S_PHONE_NUMBER_2##": str(study_subject.subject.phone_number_2),
@@ -368,26 +368,25 @@ class MailTemplate(models.Model):
 
             for i in range(1, 6):
                 virus_test_field = "##S_VIRUS_{}_RESULT##".format(i)
-                virus_test_value = virus_test_to_str(getattr(study_subject, "virus_test_{}".format(i)),
-                                                     getattr(study_subject, "virus_test_{}_updated".format(i)))
+                virus_test_value = study_subject.get_custom_field_value('Virus {} RT-PCR'.format(i))
+                if virus_test_value == "" or virus_test_value is None:
+                    virus_test_value = "N/A"
 
                 virus_test_date_field = "##S_VIRUS_{}_SAMPLE_COLLECTION_DATE##".format(i)
-                virus_test_date_value = date_to_str(getattr(study_subject, "virus_test_{}_collection_date".format(i)),
-                                                    DATE_FORMAT_SHORT)
+                virus_test_date_value = study_subject.get_custom_field_value(
+                    'Visit {} RT-PCR collection date'.format(i))
 
                 virus_iga_status_field = "##S_VIRUS_{}_IGA_STATUS##".format(i)
-                virus_iga_status_value = getattr(study_subject, "virus_test_{}_iga_status".format(i))
+                virus_iga_status_value = study_subject.get_custom_field_value('Visit {} IgA Status'.format(i))
                 if virus_iga_status_value is None:
                     virus_iga_status_value = ""
 
                 virus_igg_status_field = "##S_VIRUS_{}_IGG_STATUS##".format(i)
-                virus_igg_status_value = getattr(study_subject, "virus_test_{}_igg_status".format(i))
+                virus_igg_status_value = study_subject.get_custom_field_value('Visit {} IgG Status'.format(i))
                 if virus_igg_status_value is None:
                     virus_igg_status_value = ""
 
                 if virus_test_date_value != "":
-                    if virus_test_value == "":
-                        virus_test_value = "N/A"
                     if virus_iga_status_value == "":
                         virus_iga_status_value = "N/A"
                     if virus_igg_status_value == "":
@@ -428,14 +427,3 @@ class MailTemplate(models.Model):
                 "##C_HOURS##": str(voucher.hours),
             }
         return {}
-
-
-def virus_test_to_str(test, date):
-    if test is None and date is not None:
-        return "Inconclusive"
-    if test is None:
-        return ""
-    if test:
-        return "Positive"
-    else:
-        return "Negative"
diff --git a/smash/web/models/study_columns.py b/smash/web/models/study_columns.py
index 858db85af3c29b436f20478bed2dd63220ada7ce..3050cdc9d7de4fa5a816d21dc24cddd0a1c8fcb7 100644
--- a/smash/web/models/study_columns.py
+++ b/smash/web/models/study_columns.py
@@ -1,6 +1,8 @@
 # coding=utf-8
 from django.db import models
 
+from web.models.custom_data import CustomStudySubjectField, CustomStudySubjectVisibility
+
 
 class StudyColumns(models.Model):
     class Meta:
@@ -36,11 +38,7 @@ class StudyColumns(models.Model):
     )
     nd_number = models.BooleanField(
         default=True,
-        verbose_name='ND number',
-    )
-    mpower_id = models.BooleanField(
-        default=True,
-        verbose_name='MPower ID'
+        verbose_name='Subject number',
     )
     comments = models.BooleanField(
         default=True,
@@ -50,23 +48,11 @@ class StudyColumns(models.Model):
         default=True,
         verbose_name='Referred by'
     )
-    diagnosis = models.BooleanField(
-        default=True,
-        verbose_name='Diagnosis'
-    )
-    year_of_diagnosis = models.BooleanField(
-        default=True,
-        verbose_name='Year of diagnosis (YYYY)'
-    )
 
     information_sent = models.BooleanField(
         default=True,
         verbose_name='Information sent',
     )
-    pd_in_family = models.BooleanField(
-        default=True,
-        verbose_name='PD in family',
-    )
     resigned = models.BooleanField(
         default=True,
         verbose_name='Resigned',
@@ -97,16 +83,6 @@ class StudyColumns(models.Model):
         verbose_name='Agrees to give information to referral'
     )
 
-    screening = models.BooleanField(
-        default=False,
-        verbose_name='Screening'
-    )
-
-    previously_in_study = models.BooleanField(
-        default=False,
-        verbose_name='Previously in PDP study',
-    )
-
     voucher_types = models.BooleanField(
         default=False,
         verbose_name='Voucher types',
@@ -117,108 +93,25 @@ class StudyColumns(models.Model):
         verbose_name='Vouchers',
     )
 
-    brain_donation_agreement = models.BooleanField(
-        default=False,
-        verbose_name='Brain donation agreement',
-    )
-
-    virus_test_1 = models.BooleanField(
-        default=False,
-        verbose_name='Visit 1 virus results',
-    )
-    virus_test_2 = models.BooleanField(
-        default=False,
-        verbose_name='Visit 2 virus results',
-    )
-    virus_test_3 = models.BooleanField(
-        default=False,
-        verbose_name='Visit 3 virus results',
-    )
-    virus_test_4 = models.BooleanField(
-        default=False,
-        verbose_name='Visit 4 virus results',
-    )
-    virus_test_5 = models.BooleanField(
-        default=False,
-        verbose_name='Visit 5 virus results',
-    )
-    virus_test_1_updated = models.BooleanField(
-        default=False,
-        verbose_name='Visit 1 virus results date',
-    )
-    virus_test_2_updated = models.BooleanField(
-        default=False,
-        verbose_name='Visit 2 virus results date',
-    )
-    virus_test_3_updated = models.BooleanField(
-        default=False,
-        verbose_name='Visit 3 virus results date',
-    )
-    virus_test_4_updated = models.BooleanField(
-        default=False,
-        verbose_name='Visit 4 virus results date',
-    )
-    virus_test_5_updated = models.BooleanField(
-        default=False,
-        verbose_name='Visit 5 virus results date',
-    )
-    virus_test_1_collection_date = models.BooleanField(
-        default=False,
-        verbose_name='Visit 1 virus collection date',
-    )
-    virus_test_2_collection_date = models.BooleanField(
-        default=False,
-        verbose_name='Visit 2 virus collection date',
-    )
-    virus_test_3_collection_date = models.BooleanField(
-        default=False,
-        verbose_name='Visit 3 virus collection date',
-    )
-    virus_test_4_collection_date = models.BooleanField(
-        default=False,
-        verbose_name='Visit 4 virus collection date',
-    )
-    virus_test_5_collection_date = models.BooleanField(
-        default=False,
-        verbose_name='Visit 5 virus collection date',
-    )
-    virus_test_1_iga_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 1 virus IgA status',
-    )
-    virus_test_1_igg_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 1 virus IgG status',
-    )
-    virus_test_2_iga_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 2 virus IgA status',
-    )
-    virus_test_2_igg_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 2 virus IgG status',
-    )
-    virus_test_3_iga_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 3 virus IgA status',
-    )
-    virus_test_3_igg_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 3 virus IgG status',
-    )
-    virus_test_4_iga_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 4 virus IgA status',
-    )
-    virus_test_4_igg_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 4 virus IgG status',
-    )
-    virus_test_5_iga_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 5 virus IgA status',
-    )
-    virus_test_5_igg_status = models.BooleanField(
-        default=False,
-        verbose_name='Visit 5 virus IgG status',
-    )
+    @property
+    def custom_fields_visibility(self):
+        study = self.studysubjectlist_set.all()[0].study
+        values = CustomStudySubjectVisibility.objects.filter(visible_subject_study_columns=self)
+        fields = list(CustomStudySubjectField.objects.filter(study=study))
+        for value in values:
+            fields.remove(value.study_subject_field)
+        for field in fields:
+            CustomStudySubjectVisibility.objects.create(visible_subject_study_columns=self, study_subject_field=field)
+        return CustomStudySubjectVisibility.objects.filter(visible_subject_study_columns=self)
+
+    def set_custom_field_visibility(self, custom_study_subject_field: CustomStudySubjectField, visible: bool):
+        for existing_value in self.custom_fields_visibility.all():
+            if existing_value.study_subject_field == custom_study_subject_field:
+                existing_value.visible = visible
+                existing_value.save()
+
+    def is_custom_field_visible(self, field: CustomStudySubjectField) -> bool:
+        for existing_value in self.custom_fields_visibility.all():
+            if existing_value.study_subject_field == field:
+                return existing_value.visible
+        return False
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index 39c77a02dac4b98add0562649b69cb6f6f44c07e..b7b1ac009f3720589ca67a61b9aa02eb2aa4fea3 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -1,16 +1,18 @@
 # coding=utf-8
 import logging
 import re
-
 from typing import Optional
+
 from django.db import models
 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, BOOL_CHOICES_WITH_NONE
+from web.models.constants import BOOL_CHOICES, SUBJECT_TYPE_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__)
 
 
@@ -98,11 +100,7 @@ class StudySubject(models.Model):
                                         )
     nd_number = models.CharField(max_length=25,
                                  blank=True,
-                                 verbose_name='ND number',
-                                 )
-    mpower_id = models.CharField(max_length=20,
-                                 blank=True,
-                                 verbose_name='MPower ID'
+                                 verbose_name='Subject number',
                                  )
     comments = models.TextField(max_length=2000,
                                 blank=True,
@@ -135,47 +133,15 @@ class StudySubject(models.Model):
         default=False,
     )
 
-    screening = models.CharField(max_length=1024,
-                                 null=True,
-                                 blank=True,
-                                 verbose_name='Screening'
-                                 )
-
-    diagnosis = models.CharField(max_length=1024,
-                                 null=True,
-                                 blank=True,
-                                 verbose_name='Diagnosis'
-                                 )
-
-    previously_in_study = models.BooleanField(
-        verbose_name='Previously in PDP study',
-        default=False,
-    )
-
     voucher_types = models.ManyToManyField(VoucherType,
                                            blank=True,
                                            verbose_name='Voucher types'
                                            )
 
-    year_of_diagnosis = models.IntegerField(
-        null=True,
-        blank=True,
-        verbose_name='Year of diagnosis (YYYY)'
-    )
-
     information_sent = models.BooleanField(
         verbose_name='Information sent',
         default=False
     )
-    pd_in_family = models.BooleanField(
-        verbose_name='PD in family',
-        default=None,
-        null=True,
-    )
-    brain_donation_agreement = models.BooleanField(
-        default=False,
-        verbose_name='Brain donation agreement',
-    )
 
     resigned = models.BooleanField(
         verbose_name='Resigned',
@@ -205,123 +171,6 @@ class StudySubject(models.Model):
                                                verbose_name='Endpoint reached comments'
                                                )
 
-    virus_test_1 = models.BooleanField(choices=BOOL_CHOICES_WITH_NONE,
-                                       verbose_name='Visit 1 virus result',
-                                       default=None,
-                                       null=True
-                                       )
-    virus_test_2 = models.BooleanField(choices=BOOL_CHOICES_WITH_NONE,
-                                       verbose_name='Visit 2 virus result',
-                                       default=None,
-                                       null=True
-                                       )
-    virus_test_3 = models.BooleanField(choices=BOOL_CHOICES_WITH_NONE,
-                                       verbose_name='Visit 3 virus result',
-                                       default=None,
-                                       null=True
-                                       )
-    virus_test_4 = models.BooleanField(choices=BOOL_CHOICES_WITH_NONE,
-                                       verbose_name='Visit 4 virus result',
-                                       default=None,
-                                       null=True
-                                       )
-    virus_test_5 = models.BooleanField(choices=BOOL_CHOICES_WITH_NONE,
-                                       verbose_name='Visit 5 virus result',
-                                       default=None,
-                                       null=True
-                                       )
-    virus_test_1_updated = models.DateField(verbose_name='Visit 1 virus result date',
-                                            blank=True,
-                                            null=True,
-                                            )
-    virus_test_2_updated = models.DateField(verbose_name='Visit 2 virus result date',
-                                            blank=True,
-                                            null=True,
-                                            )
-    virus_test_3_updated = models.DateField(verbose_name='Visit 3 virus result date',
-                                            blank=True,
-                                            null=True,
-                                            )
-    virus_test_4_updated = models.DateField(verbose_name='Visit 4 virus result date',
-                                            blank=True,
-                                            null=True,
-                                            )
-    virus_test_5_updated = models.DateField(verbose_name='Visit 5 virus result date',
-                                            blank=True,
-                                            null=True,
-                                            )
-    virus_test_1_collection_date = models.DateField(verbose_name='Visit 1 virus collection date',
-                                                    blank=True,
-                                                    null=True,
-                                                    )
-    virus_test_2_collection_date = models.DateField(verbose_name='Visit 2 virus collection date',
-                                                    blank=True,
-                                                    null=True,
-                                                    )
-    virus_test_3_collection_date = models.DateField(verbose_name='Visit 3 virus collection date',
-                                                    blank=True,
-                                                    null=True,
-                                                    )
-    virus_test_4_collection_date = models.DateField(verbose_name='Visit 4 virus collection date',
-                                                    blank=True,
-                                                    null=True,
-                                                    )
-    virus_test_5_collection_date = models.DateField(verbose_name='Visit 5 virus collection date',
-                                                    blank=True,
-                                                    null=True,
-                                                    )
-
-    virus_test_1_iga_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 1 IgA status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_1_igg_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 1 IgG status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_2_iga_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 2 IgA status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_2_igg_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 2 IgG status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_3_iga_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 3 IgA status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_3_igg_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 3 IgG status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_4_iga_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 4 IgA status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_4_igg_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 4 IgG status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_5_iga_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 5 IgA status',
-                                               blank=True,
-                                               null=True,
-                                               )
-    virus_test_5_igg_status = models.CharField(max_length=255,
-                                               verbose_name='Visit 5 IgG status',
-                                               blank=True,
-                                               null=True,
-                                               )
-
     def sort_matched_screening_first(self, pattern, reverse=False):
         if self.screening_number is None:
             return None
@@ -403,12 +252,24 @@ class StudySubject(models.Model):
     def __str__(self):
         return "%s %s" % (self.subject.first_name, self.subject.last_name)
 
-    def get_custom_data_value(self, custom_field) -> Optional[CustomStudySubjectValue]:
+    def get_custom_data_value(self, custom_field: CustomStudySubjectField) -> Optional[CustomStudySubjectValue]:
         for value in self.custom_data_values:
             if value.study_subject_field == custom_field:
                 return value
         return None
 
+    def get_custom_field_value(self, param: str) -> str:
+        for value in self.custom_data_values.all():
+            if value.study_subject_field.name == param:
+                return value.value
+        return ''
+
+    def set_custom_field_value(self, param: str, value: str):
+        for custom_value in self.custom_data_values.all():
+            if custom_value.study_subject_field.name == param:
+                custom_value.value = value
+                custom_value.save()
+
 
 # SIGNALS
 @receiver(post_save, sender=StudySubject)
diff --git a/smash/web/redcap_connector.py b/smash/web/redcap_connector.py
index 70ae1d34e475aca3d2ab02faa0a0383fa9c5e8b3..7ccd582dd254dd3011da247b9098c4d6f48c772b 100644
--- a/smash/web/redcap_connector.py
+++ b/smash/web/redcap_connector.py
@@ -1,25 +1,26 @@
 # coding=utf-8
-import io
 import datetime
+import io
 import json
 import logging
+from typing import Optional, List
 
 import certifi
 import pycurl
 import timeout_decorator
 from django.forms.models import model_to_dict
 from django_cron import CronJobBase, Schedule
-
 from six import ensure_str
 
 from web.models import ConfigurationItem, StudySubject, Language, AppointmentType, Appointment, Visit, Study, \
     Provenance, Worker, User
 from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, \
-    REDCAP_BASE_URL_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT, RED_CAP_LANGUAGE_4_FIELD_TYPE, RED_CAP_LANGUAGE_3_FIELD_TYPE, \
-    RED_CAP_LANGUAGE_2_FIELD_TYPE, RED_CAP_LANGUAGE_1_FIELD_TYPE, RED_CAP_MPOWER_ID_FIELD_TYPE, RED_CAP_DEAD_FIELD_TYPE, \
-    RED_CAP_SEX_FIELD_TYPE, RED_CAP_DATE_BORN_FIELD_TYPE, RED_CAP_ND_NUMBER_FIELD_TYPE, RED_CAP_VIRUS_FIELD_TYPE, \
-    GLOBAL_STUDY_ID, RED_CAP_SAMPLE_DATE_FIELD_TYPE, RED_CAP_KIT_ID_FIELD_TYPE, RED_CAP_IGA_STATUS_FIELD_TYPE, \
-    RED_CAP_IGG_STATUS_FIELD_TYPE, IMPORTER_USER, IMPORT_APPOINTMENT_TYPE
+    REDCAP_BASE_URL_CONFIGURATION_TYPE, CRON_JOB_TIMEOUT, RED_CAP_LANGUAGE_4_FIELD_TYPE, \
+    RED_CAP_LANGUAGE_3_FIELD_TYPE, RED_CAP_LANGUAGE_2_FIELD_TYPE, RED_CAP_LANGUAGE_1_FIELD_TYPE, \
+    RED_CAP_MPOWER_ID_FIELD_TYPE, RED_CAP_DEAD_FIELD_TYPE, RED_CAP_SEX_FIELD_TYPE, RED_CAP_DATE_BORN_FIELD_TYPE, \
+    RED_CAP_ND_NUMBER_FIELD_TYPE, RED_CAP_VIRUS_FIELD_TYPE, GLOBAL_STUDY_ID, RED_CAP_SAMPLE_DATE_FIELD_TYPE, \
+    RED_CAP_KIT_ID_FIELD_TYPE, RED_CAP_IGA_STATUS_FIELD_TYPE, RED_CAP_IGG_STATUS_FIELD_TYPE, IMPORTER_USER, \
+    IMPORT_APPOINTMENT_TYPE
 from web.models.inconsistent_subject import InconsistentField, InconsistentSubject
 from web.models.missing_subject import MissingSubject
 
@@ -46,7 +47,6 @@ class RedcapSubject(object):
 
 class RedcapVisit(object):
     virus = None
-    virus_inconclusive = False
     visit_number = 0
     virus_collection_date = None
     iga_status = None
@@ -69,12 +69,12 @@ def different_string(string1, string2):
     return s1.strip() != s2.strip()
 
 
-def date_equals(date1, date2):
-    if date1 is None and date2 is None:
+def date_equals(date1: str, date2: datetime) -> bool:
+    if (date1 is None or date1 == '') and date2 is None:
         return True
-    if date1 is None or date2 is None:
+    if date1 is None or date1 == '' or date2 is None:
         return False
-    return date1.strftime("%Y-%m-%d") == date2.strftime("%Y-%m-%d")
+    return date1 == date2.strftime("%Y-%m-%d")
 
 
 class RedcapConnector(object):
@@ -225,15 +225,18 @@ class RedcapConnector(object):
                 if appointment_type_to_finish is not None:
                     for visit in red_cap_subject.visits:
                         smasch_visits = Visit.objects.filter(visit_number=visit.visit_number, subject=subject)
-                        smasch_appointments = Appointment.objects.filter(visit__in=smasch_visits,
-                                                                         appointment_types=appointment_type_to_finish,
-                                                                         status=Appointment.APPOINTMENT_STATUS_SCHEDULED)
+                        smasch_appointments = Appointment.objects.filter(
+                            visit__in=smasch_visits,
+                            appointment_types=appointment_type_to_finish,
+                            status=Appointment.APPOINTMENT_STATUS_SCHEDULED)
+
                         for smasch_appointment in smasch_appointments:
                             smasch_appointment.mark_as_finished()
-                            if smasch_appointment.visit.is_finished != True:
-                                description = '{} changed from "{}" to "{}"'.format('is_finished',
-                                                                                     smasch_appointment.visit.is_finished,
-                                                                                     True)
+                            if not smasch_appointment.visit.is_finished:
+                                description = '{} changed from "{}" to "{}"'.format(
+                                    'is_finished',
+                                    smasch_appointment.visit.is_finished,
+                                    True)
                                 p = Provenance(modified_table=Visit._meta.db_table,
                                                modified_table_id=smasch_appointment.visit.id,
                                                modification_author=self.importer_user,
@@ -241,91 +244,53 @@ class RedcapConnector(object):
                                                new_value=True,
                                                modification_description=description,
                                                modified_field='is_finished')
+                                p.save()
                                 smasch_appointment.visit.is_finished = True
                                 smasch_appointment.visit.save()
-                        if visit.virus is not None or visit.virus_inconclusive:
-                            changes = []
-                            if visit.visit_number == 1 and subject.virus_test_1 != visit.virus:
-                                changes.extend([('virus_test_1', visit.virus),
-                                                ('virus_test_1_updated', datetime.datetime.now())])
-                            if visit.visit_number == 2 and subject.virus_test_2 != visit.virus:
-                                changes.extend([('virus_test_2', visit.virus),
-                                                ('virus_test_2_updated', datetime.datetime.now())])
-                            if visit.visit_number == 3 and subject.virus_test_3 != visit.virus:
-                                changes.extend([('virus_test_3', visit.virus),
-                                                ('virus_test_3_updated', datetime.datetime.now())])
-                            if visit.visit_number == 4 and subject.virus_test_4 != visit.virus:
-                                changes.extend([('virus_test_4', visit.virus),
-                                                ('virus_test_4_updated', datetime.datetime.now())])
-                            if visit.visit_number == 5 and subject.virus_test_5 != visit.virus:
-                                changes.extend([('virus_test_5', visit.virus),
-                                                ('virus_test_5_updated', datetime.datetime.now())])
-                            if visit.visit_number == 1 and subject.virus_test_1_updated is None and visit.virus_inconclusive:
-                                changes.extend([('virus_test_1_updated', datetime.datetime.now())])
-                            if visit.visit_number == 2 and subject.virus_test_2_updated is None and visit.virus_inconclusive:
-                                changes.extend([('virus_test_2_updated', datetime.datetime.now())])
-                            if visit.visit_number == 3 and subject.virus_test_3_updated is None and visit.virus_inconclusive:
-                                changes.extend([('virus_test_3_updated', datetime.datetime.now())])
-                            if visit.visit_number == 4 and subject.virus_test_4_updated is None and visit.virus_inconclusive:
-                                changes.extend([('virus_test_4_updated', datetime.datetime.now())])
-                            if visit.visit_number == 5 and subject.virus_test_5_updated is None and visit.virus_inconclusive:
-                                changes.extend([('virus_test_5_updated', datetime.datetime.now())])
-
-                            if visit.visit_number == 1 and not date_equals(subject.virus_test_1_collection_date,
-                                                                           visit.virus_collection_date):
-                                changes.extend([('virus_test_1_collection_date', visit.virus_collection_date)])
-                            if visit.visit_number == 2 and not date_equals(subject.virus_test_2_collection_date,
-                                                                       visit.virus_collection_date):
-                                changes.extend([('virus_test_2_collection_date', visit.virus_collection_date)])
-                            if visit.visit_number == 3 and not date_equals(subject.virus_test_3_collection_date,
-                                                                       visit.virus_collection_date):
-                                changes.extend([('virus_test_3_collection_date', visit.virus_collection_date)])
-                            if visit.visit_number == 4 and not date_equals(subject.virus_test_4_collection_date,
-                                                                       visit.virus_collection_date):
-                                changes.extend([('virus_test_4_collection_date', visit.virus_collection_date)])
-                            if visit.visit_number == 5 and not date_equals(subject.virus_test_5_collection_date,
-                                                                       visit.virus_collection_date):
-                                changes.extend([('virus_test_5_collection_date', visit.virus_collection_date)])
-
-                            if visit.visit_number == 1 and subject.virus_test_1_iga_status != visit.iga_status:
-                                changes.extend([('virus_test_1_iga_status', visit.iga_status)])
-                            if visit.visit_number == 1 and subject.virus_test_1_igg_status != visit.igg_status:
-                                changes.extend([('virus_test_1_igg_status', visit.igg_status)])
-                            if visit.visit_number == 2 and subject.virus_test_2_iga_status != visit.iga_status:
-                                changes.extend([('virus_test_2_iga_status', visit.iga_status)])
-                            if visit.visit_number == 2 and subject.virus_test_2_igg_status != visit.igg_status:
-                                changes.extend([('virus_test_2_igg_status', visit.igg_status)])
-                            if visit.visit_number == 3 and subject.virus_test_3_iga_status != visit.iga_status:
-                                changes.extend([('virus_test_3_iga_status', visit.iga_status)])
-                            if visit.visit_number == 3 and subject.virus_test_3_igg_status != visit.igg_status:
-                                changes.extend([('virus_test_3_igg_status', visit.igg_status)])
-                            if visit.visit_number == 4 and subject.virus_test_4_iga_status != visit.iga_status:
-                                changes.extend([('virus_test_4_iga_status', visit.iga_status)])
-                            if visit.visit_number == 4 and subject.virus_test_4_igg_status != visit.igg_status:
-                                changes.extend([('virus_test_4_igg_status', visit.igg_status)])
-                            if visit.visit_number == 5 and subject.virus_test_5_iga_status != visit.iga_status:
-                                changes.extend([('virus_test_5_iga_status', visit.iga_status)])
-                            if visit.visit_number == 5 and subject.virus_test_5_igg_status != visit.igg_status:
-                                changes.extend([('virus_test_5_igg_status', visit.igg_status)])
-
-                            #
-                            if len(changes) > 0:
-                                for field, new_value in changes:
-                                    old_value = getattr(subject, field)
-                                    description = '{} changed from "{}" to "{}"'.format(field, old_value, new_value)
-                                    p = Provenance(modified_table=StudySubject._meta.db_table,
-                                                   modified_table_id=subject.id,
-                                                   modification_author=self.importer_user,
-                                                   previous_value=old_value,
-                                                   new_value=new_value,
-                                                   modification_description=description,
-                                                   modified_field=field)
-                                    setattr(subject, field, new_value)
-                                    p.save()
-                                subject.save()
+                        self.update_data_from_redcap(subject, visit)
 
         return result
 
+    def update_data_from_redcap(self, subject: StudySubject, visit: RedcapVisit) -> List[Provenance]:
+        result = []
+        if visit.virus is not None:
+            changes = []
+            for i in range(1, 6):
+                if visit.visit_number == i:
+                    result_label = "Virus {} RT-PCR".format(i - 1)
+                    updated_label = "Visit {} RT-PCR update date".format(i - 1)
+                    collect_label = "Visit {} RT-PCR collection date".format(i - 1)
+                    iga_label = "Visit {} IgA Status".format(i - 1)
+                    igg_label = "Visit {} IgG Status".format(i - 1)
+                    if subject.get_custom_field_value(result_label) != visit.virus:
+                        changes.extend([(result_label, visit.virus),
+                                        (updated_label, datetime.datetime.now().strftime("%Y-%m-%d"))])
+
+                    if not date_equals(subject.get_custom_field_value(collect_label), visit.virus_collection_date):
+                        changes.extend([(collect_label, visit.virus_collection_date.strftime("%Y-%m-%d"))])
+
+                    if subject.get_custom_field_value(iga_label) != visit.iga_status:
+                        changes.extend([(iga_label, visit.iga_status)])
+                    if subject.get_custom_field_value(igg_label) != visit.igg_status:
+                        changes.extend([(igg_label, visit.igg_status)])
+
+            if len(changes) > 0:
+                for field, new_value in changes:
+                    old_value = subject.get_custom_field_value(field)
+                    description = '{} changed from "{}" to "{}"'.format(field, old_value, new_value)
+                    p = Provenance(modified_table=StudySubject._meta.db_table,
+                                   modified_table_id=subject.id,
+                                   modification_author=self.importer_user,
+                                   previous_value=old_value,
+                                   new_value=new_value,
+                                   modification_description=description,
+                                   modified_field=field)
+                    subject.set_custom_field_value(field, new_value)
+                    p.save()
+                    result.append(p)
+                subject.save()
+        return result
+
     @staticmethod
     def check_sex_consistency(red_cap_subject, study_subject):
         if study_subject.subject.sex != red_cap_subject.sex:
@@ -350,9 +315,11 @@ class RedcapConnector(object):
             return InconsistentField.create("dead", str(study_subject.subject.dead), str(red_cap_subject.dead))
 
     @staticmethod
-    def check_mpower_id_consistency(red_cap_subject, study_subject):
-        if different_string(study_subject.mpower_id, red_cap_subject.mpower_id):
-            return InconsistentField.create("mpower id", study_subject.mpower_id, red_cap_subject.mpower_id)
+    def check_mpower_id_consistency(red_cap_subject: RedcapSubject, study_subject: StudySubject) \
+            -> Optional[InconsistentField]:
+        if different_string(study_subject.get_custom_field_value('MPower ID'), red_cap_subject.mpower_id):
+            return InconsistentField.create("mpower id", study_subject.get_custom_field_value('MPower ID'),
+                                            red_cap_subject.mpower_id)
 
     @staticmethod
     def check_languages_consistency(red_cap_subject, study_subject):
@@ -374,7 +341,8 @@ class RedcapConnector(object):
             return InconsistentField.create("languages", subject_languages, red_cap_subject_languages)
 
     @staticmethod
-    def create_inconsistency_subject(red_cap_subject, study_subject, url):
+    def create_inconsistency_subject(red_cap_subject: RedcapSubject, study_subject: StudySubject,
+                                     url: str) -> InconsistentSubject:
         # func dict
         field_checks = {
             'sex': RedcapConnector.check_sex_consistency,
@@ -435,11 +403,11 @@ class RedcapConnector(object):
                 visit.visit_number = 1
                 if self.virus_field != "":
                     if row.get(self.virus_field) == "Negative":
-                        visit.virus = False
+                        visit.virus = "Negative"
                     elif row.get(self.virus_field) == "Positive":
-                        visit.virus = True
+                        visit.virus = "Positive"
                     elif row.get(self.virus_field) == "Inconclusive":
-                        visit.virus_inconclusive = True
+                        visit.virus = "Inconclusive"
                 if self.sample_date_field != "":
                     date_str = row.get(self.sample_date_field)
                     if date_str is not None and date_str != "" and date_str != "Not done" and date_str != "Not known":
@@ -474,17 +442,18 @@ class RedcapConnector(object):
                             visit.visit_number = i + self.study.redcap_first_visit_number + 1
                             if self.virus_field != "":
                                 if row.get(self.virus_field) == "Negative":
-                                    visit.virus = False
+                                    visit.virus = "Negative"
                                 elif row.get(self.virus_field) == "Positive":
-                                    visit.virus = True
+                                    visit.virus = "Positive"
                                 elif row.get(self.virus_field) == "Inconclusive":
-                                    visit.virus_inconclusive = True
+                                    visit.virus_inconclusive = "Inconclusive"
                             if self.sample_date_field != "":
                                 date_str = row.get(self.sample_date_field)
                                 if date_str is not None and date_str != "" and date_str != "Not done" and date_str != "Not known":
                                     try:
-                                        visit.virus_collection_date = datetime.datetime.strptime(row.get(self.sample_date_field),
-                                                                                     "%Y-%m-%d")
+                                        visit.virus_collection_date = datetime.datetime.strptime(
+                                            row.get(self.sample_date_field),
+                                            "%Y-%m-%d")
                                     except ValueError:
                                         logger.warning("Invalid date: " + row.get(self.sample_date_field))
                                         visit.virus_collection_date = None
@@ -566,7 +535,7 @@ class RedcapConnector(object):
 
     def execute_query(self, query_data, is_json=True):
         buf = io.BytesIO()
-        
+
         curl_connection = pycurl.Curl()
         curl_connection.setopt(pycurl.CAINFO, certifi.where())
         curl_connection.setopt(curl_connection.URL, self.base_url + "/api/")
@@ -574,13 +543,13 @@ class RedcapConnector(object):
         curl_connection.setopt(curl_connection.WRITEFUNCTION, buf.write)
         curl_connection.perform()
         curl_connection.close()
-        
+
         if is_json:
             val = buf.getvalue()
             data = json.loads(val)
         else:
             data = buf.getvalue()
-        
+
         buf.close()
         return data
 
diff --git a/smash/web/static/js/smash.js b/smash/web/static/js/smash.js
index a7a3dcba9c3c820b7337fd3d5ec3451fdf77d8a7..408d2335df9751b63a12085aedc5e8e70d810ad4 100644
--- a/smash/web/static/js/smash.js
+++ b/smash/web/static/js/smash.js
@@ -314,7 +314,7 @@ function createTable(params) {
         $(this).html('<select style="width:60px" ><option value selected="selected">---</option><option value="true">YES</option><option value="false">NO</option><option value="null">N/A</option></select>');
     });
     $(tableElement).find('tfoot div[name="yes_no_null_inconclusive_filter"]').each(function () {
-        $(this).html('<select style="width:100px" ><option value selected="selected">---</option><option value="true">Positive</option><option value="false">Negative</option><option value="null">N/A</option><option value="inconclusive">Inconclusive</option></select>');
+        $(this).html('<select style="width:100px" ><option value selected="selected">---</option><option value="Positive">Positive</option><option value="Negative">Negative</option><option value="null">N/A</option><option value="Inconclusive">Inconclusive</option></select>');
     });
     $(tableElement).find('tfoot div[name="serology_filter"]').each(function () {
         $(this).html('<select style="width:100px" ><option value selected="selected">---</option><option value="Positive">Positive</option><option value="Negative">Negative</option><option value="Borderline">Borderline</option></select>');
diff --git a/smash/web/tests/api_views/test_appointment.py b/smash/web/tests/api_views/test_appointment.py
index 899575b6696ba54a412df6a107cd277c86d1a207..c24aefe71e4cd50b61ea78f50dbe800392cf6777 100644
--- a/smash/web/tests/api_views/test_appointment.py
+++ b/smash/web/tests/api_views/test_appointment.py
@@ -16,7 +16,7 @@ from web.views.notifications import get_today_midnight_date
 
 class TestAppointmentApi(LoggedInWithWorkerTestCase):
     def setUp(self):
-        super(TestAppointmentApi, self).setUp()
+        super().setUp()
         self.study_subject = create_study_subject()
 
     def test_appointments_valid(self):
diff --git a/smash/web/tests/api_views/test_subject.py b/smash/web/tests/api_views/test_subject.py
index 9ecdc98169c49f37ccd62eb893bdd55c3bc1a57c..caccfdc9827ce01817eea6b7733cd40475e2a32f 100644
--- a/smash/web/tests/api_views/test_subject.py
+++ b/smash/web/tests/api_views/test_subject.py
@@ -29,7 +29,7 @@ logger = logging.getLogger(__name__)
 
 class TestSubjectApi(LoggedInWithWorkerTestCase):
     def setUp(self):
-        super(TestSubjectApi, self).setUp()
+        super().setUp()
         self.study_subject = create_study_subject()
         self.warning_counter = MsgCounterHandler()
         logging.getLogger('').addHandler(self.warning_counter)
@@ -700,24 +700,6 @@ class TestSubjectApi(LoggedInWithWorkerTestCase):
                 get_subjects_order(StudySubject.objects.none(), order, "asc")
                 self.assertEqual(self.get_warning_count(), 0, msg="Sorting by \"" + order + "\" does not work")
 
-    def test_subjects_filter_virus_test(self):
-        study_subject_positive = create_study_subject(2)
-        study_subject_positive.virus_test_1 = True
-        study_subject_positive.save()
-
-        study_subject_negative = create_study_subject(2)
-        study_subject_negative.virus_test_1 = False
-        study_subject_negative.save()
-
-        study_subject_inconclusive = create_study_subject(2)
-        study_subject_inconclusive.virus_test_1 = None
-        study_subject_inconclusive.virus_test_1_updated = timezone.now()
-        study_subject_inconclusive.save()
-
-        self.check_subject_filtered([["virus_test_1", "true"]], [study_subject_positive])
-        self.check_subject_filtered([["virus_test_1", "false"]], [study_subject_negative])
-        self.check_subject_filtered([["virus_test_1", "inconclusive"]], [study_subject_inconclusive])
-
     @parameterized.expand([
         ('text', CUSTOM_FIELD_TYPE_TEXT, 'bla'),
         ('bool', CUSTOM_FIELD_TYPE_BOOLEAN, 'True'),
diff --git a/smash/web/tests/forms/test_StudySubjectAddForm.py b/smash/web/tests/forms/test_StudySubjectAddForm.py
index 78cce7aa90f727965d703b68a33a0044c93c6393..58cecf19c55a5ae1a6e96c0323bbc4cbe07c143e 100644
--- a/smash/web/tests/forms/test_StudySubjectAddForm.py
+++ b/smash/web/tests/forms/test_StudySubjectAddForm.py
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
 
 class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
     def setUp(self):
-        super(StudySubjectAddFormTests, self).setUp()
+        super().setUp()
 
         location = self.worker.locations.all()[0]
         self.subject = create_subject()
@@ -74,10 +74,8 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
         form_data['screening_number'] = "123"
 
         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.instance.subject_id = self.subject.id
         form.save()
 
         form2 = StudySubjectAddForm(data=form_data, user=self.user, study=self.study)
@@ -101,22 +99,6 @@ class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
         self.assertFalse(validation_status)
         self.assertTrue("nd_number" in form2.errors)
 
-    def test_invalid_mpower_id(self):
-        form_data = self.sample_data
-        form_data['mpower_id'] = "123"
-
-        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, study=self.study)
-        validation_status = form2.is_valid()
-        self.assertFalse(validation_status)
-        self.assertTrue("mpower_id" in form2.errors)
-
     def test_get_new_screening_number(self):
         prefix = "X-"
         subject = create_study_subject()
diff --git a/smash/web/tests/forms/test_StudySubjectEditForm.py b/smash/web/tests/forms/test_StudySubjectEditForm.py
index 670bf37cc5f78d54670f0c934b596cda84eac4d1..f68632dc87130d311c3cb76a0a49211dd52c33f6 100644
--- a/smash/web/tests/forms/test_StudySubjectEditForm.py
+++ b/smash/web/tests/forms/test_StudySubjectEditForm.py
@@ -16,8 +16,9 @@ logger = logging.getLogger(__name__)
 
 class StudySubjectEditFormTests(LoggedInWithWorkerTestCase):
     def setUp(self):
-        super(StudySubjectEditFormTests, self).setUp()
+        super().setUp()
         self.study_subject = create_study_subject()
+        self.study = get_test_study()
 
         location = self.worker.locations.all()[0]
         self.sample_data = {
@@ -100,21 +101,6 @@ class StudySubjectEditFormTests(LoggedInWithWorkerTestCase):
 
         self.assertTrue(form[get_study_subject_field_id(field)].initial, expected_initial_value)
 
-    def test_invalid_mpower_id_edit(self):
-        self.study_subject.mpower_id = "mpower_002"
-        self.study_subject.nd_number = "ND0002"
-        self.study_subject.screening_number = "002"
-        self.study_subject.save()
-
-        self.sample_data['mpower_id'] = "mpower_002"
-        self.sample_data['nd_number'] = "ND0001"
-        self.sample_data['screening_number'] = "001"
-        edit_form = StudySubjectEditForm(self.sample_data, instance=self.study_subject)
-
-        save_status = edit_form.is_valid()
-        self.assertTrue("mpower_id" in edit_form.errors)
-        self.assertFalse(save_status)
-
     def test_form_with_readonly_custom_field(self):
         field = CustomStudySubjectField.objects.create(study=get_test_study(), default_value="xyz", readonly=True,
                                                        type=CUSTOM_FIELD_TYPE_TEXT)
diff --git a/smash/web/tests/forms/test_subject_forms.py b/smash/web/tests/forms/test_subject_forms.py
index fe109455c9595a3b3ee475a27ff23509e41a2e1b..9b45ee08278d984c84868700a1ac987e9e14084a 100644
--- a/smash/web/tests/forms/test_subject_forms.py
+++ b/smash/web/tests/forms/test_subject_forms.py
@@ -9,7 +9,7 @@ logger = logging.getLogger(__name__)
 
 class StudySubjectAddFormTests(LoggedInWithWorkerTestCase):
     def setUp(self):
-        super(StudySubjectAddFormTests, self).setUp()
+        super().setUp()
         self.subject = create_subject()
 
     def test_is_valid_social_security_number_too_short(self):
diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py
index e34f1e3af6c3fb36acb2af10cda12cab667a5894..a4831422fe449fcf1ae7f111f790c096bd42f64f 100644
--- a/smash/web/tests/functions.py
+++ b/smash/web/tests/functions.py
@@ -51,13 +51,9 @@ def create_empty_study_columns():
         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,
         endpoint_reached=False,
         excluded=False,
diff --git a/smash/web/tests/test_RedcapConnector.py b/smash/web/tests/test_RedcapConnector.py
index 557ec07bffd3fc493a6319d07fb07debee37d79c..43d088ac760755e587cd72945bb18ec69fec30f4 100644
--- a/smash/web/tests/test_RedcapConnector.py
+++ b/smash/web/tests/test_RedcapConnector.py
@@ -1,16 +1,17 @@
 # coding=utf-8
 
 import logging
-import unittest
 
 from django.test import TestCase
 
-from .functions import create_study_subject, prepare_test_redcap_connection
-from web.models import Language
+from web.models import Language, StudySubject
 from web.models.inconsistent_subject import InconsistentSubject
 from web.models.missing_subject import MissingSubject
-from web.redcap_connector import RedcapConnector, RedcapSubject, different_string
+from web.redcap_connector import RedcapConnector, RedcapSubject, different_string, RedcapVisit
 from web.views.notifications import get_today_midnight_date
+from .functions import create_study_subject, prepare_test_redcap_connection, get_test_study
+from ..models.constants import CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_DATE
+from ..models.custom_data import CustomStudySubjectField
 
 logger = logging.getLogger(__name__)
 
@@ -120,9 +121,11 @@ class TestRedcapConnector(TestCase):
     def test_create_inconsistent_data_for_mpower_id(self):
         prepare_test_redcap_connection()
         subject = create_study_subject()
+        field = CustomStudySubjectField.objects.create(name="MPower ID", type=CUSTOM_FIELD_TYPE_TEXT,
+                                                       study=subject.study, unique=True)
 
         redcap_subject = self.create_redcap_subject_from_smash_subject(subject)
-        subject.mpower_id = "105"
+        subject.set_custom_data_value(field, "105")
 
         self.check_single_inconsistency(redcap_subject, subject)
 
@@ -139,11 +142,11 @@ class TestRedcapConnector(TestCase):
         self.assertIsNone(result)
 
     @staticmethod
-    def create_redcap_subject_from_smash_subject(study_subject):
+    def create_redcap_subject_from_smash_subject(study_subject: StudySubject) -> RedcapSubject:
         redcap_subject = RedcapSubject()
         for language in study_subject.subject.languages.all():
             redcap_subject.add_language(language)
-        redcap_subject.mpower_id = study_subject.mpower_id
+        redcap_subject.mpower_id = study_subject.get_custom_field_value('MPower ID')
         redcap_subject.dead = study_subject.subject.dead
         redcap_subject.date_born = study_subject.subject.date_born
         redcap_subject.nd_number = study_subject.nd_number
@@ -220,3 +223,26 @@ class TestRedcapConnector(TestCase):
         self.assertFalse(different_string("", None))
         self.assertFalse(different_string(" abc", "abc "))
         self.assertTrue(different_string("y", "x"))
+
+    def test_update_data_from_redcap(self):
+        redcap_connection = RedcapConnector()
+
+        collect_field = CustomStudySubjectField.objects.create(study=get_test_study(),
+                                                               default_value='',
+                                                               name='Visit 0 RT-PCR update date',
+                                                               type=CUSTOM_FIELD_TYPE_DATE)
+        status_field = CustomStudySubjectField.objects.create(study=get_test_study(),
+                                                              default_value='',
+                                                              name='Virus 0 RT-PCR',
+                                                              type=CUSTOM_FIELD_TYPE_TEXT)
+
+        subject = create_study_subject()
+        redcap_visit = RedcapVisit()
+        redcap_visit.visit_number = 1
+        redcap_visit.virus = "Inconclusive"
+
+        provenances = redcap_connection.update_data_from_redcap(subject, redcap_visit)
+        self.assertTrue(len(provenances) > 0)
+
+        self.assertEquals(subject.get_custom_data_value(status_field).value, 'Inconclusive')
+        self.assertTrue(subject.get_custom_data_value(collect_field).value != '')
diff --git a/smash/web/tests/view/test_mail.py b/smash/web/tests/view/test_mail.py
index 1f11340a796aedc4e9c0eebf7662ddd50d3cce33..c90273c97b3921d82fb3dc3a7d214d5ec22980dc 100644
--- a/smash/web/tests/view/test_mail.py
+++ b/smash/web/tests/view/test_mail.py
@@ -1,31 +1,38 @@
+import datetime
 import logging
 
-from django.urls import reverse
-
-from web.models import MailTemplate
-from web.models.constants import MAIL_TEMPLATE_CONTEXT_VOUCHER
+from web.models.constants import CUSTOM_FIELD_TYPE_DATE, GLOBAL_STUDY_ID, CUSTOM_FIELD_TYPE_TEXT
+from web.models.custom_data import CustomStudySubjectField
 from web.tests import LoggedInTestCase
-from web.tests.functions import create_voucher, get_resource_path
+from web.tests.functions import create_study_subject
+from web.views.virus_mail import KitRequestEmailSendJob, count_subjects
 
 logger = logging.getLogger(__name__)
 
 
-class MailTests(LoggedInTestCase):
-    def test_generate_vouchers(self):
-        voucher = create_voucher()
-        MailTemplate(name="name", language=None,
-                     context=MAIL_TEMPLATE_CONTEXT_VOUCHER,
-                     template_file=get_resource_path('upcoming_appointment_FR.docx')).save()
-
-        page = reverse('web.views.mail_template_generate_for_vouchers') + "?voucher_id=" + str(voucher.id)
-        response = self.client.get(page)
-        self.assertEqual(response.status_code, 200)
-
-    def test_list_mail_templates(self):
-        self.login_as_admin()
-        response = self.client.get(reverse("web.views.mail_templates"))
-        self.assertEqual(response.status_code, 200)
-
-    def test_list_mail_templates_without_permission(self):
-        response = self.client.get(reverse("web.views.mail_templates"))
-        self.assertEqual(response.status_code, 302)
+class VirusMailTests(LoggedInTestCase):
+    def test_send_email(self):
+        job = KitRequestEmailSendJob()
+        result = job.do()
+        self.assertEqual("mail sent", result)
+
+    def test_empty_count_subjects(self):
+        result = count_subjects(datetime.datetime.now() - datetime.timedelta(days=1), "Positive")
+        self.assertEquals(0, result)
+
+    def test_count_subjects(self):
+        create_study_subject()
+        study_subject = create_study_subject()
+        collect_field = CustomStudySubjectField.objects.create(study_id=GLOBAL_STUDY_ID,
+                                                               default_value='',
+                                                               name='Visit 0 RT-PCR collection date',
+                                                               type=CUSTOM_FIELD_TYPE_DATE)
+        status_field = CustomStudySubjectField.objects.create(study_id=GLOBAL_STUDY_ID,
+                                                              default_value='',
+                                                              name='Virus 0 RT-PCR',
+                                                              type=CUSTOM_FIELD_TYPE_TEXT)
+        study_subject.set_custom_data_value(collect_field, datetime.datetime.now().strftime("%Y-%m-%d"))
+        study_subject.set_custom_data_value(status_field, "Positive")
+
+        result = count_subjects(datetime.datetime.now(), "Positive")
+        self.assertEquals(1, result)
diff --git a/smash/web/tests/view/test_study_subject_list.py b/smash/web/tests/view/test_study_subject_list.py
new file mode 100644
index 0000000000000000000000000000000000000000..8f0d1154d0cab2b0ec932ca1ed334a7001ace6d7
--- /dev/null
+++ b/smash/web/tests/view/test_study_subject_list.py
@@ -0,0 +1,58 @@
+import logging
+
+from django.urls import reverse
+
+from web.forms.study_subject_list_form import StudySubjectListEditForm, StudySubjectColumnsEditForm, \
+    SubjectColumnsEditForm
+from web.models import StudySubjectList, StudyColumns, SubjectColumns
+from web.models.study_subject_list import SUBJECT_LIST_GENERIC
+from web.tests import LoggedInWithWorkerTestCase
+from web.tests.functions import get_test_study, format_form_field
+
+logger = logging.getLogger(__name__)
+
+
+class StudySubjectListViewTests(LoggedInWithWorkerTestCase):
+    def setUp(self):
+        super().setUp()
+        self.study = get_test_study()
+        visible_subject_study_columns = StudyColumns.objects.create()
+        visible_subject_columns = SubjectColumns.objects.create()
+        self.list = StudySubjectList.objects.create(study=self.study, type=SUBJECT_LIST_GENERIC,
+                                                    visible_subject_columns=visible_subject_columns,
+                                                    visible_subject_study_columns=visible_subject_study_columns
+                                                    )
+
+    def test_render_edit(self):
+        self.login_as_admin()
+        response = self.client.get(reverse('web.views.study_subject_list_edit',
+                                           kwargs={'study_id': self.study.id, 'study_subject_list_id': self.list.id}))
+        self.assertEqual(response.status_code, 200)
+
+    def test_save_study(self):
+        self.login_as_admin()
+        form_data = self.create_edit_form_data_for_study()
+
+        response = self.client.post(reverse('web.views.study_subject_list_edit',
+                                            kwargs={'study_id': self.study.id, 'study_subject_list_id': self.list.id}),
+                                    data=form_data)
+
+        self.assertEqual(response.status_code, 302)
+        print(response['Location'])
+        self.assertFalse("subject_list" in response['Location'])
+
+    def create_edit_form_data_for_study(self):
+        list_form = StudySubjectListEditForm(instance=self.list, prefix="list")
+        study_subject_columns_form = StudySubjectColumnsEditForm(instance=self.list.visible_subject_study_columns,
+                                                                 prefix="study_subject")
+        subject_columns_form = SubjectColumnsEditForm(instance=self.list.visible_subject_columns,
+                                                      prefix="subject")
+
+        form_data = {}
+        for key, value in list(list_form.initial.items()):
+            form_data['list-{}'.format(key)] = format_form_field(value)
+        for key, value in list(study_subject_columns_form.initial.items()):
+            form_data['study_subject-{}'.format(key)] = format_form_field(value)
+        for key, value in list(subject_columns_form.initial.items()):
+            form_data['subject_columns_form-{}'.format(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 9551f3e7ed64921324fd712cca08a5634e9b06b1..c7cf50653eb0743465fdb64b200cb2737594a94f 100644
--- a/smash/web/tests/view/test_subjects.py
+++ b/smash/web/tests/view/test_subjects.py
@@ -21,7 +21,7 @@ logger = logging.getLogger(__name__)
 
 class SubjectsViewTests(LoggedInWithWorkerTestCase):
     def setUp(self):
-        super(SubjectsViewTests, self).setUp()
+        super().setUp()
         self.study_subject = create_study_subject()
         self.study = get_test_study()
 
diff --git a/smash/web/tests/view/test_virus_mail.py b/smash/web/tests/view/test_virus_mail.py
new file mode 100644
index 0000000000000000000000000000000000000000..1f11340a796aedc4e9c0eebf7662ddd50d3cce33
--- /dev/null
+++ b/smash/web/tests/view/test_virus_mail.py
@@ -0,0 +1,31 @@
+import logging
+
+from django.urls import reverse
+
+from web.models import MailTemplate
+from web.models.constants import MAIL_TEMPLATE_CONTEXT_VOUCHER
+from web.tests import LoggedInTestCase
+from web.tests.functions import create_voucher, get_resource_path
+
+logger = logging.getLogger(__name__)
+
+
+class MailTests(LoggedInTestCase):
+    def test_generate_vouchers(self):
+        voucher = create_voucher()
+        MailTemplate(name="name", language=None,
+                     context=MAIL_TEMPLATE_CONTEXT_VOUCHER,
+                     template_file=get_resource_path('upcoming_appointment_FR.docx')).save()
+
+        page = reverse('web.views.mail_template_generate_for_vouchers') + "?voucher_id=" + str(voucher.id)
+        response = self.client.get(page)
+        self.assertEqual(response.status_code, 200)
+
+    def test_list_mail_templates(self):
+        self.login_as_admin()
+        response = self.client.get(reverse("web.views.mail_templates"))
+        self.assertEqual(response.status_code, 200)
+
+    def test_list_mail_templates_without_permission(self):
+        response = self.client.get(reverse("web.views.mail_templates"))
+        self.assertEqual(response.status_code, 302)
diff --git a/smash/web/views/export.py b/smash/web/views/export.py
index 8d69c70ada59bbc7fe88fac8e546a19afe98a2bb..193a35b9cf521ef6111775325d15a362e13e8f19 100644
--- a/smash/web/views/export.py
+++ b/smash/web/views/export.py
@@ -95,7 +95,7 @@ APPOINTMENT_TYPE_FIELD = CustomField({
 })
 STUDY_SUBJECT_FIELDS = [CustomField({
     'name': 'nd_number',
-    'verbose_name': 'ND number'
+    'verbose_name': 'Subject number'
 })]
 
 SUBJECT_FIELDS = [CustomField({
@@ -128,15 +128,6 @@ def get_default_subject_fields(study: Study):
             subject_fields.append(field)
     for field in StudySubject._meta.fields:
         if field.name.upper() != "ID" and field.name.upper() != "SUBJECT":
-            if visit_from_zero:
-                match = re.match(r'^virus_test_(\d*)$', field.name)
-                if match:
-                    number = int(match.groups()[0])
-                    field.verbose_name = 'Virus {} RT-PCR'.format(display_visit_number(number))
-                match = re.match(r'^virus_test_(\d*)_updated$', field.name)
-                if match:
-                    number = int(match.groups()[0])
-                    field.verbose_name = 'Virus {} RT-PCR date'.format(display_visit_number(number))
             subject_fields.append(field)
     subject_fields.append(DROP_OUT_FIELD)
     for custom_field in CustomStudySubjectField.objects.filter(study=study).all():
diff --git a/smash/web/views/virus_mail.py b/smash/web/views/virus_mail.py
index 9b6099cc04263b73261772d4e28f37317ad8f232..caf1e69eefc711657d427b2a2d3ed66ab9f88ea4 100644
--- a/smash/web/views/virus_mail.py
+++ b/smash/web/views/virus_mail.py
@@ -3,51 +3,50 @@ import datetime
 import logging
 
 import timeout_decorator
+from django.db.models import When, Case, Min
 from django_cron import CronJobBase, Schedule
 
 from web.models import ConfigurationItem
 from web.models.constants import VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE, \
-    CRON_JOB_TIMEOUT
+    CRON_JOB_TIMEOUT, GLOBAL_STUDY_ID
 from ..models import StudySubject, Worker
+from ..models.custom_data import CustomStudySubjectField
 from ..smash_email import EmailSender
 
 logger = logging.getLogger(__name__)
 
 
+def count_subjects(date_when: datetime, status: str) -> int:
+    result = 0
+    str_date = date_when.strftime("%Y-%m-%d")
+    for i in range(0, 10):
+        fields = CustomStudySubjectField.objects.filter(study_id=GLOBAL_STUDY_ID,
+                                                        name='Visit {} RT-PCR collection date'.format(i)).all()
+        collect_field = None
+        if len(fields) > 0:
+            collect_field = fields[0]
+        fields = CustomStudySubjectField.objects.filter(study_id=GLOBAL_STUDY_ID,
+                                                        name__contains='Virus {} RT-PCR'.format(i)).all()
+        status_field = None
+        if len(fields) > 0:
+            status_field = fields[0]
+        if collect_field is not None and status_field is not None:
+            print('exist')
+            result += StudySubject.objects.annotate(
+                custom_field_update_value=Min(Case(When(customstudysubjectvalue__study_subject_field=collect_field,
+                                                        then='customstudysubjectvalue__value')))).annotate(
+                custom_field_status_value=Min(Case(When(customstudysubjectvalue__study_subject_field=status_field,
+                                                        then='customstudysubjectvalue__value')))).filter(
+                custom_field_update_value=str_date, custom_field_status_value=status).count()
+    return result
+
+
 def get_subject_statistics():
     date_from = datetime.datetime.now() - datetime.timedelta(days=1)
 
-    subjects_positive_count = StudySubject.objects.filter(virus_test_1_updated__gt=date_from, virus_test_1=True).count()
-    subjects_positive_count += StudySubject.objects.filter(virus_test_2_updated__gt=date_from,
-                                                           virus_test_2=True).count()
-    subjects_positive_count += StudySubject.objects.filter(virus_test_3_updated__gt=date_from,
-                                                           virus_test_3=True).count()
-    subjects_positive_count += StudySubject.objects.filter(virus_test_4_updated__gt=date_from,
-                                                           virus_test_4=True).count()
-    subjects_positive_count += StudySubject.objects.filter(virus_test_5_updated__gt=date_from,
-                                                           virus_test_5=True).count()
-
-    subjects_negative_count = StudySubject.objects.filter(virus_test_1_updated__gt=date_from,
-                                                          virus_test_1=False).count()
-    subjects_negative_count += StudySubject.objects.filter(virus_test_2_updated__gt=date_from,
-                                                           virus_test_2=False).count()
-    subjects_negative_count += StudySubject.objects.filter(virus_test_3_updated__gt=date_from,
-                                                           virus_test_3=False).count()
-    subjects_negative_count += StudySubject.objects.filter(virus_test_4_updated__gt=date_from,
-                                                           virus_test_4=False).count()
-    subjects_negative_count += StudySubject.objects.filter(virus_test_5_updated__gt=date_from,
-                                                           virus_test_5=False).count()
-
-    subjects_inconclusive_count = StudySubject.objects.filter(virus_test_1_updated__gt=date_from,
-                                                              virus_test_1__isnull=True).count()
-    subjects_inconclusive_count += StudySubject.objects.filter(virus_test_2_updated__gt=date_from,
-                                                               virus_test_2__isnull=True).count()
-    subjects_inconclusive_count += StudySubject.objects.filter(virus_test_3_updated__gt=date_from,
-                                                               virus_test_3__isnull=True).count()
-    subjects_inconclusive_count += StudySubject.objects.filter(virus_test_4_updated__gt=date_from,
-                                                               virus_test_4__isnull=True).count()
-    subjects_inconclusive_count += StudySubject.objects.filter(virus_test_5_updated__gt=date_from,
-                                                               virus_test_5__isnull=True).count()
+    subjects_positive_count = count_subjects(date_from, "Positive")
+    subjects_negative_count = count_subjects(date_from, "Negative")
+    subjects_inconclusive_count = count_subjects(date_from, "Inconclusive")
 
     return {"Positive": subjects_positive_count, "Negative": subjects_negative_count,
             "Inconclusive": subjects_inconclusive_count,
@@ -87,11 +86,11 @@ def create_statistic_email_content(data, title):
 
     email_body += "</table>"
 
-    #SARS-COV2 Virus  | Number of Donors
-    #Inconclusive     |               5
-    #Positive         |              15
-    #Negative         |              45
-    #Total            |              65
+    # SARS-COV2 Virus  | Number of Donors
+    # Inconclusive     |               5
+    # Positive         |              15
+    # Negative         |              45
+    # Total            |              65
 
     return email_body
 
@@ -117,12 +116,12 @@ class KitRequestEmailSendJob(CronJobBase):
 
     # noinspection PyBroadException
     try:
-        times = ConfigurationItem.objects.get(
-            type=VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(";")
+        times = ConfigurationItem.objects.get(type=VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE).value.split(";")
         for entry in times:
-            # TODO it's a hack assuming that we are in CEST
-            text = str((int(entry.split(":")[0]) + 22) % 24) + ":" + entry.split(":")[1]
-            RUN_AT.append(text)
+            if entry != '':
+                # TODO it's a hack assuming that we are in CEST
+                text = str((int(entry.split(":")[0]) + 22) % 24) + ":" + entry.split(":")[1]
+                RUN_AT.append(text)
     except BaseException as e:
         logger.debug("Cannot fetch data about email hour")
     schedule = Schedule(run_at_times=RUN_AT)