diff --git a/.dockerignore b/.dockerignore
index f86479bc7e23f02f6a2fbc981fa2d3ed3b2599aa..0e6eb27466cb61ff5c9c5e396a137fa40bcc1b75 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,7 +1,10 @@
 env*
+smash/*.txt
+smash/*.sqlite3
 **/*.pyc
 .vscode/*
 Dockerfile
 docker-compose.yml
 **/__pycache__/*
 smash/__pycache__/*
+*.pem
diff --git a/.gitignore b/.gitignore
index c6232b596b3b16d1a71ce5b18d9955ead3f5b132..54229cf8866b6933833500c015ed4342c33d1ad3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -51,3 +51,5 @@ rpm
 
 #OS generated folders
 **/.DS_Store
+
+*.pem
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2b5b1c58467f3f6778429f98d013fba236f4952d..1020eb338b7b49147818f5682726f24cb26df7df 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -7,6 +7,10 @@ variables:
   POSTGRES_DB: smash
   POSTGRES_USER: runner
   POSTGRES_PASSWORD: password
+  MARIADB_USER: runner
+  MARIADB_PASSWORD: password
+  MARIADB_ROOT_PASSWORD: password
+  MARIADB_DATABASE: dbtest
 
 gemnasium-python-dependency_scanning:
   before_script:
@@ -15,7 +19,7 @@ gemnasium-python-dependency_scanning:
 .test_template: &test_definition
   stage: test
   before_script:
-    - apt-get update && apt-get install -y --allow-unauthenticated libsasl2-dev libssl-dev locales locales-all libsasl2-dev libldap2-dev libssl-dev
+    - apt-get update && apt-get install -y --allow-unauthenticated libsasl2-dev libssl-dev locales locales-all libsasl2-dev libldap2-dev libssl-dev default-libmysqlclient-dev
     - python -V
     - pip install --upgrade pip --default-timeout=180 -i https://repomanager.lcsb.uni.lu/repository/pypi-proxy/simple/ --extra-index-url https://pypi.python.org/simple/
 
@@ -24,7 +28,7 @@ gemnasium-python-dependency_scanning:
     - pip install -r requirements-dev.txt --default-timeout=180 -i https://repomanager.lcsb.uni.lu/repository/pypi-proxy/simple/ --extra-index-url https://pypi.python.org/simple/
 
 
-test_migrations_created:
+test_migrations_created_postgres:
     <<: *test_definition
     services:
          - postgres:latest
@@ -34,6 +38,16 @@ test_migrations_created:
          - python manage.py makemigrations --check --dry-run
          - test 1 = $(python manage.py makemigrations --check --dry-run | grep 'No changes detected' |wc -l)
 
+test_migrations_created_mariadb:
+    <<: *test_definition
+    services:
+         - mariadb:10.6-ubi
+    script:
+         - cp "local_settings_ci_mariadb.py" "smash/smash/local_settings.py"
+         - cd smash
+         - python manage.py makemigrations --check --dry-run
+         - test 1 = $(python manage.py makemigrations --check --dry-run | grep 'No changes detected' |wc -l)
+
 test_postgres:
     <<: *test_definition
     services:
@@ -44,6 +58,18 @@ test_postgres:
          - coverage run --source web manage.py test -v3
          - coverage report -m --omit="*/test*,*/migrations*,*debug_utils*"
 
+test_mariadb:
+    <<: *test_definition
+    services:
+         - mariadb:10.6-ubi
+    script:
+         - cp "local_settings_ci_mariadb.py" "smash/smash/local_settings.py"
+         - cd smash
+         - sleep 10
+         - coverage run --source web manage.py test --noinput -v3
+         - coverage report -m --omit="*/test*,*/migrations*,*debug_utils*"
+
+
 test_sqlite:
     <<: *test_definition
     script:
diff --git a/.pylintrc b/.pylintrc
index 003bd7d1290970436db631d79a7c47f30565b601..1072a8ecf34605bcdef4b76121744d762fcac490 100644
--- a/.pylintrc
+++ b/.pylintrc
@@ -7,6 +7,10 @@ disable=
     C0114, # missing-module-docstring
     C0115, # missing-class-docstring
     C0116, # missing-function-docstring
+    R0917, # too-many-positional-arguments
+    R1731, # consider-using-max-builtin
+    R1730, # consider-using-min-builtin,
+    E1101, # no-member
 
 # TO BE CHECKED
     W0143, # comparison-with-callable
diff --git a/CHANGELOG b/CHANGELOG
index ef620269143aa9bc6ce13c102b6c6e671cdc0cb0..21cb55fdc0dbbe162ea3934b333c328f55d6f72c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,14 @@
+smasch (1.4.5-1) stable; urgency=medium
+  * added support for mariadb
+  * allow overriding the redcap test instance in settings.
+
+  -- Carlos Vega <carlos.vega@lih.lu> Wed, 31 Jul 2024 10:11:00 +0200
+
 smasch (1.4.4-1) stable; urgency=high
   * fix bug in api/availabilities endpoint
 
+  -- Carlos Vega <carlos.vega@lih.lu> Thu, 13 Jun 2024 9:56:00 +0200
+
 smasch (1.4.3-1) stable; urgency=medium
   * make export faster
   * make API endpoint /api/subjects/GENERIC faster
diff --git a/Dockerfile b/Dockerfile
index d6e9c0a795c0b5da71e17ba7cad33e6a6546e1aa..95606d491429f50b7b26c01d9229aa1cbda5071d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,7 +10,7 @@ RUN node --version \
 FROM python:3.11-bookworm
 
 RUN apt-get update \
-    && apt-get install -y --allow-unauthenticated libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev locales locales-all
+    && apt-get install -y --allow-unauthenticated libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev locales locales-all default-libmysqlclient-dev
 
 RUN mkdir /code
 ADD . /code/
diff --git a/local_settings_ci.py b/local_settings_ci.py
index 54c32696d747b51f9a9e353644f67ecdc131fd2b..7d323b3a14620baa358b103602a34bf579cd1dcd 100644
--- a/local_settings_ci.py
+++ b/local_settings_ci.py
@@ -1,5 +1,5 @@
 # SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = 'Paste long random string here'  # Insert long random string
+SECRET_KEY = "Paste long random string here"  # Insert long random string
 
 # SECURITY WARNING: don't run with debug turned on in production!
 DEBUG = True
@@ -10,28 +10,31 @@ SERVE_STATIC = True
 # https://docs.djangoproject.com/en/1.10/ref/settings/#databases
 
 DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.postgresql_psycopg2',
-        'NAME': 'smash',  # Insert your database's name
-        'USER': 'runner',  # Insert your database's user
-        'PASSWORD': 'password',  # Insert your user's password
-        'HOST': 'postgres',
-        'PORT': '',
-        'TEST': {
-            'NAME': 'dbtest',
+    "default": {
+        "ENGINE": "django.db.backends.postgresql_psycopg2",
+        "NAME": "smash",  # Insert your database's name
+        "USER": "runner",  # Insert your database's user
+        "PASSWORD": "password",  # Insert your user's password
+        "HOST": "postgres",
+        "PORT": "",
+        "TEST": {
+            "NAME": "dbtest",
         },  # '' === default one # Empty string is OK
-
         # If to use sqlite
         # 'ENGINE': 'django.db.backends.sqlite3',
         # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
     }
 }
 
-STATIC_ROOT = '/tmp/static'  # Warning! `/tmp` directory can be flushed in any moment; use a persistent one; e.g. ~/tmp/static
-MEDIA_ROOT = '/tmp/media'  # Warning! `/tmp` directory can be flushed in any moment; use a persistent one, e.g. ~/tmp/media
-UPLOAD_ROOT = '~/tmp/upload'
-ETL_ROOT = '/tmp/etl'
+STATIC_ROOT = (
+    "/tmp/static"  # Warning! `/tmp` directory can be flushed in any moment; use a persistent one; e.g. ~/tmp/static
+)
+MEDIA_ROOT = (
+    "/tmp/media"  # Warning! `/tmp` directory can be flushed in any moment; use a persistent one, e.g. ~/tmp/media
+)
+UPLOAD_ROOT = "/tmp/upload"
+ETL_ROOT = "/tmp/etl"
 
-STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'
+STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
 
 ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
diff --git a/local_settings_ci_mariadb.py b/local_settings_ci_mariadb.py
new file mode 100644
index 0000000000000000000000000000000000000000..1c4ecc5e8df6a7860dc125c5245050e8fa3b3c47
--- /dev/null
+++ b/local_settings_ci_mariadb.py
@@ -0,0 +1,35 @@
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = "Paste long random string here"  # Insert long random string
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+# Database
+# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
+
+DATABASES = {
+    "default": {
+        "ENGINE": "django.db.backends.mysql",
+        "NAME": "smash",
+        "USER": "runner",
+        "PASSWORD": "password",
+        "HOST": "mariadb",
+        "PORT": "",
+        "TEST": {
+            "NAME": "dbtest",
+        },
+    }
+}
+
+STATIC_ROOT = (
+    "/tmp/static"  # Warning! `/tmp` directory can be flushed in any moment; use a persistent one; e.g. ~/tmp/static
+)
+MEDIA_ROOT = (
+    "/tmp/media"  # Warning! `/tmp` directory can be flushed in any moment; use a persistent one, e.g. ~/tmp/media
+)
+UPLOAD_ROOT = "/tmp/upload"
+ETL_ROOT = "/tmp/etl"
+
+STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"
+
+ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 82d46b071491f2f523491e3a2ab196fcec63262c..a577b36cdca50a526db9e8700a582a1a5710b24b 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,10 +1,9 @@
-coverage==7.3.2
+coverage==7.6.1
 django-debug-toolbar==4.2.0
 fakeldap==0.6.6
 Faker==19.13.0
 mock==5.1.0
-mockito==1.4.0
 parameterized==0.9.0
 pycodestyle==2.11.1
-pylint==3.0.2
+pylint==3.3.1
 pylint_django==2.5.5
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index f651e25d83f1b34700cea736b071787eca38dd05..1ab039f81732573c411d21377e23a997a4f31025 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,68 +1,35 @@
 pycurl==7.45.2
-asn1crypto==1.5.1
 Babel==2.13.1
-backports.functools-lru-cache==1.6.6
-certifi==2023.7.22
-cffi==1.16.0
-chardet==5.2.0
-coverage==7.3.2
-cryptography==41.0.5
-cycler==0.12.1
+certifi==2024.8.30
 Django==3.2.22
-django_auth_ldap==4.6.0
-django-cleanup==8.0.0
-django-common-helpers==0.9.2
+django_auth_ldap==4.8.0
+django-cron==0.6.0
+django-cleanup==9.0.0
 django-cron==0.6.0
 django-excel==0.0.10
-django-formtools==2.4.1
 django-npm==1.0.0
-django-otp==1.2.4
+django-otp==1.5.4
 django-phonenumber-field==6.4.0
 django-stronghold==0.4.0
-django-two-factor-auth==1.15.5
-enum34==1.1.10
-funcsigs==1.0.2
-gunicorn==21.2.0
-idna==3.4
-ipaddress==1.0.23
-kiwisolver==1.4.5
-lml==0.1.0
+django-two-factor-auth==1.16.0
+gunicorn==23.0.0
 luhn==0.2.0
-lxml==4.9.3
-matplotlib==3.8.1
-mockito==1.4.0
+matplotlib==3.9.2
+mockito==1.5.1
+mysqlclient==2.2.4
 nexmo==2.5.2
-numpy==1.26.1
-pandas==2.1.2
-packaging==23.2
+numpy==1.26.4
+pandas==2.2.3
 django-datatables-view==1.20.0
-phonenumberslite==8.13.24
-Pillow==10.1.0
+phonenumberslite==8.13.46
 psycopg2==2.9.9
-pycparser==2.21
-pyexcel==0.7.0
-pyexcel-io==0.6.6
-pyexcel-webio==0.1.4
-pyexcel-xls==0.7.0
-PyJWT==2.8.0
-pyparsing==3.1.1
 python-dateutil==2.8.2
-python-docx==1.0.1
-pytz==2023.3.post1
-py==1.10.0
+python-docx==1.1.2
+pytz==2024.2
 pytest==7.4.3
-qrcode==7.4.2
-requests==2.31.0
+pyexcel-xls==0.7.0
 six==1.16.0
-sqlparse==0.4.4
-subprocess32==3.5.4
-text-unidecode==1.3
-texttable==1.7.0
 timeout-decorator==0.5.0
-urllib3==2.0.7
-whitenoise==6.6.0
-xlrd==2.0.1
-wheel==0.41.3
-xlwt==1.3.0
+whitenoise==6.7.0
 parameterized==0.9.0
 setuptools==68.2.2
