diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e979801fbd2aed35878d7695c000ff3340e06e32..9946bf79b2f837826abe5f585e8ae24d42d6b799 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,8 +1,16 @@
 image: docker:24
 
-build:
+
+stages:
+  - "📦 build"
+  - "🩺 test"
+  - "🚀 deploy"
+  - "🔧 configure"
+
+
+"🐳 build":
   needs: []
-  stage: build
+  stage: "📦 build"
   variables:
     RUN_TEST: "1"
     POSTGRES_HOST: "db-test"
@@ -59,9 +67,9 @@ build:
 
 
 
-test-file-perms:
-  stage: test
-  needs: ["build"]
+"🩺 test-file-perms":
+  stage: "🩺 test"
+  needs: ["🐳 build"]
   variables:
     FILEPATH_TO_TEST: "/code/manage.py"
     PATH_TAG: "app"
@@ -81,8 +89,8 @@ test-file-perms:
 
 
 .deploy:
-  needs: ["build", "tailored-doc"]
-  stage: deploy
+  needs: ["🐳 build", "📝 tailored-doc"]
+  stage: "🚀 deploy"
   image: harbor.pasteur.fr/kube-system/helm-kubectl:3.13.3
   variables:
     CI_DEBUG_TRACE: "false"
@@ -93,7 +101,7 @@ test-file-perms:
   environment:
     name: "k8sdev-01-strass-dev/$CI_COMMIT_REF_SLUG"
     url: "https://strass-${CI_COMMIT_REF_SLUG}.dev.pasteur.cloud"
-    on_stop: stop_and_delete_in_dev
+    on_stop: "💥 stop_and_delete_in_dev"
   script:
     - kubectl delete secret registry-gitlab -n $NAMESPACE --ignore-not-found=true
     - kubectl create secret docker-registry -n $NAMESPACE registry-gitlab --docker-server=$CI_REGISTRY --docker-username=$DEPLOY_USER --docker-password=$DEPLOY_TOKEN --docker-email=$GITLAB_USER_EMAIL
@@ -123,7 +131,7 @@ test-file-perms:
 
 
 
-deploy_dev:
+"🚀 deploy_dev":
   extends: .deploy
   except:
     variables:
@@ -131,8 +139,12 @@ deploy_dev:
 
 
 
-deploy_prod:
+"🚀 deploy_prod":
   extends: .deploy
+  needs:
+    - "🐳 build"
+    - "📝 tailored-doc"
+    - "💯 checkCoverageMasterOrProd"
   only:
     variables:
       - $CI_COMMIT_REF_SLUG =~ /-prod$/
@@ -144,32 +156,34 @@ deploy_prod:
   environment:
     name: "k8sprod-02/strass-prod/$CI_COMMIT_REF_SLUG"
     url: "https://${CI_COMMIT_REF_SLUG::-5}.pasteur.cloud"
-    on_stop: stop_and_delete_in_prod
+    on_stop: "❗💥 stop_and_delete_in_prod"
   before_script:
     - export REL_NAME="${CI_COMMIT_REF_SLUG::-5}"
 
 
 
-open_public_access:
-  needs: ["deploy_prod"]
+"🔓🌐 open_public_access":
+  needs: ["🚀 deploy_prod"]
+  stage: "🔧 configure"
   when: manual
-  extends: deploy_prod
+  extends: "🚀 deploy_prod"
   variables:
     PUBLICLY_OPEN: "true"
 
 
 
-close_public_access:
-  needs: ["deploy_prod"]
+"🔒🌐 close_public_access":
+  needs: ["🚀 deploy_prod"]
+  stage: "🔧 configure"
   when: manual
-  extends: deploy_prod
+  extends: "🚀 deploy_prod"
   variables:
     PUBLICLY_OPEN: "false"
 
 
 
 .stop_and_delete:
-  stage: deploy
+  stage: "🔧 configure"
   when: manual
   image: harbor.pasteur.fr/kube-system/helm-kubectl:3.13.3
   variables:
@@ -186,8 +200,8 @@ close_public_access:
 
 
 
-stop_and_delete_in_dev:
-  needs: ["deploy_dev"]
+"💥 stop_and_delete_in_dev":
+  needs: ["🚀 deploy_dev"]
   extends: .stop_and_delete
   except:
     variables:
@@ -195,13 +209,13 @@ stop_and_delete_in_dev:
 
 
 
-stop_and_delete_in_prod:
-  needs: ["deploy_prod"]
+"❗💥 stop_and_delete_in_prod":
+  needs: ["🚀 deploy_prod"]
   extends: .stop_and_delete
   only:
     variables:
       - $CI_COMMIT_REF_SLUG =~ /-prod$/
