From 45e05673ef8d9726d35a6498036766e11a117d4d Mon Sep 17 00:00:00 2001
From: Bryan Brancotte <bryan.brancotte@pasteur.fr>
Date: Fri, 28 Feb 2025 09:49:28 +0100
Subject: [PATCH 1/2] Add a group in mass mailing to focus only on reviewer
 without profile, closes #187

---
 src/strass/strass_app/forms.py                |  5 ++
 .../locale/en/LC_MESSAGES/django.po           |  6 +-
 .../locale/fr/LC_MESSAGES/django.po           |  6 +-
 .../strass_app/tests/test_mass_mailing.py     | 68 +++++++++++++++++++
 4 files changed, 83 insertions(+), 2 deletions(-)

diff --git a/src/strass/strass_app/forms.py b/src/strass/strass_app/forms.py
index b0817194..1a0436e4 100644
--- a/src/strass/strass_app/forms.py
+++ b/src/strass/strass_app/forms.py
@@ -1631,6 +1631,7 @@ class MassMailingForm(forms.Form):
         for key, text in [
             ('reviewers', _("Reviewers (%i)")),
             ('reviewers_with_review_todo', _("Reviewers with editable reviews not completed (%i)")),
+            ('reviewers_without_profile', _("Reviewers with no associated profile (%i)")),
             ('pending_reviewers', _("Pending reviewers (%i)")),
             ('jurors', _("Jurors (%i)")),
             ('pending_jurors', _("Pending jurors (%i)")),
@@ -1671,6 +1672,10 @@ class MassMailingForm(forms.Form):
                 .filter(is_open_for_edition=True, is_completed=False)
                 .values_list('reviewer__pk', flat=True)
             )
+        if recipients == "reviewers_without_profile":
+            return business_logic.get_reviewers().exclude(
+                pk__in=business_logic.get_reviewers().filter(user_preferences__profiles__pk__isnull=False)
+            )
         if recipients == "jurors":
             return business_logic.get_jurors()
         if recipients == "pending_jurors":
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 6798b567..63b137f6 100644
--- a/src/strass/strass_app/locale/en/LC_MESSAGES/django.po
+++ b/src/strass/strass_app/locale/en/LC_MESSAGES/django.po
@@ -10,7 +10,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-12 18:54+0100\n"
+"POT-Creation-Date: 2025-02-28 09:44+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -744,6 +744,10 @@ msgstr ""
 msgid "Reviewers with editable reviews not completed (%i)"
 msgstr ""
 
+#, python-format
+msgid "Reviewers with no associated profile (%i)"
+msgstr "Reviewers that haven't indicated reviewable profiles (%i)"
+
 #, python-format
 msgid "Pending reviewers (%i)"
 msgstr ""
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 f88d736d..5828bdc6 100644
--- a/src/strass/strass_app/locale/fr/LC_MESSAGES/django.po
+++ b/src/strass/strass_app/locale/fr/LC_MESSAGES/django.po
@@ -10,7 +10,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-12 18:54+0100\n"
+"POT-Creation-Date: 2025-02-28 09:47+0100\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -796,6 +796,10 @@ msgstr "Reviewers (%i)"
 msgid "Reviewers with editable reviews not completed (%i)"
 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)"
+
 #, python-format
 msgid "Pending reviewers (%i)"
 msgstr "Reviewers potentiels (%i)"
diff --git a/src/strass/strass_app/tests/test_mass_mailing.py b/src/strass/strass_app/tests/test_mass_mailing.py
index f3261cb0..9d0ffe99 100644
--- a/src/strass/strass_app/tests/test_mass_mailing.py
+++ b/src/strass/strass_app/tests/test_mass_mailing.py
@@ -532,6 +532,74 @@ class TestViews2(BaseTestCase):
                     self.assertIn(f'http://testserver{reverse("strass:review-detail", args=[pk])}', m.body)
 
 