diff --git a/smash/smash/settings.py b/smash/smash/settings.py
index 53366991ed9bfea8a41a5ea7d788f979333c82d7..12e6fc6b411a20ef64351e023c6f51102f91ec4b 100644
--- a/smash/smash/settings.py
+++ b/smash/smash/settings.py
@@ -11,18 +11,29 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
 """
 
 import functools
+import logging
 import os
 import ldap
 from django_auth_ldap.config import LDAPSearch
 
 from django.contrib.staticfiles import storage
 
+logger = logging.getLogger(__name__)
+
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 PROJECT_PATH = os.path.abspath(os.path.dirname(__file__))
 # Quick-start development settings - unsuitable for production
 # See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
 
+# To run the unit tests we need a redcap instance.
+# UL
+REDCAP_TEST_API_TOKEN = os.environ.get('REDCAP_TEST_API_TOKEN', None)
+REDCAP_TEST_URL = os.environ.get('REDCAP_TEST_URL', None)
+
+if REDCAP_TEST_API_TOKEN is None or REDCAP_TEST_URL is None:
+    logging.warning('Environment variables REDCAP_TEST_API_TOKEN or REDCAP_TEST_URL does not exist. Unit tests for redcap will fail without them having proper values.')
+
 COPYRIGHT_NOTE = "2024 Bioinformatics Core, Luxembourg Centre for Systems Biomedicine"
 
 DEBUG = True
diff --git a/smash/web/__init__.py b/smash/web/__init__.py
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a5faf6259d3efda9ac2dc277bdc29872cee556a5 100644
--- a/smash/web/__init__.py
+++ b/smash/web/__init__.py
@@ -0,0 +1,16 @@
+from functools import wraps
+
+
+def disable_for_loaddata(signal_handler):
+    """
+    Decorator that turns off signal handlers when loading fixture data.
+    https://docs.djangoproject.com/en/3.2/ref/django-admin/#what-s-a-fixture
+    """
+
+    @wraps(signal_handler)
+    def wrapper(*args, **kwargs):
+        if "raw" in kwargs and kwargs["raw"]:
+            return
+        signal_handler(*args, **kwargs)
+
+    return wrapper
diff --git a/smash/web/api_views/serialization_utils.py b/smash/web/api_views/serialization_utils.py
index d919b7bd26de4c9e5f7a867ef11d14c63dd5f188..3f9cee1e19426bb73238ea253ac4b8cbf9cdfd95 100644
--- a/smash/web/api_views/serialization_utils.py
+++ b/smash/web/api_views/serialization_utils.py
@@ -21,7 +21,7 @@ def bool_to_yes_no_null(val: bool):
 
 
 def str_to_yes_no(val: str):
-    if val.lower() == 'true':
+    if val.lower() == "true":
         return "YES"
     else:
         return "NO"
@@ -30,7 +30,7 @@ def str_to_yes_no(val: str):
 def str_to_yes_no_null(val: str):
     if val is None:
         return None
-    if val.lower() == 'true':
+    if val.lower() == "true":
         return "YES"
     else:
         return "NO"
@@ -52,7 +52,7 @@ def location_to_str(location):
 
 def serialize_date(date):
     if date is not None:
-        result = date.strftime('%Y-%m-%d')
+        result = date.strftime("%Y-%m-%d")
     else:
         result = ""
     return result
@@ -60,15 +60,23 @@ def serialize_date(date):
 
 def serialize_datetime(date):
     if date is not None:
-        result = date.strftime('%Y-%m-%d %H:%M')
+        result = date.strftime("%Y-%m-%d %H:%M")
     else:
         result = ""
     return result
 
 
-def add_column(result: List, name: str, field_name: str, column_list: object, param_filter: Optional[str],
-               columns_used_in_study: Optional[object] = None, visible_param: Optional[bool] = None,
-               sortable: Optional[bool] = True, add_param: Optional[bool] = True):
+def add_column(
+    result: List,
+    name: str,
+    field_name: str,
+    column_list: object,
+    param_filter: Optional[str],
+    columns_used_in_study: Optional[object] = None,
+    visible_param: Optional[bool] = None,
+    sortable: Optional[bool] = True,
+    add_param: Optional[bool] = True,
+):
     add = add_param
     if columns_used_in_study:
         add = getattr(columns_used_in_study, field_name)
@@ -79,21 +87,30 @@ def add_column(result: List, name: str, field_name: str, column_list: object, pa
             visible = True
         else:
             visible = getattr(column_list, field_name)
-        result.append({
-            "type": field_name,
-            "name": name,
-            "filter": param_filter,
-            "visible": visible,
-            "sortable": sortable
-        })
+        result.append(
+            {
+                "type": field_name,
+                "name": name,
+                "filter": param_filter,
+                "visible": visible,
+                "sortable": sortable,
+            }
+        )
 
 
 def get_filters_for_data_table_request(request_data):
     filters = []
     column_id = 0
-    while request_data.get("columns[" + str(column_id) + "][search][value]", "unknown") != "unknown":
-        val = request_data.get("columns[" + str(column_id) + "][search][value]", "unknown")
+    while (
+        request_data.get("columns[" + str(column_id) + "][search][value]", "unknown")
+        != "unknown"
+    ):
+        val = request_data.get(
+            "columns[" + str(column_id) + "][search][value]", "unknown"
+        )
         if val != "":
-            filters.append([request_data.get("columns[" + str(column_id) + "][data]"), val])
+            filters.append(
+                [request_data.get("columns[" + str(column_id) + "][data]"), val]
+            )
         column_id += 1
     return filters
diff --git a/smash/web/auth.py b/smash/web/auth.py
index 8fc88205e85212f77d25d1b3cbda6118f59ce6a3..bf8c54958f8c7becc4f2347c411ce1a4140a4b4d 100644
--- a/smash/web/auth.py
+++ b/smash/web/auth.py
@@ -8,9 +8,8 @@ logger = logging.getLogger(__name__)
 
 
 def do_login(request):
-    user_login = request.POST.get('username', 'none')
-    user = authenticate(username=user_login,
-                        password=request.POST.get('password', 'none'))
+    user_login = request.POST.get("username", "none")
+    user = authenticate(username=user_login, password=request.POST.get("password", "none"))
     if user is not None:
         login(request, user)
         return True, "ok"
@@ -31,15 +30,15 @@ def do_logout(request):
 def user_logged_in_callback(sender, request, user, **kwargs):  # pylint: disable=unused-argument
     # to cover more complex cases:
     # http://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django
-    ip = request.META.get('REMOTE_ADDR')
-    logger.info('login user: %s via ip: %s', user, ip)
+    ip = request.META.get("REMOTE_ADDR")
+    logger.info("login user: %s via ip: %s", user, ip)
 
 
 @receiver(user_logged_out)
 def user_logged_out_callback(sender, request, user, **kwargs):  # pylint: disable=unused-argument
-    ip = request.META.get('REMOTE_ADDR')
+    ip = request.META.get("REMOTE_ADDR")
 
-    logger.info('logout user: %s via ip: %s', user, ip)
+    logger.info("logout user: %s via ip: %s", user, ip)
 
 
 @receiver(user_login_failed)
diff --git a/smash/web/importer/etl_common.py b/smash/web/importer/etl_common.py
index dc3ba581ad46ba3633222abcee36373ddcaeab3d..e1d0191489635190a56d8bdaad92b7569965520f 100644
--- a/smash/web/importer/etl_common.py
+++ b/smash/web/importer/etl_common.py
@@ -44,45 +44,65 @@ class EtlCommon:
                 return new_value
         return new_value
 
-    def create_provenance_and_change_data(self, object_to_change: models.Model, field_name: str, new_value: object,
-                                          object_type: Type[models.Model]) -> Optional[Provenance]:
+    def create_provenance_and_change_data(
+        self,
+        object_to_change: models.Model,
+        field_name: str,
+        new_value: object,
+        object_type: Type[models.Model],
+    ) -> Optional[Provenance]:
         old_value = getattr(object_to_change, field_name)
         if old_value != new_value:
             setattr(object_to_change, field_name, new_value)
-            return self.create_provenance(field_name, new_value, object_to_change, object_type, old_value)
+            return self.create_provenance(
+                field_name, new_value, object_to_change, object_type, old_value
+            )
         return None
 
-    def create_provenance(self, field_name: str, new_value: object, object_to_change: models.Model,
-                          object_type: Type[models.Model], old_value: object) -> Provenance:
+    def create_provenance(
+        self,
+        field_name: str,
+        new_value: object,
+        object_to_change: models.Model,
+        object_type: Type[models.Model],
+        old_value: object,
+    ) -> Provenance:
         description = f'{field_name} changed from "{old_value}" to "{new_value}"'
-        p = Provenance(modified_table=object_type._meta.db_table,
-                       modified_table_id=object_to_change.id,
-                       modification_author=self.etl_data.import_worker,
-                       previous_value=old_value,
-                       new_value=new_value,
-                       modification_description=description,
-                       modified_field=field_name,
-                       )
+        p = Provenance(
+            modified_table=object_type._meta.db_table,
+            modified_table_id=object_to_change.id,
+            modification_author=self.etl_data.import_worker,
+            previous_value=old_value,
+            new_value=new_value,
+            modification_description=description,
+            modified_field=field_name,
+        )
         p.save()
         return p
 
-    def create_provenance_for_new_object(self, object_type: Type[models.Model], new_object: models.Model) -> list:
+    def create_provenance_for_new_object(
+        self, object_type: Type[models.Model], new_object: models.Model
+    ) -> list:
         result = []
         for field in object_type._meta.get_fields():
-            if field.get_internal_type() == "CharField" or \
-                    field.get_internal_type() == "DateField" or \
-                    field.get_internal_type() == "IntegerField" or \
-                    field.get_internal_type() == "DateTimeField" or \
-                    field.get_internal_type() == "BooleanField":
+            if (
+                field.get_internal_type() == "CharField"
+                or field.get_internal_type() == "DateField"
+                or field.get_internal_type() == "IntegerField"
+                or field.get_internal_type() == "DateTimeField"
+                or field.get_internal_type() == "BooleanField"
+            ):
                 new_value = getattr(new_object, field.name)
                 if new_value is not None and new_value != "":
-                    p = self.create_provenance(field.name, new_value, new_object, object_type, '')
+                    p = self.create_provenance(
+                        field.name, new_value, new_object, object_type, ""
+                    )
                     result.append(p)
         return result
 
     @staticmethod
     def remove_bom(line) -> str:
         if isinstance(line, str):
-            return line[3:] if line.encode('utf8').startswith(codecs.BOM_UTF8) else line
+            return line[3:] if line.encode("utf8").startswith(codecs.BOM_UTF8) else line
         else:
             return line[3:] if line.startswith(codecs.BOM_UTF8) else line
diff --git a/smash/web/management/commands/holidays.py b/smash/web/management/commands/holidays.py
index 8804121a0d32d5c66889642e7459a5ad31983a72..c8b1878292a34ab7719fcdd89f85ee9c4eca3f74 100644
--- a/smash/web/management/commands/holidays.py
+++ b/smash/web/management/commands/holidays.py
@@ -14,27 +14,47 @@ def get_ascension_day(easter_sunday):
 
 
 class Command(BaseCommand):
-    help = 'import holidays for the specified years'
+    help = "import holidays for the specified years"
 
     def add_arguments(self, parser):
-        parser.add_argument('year', nargs='+', type=int)
+        parser.add_argument("year", nargs="+", type=int)
 
     def handle(self, *args, **options):
         for location in Location.objects.exclude(name="Flying Team").all():
-            for year in options['year']:
-                self.stdout.write(f"importing holidays for year {year} and location {location}")
+            for year in options["year"]:
+                self.stdout.write(
+                    f"importing holidays for year {year} and location {location}"
+                )
                 # new years day
                 self.create_holiday(year, 1, 1, "New Years Day", location)
                 # easter monday
                 easter_sunday = get_easter_sunday_date(year)
                 easter_monday = get_easter_monday(easter_sunday)
-                self.create_holiday(year, easter_monday.month, easter_monday.day, "Easter Monday", location)
+                self.create_holiday(
+                    year,
+                    easter_monday.month,
+                    easter_monday.day,
+                    "Easter Monday",
+                    location,
+                )
                 # ascension day
                 ascension_day = get_ascension_day(easter_sunday)
-                self.create_holiday(year, ascension_day.month, ascension_day.day, "Ascension Day", location)
+                self.create_holiday(
+                    year,
+                    ascension_day.month,
+                    ascension_day.day,
+                    "Ascension Day",
+                    location,
+                )
                 # pentecost monday
                 pentecost_day = get_pentecost_day(easter_sunday)
-                self.create_holiday(year, pentecost_day.month, pentecost_day.day, "Pentecost Monday", location)
+                self.create_holiday(
+                    year,
+                    pentecost_day.month,
+                    pentecost_day.day,
+                    "Pentecost Monday",
+                    location,
+                )
                 # labour day
                 self.create_holiday(year, 5, 1, "Labour Day", location)
                 # national day
@@ -49,22 +69,33 @@ class Command(BaseCommand):
 
     def create_holiday(self, year, month, day, comment, location):
         # check if already exists:
-        count = Appointment.objects.filter(datetime_when__year=year, datetime_when__month=month, datetime_when__day=day,
-                                           location=location, comment=comment).count()
+        count = Appointment.objects.filter(
+            datetime_when__year=year,
+            datetime_when__month=month,
+            datetime_when__day=day,
+            location=location,
+            comment=comment,
+        ).count()
         if count != 0:
             self.stdout.write(
-                f'an holiday with the same description already exists for the same day: {comment}')
+                f"an holiday with the same description already exists for the same day: {comment}"
+            )
             return
         holiday = Appointment()
-        holiday.datetime_when = datetime.datetime(year=year, month=month, day=day, hour=9)
+        holiday.datetime_when = datetime.datetime(
+            year=year, month=month, day=day, hour=9
+        )
         holiday.location = location
         holiday.length = 60 * 8
         holiday.comment = comment
         holiday.visit_id = None
         holiday.save()
-        appointment_type_other, _ = AppointmentType.objects.get_or_create(code='OTHER',
-                                                                          defaults={'default_duration': 60})
-        link = AppointmentTypeLink(appointment=holiday, appointment_type=appointment_type_other)
+        appointment_type_other, _ = AppointmentType.objects.get_or_create(
+            code="OTHER", defaults={"default_duration": 60}
+        )
+        link = AppointmentTypeLink(
+            appointment=holiday, appointment_type=appointment_type_other
+        )
         link.save()
 
 
diff --git a/smash/web/migrations/0001_initial.py b/smash/web/migrations/0001_initial.py
index 545ed4a1da48b3249d8e711983d73b08b80c46ab..688877651ca5d2fe8a1190cd40f39c0522e83418 100644
--- a/smash/web/migrations/0001_initial.py
+++ b/smash/web/migrations/0001_initial.py
@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
             fields=[
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
                 ("code", models.CharField(max_length=20, verbose_name="Appointment code")),
-                ("description", models.CharField(max_length=2000, verbose_name="Appointment description")),
+                ("description", models.TextField(max_length=2000, verbose_name="Appointment description")),
                 ("default_duration", models.IntegerField(verbose_name="Default duration (in minutes)")),
                 ("rest_time", models.IntegerField(verbose_name="Suggested rest time")),
             ],
@@ -125,7 +125,7 @@ class Migration(migrations.Migration):
                     ),
                 ),
                 ("main_pseudonym", models.CharField(max_length=45, verbose_name="Pseudonym")),
-                ("comments", models.CharField(max_length=2000, verbose_name="Comments")),
+                ("comments", models.TextField(max_length=2000, verbose_name="Comments")),
                 ("date_added", models.DateField(auto_now=True, verbose_name="Added on")),
                 ("referral", models.CharField(max_length=128, null=True, verbose_name="Referred by")),
                 ("diagnosis", models.CharField(max_length=128, null=True, verbose_name="Diagnosis")),
diff --git a/smash/web/migrations/0001_version-1-0-0.py b/smash/web/migrations/0001_version-1-0-0.py
index 251793ee9a7b8c4a8748cdb02693b5a8e922e99a..beb180f00eeb90635d1df801f6c162712f737e76 100644
--- a/smash/web/migrations/0001_version-1-0-0.py
+++ b/smash/web/migrations/0001_version-1-0-0.py
@@ -6,6 +6,7 @@ import django.core.validators
 import django.db.models.deletion
 from django.conf import settings
 from django.db import migrations, models
+from django.db import connection
 
 from web.models.appointment_list import APPOINTMENT_LIST_UNFINISHED
 from web.models.constants import (
@@ -1596,7 +1597,7 @@ class Migration(migrations.Migration):
             fields=[
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
                 ("code", models.CharField(max_length=20, verbose_name="Code")),
-                ("description", models.CharField(blank=True, max_length=1024, verbose_name="Description")),
+                ("description", models.TextField(blank=True, max_length=1024, verbose_name="Description")),
                 ("study", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="web.study")),
             ],
         ),
@@ -1673,7 +1674,7 @@ class Migration(migrations.Migration):
             fields=[
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
                 ("code", models.CharField(max_length=20, verbose_name="Appointment code")),
-                ("description", models.CharField(max_length=2000, verbose_name="Appointment description")),
+                ("description", models.TextField(max_length=2000, verbose_name="Appointment description")),
                 ("default_duration", models.IntegerField(verbose_name="Default duration (in minutes)")),
                 ("rest_time", models.IntegerField(default=0, verbose_name="Suggested rest time")),
                 (
@@ -1694,11 +1695,11 @@ class Migration(migrations.Migration):
                         verbose_name="Type of worker required for appointment",
                     ),
                 ),
-                ("calendar_color", models.CharField(default="#cfc600", max_length=2000, verbose_name="Calendar color")),
+                ("calendar_color", models.TextField(default="#cfc600", max_length=2000, verbose_name="Calendar color")),
                 ("calendar_color_priority", models.IntegerField(default=1, verbose_name="Calendar color priority")),
                 (
                     "calendar_font_color",
-                    models.CharField(default="#00000", max_length=2000, verbose_name="Calendar font color"),
+                    models.TextField(default="#00000", max_length=2000, verbose_name="Calendar font color"),
                 ),
                 ("can_be_parallelized", models.BooleanField(default=False, verbose_name="Can be parallelized")),
             ],
@@ -2157,7 +2158,7 @@ class Migration(migrations.Migration):
                         max_length=20,
                     ),
                 ),
-                ("possible_values", models.CharField(blank=True, default="", max_length=1024, null=True)),
+                ("possible_values", models.TextField(blank=True, default="", max_length=1024, null=True)),
                 ("default_value", models.CharField(blank=True, max_length=256, null=True)),
                 ("readonly", models.BooleanField(default=False)),
                 ("required", models.BooleanField(default=False)),
@@ -2177,7 +2178,7 @@ class Migration(migrations.Migration):
             name="CustomStudySubjectValue",
             fields=[
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
-                ("value", models.CharField(blank=True, max_length=2048, null=True)),
+                ("value", models.TextField(blank=True, max_length=2048, null=True)),
                 (
                     "study_subject",
                     models.ForeignKey(
@@ -2371,15 +2372,15 @@ class Migration(migrations.Migration):
             name="Provenance",
             fields=[
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
-                ("modified_table", models.CharField(max_length=1024, null=True, verbose_name="Modified table")),
+                ("modified_table", models.CharField(max_length=128, null=True, verbose_name="Modified table")),
                 ("modified_table_id", models.IntegerField(default=0, null=True, verbose_name="Modified table row")),
                 ("modification_date", models.DateTimeField(auto_now_add=True, verbose_name="Modified on")),
                 (
                     "previous_value",
-                    models.CharField(blank=True, max_length=2048, null=True, verbose_name="Previous Value"),
+                    models.TextField(blank=True, max_length=2048, null=True, verbose_name="Previous Value"),
                 ),
-                ("new_value", models.CharField(blank=True, max_length=2048, null=True, verbose_name="New Value")),
-                ("modification_description", models.CharField(max_length=20480, verbose_name="Description")),
+                ("new_value", models.TextField(blank=True, max_length=2048, null=True, verbose_name="New Value")),
+                ("modification_description", models.TextField(max_length=20480, verbose_name="Description")),
                 (
                     "modification_author",
                     models.ForeignKey(
@@ -2389,10 +2390,10 @@ class Migration(migrations.Migration):
                         verbose_name="Worker who modified the row",
                     ),
                 ),
-                ("modified_field", models.CharField(blank="", max_length=1024, verbose_name="Modified field")),
+                ("modified_field", models.CharField(blank="", max_length=128, verbose_name="Modified field")),
                 (
                     "request_path",
-                    models.CharField(blank=True, max_length=20480, null=True, verbose_name="Request Path"),
+                    models.TextField(blank=True, max_length=20480, null=True, verbose_name="Request Path"),
                 ),
                 ("request_ip_addr", models.GenericIPAddressField(null=True, verbose_name="Request IP Address")),
             ],
@@ -2566,7 +2567,7 @@ class Migration(migrations.Migration):
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
                 ("type", models.CharField(editable=False, max_length=50, verbose_name="Type")),
                 ("name", models.CharField(editable=False, max_length=255, verbose_name="Name")),
-                ("value", models.CharField(max_length=1024, verbose_name="Value")),
+                ("value", models.TextField(max_length=1024, verbose_name="Value")),
                 (
                     "value_type",
                     models.CharField(
@@ -2703,7 +2704,7 @@ class Migration(migrations.Migration):
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
                 ("table_name", models.CharField(blank=True, default="", max_length=128)),
                 ("column_name", models.CharField(blank=True, default="", max_length=128)),
-                ("csv_column_name", models.CharField(blank=True, default="", max_length=1024)),
+                ("csv_column_name", models.TextField(blank=True, default="", max_length=1024)),
                 ("enabled", models.BooleanField(default=True)),
                 (
                     "etl_data",
@@ -2992,3 +2993,6 @@ class Migration(migrations.Migration):
         migrations.RunPython(configuration_items__0171),
         migrations.RunPython(configuration_items__0176),
     ]
+
+    if connection.vendor == "mysql":
+        operations = [migrations.RunSQL("SET SESSION sql_mode='ANSI_QUOTES';")] + operations
diff --git a/smash/web/migrations/0014_auto_20170220_0812.py b/smash/web/migrations/0014_auto_20170220_0812.py
index 190888eddad882ffef72c0ac8f43c4b86d55610e..8073a8f299e9f0d9aca48dd34baa41e2a7110868 100644
--- a/smash/web/migrations/0014_auto_20170220_0812.py
+++ b/smash/web/migrations/0014_auto_20170220_0812.py
@@ -36,7 +36,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="subject",
             name="comments",
-            field=models.CharField(blank=True, max_length=2000, verbose_name="Comments"),
+            field=models.TextField(blank=True, max_length=2000, verbose_name="Comments"),
         ),
         migrations.AlterField(
             model_name="subject",
diff --git a/smash/web/migrations/0017_auto_20170301_1600.py b/smash/web/migrations/0017_auto_20170301_1600.py
index 77814d62961c638605d9d8ed25e06f524e696a1d..b9018c9abb351be55fb6667fbd024a9d3debb481 100644
--- a/smash/web/migrations/0017_auto_20170301_1600.py
+++ b/smash/web/migrations/0017_auto_20170301_1600.py
@@ -8,43 +8,43 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0016_auto_20170228_1652'),
+        ("web", "0016_auto_20170228_1652"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='appointment',
-            name='comment',
-            field=models.CharField(blank=True, max_length=1024, null=True, verbose_name='Comment'),
+            model_name="appointment",
+            name="comment",
+            field=models.TextField(blank=True, max_length=1024, null=True, verbose_name="Comment"),
         ),
         migrations.AddField(
-            model_name='appointmenttype',
-            name='calendar_color',
-            field=models.CharField(default='#cfc600', max_length=2000, verbose_name='Calendar color'),
+            model_name="appointmenttype",
+            name="calendar_color",
+            field=models.TextField(default="#cfc600", max_length=2000, verbose_name="Calendar color"),
         ),
         migrations.AddField(
-            model_name='appointmenttype',
-            name='calendar_color_priority',
-            field=models.IntegerField(default=1, verbose_name='Calendar color priority'),
+            model_name="appointmenttype",
+            name="calendar_color_priority",
+            field=models.IntegerField(default=1, verbose_name="Calendar color priority"),
         ),
         migrations.AddField(
-            model_name='appointmenttype',
-            name='calendar_font_color',
-            field=models.CharField(default='#00000', max_length=2000, verbose_name='Calendar color'),
+            model_name="appointmenttype",
+            name="calendar_font_color",
+            field=models.TextField(default="#00000", max_length=2000, verbose_name="Calendar color"),
         ),
         migrations.AlterField(
-            model_name='appointment',
-            name='is_finished',
-            field=models.BooleanField(default=False, editable=False, verbose_name='Has the appointment ended?'),
+            model_name="appointment",
+            name="is_finished",
+            field=models.BooleanField(default=False, editable=False, verbose_name="Has the appointment ended?"),
         ),
         migrations.AlterField(
-            model_name='subject',
-            name='dead',
-            field=models.BooleanField(default=False, editable=False, verbose_name='Dead'),
+            model_name="subject",
+            name="dead",
+            field=models.BooleanField(default=False, editable=False, verbose_name="Dead"),
         ),
         migrations.AlterField(
-            model_name='subject',
-            name='resigned',
-            field=models.BooleanField(default=False, editable=False, verbose_name='Resigned'),
+            model_name="subject",
+            name="resigned",
+            field=models.BooleanField(default=False, editable=False, verbose_name="Resigned"),
         ),
     ]
diff --git a/smash/web/migrations/0090_vouchertype_vouchertypeprice.py b/smash/web/migrations/0090_vouchertype_vouchertypeprice.py
index 57c52c5a706f8b5ac583e4367c799b8ed24b629b..76e6d8bdc810049cb6480af11453158492a10ab2 100644
--- a/smash/web/migrations/0090_vouchertype_vouchertypeprice.py
+++ b/smash/web/migrations/0090_vouchertype_vouchertypeprice.py
@@ -9,27 +9,27 @@ import django.db.models.deletion
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0089_unfinshed_appointment_list'),
+        ("web", "0089_unfinshed_appointment_list"),
     ]
 
     operations = [
         migrations.CreateModel(
-            name='VoucherType',
+            name="VoucherType",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('code', models.CharField(max_length=20, verbose_name='Code')),
-                ('description', models.CharField(blank=True, max_length=1024, verbose_name='Description')),
-                ('study', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.Study')),
+                ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+                ("code", models.CharField(max_length=20, verbose_name="Code")),
+                ("description", models.TextField(blank=True, max_length=1024, verbose_name="Description")),
+                ("study", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="web.Study")),
             ],
         ),
         migrations.CreateModel(
-            name='VoucherTypePrice',
+            name="VoucherTypePrice",
             fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('price', models.DecimalField(decimal_places=2, max_digits=6, verbose_name='Price')),
-                ('start_date', models.DateField(verbose_name='Start date')),
-                ('end_date', models.DateField(verbose_name='End date')),
-                ('voucher_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='web.VoucherType')),
+                ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+                ("price", models.DecimalField(decimal_places=2, max_digits=6, verbose_name="Price")),
+                ("start_date", models.DateField(verbose_name="Start date")),
+                ("end_date", models.DateField(verbose_name="End date")),
+                ("voucher_type", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="web.VoucherType")),
             ],
         ),
     ]
diff --git a/smash/web/migrations/0097_auto_20171211_1616.py b/smash/web/migrations/0097_auto_20171211_1616.py
index 9ae00ede5a5b0508d61b648430ef65cf7478827c..715aee1fd5cb4369ab85da3b221819fc139da265 100644
--- a/smash/web/migrations/0097_auto_20171211_1616.py
+++ b/smash/web/migrations/0097_auto_20171211_1616.py
@@ -95,7 +95,7 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name="studysubject",
             name="screening",
-            field=models.CharField(blank=True, max_length=1024, null=True, verbose_name="Screening"),
+            field=models.TextField(blank=True, max_length=1024, null=True, verbose_name="Screening"),
         ),
         migrations.AddField(
             model_name="studysubject",
@@ -112,7 +112,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="studysubject",
             name="diagnosis",
-            field=models.CharField(blank=True, max_length=1024, null=True, verbose_name="Diagnosis"),
+            field=models.TextField(blank=True, max_length=1024, null=True, verbose_name="Diagnosis"),
         ),
         migrations.AlterField(
             model_name="voucher",
diff --git a/smash/web/migrations/0141_auto_20200319_1040.py b/smash/web/migrations/0141_auto_20200319_1040.py
index 1ec81c4fa0c40d903297532068676e1b987c3ec2..12f552a6f96c5ca64ed17f2bad92bf74071a6387 100644
--- a/smash/web/migrations/0141_auto_20200319_1040.py
+++ b/smash/web/migrations/0141_auto_20200319_1040.py
@@ -8,13 +8,13 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0140_auto_20190528_0953'),
+        ("web", "0140_auto_20190528_0953"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='appointmenttype',
-            name='calendar_font_color',
-            field=models.CharField(default='#00000', max_length=2000, verbose_name='Calendar font color'),
+            model_name="appointmenttype",
+            name="calendar_font_color",
+            field=models.TextField(default="#00000", max_length=2000, verbose_name="Calendar font color"),
         ),
     ]
diff --git a/smash/web/migrations/0142_provenance.py b/smash/web/migrations/0142_provenance.py
index eb818bfcbd81506d18322e88cf0e7c93c95b9b51..1bbd6a42e320b06401abd8227e13ac820e8f5754 100644
--- a/smash/web/migrations/0142_provenance.py
+++ b/smash/web/migrations/0142_provenance.py
@@ -16,15 +16,15 @@ class Migration(migrations.Migration):
             name="Provenance",
             fields=[
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
-                ("modified_table", models.CharField(max_length=1024, verbose_name="Modified table")),
-                ("modified_table_id", models.CharField(max_length=1024, verbose_name="Modified table row")),
+                ("modified_table", models.CharField(max_length=128, verbose_name="Modified table")),
+                ("modified_table_id", models.IntegerField(default=0, null=True, verbose_name="Modified table row")),
                 ("modification_date", models.DateTimeField(verbose_name="Modified on")),
                 (
                     "previous_value",
-                    models.CharField(blank=True, max_length=2048, null=True, verbose_name="Previous Value"),
+                    models.TextField(blank=True, max_length=2048, null=True, verbose_name="Previous Value"),
                 ),
-                ("new_value", models.CharField(blank=True, max_length=2048, null=True, verbose_name="New Value")),
-                ("modification_description", models.CharField(max_length=2048, verbose_name="Description")),
+                ("new_value", models.TextField(blank=True, max_length=2048, null=True, verbose_name="New Value")),
+                ("modification_description", models.TextField(max_length=2048, verbose_name="Description")),
                 (
                     "modification_author",
                     models.ForeignKey(
diff --git a/smash/web/migrations/0144_auto_20200319_1221.py b/smash/web/migrations/0144_auto_20200319_1221.py
index e8b8c4bfdeb810ed9d57b6d3172d1098ab9cbc78..d7c3e07ce9e67ba8992d09dc6a2768887f9eada2 100644
--- a/smash/web/migrations/0144_auto_20200319_1221.py
+++ b/smash/web/migrations/0144_auto_20200319_1221.py
@@ -8,19 +8,19 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0143_auto_20200319_1121'),
+        ("web", "0143_auto_20200319_1121"),
     ]
 
     operations = [
         migrations.AddField(
-            model_name='provenance',
-            name='modified_field',
-            field=models.CharField(default='', max_length=1024, verbose_name='Modified field'),
+            model_name="provenance",
+            name="modified_field",
+            field=models.TextField(default="", max_length=1024, verbose_name="Modified field"),
             preserve_default=False,
         ),
         migrations.AlterField(
-            model_name='provenance',
-            name='modification_description',
-            field=models.CharField(max_length=20480, verbose_name='Description'),
+            model_name="provenance",
+            name="modification_description",
+            field=models.TextField(max_length=20480, verbose_name="Description"),
         ),
     ]
diff --git a/smash/web/migrations/0145_auto_20200319_1404.py b/smash/web/migrations/0145_auto_20200319_1404.py
index 21dfc1004facf6f15a6d8fdd4454cf275112825d..8353bfa7c3be7ad72a67e1278642df39848cdb7f 100644
--- a/smash/web/migrations/0145_auto_20200319_1404.py
+++ b/smash/web/migrations/0145_auto_20200319_1404.py
@@ -8,13 +8,13 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0144_auto_20200319_1221'),
+        ("web", "0144_auto_20200319_1221"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='provenance',
-            name='modified_field',
-            field=models.CharField(blank='', max_length=1024, verbose_name='Modified field'),
+            model_name="provenance",
+            name="modified_field",
+            field=models.TextField(blank="", max_length=1024, verbose_name="Modified field"),
         ),
     ]
diff --git a/smash/web/migrations/0148_auto_20200319_1301.py b/smash/web/migrations/0148_auto_20200319_1301.py
index 0d9ecd0e3b6720ee41082ba333d972cfbf0e312d..3c4b96684ddf0f4e1e994a000be2541cfa22180b 100644
--- a/smash/web/migrations/0148_auto_20200319_1301.py
+++ b/smash/web/migrations/0148_auto_20200319_1301.py
@@ -7,17 +7,17 @@ from django.db import migrations, models
 
 class Migration(migrations.Migration):
     dependencies = [
-        ('web', '0147_auto_20200320_0931'),
+        ("web", "0147_auto_20200320_0931"),
     ]
 
     operations = [
         migrations.AlterModelOptions(
-            name='appointmenttypelink',
-            options={'permissions': [('view_daily_planning', 'Can see daily planning')]},
+            name="appointmenttypelink",
+            options={"permissions": [("view_daily_planning", "Can see daily planning")]},
         ),
         migrations.AlterField(
-            model_name='appointmenttype',
-            name='calendar_font_color',
-            field=models.CharField(default='#00000', max_length=2000, verbose_name='Calendar font color'),
+            model_name="appointmenttype",
+            name="calendar_font_color",
+            field=models.TextField(default="#00000", max_length=2000, verbose_name="Calendar font color"),
         ),
     ]
diff --git a/smash/web/migrations/0162_auto_20200416_1212.py b/smash/web/migrations/0162_auto_20200416_1212.py
index 231b4fa2e2530b832ac05af08ccb2e875e9e86dd..4114d664b5bb98eb85d84ff322d45702f949a2ba 100644
--- a/smash/web/migrations/0162_auto_20200416_1212.py
+++ b/smash/web/migrations/0162_auto_20200416_1212.py
@@ -8,18 +8,18 @@ from django.db import migrations, models
 class Migration(migrations.Migration):
 
     dependencies = [
-        ('web', '0161_auto_20200416_0736'),
+        ("web", "0161_auto_20200416_0736"),
     ]
 
     operations = [
         migrations.AlterField(
-            model_name='configurationitem',
-            name='value',
-            field=models.CharField(max_length=1024, verbose_name='Value'),
+            model_name="configurationitem",
+            name="value",
+            field=models.TextField(max_length=1024, verbose_name="Value"),
         ),
         migrations.AlterField(
-            model_name='subject',
-            name='next_of_keen_name',
-            field=models.CharField(blank=True, max_length=255, verbose_name='Next of keen'),
+            model_name="subject",
+            name="next_of_keen_name",
+            field=models.CharField(blank=True, max_length=255, verbose_name="Next of keen"),
         ),
     ]
diff --git a/smash/web/migrations/0168_rename_radcap_field.py b/smash/web/migrations/0168_rename_radcap_field.py
index 37be8307a63d50b0248938df0d12d3df8d6aa438..bbec2ef46c3427638dab73a497332cb4b840ce4d 100644
--- a/smash/web/migrations/0168_rename_radcap_field.py
+++ b/smash/web/migrations/0168_rename_radcap_field.py
@@ -28,14 +28,14 @@ class Migration(migrations.Migration):
 
     operations = [
         migrations.RunSQL(
-            "update web_configurationitem set type = '"
+            "update web_configurationitem, set type = '"
             + RED_CAP_KIT_ID_FIELD_TYPE
             + "' where type = '"
             + RED_CAP_SAMPLE_DATE_FIELD_TYPE
             + "';"
         ),
         migrations.RunSQL(
-            "update web_configurationitem set name = 'Redcap field for sample kit id in the visit' where type = '"
+            "update web_configurationitem, set name = 'Redcap field for sample kit id in the visit' where type = '"
             + RED_CAP_KIT_ID_FIELD_TYPE
             + "';"
         ),
diff --git a/smash/web/migrations/0173_auto_20201105_1142.py b/smash/web/migrations/0173_auto_20201105_1142.py
index 74e66773d22e0c801421ca5c425c7708fd6d2aa0..b20401e89bdb9439bd4ff16d8653917469ba23fd 100644
--- a/smash/web/migrations/0173_auto_20201105_1142.py
+++ b/smash/web/migrations/0173_auto_20201105_1142.py
@@ -12,7 +12,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="provenance",
             name="modified_table",
-            field=models.CharField(max_length=1024, null=True, verbose_name="Modified table"),
+            field=models.TextField(max_length=1024, null=True, verbose_name="Modified table"),
         ),
         migrations.AlterField(
             model_name="provenance",
diff --git a/smash/web/migrations/0174_auto_20201105_1157.py b/smash/web/migrations/0174_auto_20201105_1157.py
index fdb995fec343f8d80b5af2fd26c832a6488e208e..a14890dc4b1f8fb6af8cca3b5ec6673acc6a69f1 100644
--- a/smash/web/migrations/0174_auto_20201105_1157.py
+++ b/smash/web/migrations/0174_auto_20201105_1157.py
@@ -12,6 +12,6 @@ class Migration(migrations.Migration):
         migrations.AddField(
             model_name="provenance",
             name="request_path",
-            field=models.CharField(blank=True, max_length=20480, null=True, verbose_name="Request Path"),
+            field=models.TextField(blank=True, max_length=20480, null=True, verbose_name="Request Path"),
         )
     ]
diff --git a/smash/web/migrations/0177_customstudysubjectfield_customstudysubjectvalue.py b/smash/web/migrations/0177_customstudysubjectfield_customstudysubjectvalue.py
index 053afbc6db84ede5d570f4094df5338999a79ce8..9284b02930abc3dca02e6836fde5cd931a32b65a 100644
--- a/smash/web/migrations/0177_customstudysubjectfield_customstudysubjectvalue.py
+++ b/smash/web/migrations/0177_customstudysubjectfield_customstudysubjectvalue.py
@@ -29,7 +29,7 @@ class Migration(migrations.Migration):
                         max_length=20,
                     ),
                 ),
-                ("possible_values", models.CharField(blank=True, default="", max_length=1024, null=True)),
+                ("possible_values", models.TextField(blank=True, default="", max_length=1024, null=True)),
                 ("default_value", models.CharField(blank=True, max_length=20, null=True)),
                 ("readonly", models.BooleanField(default=False)),
                 ("required", models.BooleanField(default=False)),
@@ -49,7 +49,7 @@ class Migration(migrations.Migration):
             name="CustomStudySubjectValue",
             fields=[
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
-                ("value", models.CharField(blank=True, max_length=2048, null=True)),
+                ("value", models.TextField(blank=True, max_length=2048, null=True)),
                 (
                     "study_subject",
                     models.ForeignKey(
diff --git a/smash/web/migrations/0178_auto_20201116_1250.py b/smash/web/migrations/0178_auto_20201116_1250.py
index 91febe10fabdb42e82726f4c02d8ab0dad3e08af..da37d7a941837d1d2bc8bd6d479f328bf1a1425c 100644
--- a/smash/web/migrations/0178_auto_20201116_1250.py
+++ b/smash/web/migrations/0178_auto_20201116_1250.py
@@ -118,7 +118,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="appointmenttype",
             name="calendar_font_color",
-            field=models.CharField(default="#00000", max_length=2000, verbose_name="Calendar font color"),
+            field=models.TextField(default="#00000", max_length=2000, verbose_name="Calendar font color"),
         ),
         migrations.AlterField(
             model_name="availability",
@@ -166,7 +166,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="configurationitem",
             name="value",
-            field=models.CharField(max_length=1024, verbose_name="Value"),
+            field=models.TextField(max_length=1024, verbose_name="Value"),
         ),
         migrations.AlterField(
             model_name="contactattempt",
@@ -774,22 +774,22 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="provenance",
             name="modification_description",
-            field=models.CharField(max_length=20480, verbose_name="Description"),
+            field=models.TextField(max_length=20480, verbose_name="Description"),
         ),
         migrations.AlterField(
             model_name="provenance",
             name="modified_field",
-            field=models.CharField(blank="", max_length=1024, verbose_name="Modified field"),
+            field=models.TextField(blank="", max_length=1024, verbose_name="Modified field"),
         ),
         migrations.AlterField(
             model_name="provenance",
             name="new_value",
-            field=models.CharField(blank=True, max_length=2048, null=True, verbose_name="New Value"),
+            field=models.TextField(blank=True, max_length=2048, null=True, verbose_name="New Value"),
         ),
         migrations.AlterField(
             model_name="provenance",
             name="previous_value",
-            field=models.CharField(blank=True, max_length=2048, null=True, verbose_name="Previous Value"),
+            field=models.TextField(blank=True, max_length=2048, null=True, verbose_name="Previous Value"),
         ),
         migrations.AlterField(
             model_name="study",
@@ -1248,7 +1248,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="studysubject",
             name="diagnosis",
-            field=models.CharField(blank=True, max_length=1024, null=True, verbose_name="Diagnosis"),
+            field=models.TextField(blank=True, max_length=1024, null=True, verbose_name="Diagnosis"),
         ),
         migrations.AlterField(
             model_name="studysubject",
@@ -1341,7 +1341,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="studysubject",
             name="screening",
-            field=models.CharField(blank=True, max_length=1024, null=True, verbose_name="Screening"),
+            field=models.TextField(blank=True, max_length=1024, null=True, verbose_name="Screening"),
         ),
         migrations.AlterField(
             model_name="studysubject",
@@ -1889,7 +1889,7 @@ class Migration(migrations.Migration):
         migrations.AlterField(
             model_name="vouchertype",
             name="description",
-            field=models.CharField(blank=True, max_length=1024, verbose_name="Description"),
+            field=models.TextField(blank=True, max_length=1024, verbose_name="Description"),
         ),
         migrations.AlterField(
             model_name="vouchertypeprice",
diff --git a/smash/web/migrations/0184_visitimportdata.py b/smash/web/migrations/0184_visitimportdata.py
index 366d3b1b119aca08bc3aa38559257340bdd4aa3f..1b90921513dba56a63999bae0a261675c9855863 100644
--- a/smash/web/migrations/0184_visitimportdata.py
+++ b/smash/web/migrations/0184_visitimportdata.py
@@ -140,7 +140,7 @@ class Migration(migrations.Migration):
                 ("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
                 ("table_name", models.CharField(blank=True, default="", max_length=128)),
                 ("column_name", models.CharField(blank=True, default="", max_length=128)),
-                ("csv_column_name", models.CharField(blank=True, default="", max_length=1024)),
+                ("csv_column_name", models.TextField(blank=True, default="", max_length=1024)),
                 ("enabled", models.BooleanField(default=True)),
                 (
                     "etl_data",
diff --git a/smash/web/migrations/0215_alter_studysubject_referral_letter.py b/smash/web/migrations/0215_alter_studysubject_referral_letter.py
new file mode 100644
index 0000000000000000000000000000000000000000..532b2c10e05e5e432e11df7c2d4e5479eb2598f7
--- /dev/null
+++ b/smash/web/migrations/0215_alter_studysubject_referral_letter.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.2.22 on 2024-07-30 06:09
+
+import django.core.files.storage
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("web", "0214_merge_20240404_0917"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="studysubject",
+            name="referral_letter",
+            field=models.FileField(
+                blank=True,
+                null=True,
+                storage=django.core.files.storage.FileSystemStorage(location="/tmp/upload"),
+                upload_to="referral_letters",
+                verbose_name="Referral letter",
+            ),
+        ),
+    ]
diff --git a/smash/web/migrations/0216_auto_20240730_0621.py b/smash/web/migrations/0216_auto_20240730_0621.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc86d63259d3cb4a8752f9b6055e33a3abfe7d9b
--- /dev/null
+++ b/smash/web/migrations/0216_auto_20240730_0621.py
@@ -0,0 +1,85 @@
+# Generated by Django 3.2.22 on 2024-07-30 06:21
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ("web", "0215_alter_studysubject_referral_letter"),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name="appointmenttype",
+            name="calendar_color",
+            field=models.TextField(default="#cfc600", max_length=2000, verbose_name="Calendar color"),
+        ),
+        migrations.AlterField(
+            model_name="appointmenttype",
+            name="calendar_font_color",
+            field=models.TextField(default="#00000", max_length=2000, verbose_name="Calendar font color"),
+        ),
+        migrations.AlterField(
+            model_name="appointmenttype",
+            name="description",
+            field=models.TextField(max_length=2000, verbose_name="Appointment description"),
+        ),
+        migrations.AlterField(
+            model_name="configurationitem",
+            name="value",
+            field=models.TextField(max_length=1024, verbose_name="Value"),
+        ),
+        migrations.AlterField(
+            model_name="customstudysubjectfield",
+            name="possible_values",
+            field=models.TextField(blank=True, default="", max_length=1024, null=True),
+        ),
+        migrations.AlterField(
+            model_name="customstudysubjectvalue",
+            name="value",
+            field=models.TextField(blank=True, max_length=2048, null=True),
+        ),
+        migrations.AlterField(
+            model_name="etlcolumnmapping",
+            name="csv_column_name",
+            field=models.TextField(blank=True, default="", max_length=1024),
+        ),
+        migrations.AlterField(
+            model_name="etldata",
+            name="run_at_times",
+            field=models.TextField(
+                blank=True, default="", max_length=1024, verbose_name="At what time automatic import should run"
+            ),
+        ),
+        migrations.AlterField(
+            model_name="provenance",
+            name="modification_description",
+            field=models.TextField(max_length=20480, verbose_name="Description"),
+        ),
+        migrations.AlterField(
+            model_name="provenance",
+            name="modified_field",
+            field=models.CharField(blank="", max_length=128, verbose_name="Modified field"),
+        ),
+        migrations.AlterField(
+            model_name="provenance",
+            name="modified_table",
+            field=models.CharField(max_length=128, null=True, verbose_name="Modified table"),
+        ),
+        migrations.AlterField(
+            model_name="provenance",
+            name="new_value",
+            field=models.TextField(blank=True, max_length=2048, null=True, verbose_name="New Value"),
+        ),
+        migrations.AlterField(
+            model_name="provenance",
+            name="previous_value",
+            field=models.TextField(blank=True, max_length=2048, null=True, verbose_name="Previous Value"),
+        ),
+        migrations.AlterField(
+            model_name="provenance",
+            name="request_path",
+            field=models.TextField(blank=True, max_length=20480, null=True, verbose_name="Request Path"),
+        ),
+    ]
diff --git a/smash/web/migrations/0217_alter_vouchertype_description.py b/smash/web/migrations/0217_alter_vouchertype_description.py
new file mode 100644
index 0000000000000000000000000000000000000000..eadca30d52af9a7fda1cd48986639fd5e6103e73
--- /dev/null
+++ b/smash/web/migrations/0217_alter_vouchertype_description.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.2.22 on 2024-07-30 07:16
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('web', '0216_auto_20240730_0621'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='vouchertype',
+            name='description',
+            field=models.CharField(blank=True, max_length=1024, verbose_name='Description'),
+        ),
+    ]
diff --git a/smash/web/models/appointment_type.py b/smash/web/models/appointment_type.py
index 3514afa477bd767d807fe109b4ff921dc4b33ce9..6fa48352951a59ee10a88fc18317a823cbf88e27 100644
--- a/smash/web/models/appointment_type.py
+++ b/smash/web/models/appointment_type.py
@@ -6,52 +6,26 @@ from .constants import APPOINTMENT_TYPE_DEFAULT_COLOR, APPOINTMENT_TYPE_DEFAULT_
 
 class AppointmentType(models.Model):
     class Meta:
-        app_label = 'web'
-        ordering = ['description']
+        app_label = "web"
+        ordering = ["description"]
 
-    required_equipment = models.ManyToManyField("web.Item",
-                                                verbose_name='Required equipment',
-                                                blank=True
-                                                )
-    code = models.CharField(max_length=20,
-                            verbose_name='Appointment code'
-                            )
-    description = models.CharField(max_length=2000,
-                                   verbose_name='Appointment description'
-                                   )
-    default_duration = models.IntegerField(
-        verbose_name='Default duration (in minutes)'
+    required_equipment = models.ManyToManyField("web.Item", verbose_name="Required equipment", blank=True)
+    code = models.CharField(max_length=20, verbose_name="Appointment code")
+    description = models.TextField(max_length=2000, verbose_name="Appointment description")
+    default_duration = models.IntegerField(verbose_name="Default duration (in minutes)")
+    calendar_color_priority = models.IntegerField(verbose_name="Calendar color priority", default=1)
+    calendar_color = models.TextField(
+        max_length=2000, verbose_name="Calendar color", default=APPOINTMENT_TYPE_DEFAULT_COLOR
     )
-    calendar_color_priority = models.IntegerField(
-        verbose_name='Calendar color priority',
-        default=1
+    calendar_font_color = models.TextField(
+        max_length=2000, verbose_name="Calendar font color", default=APPOINTMENT_TYPE_DEFAULT_FONT_COLOR
     )
-    calendar_color = models.CharField(max_length=2000,
-                                      verbose_name='Calendar color',
-                                      default=APPOINTMENT_TYPE_DEFAULT_COLOR
-                                      )
-    calendar_font_color = models.CharField(max_length=2000,
-                                           verbose_name='Calendar font color',
-                                           default=APPOINTMENT_TYPE_DEFAULT_FONT_COLOR
-                                           )
-    rest_time = models.IntegerField(
-        verbose_name='Suggested rest time',
-        default=0
+    rest_time = models.IntegerField(verbose_name="Suggested rest time", default=0)
+    can_be_parallelized = models.BooleanField(verbose_name="Can be parallelized", default=False)
+    REQ_ROLE_CHOICES = (("DOCTOR", "Doctor"), ("NURSE", "Nurse"), ("PSYCHOLOGIST", "Psychologist"), ("ANY", "Any"))
+    required_worker = models.CharField(
+        max_length=20, choices=REQ_ROLE_CHOICES, verbose_name="Type of worker required for appointment", default="ANY"
     )
-    can_be_parallelized = models.BooleanField(
-        verbose_name='Can be parallelized',
-        default=False
-    )
-    REQ_ROLE_CHOICES = (
-        ('DOCTOR', 'Doctor'),
-        ('NURSE', 'Nurse'),
-        ('PSYCHOLOGIST', 'Psychologist'),
-        ('ANY', 'Any')
-    )
-    required_worker = models.CharField(max_length=20, choices=REQ_ROLE_CHOICES,
-                                       verbose_name='Type of worker required for appointment',
-                                       default='ANY'
-                                       )
 
     def __str__(self):
         return self.description
diff --git a/smash/web/models/configuration_item.py b/smash/web/models/configuration_item.py
index a38cba7ce76980de662e831c9f0e54a522c95a41..f3fded7fb661fd251df2c79011246867f5e2381f 100644
--- a/smash/web/models/configuration_item.py
+++ b/smash/web/models/configuration_item.py
@@ -6,11 +6,20 @@ from django.core.validators import validate_email
 from django.db import models
 
 from web.utils import strtobool
-from web.models.constants import CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE, \
-    NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE, KIT_EMAIL_HOUR_CONFIGURATION_TYPE, \
-    KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE, KIT_DAILY_EMAIL_TIME_FORMAT_TYPE, KIT_DAILY_EMAIL_DAYS_PERIOD_TYPE, \
-    VALUE_TYPE_CHOICES, VALUE_TYPE_TEXT, DEFAULT_FROM_EMAIL, VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE, \
-    KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE, VISIT_SHOW_VISIT_NUMBER_FROM_ZERO
+from web.models.constants import (
+    CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE,
+    NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE,
+    KIT_EMAIL_HOUR_CONFIGURATION_TYPE,
+    KIT_EMAIL_DAY_OF_WEEK_CONFIGURATION_TYPE,
+    KIT_DAILY_EMAIL_TIME_FORMAT_TYPE,
+    KIT_DAILY_EMAIL_DAYS_PERIOD_TYPE,
+    VALUE_TYPE_CHOICES,
+    VALUE_TYPE_TEXT,
+    DEFAULT_FROM_EMAIL,
+    VIRUS_EMAIL_HOUR_CONFIGURATION_TYPE,
+    KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE,
+    VISIT_SHOW_VISIT_NUMBER_FROM_ZERO,
+)
 
 
 def is_valid_email(value: str) -> bool:
@@ -23,36 +32,29 @@ def is_valid_email(value: str) -> bool:
 
 class ConfigurationItem(models.Model):
     class Meta:
-        app_label = 'web'
+        app_label = "web"
 
-    type = models.CharField(max_length=50,
-                            verbose_name='Type',
-                            editable=False
-                            )
-    name = models.CharField(max_length=255,
-                            verbose_name='Name',
-                            editable=False
-                            )
+    type = models.CharField(max_length=50, verbose_name="Type", editable=False)
+    name = models.CharField(max_length=255, verbose_name="Name", editable=False)
 
-    value = models.CharField(max_length=1024,
-                             verbose_name='Value',
-                             )
-    value_type = models.CharField(max_length=32,
-                                  choices=VALUE_TYPE_CHOICES,
-                                  verbose_name='Value type',
-                                  default=VALUE_TYPE_TEXT
-                                  )
+    value = models.TextField(
+        max_length=1024,
+        verbose_name="Value",
+    )
+    value_type = models.CharField(
+        max_length=32, choices=VALUE_TYPE_CHOICES, verbose_name="Value type", default=VALUE_TYPE_TEXT
+    )
 
     def __str__(self):
         return f"{self.name} {self.value}"
 
     @staticmethod
-    def is_valid(item: 'ConfigurationItem') -> bool:
+    def is_valid(item: "ConfigurationItem") -> bool:
         message = ConfigurationItem.validation_error(item)
         return message == ""
 
     @staticmethod
-    def validation_error(item: 'ConfigurationItem') -> str:
+    def validation_error(item: "ConfigurationItem") -> str:
         pattern = None
         if item.type in (CANCELLED_APPOINTMENT_COLOR_CONFIGURATION_TYPE, NO_SHOW_APPOINTMENT_COLOR_CONFIGURATION_TYPE):
             pattern = "^#[0-9a-fA-F]+$"
@@ -66,7 +68,7 @@ class ConfigurationItem(models.Model):
             pattern = "^(%Y-%m-%d|%Y-%m-%d %H:%M)$"
         if item.type == KIT_RECIPIENT_EMAIL_CONFIGURATION_TYPE:
             for email in item.value.split(";"):
-                if email != '' and not is_valid_email(email):
+                if email != "" and not is_valid_email(email):
                     return f"Email {email} address is invalid"
 
         if item.type == DEFAULT_FROM_EMAIL:
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 9d754d61041e4f09987faeb3d627390516414d90..5664f821636eba80232e71c240ffafa9c91a02ac 100644
--- a/smash/web/models/custom_data/custom_study_subject_field.py
+++ b/smash/web/models/custom_data/custom_study_subject_field.py
@@ -9,7 +9,7 @@ class CustomStudySubjectField(models.Model):
     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='')
+    possible_values = models.TextField(max_length=1024, null=True, blank=True, default="")
 
     default_value = models.CharField(max_length=256, null=True, blank=True)
     readonly = models.BooleanField(default=False)
@@ -20,12 +20,7 @@ class CustomStudySubjectField(models.Model):
 
     tracked = models.BooleanField(default=False)
 
-    study = models.ForeignKey("web.Study",
-                              verbose_name='Study',
-                              editable=False,
-                              null=False,
-                              on_delete=models.CASCADE
-                              )
+    study = models.ForeignKey("web.Study", verbose_name="Study", editable=False, null=False, on_delete=models.CASCADE)
 
 
 def get_study_subject_field_id(study_subject_field: CustomStudySubjectField) -> str:
diff --git a/smash/web/models/custom_data/custom_study_subject_value.py b/smash/web/models/custom_data/custom_study_subject_value.py
index 6398dfe615cdbcb1e5d07ae20a179f5348e46830..0967f117bab3c46a0f46660f2227250cb580c3fb 100644
--- a/smash/web/models/custom_data/custom_study_subject_value.py
+++ b/smash/web/models/custom_data/custom_study_subject_value.py
@@ -4,17 +4,11 @@ from django.db import models
 
 
 class CustomStudySubjectValue(models.Model):
-    value = models.CharField(max_length=2048, null=True, blank=True)
-
-    study_subject_field = models.ForeignKey("web.CustomStudySubjectField",
-                                            verbose_name='Custom Field',
-                                            editable=False,
-                                            null=False,
-                                            on_delete=models.CASCADE
-                                            )
-    study_subject = models.ForeignKey("web.StudySubject",
-                                      verbose_name='Study',
-                                      editable=False,
-                                      null=False,
-                                      on_delete=models.CASCADE
-                                      )
+    value = models.TextField(max_length=2048, null=True, blank=True)
+
+    study_subject_field = models.ForeignKey(
+        "web.CustomStudySubjectField", verbose_name="Custom Field", editable=False, null=False, on_delete=models.CASCADE
+    )
+    study_subject = models.ForeignKey(
+        "web.StudySubject", verbose_name="Study", editable=False, null=False, on_delete=models.CASCADE
+    )
diff --git a/smash/web/models/etl/etl_column_mapping.py b/smash/web/models/etl/etl_column_mapping.py
index 9bc36b991eede5b48ffef5e0bbadeb246cf49a96..a08674ac2dc6dce724a338a7646eaad603b3c3a6 100644
--- a/smash/web/models/etl/etl_column_mapping.py
+++ b/smash/web/models/etl/etl_column_mapping.py
@@ -7,33 +7,19 @@ logger = logging.getLogger(__name__)
 
 
 class EtlColumnMapping(models.Model):
-    etl_data = models.ForeignKey("web.EtlData",
-                                 verbose_name='Importer',
-                                 editable=False,
-                                 null=False,
-                                 on_delete=models.CASCADE,
-                                 related_name="column_mappings"
-                                 )
-
-    table_name = models.CharField(max_length=128,
-                                  default='',
-                                  null=False,
-                                  blank=True
-                                  )
-
-    column_name = models.CharField(max_length=128,
-                                   default='',
-                                   null=False,
-                                   blank=True
-                                   )
-
-    csv_column_name = models.CharField(max_length=1024,
-                                       default='',
-                                       null=False,
-                                       blank=True
-                                       )
-
-    enabled = models.BooleanField(default=True,
-                                  null=False,
-                                  blank=False
-                                  )
+    etl_data = models.ForeignKey(
+        "web.EtlData",
+        verbose_name="Importer",
+        editable=False,
+        null=False,
+        on_delete=models.CASCADE,
+        related_name="column_mappings",
+    )
+
+    table_name = models.CharField(max_length=128, default="", null=False, blank=True)
+
+    column_name = models.CharField(max_length=128, default="", null=False, blank=True)
+
+    csv_column_name = models.TextField(max_length=1024, default="", null=False, blank=True)
+
+    enabled = models.BooleanField(default=True, null=False, blank=False)
diff --git a/smash/web/models/etl/etl_data.py b/smash/web/models/etl/etl_data.py
index 9316adb93a6b3337f74496ef00c265fbd9b5c4c2..f3cbd22ad12c509babf5c13cc8171e42362e951a 100644
--- a/smash/web/models/etl/etl_data.py
+++ b/smash/web/models/etl/etl_data.py
@@ -13,48 +13,32 @@ logger = logging.getLogger(__name__)
 
 class EtlData(models.Model):
 
-    study = models.ForeignKey("web.Study",
-                              verbose_name='Study',
-                              editable=False,
-                              null=False,
-                              on_delete=models.CASCADE
-                              )
-
-    filename = models.CharField(max_length=128,
-                                verbose_name='File used for automatic import',
-                                default='',
-                                null=False,
-                                blank=True
-                                )
-    run_at_times = models.CharField(max_length=1024,
-                                    verbose_name='At what time automatic import should run',
-                                    default='',
-                                    null=False,
-                                    blank=True
-                                    )
-
-    csv_delimiter = models.CharField(max_length=1,
-                                     verbose_name='CSV delimiter',
-                                     default=',',
-                                     null=False,
-                                     blank=False
-                                     )
-    date_format = models.CharField(max_length=20,
-                                   verbose_name='Date format',
-                                   default='%Y-%m-%d',
-                                   null=False,
-                                   blank=False
-                                   )
+    study = models.ForeignKey("web.Study", verbose_name="Study", editable=False, null=False, on_delete=models.CASCADE)
+
+    filename = models.CharField(
+        max_length=128, verbose_name="File used for automatic import", default="", null=False, blank=True
+    )
+    run_at_times = models.TextField(
+        max_length=1024, verbose_name="At what time automatic import should run", default="", null=False, blank=True
+    )
+
+    csv_delimiter = models.CharField(max_length=1, verbose_name="CSV delimiter", default=",", null=False, blank=False)
+    date_format = models.CharField(
+        max_length=20, verbose_name="Date format", default="%Y-%m-%d", null=False, blank=False
+    )
 
     def set_column_mapping(self, object_type: Type[models.Model], column_name: str, csv_column_name: str):
         for entry in self.column_mappings.all():
-            if entry.table_name == object_type._meta.db_table and \
-                    entry.column_name == column_name:
+            if entry.table_name == object_type._meta.db_table and entry.column_name == column_name:
                 entry.csv_column_name = csv_column_name
                 entry.save()
                 return
-        EtlColumnMapping.objects.create(etl_data=self, table_name=object_type._meta.db_table, column_name=column_name,
-                                        csv_column_name=csv_column_name)
+        EtlColumnMapping.objects.create(
+            etl_data=self,
+            table_name=object_type._meta.db_table,
+            column_name=column_name,
+            csv_column_name=csv_column_name,
+        )
 
     def get_absolute_file_path(self) -> str:
         return os.path.join(settings.ETL_ROOT, self.filename)
diff --git a/smash/web/models/privacy_notice.py b/smash/web/models/privacy_notice.py
index 62e7fc177dfa2d87c05feedbf21f3cfcf66a78b4..03a68bbc4c3efc0df028d72b600ad2ff83d090f6 100644
--- a/smash/web/models/privacy_notice.py
+++ b/smash/web/models/privacy_notice.py
@@ -5,19 +5,20 @@ from django.db import models
 from django.dispatch import receiver
 
 from web.templatetags.filters import basename
+from web import disable_for_loaddata
 
 
 class PrivacyNotice(models.Model):
-    name = models.CharField(max_length=255, verbose_name='Name')
-    created_at = models.DateTimeField(auto_now_add=True, verbose_name='Created at')
-    updated_at = models.DateTimeField(auto_now=True, verbose_name='Updated at')
-    summary = models.CharField(max_length=255, verbose_name='Summary', blank=False, null=False)
-    document = models.FileField(upload_to='privacy_notices/',
-                                verbose_name='Study Privacy Notice file',
-                                null=False, editable=True)
+    name = models.CharField(max_length=255, verbose_name="Name")
+    created_at = models.DateTimeField(auto_now_add=True, verbose_name="Created at")
+    updated_at = models.DateTimeField(auto_now=True, verbose_name="Updated at")
+    summary = models.CharField(max_length=255, verbose_name="Summary", blank=False, null=False)
+    document = models.FileField(
+        upload_to="privacy_notices/", verbose_name="Study Privacy Notice file", null=False, editable=True
+    )
 
     def __str__(self):
-        return f'{self.name} ({basename(self.document.url)})'
+        return f"{self.name} ({basename(self.document.url)})"
 
     @property
     def all_studies(self):
@@ -26,7 +27,9 @@ class PrivacyNotice(models.Model):
 
 # These two auto-delete files from filesystem when they are unneeded:
 
+
 @receiver(models.signals.post_delete, sender=PrivacyNotice)
+@disable_for_loaddata
 def auto_delete_file_on_delete(sender, instance: PrivacyNotice, **kwargs):  # pylint: disable=unused-argument
     """
     Deletes file from filesystem