-  stage: deploy
+  stage: "🔧 configure"
   when: manual
   variables:
     GIT_STRATEGY: none # important to not checkout source when branch is deleted
@@ -217,10 +231,10 @@ stop_and_delete_in_prod:
 
 
 
-build_pages:
+"📝 build_pages":
   needs: []
   image: python:3.11
-  stage: build
+  stage: "📦 build"
   script:
   - pip install -r doc/requirements.txt
   - cd doc
@@ -233,8 +247,8 @@ build_pages:
 
 
 pages:
-  needs: [build_pages]
-  stage: build
+  needs: ["📝 build_pages"]
+  stage: "📦 build"
   script:
     - ls -lah
   artifacts:
@@ -245,9 +259,9 @@ pages:
 
 
 
-tailored-doc:
-  needs: [build_pages]
-  stage: build
+"📝 tailored-doc":
+  needs: ["📝 build_pages"]
+  stage: "📦 build"
   before_script:
     - i=0; while [ "$i" -lt 60 ]; do docker info && break; sleep 1; i=$(( i + 1 )) ; done
     - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER $CI_REGISTRY --password-stdin
@@ -282,10 +296,10 @@ tailored-doc:
 
 
 
-black:
+"🎨 black":
   needs: []
   image: python:3.11
-  stage: build
+  stage: "📦 build"
   script:
     - python -m pip install --upgrade pip
     - cd src/strass/
@@ -296,7 +310,8 @@ black:
 
 # credits https://rpadovani.com/gitlab-code-coverage
 .checkCoverage:
-    needs: ["build"]
+    needs: ["🐳 build"]
+    stage: "🩺 test"
     image: alpine:latest
     variables:
         JOB_NAME: build
@@ -312,28 +327,25 @@ black:
         - if  [ "$CURRENT_COVERAGE" -lt "$TARGET_COVERAGE" ]; then echo "Coverage decreased from ${TARGET_COVERAGE} to ${CURRENT_COVERAGE}" && exit 1; fi;
 
 
-
-checkCoverageMasterOrProd:
+"💯 checkCoverageMasterOrProd":
   extends: .checkCoverage
   only:
     variables:
       - $CI_COMMIT_REF_SLUG =~ /-prod$/
       - $CI_COMMIT_REF_SLUG == "master"
-  stage: test
 
 
 
-checkCoverageOther:
+"💯 checkCoverageOther":
   extends: .checkCoverage
   except:
     variables:
       - $CI_COMMIT_REF_SLUG =~ /-prod$/
       - $CI_COMMIT_REF_SLUG == "master"
-  stage: deploy
 
 
-test-migrations-and-lang-ar-up-to-date:
-  stage: test
+"🩺 test-migrations-and-lang-ar-up-to-date":
+  stage: "🩺 test"
   needs: []
   image: python:3.11
   variables:
@@ -354,9 +366,9 @@ test-migrations-and-lang-ar-up-to-date:
 
 
 ssi-test:
-  needs: ["build"]
+  needs: ["🐳 build"]
   when: manual
-  stage: test
+  stage: "🩺 test"
   before_script:
     - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
   cache:
diff --git a/doc/_static/merge-request-pipelines-all-cut-dev.png b/doc/_static/merge-request-pipelines-all-cut-dev.png
new file mode 100644
index 0000000000000000000000000000000000000000..92c98b25e2b1fd0c6bed2eb3d526afc0b598fc3a
Binary files /dev/null and b/doc/_static/merge-request-pipelines-all-cut-dev.png differ
diff --git a/doc/_static/merge-request-pipelines-all-cut.png b/doc/_static/merge-request-pipelines-all-cut.png
deleted file mode 100644
index a298ec2f9d99d9c529ecc6b13b31bf42253dbffe..0000000000000000000000000000000000000000
Binary files a/doc/_static/merge-request-pipelines-all-cut.png and /dev/null differ
diff --git a/doc/_static/merge-request-pipelines-all.png b/doc/_static/merge-request-pipelines-all.png
deleted file mode 100644
index 38e84e27ab77476ad0a04f97faba5948fe9dc797..0000000000000000000000000000000000000000
Binary files a/doc/_static/merge-request-pipelines-all.png and /dev/null differ
diff --git a/doc/_static/merge-request-pipelines-delete-dev.png b/doc/_static/merge-request-pipelines-delete-dev.png
index eda7aa35d1e60097dd30e8d0a3e87779624eb792..cccf3ec0ed458305b3e5fce813de2bb22d9e6e40 100644
Binary files a/doc/_static/merge-request-pipelines-delete-dev.png and b/doc/_static/merge-request-pipelines-delete-dev.png differ
diff --git a/doc/_static/merge-request-pipelines-delete.png b/doc/_static/merge-request-pipelines-delete.png
index 70b73e781138b84fb31bb35dc748c5537c5aa273..f19deb188386f5c16d07f8782ecb098498bc1b9d 100644
Binary files a/doc/_static/merge-request-pipelines-delete.png and b/doc/_static/merge-request-pipelines-delete.png differ
diff --git a/doc/_static/merge-request-pipelines.png b/doc/_static/merge-request-pipelines.png
index 448eb544c92b5a8eaad3dd3e74ea3db1521ee564..6044d0dbee3cb61b7e6b7c53e03af9063b2ab3fe 100644
Binary files a/doc/_static/merge-request-pipelines.png and b/doc/_static/merge-request-pipelines.png differ
diff --git a/doc/configure_instance.rst b/doc/configure_instance.rst
index fe19cd6619a46e673818c0decf71d406af480d4b..3d76978a8c3a4cf9939a3b7d1fad3eca1066feb5 100644
--- a/doc/configure_instance.rst
+++ b/doc/configure_instance.rst
@@ -173,8 +173,9 @@ A profiles can be "Single cell" "Metagenomic", "Web development", ... There can
 .. warning::
     Deleting a profile would result in un-associating all existing candidate to this profile without notifying them. To prevent mis-deletion, this possibility is not possible through the recruitment management interface. It is only possible in the admin interface https://strass-master.dev.pasteur.cloud/admin/strass_app/profile/, and only for superusers.
 