+@override_settings(
+    MEDIA_ROOT=os.path.join(
+        settings.BASE_DIR,
+        'persistent_volume',
+        f'{MEDIA_DIR}_3',
+    ),
+    STORAGE_ROOT=os.path.join(
+        settings.BASE_DIR,
+        'persistent_volume',
+        '.storage_for_test_3',
+    ),
+)
+class TestViews3(BaseTestCase):
+    def test_reviewers_without_profils(self):
+        steps = list()
+        live_settings.show_email_as_message = False
+        mail_count = len(mail.outbox)
+        self.client.force_login(self.jury_manager)
+        business_logic.get_reviewers().first().profiles.clear()
+        business_logic.get_reviewers().last().profiles.clear()
+        self.assertTrue(models.Profile.objects.first().myuserpreferences_set.all().exists())
+        self.assertFalse(business_logic.get_reviewers().first().profiles.exists())
+
+        #######################################################################
+        # Use user group, also send a test email
+        #######################################################################
+        url = reverse('strass:mass-mailing')
+        response_home = self.client.get(url, follow=True)
+        target = response_home.redirect_chain[0][0]
+        step_name = target.split("/")[-2]
+        form_data = {
+            step_name + "-sender": self.jury_manager.pk,
+            step_name + "-recipients": "reviewers_without_profile",
+            step_name + "-subject": "mail subject",
+            step_name + "-body": "hi [first_name] x [last_name] !",
+            "mass_mailing_wizard-current_step": step_name,
+        }
+        response = self.client.post(target, form_data, follow=True)
+        self.assertEqual(response.status_code, 200)
+        steps.append(WizardStep(target=target, response=response, form_data=form_data, step_name=step_name))
+        self.write_in_tmp_file(response)
+        del target, response, form_data, step_name
+
+        target = steps[-1].response.redirect_chain[0][0]
+        step_name = target.split("/")[-2]
+        form_data = {
+            step_name + "-agree": 'on',
+            "mass_mailing_wizard-current_step": step_name,
+        }
+        response = self.client.post(target, form_data, follow=True)
+        self.assertEqual(response.status_code, 200)
+        steps.append(WizardStep(target=target, response=response, form_data=form_data, step_name=step_name))
+        del target, response, form_data, step_name
+
+        recipients = set()
+        for m in mail.outbox[mail_count:]:
+            recipients |= set(m.recipients())
+
+        self.assertEqual(
+            recipients,
+            {
+                business_logic.get_reviewers().get(pk=1).email,
+                business_logic.get_reviewers().first().email,
+                business_logic.get_reviewers().last().email,
+            },
+        )
+
+
 class _TestViews(BaseTestCase):
     def _test_with_reviewers(self):
         steps = list()
-- 
GitLab


From da32e7c6400b6997b69972d17d6681b526d9aecd Mon Sep 17 00:00:00 2001
From: Bryan Brancotte <bryan.brancotte@pasteur.fr>
Date: Fri, 28 Feb 2025 10:05:16 +0100
Subject: [PATCH 2/2] Document reviewers_without_profile

---
 doc/configure_instance.rst | 6 ++++++
 doc/mass_mailing.rst       | 3 +++
 2 files changed, 9 insertions(+)

diff --git a/doc/configure_instance.rst b/doc/configure_instance.rst
index 5c52e5de..1fbcba1a 100644
--- a/doc/configure_instance.rst
+++ b/doc/configure_instance.rst
@@ -358,6 +358,12 @@ Reviewers can express their conflicts of interest
 Reviewers can indicate that they have conflict of interest when reviewing a candidate.
 They can also indicate that they don't want to review a candidate. To do se the application have to be configured so they see the candidate list in https://strass-master.dev.pasteur.cloud/setup/#review-settings .
 
+Reminding reviewers to indicate their profiles
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Reviewers should have already indicated which profile they accepte to review.
+If some have forgotten it, you can use the :ref:`Mass Mailing` to automatically select this subset of reviewers and remind them to do it.
+
 Assign reviewers
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 Link: https://strass-master.dev.pasteur.cloud/setup/review/overview/
diff --git a/doc/mass_mailing.rst b/doc/mass_mailing.rst
index ea7a1a55..782da9a4 100644
--- a/doc/mass_mailing.rst
+++ b/doc/mass_mailing.rst
@@ -44,6 +44,9 @@ There are multiple recipients group, some such as ``Reviewers`` are explicit eno
 **Reviewers with editable reviews not completed**:
 The reviewers that are associated to a review that is both empty and in a review stage that still allows to provide a review.
 
+**Reviewers that haven't indicated reviewable profiles**:
+The reviewers that haven't specified the candidate's profiles they are willing to review.
+
 **Pending reviewer**:
 Users that have accepted to be reviewer, but have not been yet confirmed as reviewer, or refused.
 
-- 
GitLab