@@ -38,6 +41,7 @@ def auto_delete_file_on_delete(sender, instance: PrivacyNotice, **kwargs):  # py
 
 
 @receiver(models.signals.pre_save, sender=PrivacyNotice)
+@disable_for_loaddata
 def auto_delete_file_on_change(sender, instance: PrivacyNotice, **kwargs):  # pylint: disable=unused-argument
     """
     Deletes old file from filesystem
diff --git a/smash/web/models/provenance.py b/smash/web/models/provenance.py
index 8739b6a3abee7287f888dd56a9379d5e25fff592..ae91de57b9ef398479bc3c0b60dbb8178961a65a 100644
--- a/smash/web/models/provenance.py
+++ b/smash/web/models/provenance.py
@@ -4,48 +4,31 @@ from django.db import models
 
 class Provenance(models.Model):
     class Meta:
-        app_label = 'web'
-        index_together = ['modified_table', 'modified_table_id', 'modification_date']
+        app_label = "web"
+        index_together = ["modified_table", "modified_table_id", "modification_date"]
 
-    modified_table = models.CharField(max_length=1024,
-                                      verbose_name='Modified table',
-                                      blank=False, null=True
-                                      )
+    modified_table = models.CharField(max_length=128, verbose_name="Modified table", blank=False, null=True)
 
-    modified_table_id = models.IntegerField(default=0, verbose_name='Modified table row', blank=False, null=True)
+    modified_table_id = models.IntegerField(default=0, verbose_name="Modified table row", blank=False, null=True)
 
-    modification_date = models.DateTimeField(
-        verbose_name='Modified on',
-        null=False, blank=False,
-        auto_now_add=True
-    )
+    modification_date = models.DateTimeField(verbose_name="Modified on", null=False, blank=False, auto_now_add=True)
 
-    modification_author = models.ForeignKey("web.Worker",
-                                            verbose_name='Worker who modified the row',
-                                            null=True, blank=False, on_delete=models.deletion.CASCADE
-                                            )
+    modification_author = models.ForeignKey(
+        "web.Worker",
+        verbose_name="Worker who modified the row",
+        null=True,
+        blank=False,
+        on_delete=models.deletion.CASCADE,
+    )
 
-    modified_field = models.CharField(max_length=1024,
-                                      verbose_name='Modified field',
-                                      blank='', null=False
-                                      )
+    modified_field = models.CharField(max_length=128, verbose_name="Modified field", blank="", null=False)
 
-    previous_value = models.CharField(max_length=2048,
-                                      verbose_name='Previous Value',
-                                      blank=True, null=True)
+    previous_value = models.TextField(max_length=2048, verbose_name="Previous Value", blank=True, null=True)
 
-    new_value = models.CharField(max_length=2048,
-                                 verbose_name='New Value',
-                                 blank=True, null=True)
+    new_value = models.TextField(max_length=2048, verbose_name="New Value", blank=True, null=True)
 
-    modification_description = models.CharField(max_length=20480,
-                                                verbose_name='Description',
-                                                blank=False, null=False
-                                                )
+    modification_description = models.TextField(max_length=20480, verbose_name="Description", blank=False, null=False)
 
-    request_path = models.CharField(max_length=20480,
-                                    verbose_name='Request Path',
-                                    blank=True, null=True
-                                    )
+    request_path = models.TextField(max_length=20480, verbose_name="Request Path", blank=True, null=True)
 
-    request_ip_addr = models.GenericIPAddressField(verbose_name='Request IP Address', null=True)
+    request_ip_addr = models.GenericIPAddressField(verbose_name="Request IP Address", null=True)
diff --git a/smash/web/models/study_subject.py b/smash/web/models/study_subject.py
index 49d87c7509d0059335cfc3f190f1cadfea008ee9..f7ecc3be65a470b18d6b56bcf8b2dab1e0dbb835 100644
--- a/smash/web/models/study_subject.py
+++ b/smash/web/models/study_subject.py
@@ -10,6 +10,7 @@ from django.dispatch import receiver
 from web.models import Appointment, Location, Provenance, Visit, VoucherType
 from web.models.constants import BOOL_CHOICES, FILE_STORAGE
 from web.models.custom_data import CustomStudySubjectField, CustomStudySubjectValue
+from web import disable_for_loaddata
 
 logger = logging.getLogger(__name__)
 
@@ -214,16 +215,12 @@ class StudySubject(models.Model):
     def custom_data_values(self):
         # find the custom fields that have not yet been populated into the study subject
         # https://docs.djangoproject.com/en/3.2/ref/models/querysets/#filteredrelation-objects
-        fields = (
-            CustomStudySubjectField.objects
-            .annotate(
-                t=FilteredRelation(
-                    "customstudysubjectvalue",
-                    condition=Q(customstudysubjectvalue__study_subject=self),
-                )
+        fields = CustomStudySubjectField.objects.annotate(
+            t=FilteredRelation(
+                "customstudysubjectvalue",
+                condition=Q(customstudysubjectvalue__study_subject=self),
             )
-            .filter(t__study_subject_field__isnull=True, study=self.study)
-        )
+        ).filter(t__study_subject_field__isnull=True, study=self.study)
 
         for field in fields:
             CustomStudySubjectValue.objects.create(
@@ -273,6 +270,7 @@ class StudySubject(models.Model):
 
 # SIGNALS
 @receiver(post_save, sender=StudySubject)
+@disable_for_loaddata
 def set_as_resigned_or_excluded_or_endpoint_reached(sender, instance, **kwargs):  # pylint: disable=unused-argument
     if instance.excluded:
         instance.mark_as_excluded()
diff --git a/smash/web/models/subject.py b/smash/web/models/subject.py
index c704304db4cc058faa5fcc33e9063c01e4968ebd..3838ab81664eb61048912a7b1b7c13d29c998ce3 100644
--- a/smash/web/models/subject.py
+++ b/smash/web/models/subject.py
@@ -7,6 +7,7 @@ from django.dispatch import receiver
 
 from .constants import SEX_CHOICES, COUNTRY_OTHER_ID
 from web.models import Country, Visit, Appointment, Provenance
+from web import disable_for_loaddata
 from . import Language
 
 logger = logging.getLogger(__name__)
@@ -14,121 +15,69 @@ logger = logging.getLogger(__name__)
 
 class Subject(models.Model):
     class Meta:
-        app_label = 'web'
+        app_label = "web"
         permissions = [
             ("export_subjects", "Can export subject data to excel/csv"),
         ]
 
     @property
     def provenances(self):
-        return Provenance.objects.filter(modified_table=Subject._meta.db_table, modified_table_id=self.id)\
-            .order_by('-modification_date')
-
-    sex = models.CharField(max_length=1,
-                           choices=SEX_CHOICES,
-                           verbose_name='Sex'
-                           )
-
-    first_name = models.CharField(max_length=50,
-                                  verbose_name='First name'
-                                  )
-
-    social_security_number = models.CharField(max_length=50,
-                                              verbose_name='Social security number',
-                                              blank=True,
-                                              )
-
-    last_name = models.CharField(max_length=50,
-                                 verbose_name='Last name'
-                                 )
-
-    languages = models.ManyToManyField(Language,
-                                       blank=True,
-                                       verbose_name='Known languages'
-                                       )
-
-    default_written_communication_language = models.ForeignKey(Language,
-                                                               null=True,
-                                                               blank=True,
-                                                               related_name="subjects_written_communication",
-                                                               verbose_name='Default language for document generation',
-                                                               on_delete=models.SET_NULL
-                                                               )
-    phone_number = models.CharField(max_length=64,
-                                    null=True,
-                                    blank=True,
-                                    verbose_name='Phone number'
-                                    )
-
-    phone_number_2 = models.CharField(max_length=64,
-                                      null=True,
-                                      blank=True,
-                                      verbose_name='Phone number 2'
-                                      )
-
-    phone_number_3 = models.CharField(max_length=64,
-                                      null=True,
-                                      blank=True,
-                                      verbose_name='Phone number 3'
-                                      )
-
-    email = models.EmailField(
-        null=True,
+        return Provenance.objects.filter(modified_table=Subject._meta.db_table, modified_table_id=self.id).order_by(
+            "-modification_date"
+        )
+
+    sex = models.CharField(max_length=1, choices=SEX_CHOICES, verbose_name="Sex")
+
+    first_name = models.CharField(max_length=50, verbose_name="First name")
+
+    social_security_number = models.CharField(
+        max_length=50,
+        verbose_name="Social security number",
         blank=True,
-        verbose_name='E-mail'
     )
 
-    date_born = models.DateField(
+    last_name = models.CharField(max_length=50, verbose_name="Last name")
+
+    languages = models.ManyToManyField(Language, blank=True, verbose_name="Known languages")
+
+    default_written_communication_language = models.ForeignKey(
+        Language,
         null=True,
         blank=True,
-        verbose_name='Date of birth (YYYY-MM-DD)'
+        related_name="subjects_written_communication",
+        verbose_name="Default language for document generation",
+        on_delete=models.SET_NULL,
     )
+    phone_number = models.CharField(max_length=64, null=True, blank=True, verbose_name="Phone number")
+
+    phone_number_2 = models.CharField(max_length=64, null=True, blank=True, verbose_name="Phone number 2")
+
+    phone_number_3 = models.CharField(max_length=64, null=True, blank=True, verbose_name="Phone number 3")
+
+    email = models.EmailField(null=True, blank=True, verbose_name="E-mail")
 
-    address = models.CharField(max_length=255,
-                               blank=True,
-                               verbose_name='Address'
-                               )
-
-    postal_code = models.CharField(max_length=7,
-                                   blank=True,
-                                   verbose_name='Postal code'
-                                   )
-
-    city = models.CharField(max_length=50,
-                            blank=True,
-                            verbose_name='City'
-                            )
-
-    country = models.ForeignKey(Country,
-                                null=False,
-                                blank=False,
-                                default=COUNTRY_OTHER_ID,
-                                verbose_name='Country', on_delete=models.CASCADE
-                                )
-
-    next_of_kin_name = models.CharField(max_length=255,
-                                        blank=True,
-                                        verbose_name='Next of kin'
-                                        )
-
-    next_of_kin_phone = models.CharField(max_length=50,
-                                         blank=True,
-                                         verbose_name='Next of kin phone'
-                                         )
-
-    next_of_kin_address = models.TextField(max_length=2000,
-                                           blank=True,
-                                           verbose_name='Next of kin address'
-                                           )
-
-    dead = models.BooleanField(
-        verbose_name='Deceased',
-        default=False,
-        editable=True
+    date_born = models.DateField(null=True, blank=True, verbose_name="Date of birth (YYYY-MM-DD)")
+
+    address = models.CharField(max_length=255, blank=True, verbose_name="Address")
+
+    postal_code = models.CharField(max_length=7, blank=True, verbose_name="Postal code")
+
+    city = models.CharField(max_length=50, blank=True, verbose_name="City")
+
+    country = models.ForeignKey(
+        Country, null=False, blank=False, default=COUNTRY_OTHER_ID, verbose_name="Country", on_delete=models.CASCADE
     )
 
+    next_of_kin_name = models.CharField(max_length=255, blank=True, verbose_name="Next of kin")
+
+    next_of_kin_phone = models.CharField(max_length=50, blank=True, verbose_name="Next of kin phone")
+
+    next_of_kin_address = models.TextField(max_length=2000, blank=True, verbose_name="Next of kin address")
+
+    dead = models.BooleanField(verbose_name="Deceased", default=False, editable=True)
+
     def pretty_address(self):
-        return f'{self.address} ({self.postal_code}), {self.city}. {self.country}'
+        return f"{self.address} ({self.postal_code}), {self.city}. {self.country}"
 
     def mark_as_dead(self):
         self.dead = True
@@ -142,8 +91,9 @@ class Subject(models.Model):
             visit.save()
 
     def finish_all_appointments(self):
-        appointments = Appointment.objects.filter(visit__subject__subject=self,
-                                                  status=Appointment.APPOINTMENT_STATUS_SCHEDULED)
+        appointments = Appointment.objects.filter(
+            visit__subject__subject=self, status=Appointment.APPOINTMENT_STATUS_SCHEDULED
+        )
         for appointment in appointments:
             appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED
             appointment.save()
@@ -154,13 +104,17 @@ class Subject(models.Model):
 
 # SIGNALS
 @receiver(post_save, sender=Subject)
+@disable_for_loaddata
 def set_as_deceased(sender, instance, **kwargs):  # pylint: disable=unused-argument
     if instance.dead:
-        p = Provenance(modified_table=Subject._meta.db_table,
-                       modified_table_id=instance.id, modification_author=None,
-                       previous_value=instance.dead, new_value=True,
-                       modification_description=f'Subject "{instance}" marked as dead',
-                       modified_field='dead',
-                       )
+        p = Provenance(
+            modified_table=Subject._meta.db_table,
+            modified_table_id=instance.id,
+            modification_author=None,
+            previous_value=instance.dead,
+            new_value=True,
+            modification_description=f'Subject "{instance}" marked as dead',
+            modified_field="dead",
+        )
         instance.mark_as_dead()
         p.save()
diff --git a/smash/web/models/visit.py b/smash/web/models/visit.py
index 47fe940691c7b2b845bb508b3466238a6e1f8a2b..c45661e194e6700dce0f7a704990ec5172ef32de 100644
--- a/smash/web/models/visit.py
+++ b/smash/web/models/visit.py
@@ -8,61 +8,65 @@ from django.db.models.signals import post_save
 from django.dispatch import receiver
 
 from web.models.constants import BOOL_CHOICES
+from web import disable_for_loaddata
 
 logger = logging.getLogger(__name__)
 
 
 class Visit(models.Model):
     class Meta:
-        app_label = 'web'
+        app_label = "web"
 
-    subject = models.ForeignKey("web.StudySubject", on_delete=models.CASCADE, verbose_name='Subject'
-                                )
-    datetime_begin = models.DateTimeField(
-        verbose_name='Visit starts at'
+    subject = models.ForeignKey(
+        "web.StudySubject", on_delete=models.CASCADE, verbose_name="Subject"
     )
+    datetime_begin = models.DateTimeField(verbose_name="Visit starts at")
     datetime_end = models.DateTimeField(
-        verbose_name='Visit ends at'
+        verbose_name="Visit ends at"
     )  # Deadline before which all appointments need to be scheduled
 
-    is_finished = models.BooleanField(
-        verbose_name='Has ended',
-        default=False
+    is_finished = models.BooleanField(verbose_name="Has ended", default=False)
+    post_mail_sent = models.BooleanField(
+        choices=BOOL_CHOICES, verbose_name="Post mail sent", default=False
+    )
+    appointment_types = models.ManyToManyField(
+        "web.AppointmentType",
+        verbose_name="Requested appointments",
+        blank=True,
     )
-    post_mail_sent = models.BooleanField(choices=BOOL_CHOICES,
-                                         verbose_name='Post mail sent',
-                                         default=False
-                                         )
-    appointment_types = models.ManyToManyField("web.AppointmentType",
-                                               verbose_name='Requested appointments',
-                                               blank=True,
-                                               )
 
     # this value is automatically computed by signal handled by
     # update_visit_number method
-    visit_number = models.IntegerField(
-        verbose_name='Visit number',
-        default=1
-    )
+    visit_number = models.IntegerField(verbose_name="Visit number", default=1)
 
     @property
     def next_visit(self):
-        return Visit.objects.filter(subject=self.subject, visit_number=self.visit_number + 1) \
-            .order_by('datetime_begin', 'datetime_end').first()
+        return (
+            Visit.objects.filter(
+                subject=self.subject, visit_number=self.visit_number + 1
+            )
+            .order_by("datetime_begin", "datetime_end")
+            .first()
+        )
 
     @property
     def future_visits(self):
-        return Visit.objects.filter(subject=self.subject).filter(visit_number__gt=self.visit_number) \
-            .order_by('datetime_begin', 'datetime_end')
+        return (
+            Visit.objects.filter(subject=self.subject)
+            .filter(visit_number__gt=self.visit_number)
+            .order_by("datetime_begin", "datetime_end")
+        )
 
     def __str__(self):
-        start = self.datetime_begin.strftime('%Y-%m-%d')
-        end = self.datetime_end.strftime('%Y-%m-%d')
-        finished = '✓' if self.is_finished else ''
-        return f'#{self.visit_number:02} ' \
-               f'| {start} / {end} ' \
-               f'| {self.subject.subject.first_name} {self.subject.subject.last_name} ' \
-               f'| {finished}'
+        start = self.datetime_begin.strftime("%Y-%m-%d")
+        end = self.datetime_end.strftime("%Y-%m-%d")
+        finished = "✓" if self.is_finished else ""
+        return (
+            f"#{self.visit_number:02} "
+            f"| {start} / {end} "
+            f"| {self.subject.subject.first_name} {self.subject.subject.last_name} "
+            f"| {finished}"
+        )
 
     def mark_as_finished(self):
         self.is_finished = True
@@ -82,46 +86,68 @@ class Visit(models.Model):
 
         if create_follow_up:
             if self.subject.visit_used_to_compute_followup_date is not None:
-                visit_started = self.subject.visit_used_to_compute_followup_date.datetime_begin
-                start_number = self.subject.visit_used_to_compute_followup_date.visit_number
+                visit_started = (
+                    self.subject.visit_used_to_compute_followup_date.datetime_begin
+                )
+                start_number = (
+                    self.subject.visit_used_to_compute_followup_date.visit_number
+                )
             else:
-                visit_started = Visit.objects.filter(subject=self.subject, visit_number=1).first().datetime_begin
+                visit_started = (
+                    Visit.objects.filter(subject=self.subject, visit_number=1)
+                    .first()
+                    .datetime_begin
+                )
                 start_number = 1
 
             follow_up_number = Visit.objects.filter(subject=self.subject).count() + 1
 
             study = self.subject.study
 
-            args = {self.subject.type.follow_up_delta_units: self.subject.type.follow_up_delta_time}
+            args = {
+                self.subject.type.follow_up_delta_units: self.subject.type.follow_up_delta_time
+            }
 
-            time_to_next_visit = relativedelta(**args) * (follow_up_number - start_number)
+            time_to_next_visit = relativedelta(**args) * (
+                follow_up_number - start_number
+            )
 
-            logger.warning('new visit: %s %s %s', args, relativedelta(**args), time_to_next_visit)
+            logger.warning(
+                "new visit: %s %s %s", args, relativedelta(**args), time_to_next_visit
+            )
 
             Visit.objects.create(
                 subject=self.subject,
                 datetime_begin=visit_started + time_to_next_visit,
-                datetime_end=visit_started + time_to_next_visit + relativedelta(
-                    months=study.default_visit_duration_in_months)
+                datetime_end=visit_started
+                + time_to_next_visit
+                + relativedelta(months=study.default_visit_duration_in_months),
             )
 
     def unfinish(self):
         # if ValueError messages are changed, change test/view/test_visit.py
         # check visit is indeed finished
         if not self.is_finished:
-            raise ValueError('The visit is not finished.')
+            raise ValueError("The visit is not finished.")
 
         # check if there are some unfinished visits before this visit
-        unfinished_visits = Visit.objects.filter(subject=self.subject,
-                                                 is_finished=False, datetime_begin__lt=self.datetime_begin).count()
+        unfinished_visits = Visit.objects.filter(
+            subject=self.subject,
+            is_finished=False,
+            datetime_begin__lt=self.datetime_begin,
+        ).count()
         if unfinished_visits > 0:
-            raise ValueError("Visit can't be unfinished. There is at least one unfinished visit.")
+            raise ValueError(
+                "Visit can't be unfinished. There is at least one unfinished visit."
+            )
 
         # check that there is only one future visit
         future_visits = self.future_visits
         if len(future_visits) > 1:
-            raise ValueError("Visit can't be unfinished. "
-                             "Only visits with one immediate future visit (without appointments) can be unfinished.")
+            raise ValueError(
+                "Visit can't be unfinished. "
+                "Only visits with one immediate future visit (without appointments) can be unfinished."
+            )
         elif len(future_visits) == 1:
             # check that the future visit has no appointments
             # remove visit if it has no appointments
@@ -132,7 +158,9 @@ class Visit(models.Model):
                     self.subject.save()
                 next_visit.delete()
             else:
-                raise ValueError("Visit can't be unfinished. The next visit has appointments.")
+                raise ValueError(
+                    "Visit can't be unfinished. The next visit has appointments."
+                )
 
         else:
             # this can happen when there is no auto follow up visit
@@ -143,33 +171,58 @@ class Visit(models.Model):
 
 
 @receiver(post_save, sender=Visit)
-def check_visit_number(sender, instance, created, **kwargs):  # pylint: disable=unused-argument
+@disable_for_loaddata
+def check_visit_number(
+    sender, instance, created, **kwargs
+):  # pylint: disable=unused-argument
     # no other solution to ensure the visit_number is in chronological order than to sort the whole list if there are
     # future visits
     visit = instance
-    if visit.subject is not None:  # not sure if select_for_update has an effect, the tests work as well without it
+    if (
+        visit.subject is not None
+    ):  # not sure if select_for_update has an effect, the tests work as well without it
         # new visit, sort only future visit respect to the new one
         if created:
-            visits_before = Visit.objects.select_for_update().filter(subject=visit.subject) \
-                .filter(datetime_begin__lt=visit.datetime_begin).count()
+            visits_before = (
+                Visit.objects.select_for_update()
+                .filter(subject=visit.subject)
+                .filter(datetime_begin__lt=visit.datetime_begin)
+                .count()
+            )
             # we need to sort the future visits respect to the new one, if any
-            visits = Visit.objects.select_for_update().filter(subject=visit.subject) \
-                .filter(datetime_begin__gte=visit.datetime_begin).order_by('datetime_begin', 'id')
+            visits = (
+                Visit.objects.select_for_update()
+                .filter(subject=visit.subject)
+                .filter(datetime_begin__gte=visit.datetime_begin)
+                .order_by("datetime_begin", "id")
+            )
             with transaction.atomic():  # not sure if it has an effect, the tests work as well without it
                 for i, v in enumerate(visits):
-                    expected_visit_number = (visits_before + i + 1)
+                    expected_visit_number = visits_before + i + 1
                     if v.visit_number != expected_visit_number:
                         # does not rise post_save, we avoid recursion
-                        Visit.objects.filter(id=v.id).update(visit_number=expected_visit_number)
-                        if v.id == visit.id:  # if the iteration visit is the same that the instance that produced the
+                        Visit.objects.filter(id=v.id).update(
+                            visit_number=expected_visit_number
+                        )
+                        if (
+                            v.id == visit.id
+                        ):  # if the iteration visit is the same that the instance that produced the
                             # signal call this ensures that the upper saved object is also updated, otherwise,
                             # refresh_from_db should be called
                             visit.visit_number = v.visit_number
         else:
             # if visits are modified, then, check everything
-            visits = Visit.objects.select_for_update().filter(subject=visit.subject).order_by('datetime_begin', 'id')
+            visits = (
+                Visit.objects.select_for_update()
+                .filter(subject=visit.subject)
+                .order_by("datetime_begin", "id")
+            )
             with transaction.atomic():
                 for i, v in enumerate(visits):
-                    expected_visit_number = (i + 1)
-                    if v.visit_number != expected_visit_number:  # update only those with wrong numbers
-                        Visit.objects.filter(id=v.id).update(visit_number=expected_visit_number)
+                    expected_visit_number = i + 1
+                    if (
+                        v.visit_number != expected_visit_number
+                    ):  # update only those with wrong numbers
+                        Visit.objects.filter(id=v.id).update(
+                            visit_number=expected_visit_number
+                        )
diff --git a/smash/web/officeAvailability.py b/smash/web/officeAvailability.py
index f7a154194f7e27517f79e674776c8470f4540344..21590071cd576aec1879b0b28b3637191f46fb81 100644
--- a/smash/web/officeAvailability.py
+++ b/smash/web/officeAvailability.py
@@ -29,7 +29,15 @@ class OfficeAvailability:
     Docs: https://pandas.pydata.org/pandas-docs/stable/generated/pandas.date_range.html
     """
 