+.. _language override module:
 
-Using STRASS for a workshop
+Using STRASS for a workshop (a.k.a the Language override module)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 If you use STRASS to recruit students for a workshop, the term `Profile` does not suite the need and you probably want to rename it to `Workshop` for example. Here is how to do it:
diff --git a/doc/jm_documentation.rst b/doc/jm_documentation.rst
index e966b38b70303a39a5d0571db3be1877364177aa..acf72257a37998d3f2724aacc4e36bf14e36d7a9 100644
--- a/doc/jm_documentation.rst
+++ b/doc/jm_documentation.rst
@@ -12,4 +12,5 @@ Jury Manager Doc
     reviewer_assignation
     mass_mailing
     question_system
-    overall_score_system
\ No newline at end of file
+    overall_score_system
+    security_feature
\ No newline at end of file
diff --git a/doc/life_cycle.rst b/doc/life_cycle.rst
index ec82c500d6f404fae2f961de5d67e5de3e8929ac..161f5970b864933c185ecc44bc540892699d03fb 100644
--- a/doc/life_cycle.rst
+++ b/doc/life_cycle.rst
@@ -78,8 +78,9 @@ all pipelines are now green.
   :alt: Pipelines all in green
 
 
-On the right part, the third green checkmark (named A) allows you to see all job related to
-the deployment of the instance. You see the `deploy_prod` went well. To open the public access just click on the play
+On the right part, the fourth circle (named A) allows you to see all job related to
+the deployment of the instance.
+To open the public access just click on the play
 button next to `open_public_access` (named B) and wait for the job completion.
 Similarly, to close the public access juste click on the play
 button next to `close_public_access` (named C) and wait for the job completion.
diff --git a/doc/life_cycle_demo.rst b/doc/life_cycle_demo.rst
index b84c5c68916f600bd54c31ed50a2948293b0eec2..7c4c529d955fac79fe008f4ecbe19ddd2a6bb729 100644
--- a/doc/life_cycle_demo.rst
+++ b/doc/life_cycle_demo.rst
@@ -48,14 +48,16 @@ Load the demo ?
 -------------------------------------------------------------------------------
 
 You can load the demo, but you need kubectl installed, and you need to be granted on the dev namespace.
-If you understand this requirementsadd, then see :ref:`load_demo_k8s` on know how to load the demo in kubernetes
+If you understand this requirements, then see :ref:`load_demo_k8s` on know how to load the demo in kubernetes
 
 Stop it
 -------------------------------------------------------------------------------
 
+It is automatically done when you merge your branch, but If you just close it you need to stop it first.
+
 In the pipelines (A), see the job "stop_and_delete_instance" (D)
 
-.. image:: _static/merge-request-pipelines-all-cut.png
+.. image:: _static/merge-request-pipelines-all-cut-dev.png
   :width: 100%
   :alt: Pipelines all in green
 