-    def __init__(self, name, start=None, end=None, office_start="8:00", office_end="18:00", minimum_slot="1T"):
+    def __init__(
+        self,
+        name,
+        start=None,
+        end=None,
+        office_start="8:00",
+        office_end="18:00",
+        minimum_slot="1T",
+    ):
         self.business_hours = None
 
         today_midnight = get_today_midnight_date()
@@ -49,8 +57,12 @@ class OfficeAvailability:
         self.office_start = office_start
         self.office_end = office_end
         self.minimum_slot = minimum_slot
-        self.range = pd.date_range(start=self.start, end=self.end, freq=self.minimum_slot)
-        logger.debug("Name: %s. Min index: %s. Max index: %s", self.name, self.start, self.end)
+        self.range = pd.date_range(
+            start=self.start, end=self.end, freq=self.minimum_slot
+        )
+        logger.debug(
+            "Name: %s. Min index: %s. Max index: %s", self.name, self.start, self.end
+        )
         self.availability = pd.Series(index=self.range, data=0)  # initialize range at 0
 
     def _get_duration(self):
@@ -66,7 +78,11 @@ class OfficeAvailability:
         """
         availability_range = availability_range.round(self.minimum_slot)
         if only_working_hours:
-            availability_range = availability_range.to_series().between_time(self.office_start, self.office_end).index
+            availability_range = (
+                availability_range.to_series()
+                .between_time(self.office_start, self.office_end)
+                .index
+            )
         self.availability[availability_range] = 1
 
     def remove_availability(self, availability_range, only_working_hours=False):
@@ -76,7 +92,11 @@ class OfficeAvailability:
         """
         availability_range = availability_range.round(self.minimum_slot)
         if only_working_hours:
-            availability_range = availability_range.to_series().between_time(self.office_start, self.office_end).index
+            availability_range = (
+                availability_range.to_series()
+                .between_time(self.office_start, self.office_end)
+                .index
+            )
         self.availability[availability_range] = 0
 
     def _ensure_dates_are_in_bounds(self, given_start, given_end):
@@ -101,7 +121,9 @@ class OfficeAvailability:
 
         return start, end
 
-    def consider_this(self, appointment_availability_or_holiday, only_working_hours=False):
+    def consider_this(
+        self, appointment_availability_or_holiday, only_working_hours=False
+    ):
         """
         :appointment_availability_or_holiday can be an object from the following classes: Availability, Holiday,
         Appointment, AppointmentTypeLink.
@@ -132,9 +154,16 @@ class OfficeAvailability:
             start = appointment_availability_or_holiday.available_from
             end = appointment_availability_or_holiday.available_till
             weekday = appointment_availability_or_holiday.day_number
-            logger.debug("Considering Availability from %s to %s for weekday %d", start, end, weekday)
+            logger.debug(
+                "Considering Availability from %s to %s for weekday %d",
+                start,
+                end,
+                weekday,
+            )
             # selects the weekdays and then the specific hours
-            portion = self.availability[self.availability.index.weekday == (weekday - 1)].between_time(start, end)
+            portion = self.availability[
+                self.availability.index.weekday == (weekday - 1)
+            ].between_time(start, end)
             set_to = 1
         elif isinstance(appointment_availability_or_holiday, Holiday):
             start = appointment_availability_or_holiday.datetime_start
@@ -143,27 +172,41 @@ class OfficeAvailability:
             # from 1960 to 2120 creating a huge pd.Range)
             logger.debug(
                 "Considering %s from %s to %s",
-                "Extra Availability" if appointment_availability_or_holiday.kind == AVAILABILITY_EXTRA else "Holiday",
+                (
+                    "Extra Availability"
+                    if appointment_availability_or_holiday.kind == AVAILABILITY_EXTRA
+                    else "Holiday"
+                ),
                 start,
                 end,
             )
             try:
                 start, end = self._ensure_dates_are_in_bounds(start, end)
             except ValueError:
-                logger.debug("Holiday range does not overlap the availability range. Ignoring Holiday.")
+                logger.debug(
+                    "Holiday range does not overlap the availability range. Ignoring Holiday."
+                )
                 return
             portion = self.availability[
                 pd.date_range(start=start, end=end, freq=self.minimum_slot)
             ]  # select the specific range
-            set_to = 1 if appointment_availability_or_holiday.kind == AVAILABILITY_EXTRA else 0
+            set_to = (
+                1
+                if appointment_availability_or_holiday.kind == AVAILABILITY_EXTRA
+                else 0
+            )
         elif isinstance(appointment_availability_or_holiday, Appointment):
             start = appointment_availability_or_holiday.datetime_when
-            end = start + datetime.timedelta(minutes=appointment_availability_or_holiday.length)
+            end = start + datetime.timedelta(
+                minutes=appointment_availability_or_holiday.length
+            )
             logger.debug("Considering General Appointment from %s to %s", start, end)
             try:
                 start, end = self._ensure_dates_are_in_bounds(start, end)
             except ValueError:
-                logger.debug("Appointment range does not overlap the availability range. Ignoring Appointment.")
+                logger.debug(
+                    "Appointment range does not overlap the availability range. Ignoring Appointment."
+                )
                 return
             portion = self.availability[
                 pd.date_range(start=start, end=end, freq=self.minimum_slot)
@@ -187,7 +230,9 @@ class OfficeAvailability:
             ]  # select the specific range
             set_to = 0
         else:
-            logger.error("Expected Availability, Holiday, Appointment or AppointmentTypeLink objects.")
+            logger.error(
+                "Expected Availability, Holiday, Appointment or AppointmentTypeLink objects."
+            )
             raise TypeError
 
         if only_working_hours:
@@ -195,7 +240,8 @@ class OfficeAvailability:
 
         # limit portion to be changed to the bounds of the object time space (solution 1 of the aforementioned problem)
         portion = portion[
-            (self.availability.index.min() <= portion.index) & (portion.index <= self.availability.index.max())
+            (self.availability.index.min() <= portion.index)
+            & (portion.index <= self.availability.index.max())
         ]
 
         self.availability[portion.index] = set_to
@@ -219,18 +265,25 @@ class OfficeAvailability:
                                                        10000 loops each)
         """
         if only_working_hours:
-            availability = self.availability.between_time(self.office_start, self.office_end)
+            availability = self.availability.between_time(
+                self.office_start, self.office_end
+            )
         else:
             availability = self.availability
 
-        return availability.mean() * 100  # better to isolate the operation in case we change it later
+        return (
+            availability.mean() * 100
+        )  # better to isolate the operation in case we change it later
 
     def is_available(self, only_working_hours=False):
         """
         Returns True if on the selected period is available at least 50% of the time
         Otherwise returns False
         """
-        return self.get_availability_percentage(only_working_hours=only_working_hours) > 50.0
+        return (
+            self.get_availability_percentage(only_working_hours=only_working_hours)
+            > 50.0
+        )
 
     def plot_availability(self):
         """
@@ -244,7 +297,12 @@ class OfficeAvailability:
         mask = business_hours.between_time(self.office_start, self.office_end).index
         business_hours[mask] = 1
         axes = business_hours.plot(
-            kind="area", alpha=0.33, color="#1190D8", label="Business Hours", legend=True, ax=axes
+            kind="area",
+            alpha=0.33,
+            color="#1190D8",
+            label="Business Hours",
+            legend=True,
+            ax=axes,
         )
 
         # calculate good xticks
@@ -293,4 +351,6 @@ class OfficeAvailability:
         axes.set_xlabel("Date & Time")
 
         fig.tight_layout()
-        fig.savefig(f"{self.name}_{self.start.strftime('%Y%m%d%H%M')}_{self.end.strftime('%Y%m%d%H%M')}.pdf")
+        fig.savefig(
+            f"{self.name}_{self.start.strftime('%Y%m%d%H%M')}_{self.end.strftime('%Y%m%d%H%M')}.pdf"
+        )
diff --git a/smash/web/statistics.py b/smash/web/statistics.py
index 75991b35fca4b40715fd8fd4e8f3119bc63151af..bd3eb875fab49207d0cb1f07dd1f86836ff0e0a9 100644
--- a/smash/web/statistics.py
+++ b/smash/web/statistics.py
@@ -10,7 +10,7 @@ from django.db.models import Q, Count
 from web.migration_functions import is_sqlite_db
 from .models import AppointmentType, Appointment, Visit, SubjectType
 
-__author__ = 'Valentin Grouès'
+__author__ = "Valentin Grouès"
 
 
 def extract_month_sql(field_name):
@@ -31,59 +31,94 @@ QUERY_VISITS_RANKS = """
 SELECT DISTINCT(visit_number) AS rank FROM web_visit
 """
 
-QUERY_APPOINTMENTS_COUNT = """
+QUERY_APPOINTMENTS_COUNT = (
+    """
 SELECT count(*) FROM web_appointment LEFT JOIN (SELECT id, subject_id as web_visit_subject_id,
 visit_number AS rnk FROM web_visit)
 a ON a.id = web_appointment.visit_id
 LEFT JOIN web_studysubject ON web_studysubject.id = web_visit_subject_id
 WHERE a.rnk = %s
-AND """ + extract_month_sql("web_appointment.datetime_when") + """ = %s
-AND """ + extract_year_sql("web_appointment.datetime_when") + """ = %s
+AND """
+    + extract_month_sql("web_appointment.datetime_when")
+    + """ = %s
+AND """
+    + extract_year_sql("web_appointment.datetime_when")
+    + """ = %s
 """
+)
 
-QUERY_VISITS_ENDED_COUNT = """
+QUERY_VISITS_ENDED_COUNT = (
+    """
 SELECT count(*) FROM  (SELECT id, subject_id as web_visit_subject_id, datetime_begin, datetime_end,
 visit_number AS rnk FROM web_visit) a
 LEFT JOIN web_studysubject ON web_studysubject.id = web_visit_subject_id
 WHERE a.rnk = %s
-AND """ + extract_month_sql("a.datetime_end") + """ = %s
-AND """ + extract_year_sql("a.datetime_end") + """ = %s
+AND """
+    + extract_month_sql("a.datetime_end")
+    + """ = %s
+AND """
+    + extract_year_sql("a.datetime_end")
+    + """ = %s
 """
+)
 
-QUERY_VISITS_STARTED_COUNT = """
+QUERY_VISITS_STARTED_COUNT = (
+    """
 SELECT count(*) FROM  (SELECT id, subject_id as web_visit_subject_id, datetime_begin, datetime_end,
 visit_number AS rnk FROM web_visit) a
 LEFT JOIN web_studysubject ON web_studysubject.id = web_visit_subject_id
 WHERE a.rnk = %s
-AND """ + extract_month_sql("a.datetime_begin") + """ = %s
-AND """ + extract_year_sql("a.datetime_begin") + """ = %s
+AND """
+    + extract_month_sql("a.datetime_begin")
+    + """ = %s
+AND """
+    + extract_year_sql("a.datetime_begin")
+    + """ = %s
 """
+)
 
-QUERY_APPOINTMENTS = """
+QUERY_APPOINTMENTS = (
+    """
 SELECT types.appointment_type_id, web_appointment.status, count(*) FROM web_appointment
 LEFT JOIN (SELECT id, subject_id as web_visit_subject_id, visit_number AS rnk
 FROM web_visit) a ON a.id = web_appointment.visit_id LEFT JOIN web_appointmenttypelink types
 ON types.appointment_id = web_appointment.id
 LEFT JOIN web_studysubject ON web_studysubject.id = web_visit_subject_id
 WHERE a.rnk = %s
-AND """ + extract_month_sql("web_appointment.datetime_when") + """ = %s
-AND """ + extract_year_sql("web_appointment.datetime_when") + """ = %s
+AND """
+    + extract_month_sql("web_appointment.datetime_when")
+    + """ = %s
+AND """
+    + extract_year_sql("web_appointment.datetime_when")
+    + """ = %s
 {}
-GROUP BY TYPES.appointment_type_id,  web_appointment.status
+GROUP BY types.appointment_type_id,  web_appointment.status
 """
+)
 
 
 class StatisticsManager:
     def __init__(self):
-        self.appointment_types = {appointment_type.id: appointment_type for appointment_type in
-                                  AppointmentType.objects.all()}
-        self.statuses_list = Appointment.objects.filter().values_list('status', flat=True).distinct().order_by(
-            'status').all()
-        self.statuses_labels = [Appointment.APPOINTMENT_STATUS_CHOICES.get(status, status.title()) for status in
-                                self.statuses_list]
+        self.appointment_types = {
+            appointment_type.id: appointment_type
+            for appointment_type in AppointmentType.objects.all()
+        }
+        self.statuses_list = (
+            Appointment.objects.filter()
+            .values_list("status", flat=True)
+            .distinct()
+            .order_by("status")
+            .all()
+        )
+        self.statuses_labels = [
+            Appointment.APPOINTMENT_STATUS_CHOICES.get(status, status.title())
+            for status in self.statuses_list
+        ]
         self.visits_ranks = self._get_visits_ranks()
 
-    def get_statistics_for_month(self, month, year, subject_type: SubjectType = None, visit=None):
+    def get_statistics_for_month(
+        self, month, year, subject_type: SubjectType = None, visit=None
+    ):
         """
         Build dict with statistics for a given month of a given year.
         Statistics include:
@@ -108,15 +143,21 @@ class StatisticsManager:
         if visit is not None:
             visit = int(visit)
 
-        filters_month_year_appointments, filters_month_year_visits_ended, filters_month_year_visits_started = \
-            self._build_filters(month, subject_type, year)
-
-        number_of_appointments = self._get_number_of_appointments(filters_month_year_appointments, visit, month, year,
-                                                                  subject_type)
-        number_of_visits_started = self._get_number_visits_started(filters_month_year_visits_started, visit, month,
-                                                                   year, subject_type)
-        number_of_visits_ended = self._get_number_visits_ended(filters_month_year_visits_ended, visit, month, year,
-                                                               subject_type)
+        (
+            filters_month_year_appointments,
+            filters_month_year_visits_ended,
+            filters_month_year_visits_started,
+        ) = self._build_filters(month, subject_type, year)
+
+        number_of_appointments = self._get_number_of_appointments(
+            filters_month_year_appointments, visit, month, year, subject_type
+        )
+        number_of_visits_started = self._get_number_visits_started(
+            filters_month_year_visits_started, visit, month, year, subject_type
+        )
+        number_of_visits_ended = self._get_number_visits_ended(
+            filters_month_year_visits_ended, visit, month, year, subject_type
+        )
 
         general_results["appointments"] = number_of_appointments
         general_results["visits_started"] = number_of_visits_started
@@ -124,38 +165,70 @@ class StatisticsManager:
 
         results["general"] = general_results
 
-        results_appointments = self.get_appointments_per_type_and_status(filters_month_year_appointments, month,
-                                                                         self.statuses_list, visit, year, subject_type)
+        results_appointments = self.get_appointments_per_type_and_status(
+            filters_month_year_appointments,
+            month,
+            self.statuses_list,
+            visit,
+            year,
+            subject_type,
+        )
         results["appointments"] = results_appointments
         results["statuses_list"] = self.statuses_labels
-        appointment_types_values = list(map(attrgetter('code'), list(self.appointment_types.values())))
+        appointment_types_values = list(
+            map(attrgetter("code"), list(self.appointment_types.values()))
+        )
         sorted_appointment_types_values = sorted(appointment_types_values)
         results["appointments_types_list"] = sorted_appointment_types_values
         return results
 
-    def get_appointments_per_type_and_status(self, filters_month_year_appointments, month, statuses_list, visit, year,
-                                             subject_type=None):
+    def get_appointments_per_type_and_status(
+        self,
+        filters_month_year_appointments,
+        month,
+        statuses_list,
+        visit,
+        year,
+        subject_type=None,
+    ):
         if not visit:
             results_appointments = {}
             for appointment_type in list(self.appointment_types.values()):
-                appointment_type_filters = copy.deepcopy(filters_month_year_appointments)
-                appointment_type_filters.add(Q(appointment_types=appointment_type), Q.AND)
-                results_appointment_set = Appointment.objects.filter(appointment_type_filters).values(
-                    'status').order_by(
-                    'status').annotate(
-                    Count('status'))
-                results_appointment = [Appointment.objects.filter(appointment_type_filters).count()]
-                results_appointment_per_status = {result['status']: result['status__count'] for result in
-                                                  results_appointment_set}
-
-                results_appointment.extend([results_appointment_per_status.get(status, 0) for status in statuses_list])
+                appointment_type_filters = copy.deepcopy(
+                    filters_month_year_appointments
+                )
+                appointment_type_filters.add(
+                    Q(appointment_types=appointment_type), Q.AND
+                )
+                results_appointment_set = (
+                    Appointment.objects.filter(appointment_type_filters)
+                    .values("status")
+                    .order_by("status")
+                    .annotate(Count("status"))
+                )
+                results_appointment = [
+                    Appointment.objects.filter(appointment_type_filters).count()
+                ]
+                results_appointment_per_status = {
+                    result["status"]: result["status__count"]
+                    for result in results_appointment_set
+                }
+
+                results_appointment.extend(
+                    [
+                        results_appointment_per_status.get(status, 0)
+                        for status in statuses_list
+                    ]
+                )
                 results_appointments[appointment_type.code] = results_appointment
         else:
             results_appointment_set = defaultdict(dict)
             query = QUERY_APPOINTMENTS
             subject_type_clause = ""
             if subject_type is not None:
-                subject_type_clause = f" AND web_studysubject.type_id = '{subject_type.id}'"
+                subject_type_clause = (
+                    f" AND web_studysubject.type_id = '{subject_type.id}'"
+                )
             query = query.format(subject_type_clause)
             with connection.cursor() as cursor:
                 cursor.execute(query, [visit, month, year])
@@ -166,42 +239,86 @@ class StatisticsManager:
             results_appointments = {}
             for appointment_type in list(self.appointment_types.values()):
                 if appointment_type.id not in results_appointment_set:
-                    results_appointments[appointment_type.code] = [0 * i for i in range(0, len(statuses_list) + 1)]
+                    results_appointments[appointment_type.code] = [
+                        0 * i for i in range(0, len(statuses_list) + 1)
+                    ]
                     continue
-                results_appointment_set_for_type = results_appointment_set[appointment_type.id]
+                results_appointment_set_for_type = results_appointment_set[
+                    appointment_type.id
+                ]
                 total = [sum(results_appointment_set_for_type.values())]
-                total.extend([results_appointment_set_for_type.get(status, 0) for status in statuses_list])
+                total.extend(
+                    [
+                        results_appointment_set_for_type.get(status, 0)
+                        for status in statuses_list
+                    ]
+                )
                 results_appointments[appointment_type.code] = total
         return results_appointments
 
     @staticmethod
-    def _get_count_from_filters_or_sql(model, filters, query, visit, month, year, subject_type: SubjectType):
+    def _get_count_from_filters_or_sql(
+        model, filters, query, visit, month, year, subject_type: SubjectType
+    ):
         if visit:
             if subject_type is not None:
                 query += f" AND web_studysubject.type_id = '{subject_type.id}'"
             with connection.cursor() as cursor:
-                cursor.execute(
-                    query,
-                    [visit, month, year])
+                cursor.execute(query, [visit, month, year])
                 row = cursor.fetchone()
                 count = int(row[0])
         else:
             count = model.objects.filter(filters).count()
         return count
 