diff --git a/doc/security_feature.rst b/doc/security_feature.rst
new file mode 100644
index 0000000000000000000000000000000000000000..6433cd879acc858cdcea2b57bc114c619214c560
--- /dev/null
+++ b/doc/security_feature.rst
@@ -0,0 +1,29 @@
+.. note::
+    **Audience:** Jury Manager to adapt, all for information.
+
+.. _Overall score:
+
+Security considerations and features
+===============================================================================
+
+The application is thoroughly tested with 100% test coverage, ensuring that every part of the application and every scenario is tested. Before testing all scenarios, all role-based permissions and controls are tested separately, with a required coverage of 100%, ensuring that this key feature is well tested.
+
+The application's source code is hosted on GitLab, a software forge. Continuous integration has been set up to test the application at each commit. Unit tests act as a safeguard, preventing new versions from being deployed if they introduce regressions. Reducing test coverage is also prohibited for the master branch and production instances.
+
+The application runs in a "hardened" Docker container. This means that several security measures have been implemented to prevent code injection and ensure stability. The source code is set to read-only within the container, preventing unauthorized modifications or tampering. Additionally, it runs as a non-root user, limiting access to critical system resources and preventing unauthorized modifications or tampering with the system and sources. As a consequence, patching the application during execution is impossible, changes must be committed to the source repository where authorship is unquestionable and changes are thoroughly tested.
+
+These combined measures provide a secure and stable environment for our users.
+
+Protection from attacks using PDF
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A CV can be provided in a PDF file. This file format comes with features that can be exploited to attack the application, such as stealing session cookies or extracting application content. To prevent this, all executable code is stripped out from uploaded PDF files.
+
+Uploading a CV in PDF file can be disabled in https://strass-master.dev.pasteur.cloud/setup/#candidate-settings. If disabled, you may need to adapt the introduction text presented to applicants on how to apply. This text is visible at https://strass-master.dev.pasteur.cloud/candidate/apply/Intro/. To do so, use the :ref:`Language override module <language override module>`.
+
+Protection from attacks using Markdown
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Markdown is used to format motivation letters, calls, and more. This standard is exposed to JavaScript or CSS injection from attackers. A strict whitelist approach has been adopted, preventing almost all use of HTML within Markdown.
+
+Markdown can be disabled for the entire application in https://strass-master.dev.pasteur.cloud/setup/#misc-settings. If disabled, all content is escaped, and line breaks are replaced by ``<br>``.
diff --git a/src/strass/live_settings/__init__.py b/src/strass/live_settings/__init__.py
deleted file mode 100644
index 7b7e019a9ace42a69086a2ba16c7d621b3d5f3c8..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/__init__.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from datetime import datetime
-
-from django.core.cache import cache
-from django.db import OperationalError, ProgrammingError
-
-__LIVE_SETTING_EDITOR = "LiveSettingEditor"
-
-
-def get_live_settings_editor_group_name():
-    return __LIVE_SETTING_EDITOR
-
-
-class LiveSettings(object):
-    def __setattr__(self, key, value, *args, **kwargs):
-        try:
-            from live_settings.models import LiveSettings
-
-            if value is None:
-                LiveSettings.objects.filter(key=key).delete()
-            else:
-                LiveSettings.objects.update_or_create(key=key, defaults=dict(value=str(value)))
-        except (ProgrammingError, OperationalError):
-            return super().__setattr__(key, value)
-
-    def __getattribute__(self, key):
-        as_int = False
-        as_bool = False
-        as_date = False
-        if key.endswith("__int"):
-            key = key[:-5]
-            as_int = True
-        if key.endswith("__bool"):
-            key = key[:-6]
-            as_bool = True
-        if key.endswith("__date"):
-            key = key[:-6]
-            as_date = True
-        live_settings_dict = cache.get("live_settings_dict")
-        if live_settings_dict is None:
-            try:
-                from live_settings.models import LiveSettings
-
-                live_settings_dict = dict(LiveSettings.objects.values_list("key", "value"))
-                cache.set("live_settings_dict", live_settings_dict)
-            except (ProgrammingError, OperationalError):
-                return super().__getattribute__(key)
-        value = live_settings_dict.get(key, None)
-        if value is None:
-            return value
-        if as_int:
-            return int(value)
-        if as_bool:
-            return str(value).upper() == "TRUE"
-        if as_date:
-            return datetime.strptime(str(value), "%Y-%m-%d").date()
-        return value
-
-
-live_settings = LiveSettings()
diff --git a/src/strass/live_settings/admin.py b/src/strass/live_settings/admin.py
deleted file mode 100644
index 31c733caf05f3f49bceb70104dab46dd989deab8..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/admin.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from django.contrib import admin
-
-from live_settings import models
-
-
-class LiveSettingsAdmin(admin.ModelAdmin):
-    list_display = ("key", "last_edition_date")
-    readonly_fields = ["last_edition_date"]
-
-
-admin.site.register(models.LiveSettings, LiveSettingsAdmin)
diff --git a/src/strass/live_settings/context_processors.py b/src/strass/live_settings/context_processors.py
deleted file mode 100644
index 9fc38cc3fe1c0f34998ba57df5194cbbe2655749..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/context_processors.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from live_settings import live_settings
-
-
-def processors(request):
-    return dict(live_settings=live_settings)
diff --git a/src/strass/live_settings/forms.py b/src/strass/live_settings/forms.py
deleted file mode 100644
index c636c9a796c9956a2122bb8dc100043cc0069844..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/forms.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from django import forms
-
-from live_settings import models
-
-
-class LiveSettingsForm(forms.ModelForm):
-    next = forms.CharField(widget=forms.HiddenInput(), required=True)
-
-    class Meta:
-        model = models.LiveSettings
-        fields = ("value",)
-        widgets = {"value": forms.HiddenInput()}
diff --git a/src/strass/live_settings/migrations/0001_initial.py b/src/strass/live_settings/migrations/0001_initial.py
deleted file mode 100644
index cabe4e90c83a589304b6d4a795de420cfbfd3a7c..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/migrations/0001_initial.py
+++ /dev/null
@@ -1,24 +0,0 @@
-# Generated by Django 2.2.5 on 2019-11-20 10:20
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    initial = True
-
-    dependencies = [
-    ]
-
-    operations = [
-        migrations.CreateModel(
-            name='LiveSettings',
-            fields=[
-                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
-                ('key', models.CharField(max_length=256, unique=True, validators=[django.core.validators.RegexValidator(code='invalid_live_settings_key', message='LiveSettings key must must be Alphanumeric and start with a letter: <code>^[a-zA-Z][\\w]*$</code>.', regex='^[a-zA-Z][\\w]*$')])),
-                ('value', models.TextField(blank=True, null=True)),
-                ('last_edition_date', models.DateTimeField()),
-            ],
-        ),
-    ]
diff --git a/src/strass/live_settings/migrations/0002_create_group_settings_changer.py b/src/strass/live_settings/migrations/0002_create_group_settings_changer.py
deleted file mode 100644
index efcdf440c9c75a6b329f2caf6f22abebc6a72982..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/migrations/0002_create_group_settings_changer.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-from django.contrib.auth import get_permission_codename
-from django.contrib.auth.models import Permission, Group
-from django.contrib.contenttypes.models import ContentType
-from django.db import migrations
-
-
-def migration_code(apps, schema_editor):
-    # inspired by django.contrib.auth.management.create_permissions
-    group, _ = Group.objects.get_or_create(name="LiveSettingEditor")
-
-    for klass in [
-        apps.get_model("live_settings", "LiveSettings"),
-    ]:
-        ct = ContentType.objects.get_for_model(klass)
-        opts = klass._meta
-
-        for action in klass._meta.default_permissions:
-            perm, _ = Permission.objects.get_or_create(
-                codename=get_permission_codename(action, opts),
-                name='Can %s %s' % (action, opts.verbose_name_raw),
-                content_type=ct)
-            group.permissions.add(perm)
-
-
-def reverse_code(apps, schema_editor):
-    Group.objects.filter(name="LiveSettingEditor").delete()
-
-
-class Migration(migrations.Migration):
-    dependencies = [
-        ('live_settings', '0001_initial'),
-    ]
-
-    operations = [
-        migrations.RunPython(migration_code, reverse_code=reverse_code),
-    ]
diff --git a/src/strass/live_settings/migrations/0003_auto_20200311_1037.py b/src/strass/live_settings/migrations/0003_auto_20200311_1037.py
deleted file mode 100644
index 498cafe9b0b7b96e8db499fff04b4b38c307d4a1..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/migrations/0003_auto_20200311_1037.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 2.2.5 on 2020-03-11 09:37
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('live_settings', '0002_create_group_settings_changer'),
-    ]
-
-    operations = [
-        migrations.AlterModelOptions(
-            name='livesettings',
-            options={'verbose_name': 'Live setting', 'verbose_name_plural': 'Live settings'},
-        ),
-    ]
diff --git a/src/strass/live_settings/migrations/0004_alter_livesettings_id.py b/src/strass/live_settings/migrations/0004_alter_livesettings_id.py
deleted file mode 100644
index dc6d6878e2a96de8e0e96a92daa5e5318ea8067b..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/migrations/0004_alter_livesettings_id.py
+++ /dev/null
@@ -1,18 +0,0 @@
-# Generated by Django 3.2.4 on 2021-07-01 09:15
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
-    dependencies = [
-        ('live_settings', '0003_auto_20200311_1037'),
-    ]
-
-    operations = [
-        migrations.AlterField(
-            model_name='livesettings',
-            name='id',
-            field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
-        ),
-    ]
diff --git a/src/strass/live_settings/migrations/__init__.py b/src/strass/live_settings/migrations/__init__.py
deleted file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000
diff --git a/src/strass/live_settings/models.py b/src/strass/live_settings/models.py
deleted file mode 100644
index bec35c8903b6424cb1536c9d0b083f5d1f70fc65..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/models.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from __future__ import unicode_literals
-
-from django.core.cache import cache
-from django.core.validators import RegexValidator
-from django.db import models
-from django.db.models.signals import post_save, post_delete
-from django.dispatch import receiver
-from django.utils import timezone
-from django.utils.safestring import mark_safe
-
-
-class LiveSettings(models.Model):
-    class Meta:
-        verbose_name = "Live setting"
-        verbose_name_plural = "Live settings"
-
-    regex = "^[a-zA-Z][\\w]*$"
-    key = models.CharField(
-        max_length=256,
-        unique=True,
-        validators=(
-            RegexValidator(
-                regex=regex,
-                message=mark_safe(
-                    "LiveSettings key must must be Alphanumeric and start with a letter: " "<code>%s</code>." % regex
-                ),
-                code="invalid_live_settings_key",
-            ),
-        ),
-    )
-    value = models.TextField(
-        blank=True,
-        null=True,
-    )
-    last_edition_date = models.DateTimeField()
-
-    def save(self, *args, **kwargs):
-        self.last_edition_date = timezone.now()
-        super().save(*args, **kwargs)
-
-
-@receiver(post_save, sender=LiveSettings)
-@receiver(post_delete, sender=LiveSettings)
-def flush_live_settings_in_cache(*args, **kwargs):
-    cache.delete("live_settings_dict")
diff --git a/src/strass/live_settings/templates/admin/index.html b/src/strass/live_settings/templates/admin/index.html
deleted file mode 100644
index ff93bb55311fe33270af1e21b89df56c02b7045a..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/templates/admin/index.html
+++ /dev/null
@@ -1,57 +0,0 @@
-{% extends "admin/index.html" %}
-{% load i18n basetheme_bootstrap %}
-
-{% block extrastyle %}
-{{ block.super }}
-<style>
-#content-related{
-    background: none;
-}
-#content-related .module{
-    background: var(--darkened-bg);
-}
-#site-wide-commands form{
-    margin:15px 7px;
-}
-</style>
-{% endblock %}
-
-{% block sidebar %}
-<div id="content-related">
-    {% if perms.live_settings.change_livesettings %}
-    <div class="module" id="site-wide-commands">
-        <h2>{% trans 'Site wide settings' %}</h2>
-        {%include_if_exists "live_settings/sitewidecommands.html" %}
-    </div>
-    {%endif%}
-
-    <div class="module" id="recent-actions-module">
-        <h2>{% trans 'Recent actions' %}</h2>
-        <h3>{% trans 'My actions' %}</h3>
-            {% load log %}
-            {% get_admin_log 10 as admin_log for_user user %}
-            {% if not admin_log %}
-            <p>{% trans 'None available' %}</p>
-            {% else %}
-            <ul class="actionlist">
-            {% for entry in admin_log %}
-            <li class="{% if entry.is_addition %}addlink{% endif %}{% if entry.is_change %}changelink{% endif %}{% if entry.is_deletion %}deletelink{% endif %}">
-                {% if entry.is_deletion or not entry.get_admin_url %}
-                    {{ entry.object_repr }}
-                {% else %}
-                    <a href="{{ entry.get_admin_url }}">{{ entry.object_repr }}</a>
-                {% endif %}
-                <br>
-                {% if entry.content_type %}
-                    <span class="mini quiet">{% filter capfirst %}{{ entry.content_type }}{% endfilter %}</span>
-                {% else %}
-                    <span class="mini quiet">{% trans 'Unknown content' %}</span>
-                {% endif %}
-            </li>
-            {% endfor %}
-            </ul>
-            {% endif %}
-        <hr/>
-    </div>
-</div>
-{% endblock %}
\ No newline at end of file
diff --git a/src/strass/live_settings/tests.py b/src/strass/live_settings/tests.py
deleted file mode 100644
index ade60b313bb01eb822125bf03b5c86a6f9f9be29..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/tests.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import logging
-from unittest import TestCase
-
-from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Permission
-from django.contrib.contenttypes.models import ContentType
-from django.core import management
-from django.core.cache import cache
-from django.test import TestCase as DjangoTestCase
-from django.urls import reverse
-
-from live_settings import live_settings, models
-from live_settings.tools import set_default_live_setting
-
-logger = logging.getLogger(__name__)
-
-
-class LiveSettingsTestCase(DjangoTestCase):
-    def setUp(self) -> None:
-        super().setUp()
-        ################################################################################
-        self.user = get_user_model().objects.create(
-            username="root",
-        )
-
-    def test_get_value(self):
-        self.assertIsNone(live_settings.toto)
-        models.LiveSettings.objects.create(key="toto", value="tata")
-        self.assertIsNotNone(live_settings.toto)
-        self.assertEqual(live_settings.toto, "tata")
-
-    def test_get_set(self):
-        live_settings.tralala = 1
-        self.assertEqual(str(live_settings.tralala), "1")
-        live_settings.tralala = None
-        self.assertIsNone(live_settings.tralala)
-
-    def test_view_works(self):
-        form_data = dict(value="titi", next="/")
-        url = reverse('live_settings:update', args=["toto"])
-
-        response = self.client.post(url, form_data)
-        self.assertEqual(response.status_code, 302)
-        self.assertNotEqual(live_settings.toto, form_data["value"])
-
-        self.client.force_login(self.user)
-
-        response = self.client.post(url, form_data)
-        self.assertEqual(response.status_code, 302)
-        self.assertNotEqual(live_settings.toto, form_data["value"])
-
-        change = Permission.objects.get(
-            content_type=ContentType.objects.get_for_model(models.LiveSettings), codename__startswith="change"
-        )
-        self.user.user_permissions.add(change)
-
-        response = self.client.get(url)
-        self.assertEqual(response.status_code, 403)
-        self.assertNotEqual(live_settings.toto, form_data["value"])
-
-        response = self.client.post(url, dict(value=form_data["value"]))
-        self.assertEqual(response.status_code, 400)
-        self.assertNotEqual(live_settings.toto, form_data["value"])
-
-        response = self.client.post(url, form_data)
-        self.assertEqual(response.status_code, 302)
-        self.assertEqual(live_settings.toto, form_data["value"])
-
-
-class LiveSettingsNoDBTestCase(TestCase):
-    def test_get_value(self):
-        management.call_command("migrate", "live_settings", "zero", no_input=True, skip_checks=True)
-        cache.delete("live_settings_dict")
-        try:
-            ex = live_settings.tralala
-        except Exception as e:
-            ex = e
-        assert isinstance(ex, AttributeError)
-        live_settings.tralala = 1
-        set_default_live_setting("tralala", 2)
diff --git a/src/strass/live_settings/tools.py b/src/strass/live_settings/tools.py
deleted file mode 100644
index dee56ab3fff56a8e1f90d6e191921474e1e7cbac..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/tools.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from django.core.cache import cache
-from django.db import OperationalError, ProgrammingError
-
-
-def set_default_live_setting(key, value):
-    try:
-        from live_settings.models import LiveSettings
-
-        LiveSettings.objects.get_or_create(key=key, defaults=dict(value=str(value)))
-        cache.delete("live_settings_dict")
-    except (ProgrammingError, OperationalError):
-        pass
diff --git a/src/strass/live_settings/urls.py b/src/strass/live_settings/urls.py
deleted file mode 100644
index be3156db944122a5bced9ae97b96e55728540b5e..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/urls.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from django.urls import re_path
-
-from live_settings import views
-
-app_name = "live_settings"
-urlpatterns = [re_path(r"^update/(?P<slug>[a-zA-Z][\w]*)/$", views.change_value, name="update")]
diff --git a/src/strass/live_settings/views.py b/src/strass/live_settings/views.py
deleted file mode 100644
index 2a20f019516b6b8080feb0ff1bbf4241fbe82f68..0000000000000000000000000000000000000000
--- a/src/strass/live_settings/views.py
+++ /dev/null
@@ -1,20 +0,0 @@
-from django.contrib.auth.decorators import permission_required
-from django.http import HttpResponseForbidden, HttpResponseBadRequest
-from django.shortcuts import redirect
-
-from live_settings import forms
-from live_settings import live_settings
-
-
-@permission_required("live_settings.change_livesettings")
-def change_value(request, slug):
-    if request.method != "POST":
-        return HttpResponseForbidden()
-
-    form = forms.LiveSettingsForm(data=request.POST)
-    if not form.is_valid():
-        return HttpResponseBadRequest()
-
-    setattr(live_settings, slug, form.cleaned_data["value"])
-
-    return redirect(form.cleaned_data["next"])
diff --git a/src/strass/requirements.txt b/src/strass/requirements.txt
index 37c17364dda2cc1cafa30ed1b1fb187876e739a4..679ee0a87ab619044e409f016ad5bf7b0f995e45 100644
--- a/src/strass/requirements.txt
+++ b/src/strass/requirements.txt
@@ -11,6 +11,8 @@ django-kubernetes-probes>=1.1
 django-basetheme-bootstrap-ebaii-theme>=1.3.6
 --extra-index-url https://gitlab.pasteur.fr/api/v4/projects/hub%2Fdjango-csp-mail-reports/packages/pypi/simple
 django-csp-mail-reports>=1.0