-    def _get_number_visits_started(self, filters_month_year_visits_started, visit, month, year,
-                                   subject_type: SubjectType = None):
-        return self._get_count_from_filters_or_sql(Visit, filters_month_year_visits_started, QUERY_VISITS_STARTED_COUNT,
-                                                   visit, month, year, subject_type)
-
-    def _get_number_visits_ended(self, filters_month_year_visits_ended, visit, month, year,
-                                 subject_type: SubjectType = None):
-        return self._get_count_from_filters_or_sql(Visit, filters_month_year_visits_ended, QUERY_VISITS_ENDED_COUNT,
-                                                   visit, month, year, subject_type)
-
-    def _get_number_of_appointments(self, filters, visit, month, year, subject_type: SubjectType = None):
-        return self._get_count_from_filters_or_sql(Appointment, filters, QUERY_APPOINTMENTS_COUNT, visit, month, year,
-                                                   subject_type)
+    def _get_number_visits_started(
+        self,
+        filters_month_year_visits_started,
+        visit,
+        month,
+        year,
+        subject_type: SubjectType = None,
+    ):
+        return self._get_count_from_filters_or_sql(
+            Visit,
+            filters_month_year_visits_started,
+            QUERY_VISITS_STARTED_COUNT,
+            visit,
+            month,
+            year,
+            subject_type,
+        )
+
+    def _get_number_visits_ended(
+        self,
+        filters_month_year_visits_ended,
+        visit,
+        month,
+        year,
+        subject_type: SubjectType = None,
+    ):
+        return self._get_count_from_filters_or_sql(
+            Visit,
+            filters_month_year_visits_ended,
+            QUERY_VISITS_ENDED_COUNT,
+            visit,
+            month,
+            year,
+            subject_type,
+        )
+
+    def _get_number_of_appointments(
+        self, filters, visit, month, year, subject_type: SubjectType = None
+    ):
+        return self._get_count_from_filters_or_sql(
+            Appointment,
+            filters,
+            QUERY_APPOINTMENTS_COUNT,
+            visit,
+            month,
+            year,
+            subject_type,
+        )
 
     @staticmethod
     def _build_filters(month, subject_type, year):
@@ -223,7 +340,11 @@ class StatisticsManager:
             filters_month_year_visits_ended.add(subject_type_filter, Q.AND)
             subject_type_filter_appointments = Q(visit__subject__type=subject_type)
             filters_month_year_appointments.add(subject_type_filter_appointments, Q.AND)
-        return filters_month_year_appointments, filters_month_year_visits_ended, filters_month_year_visits_started
+        return (
+            filters_month_year_appointments,
+            filters_month_year_visits_ended,
+            filters_month_year_visits_started,
+        )
 
     @staticmethod
     def _get_visits_ranks():
diff --git a/smash/web/templates/_base.html b/smash/web/templates/_base.html
index c4a0863a1c611aa62ec9f83b7cf23201951a226b..3c75a7588ad9327438d41f38965c353f427c64a2 100644
--- a/smash/web/templates/_base.html
+++ b/smash/web/templates/_base.html
@@ -258,7 +258,7 @@ desired effect
         {% block footer %}
             <!-- To the right -->
             <div class="pull-right hidden-xs">
-                Version: <strong>1.4.4</strong>
+                Version: <strong>1.4.5</strong>
             </div>
 
             <!-- Default to the left -->
diff --git a/smash/web/tests/api_views/test_flying_team.py b/smash/web/tests/api_views/test_flying_team.py
index f735413e8806a27c492bf051b47230c412282afa..e07eb6ab19c0fca591684b59f0ba337d4242f377 100644
--- a/smash/web/tests/api_views/test_flying_team.py
+++ b/smash/web/tests/api_views/test_flying_team.py
@@ -12,17 +12,17 @@ class TestFlyingTeamApi(LoggedInTestCase):
     def test_flying_teams(self):
         flying_team_name = "some flying_team"
 
-        response = self.client.get(reverse('web.api.flying_teams'))
+        response = self.client.get(reverse("web.api.flying_teams"))
         self.assertEqual(response.status_code, 200)
 
         create_flying_team(flying_team_name)
 
-        response = self.client.get(reverse('web.api.flying_teams'))
-        flying_teams = json.loads(response.content)['flying_teams']
+        response = self.client.get(reverse("web.api.flying_teams"))
+        flying_teams = json.loads(response.content)["flying_teams"]
 
         found = False
         for flying_team in flying_teams:
-            if flying_team['name'] == flying_team_name:
+            if flying_team["name"] == flying_team_name:
                 found = True
 
         self.assertTrue(found)
diff --git a/smash/web/tests/forms/test_CustomStudySubjectFieldAddForm.py b/smash/web/tests/forms/test_CustomStudySubjectFieldAddForm.py
index a96a7aff4f62f6d1c046be6f825416d44df43907..e1aaa3eb45c5d1e5431d222f2c60da69bde09dbe 100644
--- a/smash/web/tests/forms/test_CustomStudySubjectFieldAddForm.py
+++ b/smash/web/tests/forms/test_CustomStudySubjectFieldAddForm.py
@@ -2,37 +2,60 @@ from django.test import TestCase
 from parameterized import parameterized
 
 from web.forms.custom_study_subject_field_forms import CustomStudySubjectFieldAddForm
-from web.models.constants import CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, CUSTOM_FIELD_TYPE_INTEGER, \
-    CUSTOM_FIELD_TYPE_DOUBLE, CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_FILE
+from web.models.constants import (
+    CUSTOM_FIELD_TYPE_TEXT,
+    CUSTOM_FIELD_TYPE_BOOLEAN,
+    CUSTOM_FIELD_TYPE_INTEGER,
+    CUSTOM_FIELD_TYPE_DOUBLE,
+    CUSTOM_FIELD_TYPE_DATE,
+    CUSTOM_FIELD_TYPE_SELECT_LIST,
+    CUSTOM_FIELD_TYPE_FILE,
+)
 from web.models.custom_data import CustomStudySubjectField
 from web.tests.functions import get_test_study
 
 
 class CustomStudySubjectFieldAddFormTest(TestCase):
 
-    @parameterized.expand([
-        ('text', CUSTOM_FIELD_TYPE_TEXT, 'bla', True),
-        ('bool valid', CUSTOM_FIELD_TYPE_BOOLEAN, 'True', True),
-        ('bool invalid', CUSTOM_FIELD_TYPE_BOOLEAN, 'bla', False),
-        ('int valid', CUSTOM_FIELD_TYPE_INTEGER, '102', True),
-        ('int invalid', CUSTOM_FIELD_TYPE_INTEGER, 'bla', False),
-        ('double valid', CUSTOM_FIELD_TYPE_DOUBLE, '202.25', True),
-        ('double invalid', CUSTOM_FIELD_TYPE_DOUBLE, 'bla', False),
-        ('date valid', CUSTOM_FIELD_TYPE_DATE, '2021-01-20', True),
-        ('date invalid', CUSTOM_FIELD_TYPE_DATE, 'bla', False),
-        ('select list valid', CUSTOM_FIELD_TYPE_SELECT_LIST, 'abc', True, 'abc;def;xyz'),
-        ('select list invalid', CUSTOM_FIELD_TYPE_SELECT_LIST, 'bla', False, 'abc;def'),
-        ('file', CUSTOM_FIELD_TYPE_FILE, None, True),
-        ('file invalid', CUSTOM_FIELD_TYPE_FILE, 'tmp', False),
-        ('text', CUSTOM_FIELD_TYPE_TEXT, 'bla', True, True),
-    ])
-    def test_add_field(self, _, field_type, default_value, valid, possible_values='', tracked=False):
+    @parameterized.expand(
+        [
+            ("text", CUSTOM_FIELD_TYPE_TEXT, "bla", True),
+            ("bool valid", CUSTOM_FIELD_TYPE_BOOLEAN, "True", True),
+            ("bool invalid", CUSTOM_FIELD_TYPE_BOOLEAN, "bla", False),
+            ("int valid", CUSTOM_FIELD_TYPE_INTEGER, "102", True),
+            ("int invalid", CUSTOM_FIELD_TYPE_INTEGER, "bla", False),
+            ("double valid", CUSTOM_FIELD_TYPE_DOUBLE, "202.25", True),
+            ("double invalid", CUSTOM_FIELD_TYPE_DOUBLE, "bla", False),
+            ("date valid", CUSTOM_FIELD_TYPE_DATE, "2021-01-20", True),
+            ("date invalid", CUSTOM_FIELD_TYPE_DATE, "bla", False),
+            (
+                "select list valid",
+                CUSTOM_FIELD_TYPE_SELECT_LIST,
+                "abc",
+                True,
+                "abc;def;xyz",
+            ),
+            (
+                "select list invalid",
+                CUSTOM_FIELD_TYPE_SELECT_LIST,
+                "bla",
+                False,
+                "abc;def",
+            ),
+            ("file", CUSTOM_FIELD_TYPE_FILE, None, True),
+            ("file invalid", CUSTOM_FIELD_TYPE_FILE, "tmp", False),
+            ("text", CUSTOM_FIELD_TYPE_TEXT, "bla", True, True),
+        ]
+    )
+    def test_add_field(
+        self, _, field_type, default_value, valid, possible_values="", tracked=False
+    ):
         sample_data = {
-            'default_value': default_value,
-            'name': '1. name',
-            'type': field_type,
-            'possible_values': possible_values,
-            'tracked': tracked,
+            "default_value": default_value,
+            "name": "1. name",
+            "type": field_type,
+            "possible_values": possible_values,
+            "tracked": tracked,
         }
 
         form = CustomStudySubjectFieldAddForm(sample_data, study=get_test_study())
@@ -40,7 +63,9 @@ class CustomStudySubjectFieldAddFormTest(TestCase):
             self.assertTrue(form.is_valid())
             field = form.save()
 
-            self.assertEqual(1, CustomStudySubjectField.objects.filter(id=field.id).count())
+            self.assertEqual(
+                1, CustomStudySubjectField.objects.filter(id=field.id).count()
+            )
             self.assertEqual(default_value, field.default_value)
             self.assertEqual(tracked, field.tracked)
         else:
diff --git a/smash/web/tests/forms/test_CustomStudySubjectFieldEditForm.py b/smash/web/tests/forms/test_CustomStudySubjectFieldEditForm.py
index cdbd8aa67710d2f5f70f9f32299e07eeacec1792..a9dd10e22530fb376b708ac881e9224241154282 100644
--- a/smash/web/tests/forms/test_CustomStudySubjectFieldEditForm.py
+++ b/smash/web/tests/forms/test_CustomStudySubjectFieldEditForm.py
@@ -2,39 +2,64 @@ from django.test import TestCase
 from parameterized import parameterized
 
 from web.forms.custom_study_subject_field_forms import CustomStudySubjectFieldEditForm
-from web.models.constants import CUSTOM_FIELD_TYPE_TEXT, CUSTOM_FIELD_TYPE_BOOLEAN, CUSTOM_FIELD_TYPE_INTEGER, \
-    CUSTOM_FIELD_TYPE_DOUBLE, CUSTOM_FIELD_TYPE_DATE, CUSTOM_FIELD_TYPE_SELECT_LIST, CUSTOM_FIELD_TYPE_FILE
+from web.models.constants import (
+    CUSTOM_FIELD_TYPE_TEXT,
+    CUSTOM_FIELD_TYPE_BOOLEAN,
+    CUSTOM_FIELD_TYPE_INTEGER,
+    CUSTOM_FIELD_TYPE_DOUBLE,
+    CUSTOM_FIELD_TYPE_DATE,
+    CUSTOM_FIELD_TYPE_SELECT_LIST,
+    CUSTOM_FIELD_TYPE_FILE,
+)
 from web.models.custom_data import CustomStudySubjectField
 from web.tests.functions import get_test_study
 
 
 class CustomStudySubjectFieldEditFormTest(TestCase):
 
-    @parameterized.expand([
-        ('text', CUSTOM_FIELD_TYPE_TEXT, 'bla', True),
-        ('bool valid', CUSTOM_FIELD_TYPE_BOOLEAN, 'True', True),
-        ('bool invalid', CUSTOM_FIELD_TYPE_BOOLEAN, 'bla', False),
-        ('int valid', CUSTOM_FIELD_TYPE_INTEGER, '911', True),
-        ('int invalid', CUSTOM_FIELD_TYPE_INTEGER, 'bla', False),
-        ('double valid', CUSTOM_FIELD_TYPE_DOUBLE, '821.45', True),
-        ('double invalid', CUSTOM_FIELD_TYPE_DOUBLE, 'bla', False),
-        ('date valid', CUSTOM_FIELD_TYPE_DATE, '2020-10-04', True),
-        ('date invalid', CUSTOM_FIELD_TYPE_DATE, 'bla', False),
-        ('select list valid', CUSTOM_FIELD_TYPE_SELECT_LIST, 'abc', True, 'abc;def;xyz'),
-        ('select list invalid', CUSTOM_FIELD_TYPE_SELECT_LIST, 'bla', False, 'abc;def'),
-        ('file', CUSTOM_FIELD_TYPE_FILE, None, True),
-        ('file invalid', CUSTOM_FIELD_TYPE_FILE, 'tmp', False),
-        ('text', CUSTOM_FIELD_TYPE_TEXT, 'bla', True, True),
-    ])
-    def test_edit_field(self, _, field_type, default_value, valid, possible_values='', tracked=False):
-        field = CustomStudySubjectField.objects.create(study=get_test_study(), default_value="", type=field_type)
+    @parameterized.expand(
+        [
+            ("text", CUSTOM_FIELD_TYPE_TEXT, "bla", True),
+            ("bool valid", CUSTOM_FIELD_TYPE_BOOLEAN, "True", True),
+            ("bool invalid", CUSTOM_FIELD_TYPE_BOOLEAN, "bla", False),
+            ("int valid", CUSTOM_FIELD_TYPE_INTEGER, "911", True),
+            ("int invalid", CUSTOM_FIELD_TYPE_INTEGER, "bla", False),
+            ("double valid", CUSTOM_FIELD_TYPE_DOUBLE, "821.45", True),
+            ("double invalid", CUSTOM_FIELD_TYPE_DOUBLE, "bla", False),
+            ("date valid", CUSTOM_FIELD_TYPE_DATE, "2020-10-04", True),
+            ("date invalid", CUSTOM_FIELD_TYPE_DATE, "bla", False),
+            (
+                "select list valid",
+                CUSTOM_FIELD_TYPE_SELECT_LIST,
+                "abc",
+                True,
+                "abc;def;xyz",
+            ),
+            (
+                "select list invalid",
+                CUSTOM_FIELD_TYPE_SELECT_LIST,
+                "bla",
+                False,
+                "abc;def",
+            ),
+            ("file", CUSTOM_FIELD_TYPE_FILE, None, True),
+            ("file invalid", CUSTOM_FIELD_TYPE_FILE, "tmp", False),
+            ("text", CUSTOM_FIELD_TYPE_TEXT, "bla", True, True),
+        ]
+    )
+    def test_edit_field(
+        self, _, field_type, default_value, valid, possible_values="", tracked=False
+    ):
+        field = CustomStudySubjectField.objects.create(
+            study=get_test_study(), default_value="", type=field_type
+        )
 
         sample_data = {
-            'default_value': default_value,
-            'name': '1. name',
-            'type': field_type,
-            'possible_values': possible_values,
-            'tracked': tracked,
+            "default_value": default_value,
+            "name": "1. name",
+            "type": field_type,
+            "possible_values": possible_values,
+            "tracked": tracked,
         }
 
         form = CustomStudySubjectFieldEditForm(sample_data, instance=field)
diff --git a/smash/web/tests/functions.py b/smash/web/tests/functions.py
index 51d91ea3e089bcc4d87e9c376fc83cef3ab18d39..759e14f05782511adcd1be3799190419bc3a7b5a 100644
--- a/smash/web/tests/functions.py
+++ b/smash/web/tests/functions.py
@@ -6,14 +6,46 @@ from typing import Union
 from django.contrib.auth.models import Permission
 from django.contrib.auth import get_user_model
 from django.utils.timezone import make_aware, is_aware
-
-from web.models import Location, AppointmentType, StudySubject, Worker, Visit, Appointment, ConfigurationItem, \
-    Language, ContactAttempt, FlyingTeam, Availability, Subject, Study, StudyColumns, StudyNotificationParameters, \
-    VoucherType, VoucherTypePrice, Voucher, Room, Item, WorkerStudyRole, StudyRedCapColumns, EtlColumnMapping, \
-    SubjectImportData, SubjectType
-from web.models.constants import REDCAP_TOKEN_CONFIGURATION_TYPE, REDCAP_BASE_URL_CONFIGURATION_TYPE, \
-    SEX_CHOICES_MALE, CONTACT_TYPES_PHONE, \
-    MONDAY_AS_DAY_OF_WEEK, COUNTRY_AFGHANISTAN_ID, VOUCHER_STATUS_NEW, GLOBAL_STUDY_ID, DEFAULT_LOCALE_NAME
+from django.conf import settings
+
+from web.models import (
+    Location,
+    AppointmentType,
+    StudySubject,
+    Worker,
+    Visit,
+    Appointment,
+    ConfigurationItem,
+    Language,
+    ContactAttempt,
+    FlyingTeam,
+    Availability,
+    Subject,
+    Study,
+    StudyColumns,
+    StudyNotificationParameters,
+    VoucherType,
+    VoucherTypePrice,
+    Voucher,
+    Room,
+    Item,
+    WorkerStudyRole,
+    StudyRedCapColumns,
+    EtlColumnMapping,
+    SubjectImportData,
+    SubjectType,
+)
+from web.models.constants import (
+    REDCAP_TOKEN_CONFIGURATION_TYPE,
+    REDCAP_BASE_URL_CONFIGURATION_TYPE,
+    SEX_CHOICES_MALE,
+    CONTACT_TYPES_PHONE,
+    MONDAY_AS_DAY_OF_WEEK,
+    COUNTRY_AFGHANISTAN_ID,
+    VOUCHER_STATUS_NEW,
+    GLOBAL_STUDY_ID,
+    DEFAULT_LOCALE_NAME,
+)
 from web.models.worker_study_role import ROLE_CHOICES_DOCTOR, WORKER_VOUCHER_PARTNER
 from web.redcap_connector import RedcapSubject
 from web.views.notifications import get_today_midnight_date
@@ -37,10 +69,12 @@ def create_voucher_type():
 
 
 def create_voucher_type_price():
-    return VoucherTypePrice.objects.create(voucher_type=create_voucher_type(),
-                                           price=12.34,
-                                           start_date=get_today_midnight_date(),
-                                           end_date=get_today_midnight_date())
+    return VoucherTypePrice.objects.create(
+        voucher_type=create_voucher_type(),
+        price=12.34,
+        start_date=get_today_midnight_date(),
+        end_date=get_today_midnight_date(),
+    )
 
 
 def create_empty_study_columns():
@@ -68,8 +102,12 @@ def create_study(name="test"):
     study_columns = StudyColumns.objects.create()
     notification_parameters = StudyNotificationParameters.objects.create()
     redcap_columns = StudyRedCapColumns.objects.create()
-    return Study.objects.create(name=name, columns=study_columns, notification_parameters=notification_parameters,
-                                redcap_columns=redcap_columns)
+    return Study.objects.create(
+        name=name,
+        columns=study_columns,
+        notification_parameters=notification_parameters,
+        redcap_columns=redcap_columns,
+    )
 
 
 TEST_ID_COUNTER = 0
@@ -90,14 +128,16 @@ def create_voucher(study_subject=None, partner=None, worker=None):
     if worker is None:
         worker = create_worker()
     number = str(get_test_id())
-    return Voucher.objects.create(number=number,
-                                  study_subject=study_subject,
-                                  issue_date=get_today_midnight_date(),
-                                  expiry_date=get_today_midnight_date(),
-                                  voucher_type=create_voucher_type(),
-                                  usage_partner=partner,
-                                  issue_worker=worker,
-                                  status=VOUCHER_STATUS_NEW)
+    return Voucher.objects.create(
+        number=number,
+        study_subject=study_subject,
+        issue_date=get_today_midnight_date(),
+        expiry_date=get_today_midnight_date(),
+        voucher_type=create_voucher_type(),
+        usage_partner=partner,
+        issue_worker=worker,
+        status=VOUCHER_STATUS_NEW,
+    )
 
 
 def create_empty_notification_parameters():
@@ -158,7 +198,7 @@ def get_test_study() -> Study:
         return create_study("test-study")
 
 