+--extra-index-url https://gitlab.pasteur.fr/api/v4/projects/hub%2Fdjango-live-settings/packages/pypi/simple
+django-live-settings>=1.0.1
 django-formtools
 csscompressor
 coverage
diff --git a/src/strass/strass_app/locale/en/LC_MESSAGES/django.po b/src/strass/strass_app/locale/en/LC_MESSAGES/django.po
index e0b64836ac523e8b18232f8385e50befbe5872ae..d728cb6481b18e6218fc3002f93d588577164cc1 100644
--- a/src/strass/strass_app/locale/en/LC_MESSAGES/django.po
+++ b/src/strass/strass_app/locale/en/LC_MESSAGES/django.po
@@ -728,7 +728,9 @@ msgid "MassMailingForm.body.placeholder"
 msgstr ""
 "Dear [first_name] [last_name],\n"
 "\n"
-"The email we have for you is [email], if you are a candidate your "
+"The email we have for you is [email].\n"
+"\n"
+"If you are a candidate your "
 "application is accessible at [application_url].\n"
 "\n"
 "If you are a reviewer, your editable reviews are:\n"
@@ -2846,12 +2848,13 @@ msgstr ""
 
 msgid "ComputeAutoReviewView information about"
 msgstr ""
-"When re-running assignation data loss is prevented. Any review that is "
-"either manually assigned or already answered by the author will be kept, i.e "
-"a reviewer will not be unsigned if zhe have started to review.<br/>Re-"
+"Re-"
 "running the assignation is useful when new reviewers arrive or new conflicts "
-"of interest declared, see <a href=\"/doc/configure_instance.html#assign-"
-"reviewers-again\" target=\"_blank\">documentation</a> for more."
+"of interest declared (cf <a href=\"/doc/configure_instance.html#assign-"
+"reviewers-again\" target=\"_blank\">documentation</a> for more).<br/>"
+"Note that data loss is prevented when re-running assignation. Any review that is "
+"either manually assigned or already answered by the author will be kept, i.e "
+"a reviewer will not be unsigned if zhe have started to review."
 
 msgid "MassMailingWizard.Prepare.title"
 msgstr "Preparing the mass mailing"
diff --git a/src/strass/strass_app/locale/fr/LC_MESSAGES/django.po b/src/strass/strass_app/locale/fr/LC_MESSAGES/django.po
index 380375970a200105b90baa86c2743ebf0edfa590..f9fdc50c16836cc985730bc1e9986f552b307618 100644
--- a/src/strass/strass_app/locale/fr/LC_MESSAGES/django.po
+++ b/src/strass/strass_app/locale/fr/LC_MESSAGES/django.po
@@ -779,9 +779,10 @@ msgid "MassMailingForm.body.placeholder"
 msgstr ""
 "Bonjour [prenom] [nom]\n"
 "\n"
-"Votre courriel est [mail]. Si vous êtes un.e candidat.e, vous pouvez "
-"consulter et modifier votre candidature en utilisant le lien suivant visible "
-"à [url_candidature].\n"
+"Votre courriel est [mail].\n"
+"\n"
+"Si vous êtes un.e candidat.e, vous pouvez "
+"consulter et modifier votre candidature en utilisant le lien suivant [url_candidature].\n"
 "\n"
 "Si vous êtes un reviewer, vos reviews editable sont :\n"
 "[liste_reviews_editable]\n"