-def create_appointment_type(code='C', default_duration=10, description='test'):
+def create_appointment_type(code="C", default_duration=10, description="test"):
     return AppointmentType.objects.create(
         code=code,
         default_duration=default_duration,
@@ -172,13 +212,14 @@ def create_contact_attempt(subject=None, worker=None):
     if worker is None:
         worker = create_worker()
 
-    return ContactAttempt.objects.create(subject=subject,
-                                         worker=worker,
-                                         type=CONTACT_TYPES_PHONE,
-                                         datetime_when=get_today_midnight_date(),
-                                         success=True,
-                                         comment="Successful contact attempt",
-                                         )
+    return ContactAttempt.objects.create(
+        subject=subject,
+        worker=worker,
+        type=CONTACT_TYPES_PHONE,
+        datetime_when=get_today_midnight_date(),
+        success=True,
+        comment="Successful contact attempt",
+    )
 
 
 def create_subject():
@@ -186,12 +227,16 @@ def create_subject():
         first_name="Piotr",
         last_name="Gawron",
         sex=SEX_CHOICES_MALE,
-        country_id=COUNTRY_AFGHANISTAN_ID
+        country_id=COUNTRY_AFGHANISTAN_ID,
     )
 
 
-def create_study_subject(subject_id: int = 1, subject: Subject = None, nd_number: str = 'ND0001',
-                         study: Study = None) -> StudySubject:
+def create_study_subject(
+    subject_id: int = 1,
+    subject: Subject = None,
+    nd_number: str = "ND0001",
+    study: Study = None,
+) -> StudySubject:
     if study is None:
         study = get_test_study()
     if subject is None:
@@ -201,9 +246,11 @@ def create_study_subject(subject_id: int = 1, subject: Subject = None, nd_number
         type=get_control_subject_type(),
         screening_number="piotr's number" + str(subject_id),
         study=study,
-        subject=subject
+        subject=subject,
     )
-    if nd_number is not None:  # null value in column "nd_number" violates not-null constraint
+    if (
+        nd_number is not None
+    ):  # null value in column "nd_number" violates not-null constraint
         study_subject.nd_number = nd_number
         study_subject.save()
 
@@ -211,25 +258,27 @@ def create_study_subject(subject_id: int = 1, subject: Subject = None, nd_number
 
 
 def get_control_subject_type() -> SubjectType:
-    return SubjectType.objects.filter(name='CONTROL').first()
+    return SubjectType.objects.filter(name="CONTROL").first()
 
 
 def get_patient_subject_type() -> SubjectType:
-    return SubjectType.objects.filter(name='PATIENT').first()
+    return SubjectType.objects.filter(name="PATIENT").first()
 
 
-def create_study_subject_with_multiple_screening_numbers(subject_id=1, subject=None, screening_number=None):
+def create_study_subject_with_multiple_screening_numbers(
+    subject_id=1, subject=None, screening_number=None
+):
     if subject is None:
         subject = create_subject()
 
     if screening_number is None:
-        screening_number = f'E-00{subject_id}; L-00{subject_id}'
+        screening_number = f"E-00{subject_id}; L-00{subject_id}"
     return StudySubject.objects.create(
         default_location=get_test_location(),
         type=get_control_subject_type(),
         screening_number=screening_number,
         study=get_test_study(),
-        subject=subject
+        subject=subject,
     )
 
 
@@ -239,15 +288,16 @@ def create_red_cap_subject():
     return result
 
 
-def create_user(username: str = None, password: str = None, email: str = 'jacob@bla') -> get_user_model():
+def create_user(
+    username: str = None, password: str = None, email: str = "jacob@bla"
+) -> get_user_model():
     if username is None:
-        username = 'piotr'
+        username = "piotr"
     if password is None:
-        password = 'top_secret'
+        password = "top_secret"
     user = get_user_model().objects.create_user(
-        username=username,
-        email=email,
-        password=password)
+        username=username, email=email, password=password
+    )
 
     create_worker(user)
     return user
@@ -262,63 +312,75 @@ def add_permissions_to_worker(worker, codenames):
 
 def create_worker(user=None, with_test_location=False):
     worker = Worker.objects.create(
-        first_name='piotr',
+        first_name="piotr",
         last_name="gawron",
-        email='jacob@bla.com',
+        email="jacob@bla.com",
         user=user,
         specialization="spec",
         unit="LCSB",
-        phone_number="0123456789"
+        phone_number="0123456789",
     )
     if with_test_location:
         worker.locations.set([get_test_location()])
         worker.save()
     WorkerStudyRole.objects.create(
-        worker=worker, study_id=GLOBAL_STUDY_ID, name=ROLE_CHOICES_DOCTOR)
+        worker=worker, study_id=GLOBAL_STUDY_ID, name=ROLE_CHOICES_DOCTOR
+    )
     return worker
 
 
 def create_voucher_partner():
     worker = Worker.objects.create(
-        first_name='piotr',
+        first_name="piotr",
         last_name="gawron",
-        email='jacob@bla.com',
+        email="jacob@bla.com",
         specialization="spec",
         unit="LCSB",
-        phone_number="0123456789"
+        phone_number="0123456789",
     )
     WorkerStudyRole.objects.create(
-        worker=worker, study_id=GLOBAL_STUDY_ID, name=WORKER_VOUCHER_PARTNER)
+        worker=worker, study_id=GLOBAL_STUDY_ID, name=WORKER_VOUCHER_PARTNER
+    )
     return worker
 
 
-def create_availability(worker=None, available_from=None, available_till=None, day_number=MONDAY_AS_DAY_OF_WEEK):
+def create_availability(
+    worker=None,
+    available_from=None,
+    available_till=None,
+    day_number=MONDAY_AS_DAY_OF_WEEK,
+):
     if available_from is None:
-        available_from = '8:00'
+        available_from = "8:00"
     if available_till is None:
-        available_till = '18:00'
+        available_till = "18:00"
 
     if worker is None:
         worker = create_worker()
-    availability = Availability.objects.create(person=worker,
-                                               day_number=day_number,
-                                               available_from=available_from,
-                                               available_till=available_till,
-                                               )
+    availability = Availability.objects.create(
+        person=worker,
+        day_number=day_number,
+        available_from=available_from,
+        available_till=available_till,
+    )
     return availability
 
 
-def create_visit(subject: StudySubject = None, datetime_begin=None, datetime_end=None) -> Visit:
+def create_visit(
+    subject: StudySubject = None, datetime_begin=None, datetime_end=None
+) -> Visit:
     if subject is None:
         subject = create_study_subject()
     if datetime_begin is None:
         datetime_begin = get_today_midnight_date() + datetime.timedelta(days=-31)
     if datetime_end is None:
         datetime_end = get_today_midnight_date() + datetime.timedelta(days=31)
-    return Visit.objects.create(datetime_begin=datetime_begin,
-                                datetime_end=datetime_end,
-                                subject=subject,
-                                is_finished=False)
+    return Visit.objects.create(
+        datetime_begin=datetime_begin,
+        datetime_end=datetime_end,
+        subject=subject,
+        is_finished=False,
+    )
 
 
 def create_appointment(visit=None, when=None, length=30) -> Appointment:
@@ -339,7 +401,8 @@ def create_appointment(visit=None, when=None, length=30) -> Appointment:
         length=length,
         location=get_test_location(),
         status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
-        datetime_when=when_datetime)
+        datetime_when=when_datetime,
+    )
 
 
 def create_appointment_without_visit(when=None, length=30):
@@ -347,7 +410,8 @@ def create_appointment_without_visit(when=None, length=30):
         length=length,
         location=get_test_location(),
         status=Appointment.APPOINTMENT_STATUS_SCHEDULED,
-        datetime_when=when)
+        datetime_when=when,
+    )
 
 
 def create_configuration_item():
@@ -366,19 +430,31 @@ def create_flying_team(place=None):
     return result
 
 
-def create_item(name='Test item', is_fixed=False, disposable=False):
+def create_item(name="Test item", is_fixed=False, disposable=False):
     item = Item(name=name, is_fixed=is_fixed, disposable=disposable)
     item.save()
     return item
 
 
-def create_room(owner='Test owner', city='Test city',
-                address='Test address', equipment=None,
-                floor=1, is_vehicle=False, room_number=1):
+def create_room(
+    owner="Test owner",
+    city="Test city",
+    address="Test address",
+    equipment=None,
+    floor=1,
+    is_vehicle=False,
+    room_number=1,
+):
     if equipment is None:
         equipment = []
-    room = Room(owner=owner, city=city, address=address,
-                floor=floor, is_vehicle=is_vehicle, room_number=room_number)
+    room = Room(
+        owner=owner,
+        city=city,
+        address=address,
+        floor=floor,
+        is_vehicle=is_vehicle,
+        room_number=room_number,
+    )
     room.save()
     room.equipment.set(equipment)  # Cannot be made in constructor/with single save
     room.save()
@@ -392,14 +468,14 @@ def create_language(name="French", locale=DEFAULT_LOCALE_NAME) -> Language:
 
 
 def get_resource_path(filename):
-    return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', filename)
+    return os.path.join(os.path.dirname(os.path.realpath(__file__)), "data", filename)
 
 
 def format_form_field(value):
     if isinstance(value, datetime.date):
-        return value.strftime('%Y-%m-%d')
+        return value.strftime("%Y-%m-%d")
     elif isinstance(value, datetime.datetime):
-        return value.strftime('%Y-%m-%d %H:%M')
+        return value.strftime("%Y-%m-%d %H:%M")
     elif value is None:
         return ""
     else:
@@ -409,67 +485,92 @@ def format_form_field(value):
 def prepare_test_redcap_connection():
     Language.objects.create(name="Finnish").save()
     Language.objects.create(name="Italian").save()
-    token_item = ConfigurationItem.objects.filter(
-        type=REDCAP_TOKEN_CONFIGURATION_TYPE)[0]
+    token_item = ConfigurationItem.objects.filter(type=REDCAP_TOKEN_CONFIGURATION_TYPE)[
+        0
+    ]
     # noinspection SpellCheckingInspection
-    token_item.value = "5DC21D45E3A2E068659F11046EA88734"
+    token_item.value = settings.REDCAP_TEST_API_TOKEN
     token_item.save()
     url_item = ConfigurationItem.objects.filter(
-        type=REDCAP_BASE_URL_CONFIGURATION_TYPE)[0]
-    url_item.value = "https://luxparktest.lcsb.uni.lu/redcap/"
+        type=REDCAP_BASE_URL_CONFIGURATION_TYPE
+    )[0]
+    url_item.value = settings.REDCAP_TEST_URL
     url_item.save()
 
 
 def datetimeify_date(date: Union[datetime.date, str, bytes]) -> datetime.datetime:
-    if isinstance(date, datetime.date):  # If it's date, then just make sure that timezone support is there
+    if isinstance(
+        date, datetime.date
+    ):  # If it's date, then just make sure that timezone support is there
         if is_aware(date):
             return date
         else:
             return make_aware(date)
     if isinstance(date, bytes):  # If it's bytes, then convert to string and carry on...
-        date = date.decode('utf8')
-    if isinstance(date, str):  # If it's string, convert to datetime with timezone support
-        return make_aware(datetime.datetime.strptime(date, '%Y-%m-%d'))
+        date = date.decode("utf8")
+    if isinstance(
+        date, str
+    ):  # If it's string, convert to datetime with timezone support
+        return make_aware(datetime.datetime.strptime(date, "%Y-%m-%d"))
 
     actual_type = str(type(date))
     raise TypeError(
-        f"Date should be either a subclass of 'datetime.date', string or bytes! But is: {actual_type} instead")
+        f"Date should be either a subclass of 'datetime.date', string or bytes! But is: {actual_type} instead"
+    )
 
 
 def create_tns_column_mapping(subject_import_data: SubjectImportData):
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="nd_number",
-                                    csv_column_name="donor_id",
-                                    table_name=StudySubject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="comments",
-                                    csv_column_name="treatingphysician",
-                                    table_name=StudySubject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="first_name",
-                                    csv_column_name="firstname",
-                                    table_name=Subject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="first_name",
-                                    csv_column_name="sig_firstname",
-                                    table_name=Subject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="last_name",
-                                    csv_column_name="lastname",
-                                    table_name=Subject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="last_name",
-                                    csv_column_name="sig_lastname",
-                                    table_name=Subject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="phone_number",
-                                    csv_column_name="phonenr",
-                                    table_name=Subject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="date_born",
-                                    csv_column_name="dateofbirth",
-                                    table_name=Subject._meta.db_table)
-    EtlColumnMapping.objects.create(etl_data=subject_import_data,
-                                    column_name="next_of_kin_name",
-                                    csv_column_name="representative",
-                                    table_name=Subject._meta.db_table)
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="nd_number",
+        csv_column_name="donor_id",
+        table_name=StudySubject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="comments",
+        csv_column_name="treatingphysician",
+        table_name=StudySubject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="first_name",
+        csv_column_name="firstname",
+        table_name=Subject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="first_name",
+        csv_column_name="sig_firstname",
+        table_name=Subject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="last_name",
+        csv_column_name="lastname",
+        table_name=Subject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="last_name",
+        csv_column_name="sig_lastname",
+        table_name=Subject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="phone_number",
+        csv_column_name="phonenr",
+        table_name=Subject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="date_born",
+        csv_column_name="dateofbirth",
+        table_name=Subject._meta.db_table,
+    )
+    EtlColumnMapping.objects.create(
+        etl_data=subject_import_data,
+        column_name="next_of_kin_name",
+        csv_column_name="representative",
+        table_name=Subject._meta.db_table,
+    )
diff --git a/smash/web/tests/test_statistics.py b/smash/web/tests/test_statistics.py
index f48ead719afde2fe3eba98391c5e17c1101be3d6..869a3f6c8272d615a01d2b60216cc7ea900c5216 100644
--- a/smash/web/tests/test_statistics.py
+++ b/smash/web/tests/test_statistics.py
@@ -5,11 +5,15 @@ from django.test import TestCase
 
 from web.models import Visit, AppointmentTypeLink
 from web.statistics import get_previous_year_and_month_for_date, StatisticsManager
-from web.tests.functions import create_appointment, create_appointment_type, get_control_subject_type, \
-    get_patient_subject_type
+from web.tests.functions import (
+    create_appointment,
+    create_appointment_type,
+    get_control_subject_type,
+    get_patient_subject_type,
+)
 from web.views.notifications import get_today_midnight_date
 
-__author__ = 'Valentin Grouès'
+__author__ = "Valentin Grouès"
 
 
 class TestStatistics(TestCase):
@@ -17,7 +21,9 @@ class TestStatistics(TestCase):
         self.now = get_today_midnight_date()
         self.appointment_type = create_appointment_type()
         appointment = create_appointment(when=self.now)
-        AppointmentTypeLink.objects.create(appointment=appointment, appointment_type=self.appointment_type)
+        AppointmentTypeLink.objects.create(
+            appointment=appointment, appointment_type=self.appointment_type
+        )
         self.visit_start = appointment.visit.datetime_begin
         self.visit_end = appointment.visit.datetime_end
         appointment.save()
@@ -35,67 +41,112 @@ class TestStatistics(TestCase):
         self.assertEqual(12, previous_month)
 
     def test_get_statistics_for_month_one_appointment(self):
-        statistics = self.statistics_manager.get_statistics_for_month(self.visit_start.month, self.visit_start.year)
-        self.check_statistics(statistics, 1, 0, 0, {"C": [0, 0]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.visit_start.month, self.visit_start.year
+        )
+        self.check_statistics(statistics, 1, 0, 0, {"C": [0, 0]}, ["Scheduled"])
 
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year)
-        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year
+        )
+        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ["Scheduled"])
 
-        statistics = self.statistics_manager.get_statistics_for_month(self.visit_end.month, self.visit_end.year)
-        self.check_statistics(statistics, 0, 1, 0, {"C": [0, 0]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.visit_end.month, self.visit_end.year
+        )
+        self.check_statistics(statistics, 0, 1, 0, {"C": [0, 0]}, ["Scheduled"])
 
     def test_get_statistics_for_month_one_appointment_visit(self):
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, visit="1")
-        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year, visit="1"
+        )
+        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ["Scheduled"])
 
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, visit="2")
-        self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year, visit="2"
+        )
+        self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ["Scheduled"])
 
     def test_get_statistics_for_month_one_appointment_subject_type(self):
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
-                                                                      subject_type=get_control_subject_type())
-        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year, subject_type=get_control_subject_type()
+        )
+        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ["Scheduled"])
 
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
-                                                                      subject_type=get_patient_subject_type())
-        self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year, subject_type=get_patient_subject_type()
+        )
+        self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ["Scheduled"])
 
     def test_get_statistics_for_month_one_appointment_subject_type_and_visit(self):
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
-                                                                      subject_type=get_control_subject_type(),
-                                                                      visit='1')
-        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ['Scheduled'])
-
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year,
-                                                                      subject_type=get_patient_subject_type(),
-                                                                      visit='1')
-        self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ['Scheduled'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month,
+            self.now.year,
+            subject_type=get_control_subject_type(),
+            visit="1",
+        )
+        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1]}, ["Scheduled"])
+
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month,
+            self.now.year,
+            subject_type=get_patient_subject_type(),
+            visit="1",
+        )
+        self.check_statistics(statistics, 0, 0, 0, {"C": [0, 0]}, ["Scheduled"])
 
     def test_get_statistics_for_month_multiple_visits(self):
-        second_visit = Visit.objects.create(datetime_begin=self.now + datetime.timedelta(days=-32),
-                                            datetime_end=self.now + datetime.timedelta(days=31),
-                                            subject=self.subject,
-                                            is_finished=False)
+        second_visit = Visit.objects.create(
+            datetime_begin=self.now + datetime.timedelta(days=-32),
+            datetime_end=self.now + datetime.timedelta(days=31),
+            subject=self.subject,
+            is_finished=False,
+        )
         second_appointment = create_appointment(second_visit, when=self.now)
-        AppointmentTypeLink.objects.create(appointment=second_appointment, appointment_type=self.appointment_type)
+        AppointmentTypeLink.objects.create(
+            appointment=second_appointment, appointment_type=self.appointment_type
+        )
 
         second_appointment.status = "Cancelled"
         second_appointment.save()
         self.statistics_manager = StatisticsManager()
 
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year)
-        self.check_statistics(statistics, 0, 0, 2, {"C": [2, 1, 1]}, ['Cancelled', 'Scheduled'])
-
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, visit="1")
-        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 1, 0]}, ['Cancelled', 'Scheduled'])
-
-        statistics = self.statistics_manager.get_statistics_for_month(self.now.month, self.now.year, visit="2")
-        self.check_statistics(statistics, 0, 0, 1, {"C": [1, 0, 1]}, ['Cancelled', 'Scheduled'])
-
-    def check_statistics(self, statistics, expected_visits_started, expected_visits_ended, expected_appointments_count,
-                         expected_appointments_details, expected_statuses):
-        self.assertEqual(expected_visits_started, statistics['general']['visits_started'])
-        self.assertEqual(expected_visits_ended, statistics['general']['visits_ended'])
-        self.assertEqual(expected_statuses, statistics['statuses_list'])
-        self.assertEqual(expected_appointments_count, statistics['general']['appointments'])
-        self.assertEqual(expected_appointments_details, statistics['appointments'])
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year
+        )
+        self.check_statistics(
+            statistics, 0, 0, 2, {"C": [2, 1, 1]}, ["Cancelled", "Scheduled"]
+        )
+
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year, visit="1"
+        )
+        self.check_statistics(
+            statistics, 0, 0, 1, {"C": [1, 1, 0]}, ["Cancelled", "Scheduled"]
+        )
+
+        statistics = self.statistics_manager.get_statistics_for_month(
+            self.now.month, self.now.year, visit="2"
+        )
+        self.check_statistics(
+            statistics, 0, 0, 1, {"C": [1, 0, 1]}, ["Cancelled", "Scheduled"]
+        )
+
+    def check_statistics(
+        self,
+        statistics,
+        expected_visits_started,
+        expected_visits_ended,
+        expected_appointments_count,
+        expected_appointments_details,
+        expected_statuses,
+    ):
+        self.assertEqual(
+            expected_visits_started, statistics["general"]["visits_started"]
+        )
+        self.assertEqual(expected_visits_ended, statistics["general"]["visits_ended"])
+        self.assertEqual(expected_statuses, statistics["statuses_list"])
+        self.assertEqual(
+            expected_appointments_count, statistics["general"]["appointments"]
+        )
+        self.assertEqual(expected_appointments_details, statistics["appointments"])