@@ -798,7 +799,7 @@ msgstr "Reviewers avec des reviews éditables et pas encore finalisées (%i)"
 
 #, python-format
 msgid "Reviewers with no associated profile (%i)"
-msgstr "Reviewers qui n'ont pas indiqués leur profiles (%i)"
+msgstr "Reviewers qui n'ont pas indiqués leur profiles évaluables (%i)"
 
 #, python-format
 msgid "Pending reviewers (%i)"
@@ -2975,13 +2976,14 @@ msgstr ""
 
 msgid "ComputeAutoReviewView information about"
 msgstr ""
+"Relancer l'assignation est utile en cas de "
+"nouveau reviewer, ou de nouveau conflit d'intérêt (cf <a href=\"/doc/"
+"configure_instance.html#assign-reviewers-again\" "
+"target=\"_blank\">documentation</a>).<br/>"
 "Vous ne pouvez pas perdre de donnée en relançant l'assignation. Toutes les "
 "reviews attribuées manuellement ou déjà commencées seront conservées. En "
 "d'autre terme un(e) reviewer ne sera pas dé-assigné d'un candidat si iel à "
-"commencé à écrire la review.<br/>Relancer l'assignation est utile en cas de "
-"nouveau reviewer, ou de nouveau conflit d'intérêt, voir la <a href=\"/doc/"
-"configure_instance.html#assign-reviewers-again\" "
-"target=\"_blank\">documentation</a>."
+"commencé à écrire la review."
 
 msgid "MassMailingWizard.Prepare.title"
 msgstr "Préparer le message"