From 3e3feb881565e0719b9e32d03abab3793807331d Mon Sep 17 00:00:00 2001
From: George Katsikas <giorgakis.katsikas@gmail.com>
Date: Tue, 24 Sep 2024 11:26:12 +0200
Subject: [PATCH] refactor refereeing invitations model

fixes #305
---
 scipost_django/profiles/forms.py              |  2 +-
 scipost_django/profiles/models.py             |  9 +-
 .../templates/profiles/_profile_card.html     |  2 +-
 .../profiles/_profile_card_narrow.html        |  4 +-
 .../templates/profiles/profile_list.html      | 13 ---
 scipost_django/profiles/utils.py              |  2 +-
 scipost_django/profiles/views.py              | 16 +--
 .../scipost/personal_page/_hx_refereeing.html |  6 +-
 scipost_django/submissions/admin.py           |  6 +-
 .../factories/referee_invitation.py           |  9 +-
 scipost_django/submissions/forms/__init__.py  | 10 +-
 .../commands/anonymize_reports_long_term.py   |  9 +-
 .../commands/restore_reports_long_term.py     | 11 +--
 .../commands/send_refereeing_reminders.py     | 98 +++++++++----------
 ...e_refereeinvitation_first_name_and_more.py | 35 +++++++
 .../submissions/models/referee_invitation.py  | 52 +++++-----
 scipost_django/submissions/models/report.py   | 18 +---
 .../_refereeing_invitations_ongoing.html      |  8 +-
 .../pool/_referee_invitations.html            | 20 ++--
 .../pool/_submission_cycle_choice_form.html   | 79 +++++++--------
 .../templatetags/submissions_extras.py        |  4 +-
 .../submissions/tests/test_factories.py       |  3 -
 scipost_django/submissions/utils.py           | 78 +++++----------
 scipost_django/submissions/views/__init__.py  | 51 +++++-----
 .../templates/email/eic/referee_response.html |  5 +-
 .../email/eic/referee_unresponsive.html       |  4 +-
 .../confirmation_invitation_response.html     |  2 +-
 .../inform_referee_manuscript_withdrawn.html  |  2 +-
 .../invite_contributor_to_referee.html        |  4 +-
 ...vite_contributor_to_referee_reminder1.html |  4 +-
 ...vite_contributor_to_referee_reminder2.html |  4 +-
 .../invite_unregistered_to_referee.html       |  4 +-
 ...ite_unregistered_to_referee_reminder1.html |  4 +-
 ...ite_unregistered_to_referee_reminder2.html |  4 +-
 .../reinvite_contributor_to_referee.html      |  4 +-
 .../remind_referee_deadline_1week.html        |  2 +-
 36 files changed, 274 insertions(+), 314 deletions(-)
 create mode 100644 scipost_django/submissions/migrations/0161_remove_refereeinvitation_first_name_and_more.py

diff --git a/scipost_django/profiles/forms.py b/scipost_django/profiles/forms.py
index 23d4886d2..4fd0936aa 100644
--- a/scipost_django/profiles/forms.py
+++ b/scipost_django/profiles/forms.py
@@ -226,7 +226,7 @@ class ProfileMergeForm(forms.Form):
         profile_old.publicationauthorstable_set.all().update(profile=profile)
 
         # Move all invitations to the "new" profile
-        profile_old.refereeinvitation_set.all().update(
+        profile_old.referee_invitations.all().update(
             profile=profile,
             referee=getattr(profile, "contributor", None)
             or getattr(profile_old, "contributor", None),
diff --git a/scipost_django/profiles/models.py b/scipost_django/profiles/models.py
index fb4bc5fae..b240ea54f 100644
--- a/scipost_django/profiles/models.py
+++ b/scipost_django/profiles/models.py
@@ -32,6 +32,10 @@ from .constants import (
 )
 from .managers import ProfileManager, ProfileQuerySet, AffiliationQuerySet
 
+if TYPE_CHECKING:
+    from django.db.models.manager import RelatedManager
+    from submissions.models.referee_invitation import RefereeInvitation
+
 
 class Profile(models.Model):
     """
@@ -61,6 +65,7 @@ class Profile(models.Model):
     if TYPE_CHECKING:
         id: int
         contributor: Contributor | None
+        referee_invitations: "RelatedManager[RefereeInvitation]"
 
     title = models.CharField(max_length=4, choices=TITLE_CHOICES, default=TITLE_DR)
     first_name = models.CharField(max_length=64)
@@ -190,9 +195,7 @@ class Profile(models.Model):
     @property
     def responsiveness_as_referee(self):
         """Simple stats on last 5 years' responsiveness as a referee."""
-        invitations = self.refereeinvitation_set.all()
-        if self.contributor:
-            invitations = invitations | self.contributor.referee_invitations.all()
+        invitations = self.referee_invitations.all()
 
         invitations = invitations.distinct().filter(
             date_invited__gt=timezone.now() - datetime.timedelta(days=1825)
diff --git a/scipost_django/profiles/templates/profiles/_profile_card.html b/scipost_django/profiles/templates/profiles/_profile_card.html
index 679d55925..bd7e4a084 100644
--- a/scipost_django/profiles/templates/profiles/_profile_card.html
+++ b/scipost_django/profiles/templates/profiles/_profile_card.html
@@ -194,7 +194,7 @@
 	</div>
 	<div class="card-body">
 	  <ul>
-	    {% for inv in profile.refereeinvitation_set.all %}
+	    {% for inv in profile.referee_invitations.all %}
 	      <li>{{ inv.submission.title }}<br/>(invited {{ inv.date_invited }}; fulfilled: {% if inv.fulfilled %}<span class="text-success">{% include 'bi/check-square-fill.html' %}</span>{% else %}{% include 'bi/x-circle-fill.html' %}{% endif %})</li>
 	    {% empty %}
 	      <li>No refereeing invitation found</li>
diff --git a/scipost_django/profiles/templates/profiles/_profile_card_narrow.html b/scipost_django/profiles/templates/profiles/_profile_card_narrow.html
index c3e0f980c..eea5ff8fc 100644
--- a/scipost_django/profiles/templates/profiles/_profile_card_narrow.html
+++ b/scipost_django/profiles/templates/profiles/_profile_card_narrow.html
@@ -131,7 +131,7 @@
   </tr>
   <tr>
     <td>Refereeing invitations</td>
-    <td>{{ profile.refereeinvitation_set.count }}</td>
+    <td>{{ profile.referee_invitations.count }}</td>
   </tr>
 
 </table>
@@ -188,7 +188,7 @@
 	</div>
 	<div class="card-body">
 	  <ul>
-	    {% for inv in profile.refereeinvitation_set.all %}
+	    {% for inv in profile.referee_invitations.all %}
 	      <li>{{ inv.submission.title }}<br />(invited {{ inv.date_invited }}; fulfilled: {% if inv.fulfilled %}<span class="text-success">{% include 'bi/check-square-fill.html' %}</span>{% else %}{% include 'bi/x-circle-fill.html' %}{% endif %})</li>
 	    {% empty %}
 	      <li>No refereeing invitation found</li>
diff --git a/scipost_django/profiles/templates/profiles/profile_list.html b/scipost_django/profiles/templates/profiles/profile_list.html
index 8dd4c92a3..e93e4409e 100644
--- a/scipost_django/profiles/templates/profiles/profile_list.html
+++ b/scipost_django/profiles/templates/profiles/profile_list.html
@@ -102,19 +102,6 @@
  
                     {% endif %}
  
-                    {% if next_refinv_wo_profile %}
- 
-                        <li>
-                            <span class="text-warning">{% include 'bi/exclamation-circle-fill.html' %}</span> Create a Profile for <a href="{% url 'profiles:profile_create' from_type='refereeinvitation' pk=next_refinv_wo_profile.id %}">the next</a> Referee Invitation without one ({{ nr_refinv_wo_profile }} to handle)
-                        </li>
- 
-                    {% else %}
- 
-                        <li>
-                            <span class="text-success">{% include 'bi/check-circle-fill.html' %}</span> All Referee Invitations have a Profile
-                        </li>
- 
-                    {% endif %}
 	
                 {% endif %}
 	
diff --git a/scipost_django/profiles/utils.py b/scipost_django/profiles/utils.py
index be67d0fad..9002355a0 100644
--- a/scipost_django/profiles/utils.py
+++ b/scipost_django/profiles/utils.py
@@ -16,7 +16,7 @@ def resolve_profile(view):
             referee_invitation = RefereeInvitation.objects.filter(
                 invitation_key=invitation_key
             ).first()
-            profile = referee_invitation.profile if referee_invitation else None
+            profile = referee_invitation.referee if referee_invitation else None
         else:
             profile = None
 
diff --git a/scipost_django/profiles/views.py b/scipost_django/profiles/views.py
index d9430bfd9..75731d27a 100644
--- a/scipost_django/profiles/views.py
+++ b/scipost_django/profiles/views.py
@@ -124,14 +124,16 @@ class ProfileCreateView(PermissionsMixin, CreateView):
         initial = super().get_initial()
         from_type = self.kwargs.get("from_type", None)
         pk = self.kwargs.get("pk", None)
-        
+
         # Build initial data from GET parameters of the name.
         first_name = self.request.GET.get("first_name", None)
         last_name = self.request.GET.get("last_name", None)
-        
-        if first_name: initial.update({"first_name": first_name})
-        if last_name: initial.update({"last_name": last_name})
-        
+
+        if first_name:
+            initial.update({"first_name": first_name})
+        if last_name:
+            initial.update({"last_name": last_name})
+
         if pk and from_type:
             pk = int(pk)
             if from_type == "contributor":
@@ -301,7 +303,7 @@ class ProfileListView(PermissionsMixin, PaginationMixin, ListView):
             profile__isnull=True
         )
         nr_potential_duplicate_profiles = Profile.objects.potential_duplicates().count()
-        refinv_wo_profile = RefereeInvitation.objects.filter(profile__isnull=True)
+
         reginv_wo_profile = RegistrationInvitation.objects.filter(profile__isnull=True)
 
         academic_fields = (
@@ -322,8 +324,6 @@ class ProfileListView(PermissionsMixin, PaginationMixin, ListView):
                 "nr_contributors_wo_profile": contributors_wo_profile.count(),
                 "nr_potential_duplicate_profiles": nr_potential_duplicate_profiles,
                 "next_contributor_wo_profile": contributors_wo_profile.first(),
-                "nr_refinv_wo_profile": refinv_wo_profile.count(),
-                "next_refinv_wo_profile": refinv_wo_profile.first(),
                 "nr_reginv_wo_profile": reginv_wo_profile.count(),
                 "next_reginv_wo_profile": reginv_wo_profile.first(),
             }
diff --git a/scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html b/scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html
index b1c9f5628..c60692b45 100644
--- a/scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html
+++ b/scipost_django/scipost/templates/scipost/personal_page/_hx_refereeing.html
@@ -5,11 +5,11 @@
 <div class="row">
   <div class="col-12">
     <h3 class="highlight">Refereeing Invitations</h3>
-    {% if contributor.referee_invitations.all %}
+    {% if contributor.profile.referee_invitations.all %}
       <details class="p-2">
-	<summary>See all your refereeing invitations ({{ contributor.referee_invitations.all|length }})</summary>
+	<summary>See all your refereeing invitations ({{ contributor.profile.referee_invitations.all|length }})</summary>
 	<ul class="list-group list-group-flush ms-md-4">
-          {% for invitation in contributor.referee_invitations.all %}
+          {% for invitation in contributor.profile.referee_invitations.all %}
             <li class="list-group-item py-2">
               {% include 'submissions/_submission_li.html' with submission=invitation.submission %}
               <table>
diff --git a/scipost_django/submissions/admin.py b/scipost_django/submissions/admin.py
index 28c2fb09c..b9cc20fbb 100644
--- a/scipost_django/submissions/admin.py
+++ b/scipost_django/submissions/admin.py
@@ -314,9 +314,8 @@ class RefereeInvitationAdmin(admin.ModelAdmin):
         "submission__title",
         "submission__author_list",
         "submission__preprint__identifier_w_vn_nr",
-        "referee__user__last_name",
-        "first_name",
-        "last_name",
+        "referee__first_name",
+        "referee__last_name",
         "email_address",
     ]
     list_display = ("__str__", "accepted", "fulfilled", "cancelled")
@@ -327,7 +326,6 @@ class RefereeInvitationAdmin(admin.ModelAdmin):
     )
     date_hierarchy = "date_invited"
     autocomplete_fields = [
-        "profile",
         "submission",
         "referee",
         "invited_by",
diff --git a/scipost_django/submissions/factories/referee_invitation.py b/scipost_django/submissions/factories/referee_invitation.py
index 3d19a7b88..bb8c10440 100644
--- a/scipost_django/submissions/factories/referee_invitation.py
+++ b/scipost_django/submissions/factories/referee_invitation.py
@@ -22,10 +22,9 @@ class RefereeInvitationFactory(factory.django.DjangoModelFactory):
             profile_info=factory.SelfAttribute("referee.profile"),
         )
 
-    referee = None
-    profile = factory.SubFactory("profiles.factories.ProfileFactory")
+    referee = factory.SubFactory("profiles.factories.ProfileFactory")
 
-    profile_info = factory.SelfAttribute("profile")
+    profile_info = factory.SelfAttribute("referee")
     title = factory.SelfAttribute("profile_info.title")
     first_name = factory.SelfAttribute("profile_info.first_name")
     last_name = factory.SelfAttribute("profile_info.last_name")
@@ -55,7 +54,9 @@ class AcceptedRefereeInvitationFactory(RefereeInvitationFactory):
         if create:
             from submissions.factories import VettedReportFactory
 
-            VettedReportFactory(submission=self.submission, author=self.referee)
+            VettedReportFactory(
+                submission=self.submission, author=self.referee.contributor
+            )
 
 
 class FulfilledRefereeInvitationFactory(AcceptedRefereeInvitationFactory):
diff --git a/scipost_django/submissions/forms/__init__.py b/scipost_django/submissions/forms/__init__.py
index a7d9cdd59..29f4a329d 100644
--- a/scipost_django/submissions/forms/__init__.py
+++ b/scipost_django/submissions/forms/__init__.py
@@ -2637,7 +2637,7 @@ class InviteRefereeSearchFrom(forms.Form):
             .annotate(
                 has_accepted_previous_invitation=Exists(
                     RefereeInvitation.objects.filter(
-                        profile=OuterRef("id"),
+                        referee=OuterRef("id"),
                         submission__thread_hash=self.submission.thread_hash,
                         accepted=True,
                     ).exclude(submission=self.submission)
@@ -2646,7 +2646,7 @@ class InviteRefereeSearchFrom(forms.Form):
             .annotate(
                 already_invited=Exists(
                     RefereeInvitation.objects.filter(
-                        profile=OuterRef("id"),
+                        referee=OuterRef("id"),
                         submission=self.submission,
                         cancelled=False,
                     )
@@ -2851,7 +2851,7 @@ class ConsiderRefereeInvitationForm(forms.Form):
         super().save()
 
         self.invitation.submission.add_event_for_eic(
-            f"Referee {self.invitation.profile} set intended report delivery date to {self.invitation.intended_delivery_date}."
+            f"Referee {self.invitation.referee.full_name} set intended report delivery date to {self.invitation.intended_delivery_date}."
         )
         self.invitation.submission.add_event_for_author(
             f"A referee has set their intended report delivery date to {self.invitation.intended_delivery_date}."
@@ -2892,7 +2892,7 @@ class ReportIntendedDeliveryForm(forms.ModelForm):
         super().save()
 
         self.instance.submission.add_event_for_eic(
-            f"Referee {self.instance.profile} set intended report delivery date to {self.instance.intended_delivery_date}."
+            f"Referee {self.referee.full_name} set intended report delivery date to {self.instance.intended_delivery_date}."
         )
         self.instance.submission.add_event_for_author(
             f"A referee has set their intended report delivery date to {self.instance.intended_delivery_date}."
@@ -3097,7 +3097,7 @@ class ReportForm(forms.ModelForm):
 
             # Update invitation and report meta data if exist
             invitations = self.submission.referee_invitations.filter(
-                referee=report.author
+                referee=report.author.profile
             )
             updated_invitations = invitations.update(fulfilled=True)
             invitations.filter(accepted=None).update(
diff --git a/scipost_django/submissions/management/commands/anonymize_reports_long_term.py b/scipost_django/submissions/management/commands/anonymize_reports_long_term.py
index f199b3559..f695e6662 100644
--- a/scipost_django/submissions/management/commands/anonymize_reports_long_term.py
+++ b/scipost_django/submissions/management/commands/anonymize_reports_long_term.py
@@ -123,14 +123,7 @@ class Command(BaseCommand):
         Report.objects.bulk_update(reports, ["author"])
         RefereeInvitation.objects.bulk_update(
             invitations,
-            [
-                "referee",
-                "profile",
-                "title",
-                "first_name",
-                "last_name",
-                "email_address",
-            ],
+            ["referee", "email_address"],
         )
 
         self.stdout.write(
diff --git a/scipost_django/submissions/management/commands/restore_reports_long_term.py b/scipost_django/submissions/management/commands/restore_reports_long_term.py
index 1a938ceae..6262ced66 100644
--- a/scipost_django/submissions/management/commands/restore_reports_long_term.py
+++ b/scipost_django/submissions/management/commands/restore_reports_long_term.py
@@ -69,7 +69,7 @@ class Command(BaseCommand):
                     f"Loaded {updated} AnonymizedReportContributors from {kwargs['backup_file']}."
                 )
             )
-            return
+
         else:
             reports_to_restore = [arc.report for arc in ARCs]
 
@@ -91,14 +91,7 @@ class Command(BaseCommand):
             Report.objects.bulk_update(reports, ["author"])
             RefereeInvitation.objects.bulk_update(
                 invitations,
-                [
-                    "referee",
-                    "profile",
-                    "title",
-                    "first_name",
-                    "last_name",
-                    "email_address",
-                ],
+                ["referee", "email_address"],
             )
 
             self.stdout.write(
diff --git a/scipost_django/submissions/management/commands/send_refereeing_reminders.py b/scipost_django/submissions/management/commands/send_refereeing_reminders.py
index 2d02d8943..557d00675 100644
--- a/scipost_django/submissions/management/commands/send_refereeing_reminders.py
+++ b/scipost_django/submissions/management/commands/send_refereeing_reminders.py
@@ -10,69 +10,69 @@ from mails.utils import DirectMailUtil
 
 from ...models import Submission
 
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from ...models import RefereeInvitation
+
 
 class Command(BaseCommand):
     help = "Sends all email reminders needed for Submissions undergoing refereeing"
 
     def handle(self, *args, **options):
         for submission in Submission.objects.open_for_reporting():
-            # Send reminders to referees who have not responded:
-            for (
-                invitation
-            ) in (
-                submission.referee_invitations.awaiting_response().auto_reminders_allowed()
-            ):
-                # 2 days after ref invite sent out: first auto reminder
-                if workdays_between(invitation.date_invited, timezone.now()) == 2:
-                    if invitation.referee:
-                        mail_sender = DirectMailUtil(
-                            "referees/invite_contributor_to_referee_reminder1",
-                            invitation=invitation,
-                        )
-                        mail_sender.send_mail()
-                    else:
-                        mail_sender = DirectMailUtil(
-                            "referees/invite_unregistered_to_referee_reminder1",
+
+            invitations_w_auto_reminders = (
+                submission.referee_invitations.auto_reminders_allowed()
+            )
+            invitation: "RefereeInvitation"
+
+            # Send automatic reminders to referees who have not responded
+            for invitation in invitations_w_auto_reminders.awaiting_response():
+                referee_registration_status = (
+                    "contributor"
+                    if invitation.to_registered_referee
+                    else "unregistered"
+                )
+                workdays_since_last_reminder = workdays_between(
+                    invitation.date_last_reminded, timezone.now()
+                )
+
+                # Send the appropriate reminder email based on the number of days since the last reminder
+                mail = None
+                match workdays_since_last_reminder:
+                    case 2:
+                        # First reminder according to the referee registration status
+                        mail = DirectMailUtil(
+                            f"referees/invite_{referee_registration_status}_to_referee_reminder1",
                             invitation=invitation,
                         )
-                        mail_sender.send_mail()
-                    invitation.nr_reminders += 1
-                    invitation.date_last_reminded = timezone.now()
-                    invitation.save()
-                # second (and final) reminder after 4 days
-                elif workdays_between(invitation.date_invited, timezone.now()) == 4:
-                    if invitation.referee:
-                        mail_sender = DirectMailUtil(
-                            "referees/invite_contributor_to_referee_reminder2",
+                        invitation.reminder_sent()
+                    case 4:
+                        # Second reminder according to the referee registration status
+                        mail = DirectMailUtil(
+                            f"referees/invite_{referee_registration_status}_to_referee_reminder2",
                             invitation=invitation,
                         )
-                        mail_sender.send_mail()
-                    else:
-                        mail_sender = DirectMailUtil(
-                            "referees/invite_unregistered_to_referee_reminder2",
-                            invitation=invitation,
+                        invitation.reminder_sent()
+                    case 6:
+                        # EIC is automatically emailed with the suggestion of removing and replacing this referee
+                        mail = DirectMailUtil(
+                            "eic/referee_unresponsive", invitation=invitation
                         )
-                        mail_sender.send_mail()
-                    invitation.nr_reminders += 1
-                    invitation.date_last_reminded = timezone.now()
-                    invitation.save()
-                # after 6 days of no response, EIC is automatically emailed
-                # with the suggestion of removing and replacing this referee
-                elif workdays_between(invitation.date_invited, timezone.now()) == 6:
-                    mail_sender = DirectMailUtil(
-                        "eic/referee_unresponsive", invitation=invitation
-                    )
-                    mail_sender.send_mail()
-            # one week before refereeing deadline: auto email reminder to ref
+
+                if mail is not None:
+                    mail.send_mail()
+
+            # Send automatic reminder that the deadline is approaching (less than one week left)
+            workdays_until_deadline = workdays_between(
+                timezone.now(), submission.reporting_deadline
+            )
             if (
                 submission.reporting_deadline is not None
-                and workdays_between(timezone.now(), submission.reporting_deadline) == 5
+                and workdays_until_deadline == 5
             ):
-                for (
-                    invitation
-                ) in (
-                    submission.referee_invitations.in_process().auto_reminders_allowed()
-                ):
+                for invitation in invitations_w_auto_reminders.in_process():
                     mail_sender = DirectMailUtil(
                         "referees/remind_referee_deadline_1week", invitation=invitation
                     )
diff --git a/scipost_django/submissions/migrations/0161_remove_refereeinvitation_first_name_and_more.py b/scipost_django/submissions/migrations/0161_remove_refereeinvitation_first_name_and_more.py
new file mode 100644
index 000000000..58421469c
--- /dev/null
+++ b/scipost_django/submissions/migrations/0161_remove_refereeinvitation_first_name_and_more.py
@@ -0,0 +1,35 @@
+# Generated by Django 4.2.15 on 2024-09-20 13:17
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("profiles", "0043_alter_profile_first_name_original_and_more"),
+        ("submissions", "0160_refereeinvitation_intended_delivery_date"),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name="refereeinvitation",
+            name="title",
+        ),
+        migrations.RemoveField(
+            model_name="refereeinvitation",
+            name="referee",
+        ),
+        migrations.RemoveField(
+            model_name="refereeinvitation",
+            name="first_name",
+        ),
+        migrations.RemoveField(
+            model_name="refereeinvitation",
+            name="last_name",
+        ),
+        migrations.RenameField(
+            model_name="refereeinvitation",
+            old_name="profile",
+            new_name="referee",
+        ),
+    ]
diff --git a/scipost_django/submissions/models/referee_invitation.py b/scipost_django/submissions/models/referee_invitation.py
index 8dab0e58b..53356aa7e 100644
--- a/scipost_django/submissions/models/referee_invitation.py
+++ b/scipost_django/submissions/models/referee_invitation.py
@@ -9,7 +9,6 @@ from django.db import models
 from django.urls import reverse
 from django.utils import timezone
 
-from scipost.constants import TITLE_CHOICES
 
 from ..behaviors import SubmissionRelatedObjectMixin
 from ..managers import RefereeInvitationQuerySet
@@ -17,38 +16,29 @@ from ..models import EditorialAssignment
 
 if TYPE_CHECKING:
     from profiles.models import Profile
-    from scipost.models import Contributor
     from submissions.models import Submission
 
 
 class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model):
-    """Invitation to an active professional scientist to referee a Submission.
+    """
+    Invitation to an active professional scientist to referee a Submission.
 
-    A RefereeInvitation represents an invitation to a Contributor
-    or a non-registered scientist to write a Report for a specific Submission.
+    A RefereeInvitation represents an invitation sent to a professional scientist
+    (modeled through their Profile) to write a Report for a specific Submission.
     The instance will register the response to the invitation and
     the current status of the refereeing duty if the invitation has been accepted.
-
     """
 
-    profile = models.ForeignKey["Profile"](
-        "profiles.Profile", on_delete=models.SET_NULL, blank=True, null=True
+    referee = models.ForeignKey["Profile"](
+        "profiles.Profile",
+        on_delete=models.CASCADE,
+        related_name="referee_invitations",
     )
     submission = models.ForeignKey["Submission"](
         "submissions.Submission",
         on_delete=models.CASCADE,
         related_name="referee_invitations",
     )
-    referee = models.ForeignKey["Contributor"](
-        "scipost.Contributor",
-        related_name="referee_invitations",
-        blank=True,
-        null=True,
-        on_delete=models.CASCADE,
-    )
-    title = models.CharField(max_length=4, choices=TITLE_CHOICES)
-    first_name = models.CharField(max_length=30)
-    last_name = models.CharField(max_length=30)
     email_address = models.EmailField()
 
     # if Contributor not found, person is invited to register
@@ -96,7 +86,7 @@ class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model):
     def __str__(self):
         """Summarize the RefereeInvitation's basic information."""
         value = (
-            self.referee_str
+            self.referee.full_name
             + " to referee "
             + self.submission.title[:30]
             + " by "
@@ -113,16 +103,22 @@ class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model):
         return reverse("submissions:accept_or_decline_ref_invitations", args=(self.id,))
 
     @property
-    def referee_str(self):
-        """Return the most up-to-date name of the Referee."""
-        if self.referee:
-            return str(self.referee)
-        return self.last_name + ", " + self.first_name
+    def contributor(self):
+        """The Contributor of the associated Profile, if any."""
+        return getattr(self.referee, "contributor", None)
+
+    @property
+    def to_registered_referee(self):
+        """Check if the invitation is to a registered referee."""
+        return self.referee.has_active_contributor
 
     @property
     def related_report(self):
         """Return the Report that's been created for this invitation."""
-        return self.submission.reports.filter(author=self.referee).last()
+        if self.contributor is None:
+            return None
+
+        return self.submission.reports.filter(author=self.contributor).last()
 
     @property
     def needs_sending(self):
@@ -204,3 +200,9 @@ class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model):
         self.refusal_reason = None
         self.fulfilled = False
         self.cancelled = False
+
+    def reminder_sent(self):
+        """Update the invitation's reminder count and date."""
+        self.nr_reminders += 1
+        self.date_last_reminded = timezone.now()
+        self.save()
diff --git a/scipost_django/submissions/models/report.py b/scipost_django/submissions/models/report.py
index d953a2772..11645e064 100644
--- a/scipost_django/submissions/models/report.py
+++ b/scipost_django/submissions/models/report.py
@@ -352,8 +352,7 @@ class Report(SubmissionRelatedObjectMixin, models.Model):
 
         # Get any referee invitations belonging to the author of this Report (anonymous or not)
         invitations = RefereeInvitation.objects.filter(
-            Q(submission=self.submission)
-            & (Q(referee=self.author) | Q(profile=self.author.profile))
+            submission=self.submission, referee=self.author.profile
         )
 
         arc, created = AnonymizedReportContributor.objects.get_or_create(report=self)
@@ -424,25 +423,14 @@ class Report(SubmissionRelatedObjectMixin, models.Model):
 
         # Update the invitation with the new referee information (anonymous or not)
         for inv in invitations:
-            inv.referee = self.author
-            inv.profile = self.author.profile
-            inv.title = self.author.profile.title if self.author.profile else "MX"
-            inv.first_name = self.author.user.first_name if restore else ""
-            inv.last_name = self.author.user.last_name if restore else ""
+            inv.referee = self.author.profile
 
         if commit:
             arc.save()
             self.save()
             RefereeInvitation.objects.bulk_update(
                 invitations,
-                [
-                    "referee",
-                    "profile",
-                    "title",
-                    "first_name",
-                    "last_name",
-                    "email_address",
-                ],
+                ["referee", "email_address"],
             )
 
         return arc, self, invitations
diff --git a/scipost_django/submissions/templates/submissions/_refereeing_invitations_ongoing.html b/scipost_django/submissions/templates/submissions/_refereeing_invitations_ongoing.html
index cf8ec6ca1..21c4ac518 100644
--- a/scipost_django/submissions/templates/submissions/_refereeing_invitations_ongoing.html
+++ b/scipost_django/submissions/templates/submissions/_refereeing_invitations_ongoing.html
@@ -1,8 +1,8 @@
-{% if contributor.referee_invitations.awaiting_response %}
+{% if contributor.profile.referee_invitations.awaiting_response %}
   <div class="border border-2 border-danger m-2 mb-4">
     <h3 class="highlight mt-0">Refereeing invitations awaiting your response</h3>
     <ul>
-      <li><a href="{% url 'submissions:accept_or_decline_ref_invitations' %}">Accept/decline refereeing invitations</a> ({{ contributor.referee_invitations.awaiting_response|length }})</li>
+      <li><a href="{% url 'submissions:accept_or_decline_ref_invitations' %}">Accept/decline refereeing invitations</a> ({{ contributor.profile.referee_invitations.awaiting_response|length }})</li>
     </ul>
   </div>
 {% endif %}
@@ -22,11 +22,11 @@
   </div>
 {% endif %}
 
-{% if contributor.referee_invitations.in_process.all %}
+{% if contributor.profile.referee_invitations.in_process.all %}
   <div class="border border-2 border-warning m-2 mb-4">
     <h3 class="highlight mt-0">Your pending refereeing invitations</h3>
     <ul class="list-group m-2">
-      {% for invitation in contributor.referee_invitations.in_process.all %}
+      {% for invitation in contributor.profile.referee_invitations.in_process.all %}
         <li class="list-group-item p-2">
           {% include 'submissions/_submission_li.html' with submission=invitation.submission %}
           <table>
diff --git a/scipost_django/submissions/templates/submissions/pool/_referee_invitations.html b/scipost_django/submissions/templates/submissions/pool/_referee_invitations.html
index 444f5ffd1..c1caba1b2 100644
--- a/scipost_django/submissions/templates/submissions/pool/_referee_invitations.html
+++ b/scipost_django/submissions/templates/submissions/pool/_referee_invitations.html
@@ -35,13 +35,13 @@
             <div class="badge bg-danger">overdue</div>
           {% endif %}
         </td>
-        <td class="py-3">{{ invitation.get_title_display }} {{ invitation.first_name }} {{ invitation.last_name }} <br /> <span class="text-muted text-truncate">{{invitation.email_address}}</span> </td>
+        <td class="py-3"><a href="{{ invitation.referee.get_absolute_url }}">{{ invitation.referee.full_name }}</a><div class="text-muted text-truncate">{{invitation.email_address}}</div> </td>
         <td>
 	  {% if not invitation.date_invited %}
       {% if not invitation.cancelled %}
         <span class="text-danger">Invitation email not sent!!</span>
         <br>
-        <a class="btn btn-sm btn-danger text-white" href="{% url 'submissions:invite_referee' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr profile_id=invitation.profile.id profile_email=invitation.email_address auto_reminders_allowed=invitation.auto_reminders_allowed %}">
+        <a class="btn btn-sm btn-danger text-white" href="{% url 'submissions:invite_referee' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr profile_id=invitation.referee.id profile_email=invitation.email_address auto_reminders_allowed=invitation.auto_reminders_allowed %}">
           {% include 'bi/arrow-right.html' %}&nbsp;Resend
         </a>
       {% endif %}
@@ -98,12 +98,12 @@
             {% endif %}
           </td>
           <td>
-            {% if invitation.referee %}
-              <a href="{% url 'submissions:communication' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr comtype='EtoR' referee_id=invitation.referee.id %}">Write a communication</a>
-              {% if invitation.referee.editorial_communications|filter_for_submission:submission %}
+            {% if invitation.contributor %}
+              <a href="{% url 'submissions:communication' identifier_w_vn_nr=submission.preprint.identifier_w_vn_nr comtype='EtoR' referee_id=invitation.contributor.id %}">Write a communication</a>
+              {% if invitation.contributor.editorial_communications|filter_for_submission:submission %}
                 <br>
                 <button type="button" class="btn btn-link p-0" data-bs-toggle="toggle" data-bs-target="#comm-row-{{ invitation.id }}">
-                  <small>Show communication ({{ invitation.referee.editorial_communications|filter_for_submission:submission|length }})</small>
+                  <small>Show communication ({{ invitation.contributor.editorial_communications|filter_for_submission:submission|length }})</small>
                 </button>
               {% endif %}
             {% else %}
@@ -128,19 +128,19 @@
         {% else %}
           <td colspan="3"></td>
           <td colspan="2">
-            {% if invitation.referee %}
+            {% if invitation.contributor %}
               <button type="button" class="btn btn-link p-0" data-bs-toggle="toggle" data-bs-target="#comm-row-{{ invitation.id }}">
-                <small>Show communication ({{ invitation.referee.editorial_communications|filter_for_submission:submission|length }})</small>
+                <small>Show communication ({{ invitation.contributor.editorial_communications|filter_for_submission:submission|length }})</small>
               </button>
             {% endif %}
           </td>
         {% endif %}
       </tr>
-      {% if invitation.referee %}
+      {% if invitation.contributor %}
         <tr style="display: none;" class="pt-1 table-info" id="comm-row-{{ invitation.id }}">
           <td></td>
           <td colspan="8">
-            {% include 'submissions/_communication_thread.html' with communication=invitation.referee.editorial_communications|filter_for_submission:submission css_class='wide' %}
+            {% include 'submissions/_communication_thread.html' with communication=invitation.contributor.editorial_communications|filter_for_submission:submission css_class='wide' %}
             <button type="button" class="btn btn-link p-0 d-inline-block mb-2" data-bs-toggle="toggle" data-bs-target="#comm-row-{{ invitation.id }}"><small>Hide communication</small></button>
           </td>
         </tr>
diff --git a/scipost_django/submissions/templates/submissions/pool/_submission_cycle_choice_form.html b/scipost_django/submissions/templates/submissions/pool/_submission_cycle_choice_form.html
index 327b3abfe..a0715ce5e 100644
--- a/scipost_django/submissions/templates/submissions/pool/_submission_cycle_choice_form.html
+++ b/scipost_django/submissions/templates/submissions/pool/_submission_cycle_choice_form.html
@@ -17,20 +17,18 @@
           <label for="{{ widget.id_for_label }}" class="mb-0">
             {{ widget.tag }}
             {{ widget.choice_label }}
+            <p class="help-block text-muted">
+              {{ widget.help_text|safe }}
+              {% if widget.data.value == 'short' %}
+              Run a speedy refereeing round: two weeks, with option of reinviting previous referees
+              {% elif widget.data.value == 'direct_rec' %}
+              Immediately write an editorial recommendation.
+              {% else %}
+              Run a new full refereeing round: four weeks as usual, can invite previous referees and/or new ones.
+              {% endif %}
+            </p>
           </label>
         </div>
-        <p class="help-block text-muted">
-          {{ widget.help_text|safe }}
-          {% if widget.data.value == 'short' %}
-            Run a speedy refereeing round: two weeks, with option of reinviting previous referees
-          {% elif widget.data.value == 'direct_rec' %}
-            Immediately write an editorial recommendation.
-          {% else %}
-            Run a new full refereeing round: four weeks as usual, can invite previous referees and/or new ones.
-          {% endif %}
-        </p>
-
-
       {% endfor %}
       {% for error in form.refereeing_cycle.errors %}
         <span class="help-block {{ form.error_css_class }}">{{ error }}</span>
@@ -50,40 +48,39 @@
       <label class="col-form-label col-md-2 ">Reinvite referees</label>
       <div class="col-md-10">
         <ul class="list-group list-group-flush">
-          {% for referee in form.referees_reinvite.field.queryset %}
+          {% for invitation in form.referees_reinvite.field.queryset %}
             <li class="list-group-item py-1">
-              <div class="d-flex flex-row">
-                  <label for="{{ form.referees_reinvite.name }}_{{ forloop.counter0 }}" class="mb-0"></label>
-                  <input class="me-2" checked="checked" id="{{ form.referees_reinvite.name }}_{{ forloop.counter0 }}" name="{{ form.referees_reinvite.name }}" type="checkbox" value="{{referee.id}}">
-                  <div class="d-flex flex-column">
-                    <div>{{ referee.referee_str }} <span class="text-muted">({{ referee.email_address }})</span></div>
-                    <div><span class="text-muted">Originally invited on {{ referee.date_invited }}</span></div>
+              <label for="{{ form.referees_reinvite.name }}_{{ forloop.counter0 }}" class="mb-0 d-flex flex-row">
+                <input class="me-2" checked="checked" id="{{ form.referees_reinvite.name }}_{{ forloop.counter0 }}" name="{{ form.referees_reinvite.name }}" type="checkbox" value="{{ invitation.id }}">
+                <div class="d-flex flex-column">
+                  <div><a href="{{ invitation.referee.get_absolute_url }}">{{ invitation.referee.full_name }}</a> <span class="text-muted">({{ invitation.email_address }})</span></div>
+                  <div><span class="text-muted">Originally invited on {{ invitation.date_invited }}</span></div>
+                </div>
+                <div class="ms-auto d-flex flex-column align-items-end">
+                  <div>Response: 
+                    {% if invitation.accepted %}
+                      <span title="Accepted" class="text-success">{% include "bi/check-circle-fill.html" %}</span>
+                    {% elif invitation.cancelled %}
+                      <span title="Cancelled" class="text-black">{% include "bi/circle-fill.html" %}</span>
+                    {% elif invitation.accepted == False %}
+                      <span title="Refused" class="text-danger">{% include "bi/x-circle-fill.html" %}</span>
+                    {% elif not invitation.accepted %}
+                      <span title="Pending" class="text-warning">{% include "bi/question-circle-fill.html" %}</span>
+                    {% endif %}
                   </div>
-                  <div class="ms-auto d-flex flex-column align-items-end">
-                    <div>Response: 
-                      {% if referee.accepted %}
-                        <span title="Accepted" class="text-success">{% include "bi/check-circle-fill.html" %}</span>
-                      {% elif referee.cancelled %}
-                        <span title="Cancelled" class="text-black">{% include "bi/circle-fill.html" %}</span>
-                      {% elif referee.accepted == False %}
-                        <span title="Refused" class="text-danger">{% include "bi/x-circle-fill.html" %}</span>
-                      {% elif not referee.accepted %}
-                        <span title="Pending" class="text-warning">{% include "bi/question-circle-fill.html" %}</span>
+                  {% if invitation.accepted %}
+                    <div>Fulfilled: 
+                      {% if not invitation.fulfilled or invitation.cancelled %}
+                        <span title="Undelivered" class="text-black">{% include "bi/circle-fill.html" %}</span>
+                      {% elif invitation.fulfilled %}
+                        <span title="Fulfilled" class="text-success">{% include "bi/check-circle-fill.html" %}</span>
                       {% endif %}
                     </div>
-                    {% if referee.accepted %}
-                      <div>Fulfilled: 
-                        {% if not referee.fulfilled or referee.cancelled %}
-                          <span title="Undelivered" class="text-black">{% include "bi/circle-fill.html" %}</span>
-                        {% elif referee.fulfilled %}
-                          <span title="Fulfilled" class="text-success">{% include "bi/check-circle-fill.html" %}</span>
-                        {% endif %}
-                      </div>
-                    {% elif referee.accepted == False %}
-                      <span>{{ referee.get_refusal_reason_display }}</span>
-                    {% endif %}
-                  </div>
+                  {% elif invitation.accepted == False %}
+                    <span>{{ invitation.get_refusal_reason_display }}</span>
+                  {% endif %}
                 </div>
+              </label>
             </li>
           {% empty %}
             <li class="list-group-item py-1"><em>No former referees found</em></li>
diff --git a/scipost_django/submissions/templatetags/submissions_extras.py b/scipost_django/submissions/templatetags/submissions_extras.py
index dda71ee57..bf52b52db 100644
--- a/scipost_django/submissions/templatetags/submissions_extras.py
+++ b/scipost_django/submissions/templatetags/submissions_extras.py
@@ -70,7 +70,9 @@ def user_is_referee(submission, user):
     """Check if the User is invited to be Referee of the Submission."""
     if not user.is_authenticated:
         return False
-    return submission.referee_invitations.filter(referee__user=user).exists()
+    return submission.referee_invitations.filter(
+        referee=user.contributor.profile
+    ).exists()
 
 
 @register.filter
diff --git a/scipost_django/submissions/tests/test_factories.py b/scipost_django/submissions/tests/test_factories.py
index 41f3deb4c..eb41fe88e 100644
--- a/scipost_django/submissions/tests/test_factories.py
+++ b/scipost_django/submissions/tests/test_factories.py
@@ -64,14 +64,11 @@ class TestReportFactory(TestCase):
 class TestRefereeInvitationFactory(TestCase):
     def test_can_create_unregistered_referee_invitations(self):
         referee_invitation = RefereeInvitationFactory()
-        self.assertIsNotNone(referee_invitation.profile)
         self.assertIsNone(referee_invitation.referee)
         self.assertIsNotNone(referee_invitation)
 
     def test_can_create_registered_referee_invitations(self):
         referee_invitation = RefereeInvitationFactory(registered=True)
-        self.assertIsNotNone(referee_invitation.referee)
-        self.assertIsNone(referee_invitation.profile)
         self.assertIsNotNone(referee_invitation)
 
 
diff --git a/scipost_django/submissions/utils.py b/scipost_django/submissions/utils.py
index e4ba73869..737a9bcc5 100644
--- a/scipost_django/submissions/utils.py
+++ b/scipost_django/submissions/utils.py
@@ -33,6 +33,7 @@ domain = get_current_domain()
 if TYPE_CHECKING:
     from submissions.models.communication import EditorialCommunication
     from scipost.models import Contributor
+    from submissions.models import RefereeInvitation
 
 
 class SubmissionUtils(BaseMailUtil):
@@ -218,17 +219,9 @@ class SubmissionUtils(BaseMailUtil):
         It is called from the ref_invitation_reminder method in submissions/views.py.
         """
         email_text = (
-            "Dear "
-            + cls.invitation.get_title_display()
-            + " "
-            + cls.invitation.last_name
-            + ",\n\n"
+            "Dear " + cls.invitation.referee.formal_name + ",\n\n"
             "On behalf of the Editor-in-charge "
-            + cls.invitation.submission.editor_in_charge.profile.get_title_display()
-            + " "
-            + cls.invitation.submission.editor_in_charge.user.first_name
-            + " "
-            + cls.invitation.submission.editor_in_charge.user.last_name
+            + cls.invitation.submission.editor_in_charge.profile.formal_name
             + ", we would like to cordially remind you of our recent request to referee\n\n"
             + cls.invitation.submission.title
             + " by "
@@ -236,8 +229,8 @@ class SubmissionUtils(BaseMailUtil):
             + "."
         )
         email_text_html = (
-            "<p>Dear {{ title }} {{ last_name }},</p>"
-            "<p>On behalf of the Editor-in-charge {{ EIC_title }} {{ EIC_first_name }} {{ EIC_last_name }}, "
+            "<p>Dear {{ referee_formal_name }},</p>"
+            "<p>On behalf of the Editor-in-charge {{ EIC_formal_name }}, "
             "we would like to cordially remind you of our recent request to referee</p>"
             "<p>{{ sub_title }}</p>"
             "\n<p>by {{ author_list }}.</p>"
@@ -318,11 +311,8 @@ class SubmissionUtils(BaseMailUtil):
             "<p>The SciPost Team</p>"
         )
         email_context = {
-            "title": cls.invitation.get_title_display(),
-            "last_name": cls.invitation.last_name,
-            "EIC_title": cls.invitation.submission.editor_in_charge.profile.get_title_display(),
-            "EIC_last_name": cls.invitation.submission.editor_in_charge.user.last_name,
-            "EIC_first_name": cls.invitation.submission.editor_in_charge.user.first_name,
+            "referee_formal_name": cls.invitation.referee.formal_name,
+            "EIC_formal_name": cls.invitation.submission.editor_in_charge.profile.formal_name,
             "sub_title": cls.invitation.submission.title,
             "author_list": cls.invitation.submission.author_list,
             "identifier_w_vn_nr": cls.invitation.submission.preprint.identifier_w_vn_nr,
@@ -360,17 +350,9 @@ class SubmissionUtils(BaseMailUtil):
         It is called from the ref_invitation_reminder method in submissions/views.py.
         """
         email_text = (
-            "Dear "
-            + cls.invitation.get_title_display()
-            + " "
-            + cls.invitation.last_name
-            + ",\n\n"
+            "Dear " + cls.invitation.referee.formal_name + ",\n\n"
             "On behalf of the Editor-in-charge "
-            + cls.invitation.submission.editor_in_charge.profile.get_title_display()
-            + " "
-            + cls.invitation.submission.editor_in_charge.user.first_name
-            + " "
-            + cls.invitation.submission.editor_in_charge.user.last_name
+            + cls.invitation.submission.editor_in_charge.profile.formal_name
             + ", we would like to cordially remind you of our recent request to referee\n\n"
             + cls.invitation.submission.title
             + " by "
@@ -378,8 +360,8 @@ class SubmissionUtils(BaseMailUtil):
             + "."
         )
         email_text_html = (
-            "<p>Dear {{ title }} {{ last_name }},</p>"
-            "<p>On behalf of the Editor-in-charge {{ EIC_title }} {{ EIC_first_name }} {{ EIC_last_name }}, "
+            "<p>Dear {{ referee_formal_name }},</p>"
+            "<p>On behalf of the Editor-in-charge {{ EIC_formal_name }}, "
             "we would like to cordially remind you of our recent request to referee</p>"
             "<p>{{ sub_title }}</p>"
             "\n<p>by {{ author_list }}.</p>"
@@ -434,11 +416,8 @@ class SubmissionUtils(BaseMailUtil):
             "<p>The SciPost Team</p>"
         )
         email_context = {
-            "title": cls.invitation.get_title_display(),
-            "last_name": cls.invitation.last_name,
-            "EIC_title": cls.invitation.submission.editor_in_charge.profile.get_title_display(),
-            "EIC_last_name": cls.invitation.submission.editor_in_charge.user.last_name,
-            "EIC_first_name": cls.invitation.submission.editor_in_charge.user.first_name,
+            "referee_formal_name": cls.invitation.referee.formal_name,
+            "EIC_formal_name": cls.invitation.submission.editor_in_charge.profile.formal_name,
             "sub_title": cls.invitation.submission.title,
             "author_list": cls.invitation.submission.author_list,
             "identifier_w_vn_nr": cls.invitation.submission.preprint.identifier_w_vn_nr,
@@ -470,18 +449,16 @@ class SubmissionUtils(BaseMailUtil):
         This method is used to inform a referee that his/her services are no longer required.
         It is called from the _hx_cancel_ref_invitation method in submissions/views.py.
         """
+        if getattr(cls, "invitation", None) is None:
+            raise ValueError(
+                "The RefereeInvitation is missing. Please `load()` it first."
+            )
+
+        cls.invitation: "RefereeInvitation"
         email_text = (
-            "Dear "
-            + cls.invitation.get_title_display()
-            + " "
-            + cls.invitation.last_name
-            + ",\n\n"
+            "Dear " + cls.invitation.referee.formal_name + ",\n\n"
             "On behalf of the Editor-in-charge "
-            + cls.invitation.submission.editor_in_charge.profile.get_title_display()
-            + " "
-            + cls.invitation.submission.editor_in_charge.user.first_name
-            + " "
-            + cls.invitation.submission.editor_in_charge.user.last_name
+            + cls.invitation.submission.editor_in_charge.profile.formal_name
             + ", we would like to inform you that your report on\n\n"
             + cls.invitation.submission.title
             + " by "
@@ -492,8 +469,8 @@ class SubmissionUtils(BaseMailUtil):
             "\n\nMany thanks for your time,\n\nThe SciPost Team"
         )
         email_text_html = (
-            "<p>Dear {{ title }} {{ last_name }},</p>"
-            "<p>On behalf of the Editor-in-charge {{ EIC_title }} {{ EIC_first_name }} {{ EIC_last_name }}, "
+            "<p>Dear {{ referee_formal_name }},</p>"
+            "<p>On behalf of the Editor-in-charge {{ EIC_formal_name }}, "
             "we would like to inform you that your report on</p>"
             "<p>{{ sub_title }}</p>"
             "\n<p>by {{ author_list }}</p>"
@@ -503,7 +480,7 @@ class SubmissionUtils(BaseMailUtil):
             "<p>Many thanks for your time,</p>"
             "<p>The SciPost Team</p>"
         )
-        if cls.invitation.referee is None:
+        if not cls.invitation.to_registered_referee:
             email_text += (
                 "\n\nP.S.: We would also like to renew "
                 "our invitation to become a Contributor on SciPost "
@@ -524,11 +501,8 @@ class SubmissionUtils(BaseMailUtil):
                 "the portal's facilities (in particular allowing you to provide referee reports).</p>"
             )
         email_context = {
-            "title": cls.invitation.get_title_display(),
-            "last_name": cls.invitation.last_name,
-            "EIC_title": cls.invitation.submission.editor_in_charge.profile.get_title_display(),
-            "EIC_last_name": cls.invitation.submission.editor_in_charge.user.last_name,
-            "EIC_first_name": cls.invitation.submission.editor_in_charge.user.first_name,
+            "referee_formal_name": cls.invitation.referee.formal_name,
+            "EIC_formal_name": cls.invitation.submission.editor_in_charge.profile.formal_name,
             "sub_title": cls.invitation.submission.title,
             "author_list": cls.invitation.submission.author_list,
             "invitation_key": cls.invitation.invitation_key,
diff --git a/scipost_django/submissions/views/__init__.py b/scipost_django/submissions/views/__init__.py
index 8c6e641ea..d15da9d64 100644
--- a/scipost_django/submissions/views/__init__.py
+++ b/scipost_django/submissions/views/__init__.py
@@ -722,7 +722,7 @@ def submission_detail(request, identifier_w_vn_nr):
             submission.reports.in_draft().filter(author__user=request.user).first()
         )
         context["invitations"] = submission.referee_invitations.non_cancelled().filter(
-            referee__user=request.user
+            referee=request.user.contributor.profile
         )
 
         if is_author or is_author_unchecked:
@@ -1476,7 +1476,7 @@ def invite_referee(
     # Guard against already invited referees
     if (
         RefereeInvitation.objects.filter(
-            profile=profile, submission=submission, cancelled=False
+            referee=profile, submission=submission, cancelled=False
         )
         .exclude(email_address=profile_email)
         .exists()
@@ -1520,12 +1520,8 @@ def invite_referee(
         contributor = profile.contributor
 
     referee_invitation, created = RefereeInvitation.objects.get_or_create(
-        profile=profile,
-        referee=contributor,
+        referee=profile,
         submission=submission,
-        title=profile.title if profile.title else TITLE_DR,
-        first_name=profile.first_name,
-        last_name=profile.last_name,
         email_address=profile_email.email,
         auto_reminders_allowed=auto_reminders_allowed,
         invited_by=request.user.contributor,
@@ -1542,7 +1538,7 @@ def invite_referee(
 
     registration_invitation = None
     has_agreed_to_previous_invitation = RefereeInvitation.objects.filter(
-        profile=profile, submission__thread_hash=submission.thread_hash, accepted=True
+        referee=profile, submission__thread_hash=submission.thread_hash, accepted=True
     ).exists()
 
     if contributor:
@@ -1628,7 +1624,7 @@ def _hx_quick_invite_referee(request, identifier_w_vn_nr, profile_id):
 
     # Guard against already invited referees
     if RefereeInvitation.objects.filter(
-        profile=profile, submission=submission, cancelled=False
+        referee=profile, submission=submission, cancelled=False
     ).exists():
         return HTMXResponse(
             "This referee has already been invited.",
@@ -1652,12 +1648,8 @@ def _hx_quick_invite_referee(request, identifier_w_vn_nr, profile_id):
 
     primary_or_first_email = profile.emails.order_by("-primary").first().email
     referee_invitation, created = RefereeInvitation.objects.get_or_create(
-        profile=profile,
-        referee=contributor,
+        referee=profile,
         submission=submission,
-        title=profile.title if profile.title else TITLE_DR,
-        first_name=profile.first_name,
-        last_name=profile.last_name,
         email_address=primary_or_first_email,
         auto_reminders_allowed=True,
         invited_by=request.user.contributor,
@@ -1674,7 +1666,7 @@ def _hx_quick_invite_referee(request, identifier_w_vn_nr, profile_id):
 
     registration_invitation = None
     has_agreed_to_previous_invitation = RefereeInvitation.objects.filter(
-        profile=profile, submission__thread_hash=submission.thread_hash, accepted=True
+        referee=profile, submission__thread_hash=submission.thread_hash, accepted=True
     ).exists()
 
     if contributor:
@@ -1767,18 +1759,17 @@ def ref_invitation_reminder(request, identifier_w_vn_nr, invitation_id):
         Submission.objects.in_pool_filter_for_eic(request.user),
         preprint__identifier_w_vn_nr=identifier_w_vn_nr,
     )
-    invitation = get_object_or_404(
+    invitation: "RefereeInvitation" = get_object_or_404(
         submission.referee_invitations.all(), pk=invitation_id
     )
-    invitation.nr_reminders += 1
-    invitation.date_last_reminded = timezone.now()
-    invitation.save()
+    invitation.reminder_sent()
+
     SubmissionUtils.load({"invitation": invitation}, request)
-    if invitation.referee is not None:
+    if invitation.to_registered_referee:
         SubmissionUtils.send_ref_reminder_email()
     else:
         SubmissionUtils.send_unreg_ref_reminder_email()
-    messages.success(request, "Reminder sent succesfully.")
+    messages.success(request, "Reminder sent successfully.")
     return redirect(
         reverse(
             "submissions:editorial_page",
@@ -1796,7 +1787,7 @@ def accept_or_decline_ref_invitations(request, invitation_id=None):
     using this view. The decision will be taken one invitation at a time.
     """
     invitation = RefereeInvitation.objects.awaiting_response().filter(
-        referee__user=request.user
+        referee=request.user.contributor.profile
     )
     if invitation_id:
         try:
@@ -1875,10 +1866,12 @@ def accept_or_decline_ref_invitations(request, invitation_id=None):
         )
         invitation.submission.add_event_for_eic(
             "Referee %s has %s the refereeing invitation."
-            % (invitation.referee.user.last_name, decision_string)
+            % (invitation.referee.full_name, decision_string)
         )
 
-        if request.user.contributor.referee_invitations.awaiting_response().exists():
+        if (
+            request.user.contributor.profile.referee_invitations.awaiting_response().exists()
+        ):
             return redirect("submissions:accept_or_decline_ref_invitations")
         return redirect(invitation.submission.get_absolute_url())
     context = {"invitation": invitation, "form": form}
@@ -1930,7 +1923,7 @@ def decline_ref_invitation(request, invitation_key):
         )
         invitation.submission.add_event_for_eic(
             "Referee %s has declined the refereeing "
-            "invitation." % invitation.last_name
+            "invitation." % invitation.referee.full_name
         )
 
         messages.success(
@@ -1971,7 +1964,7 @@ def _hx_cancel_ref_invitation(request, identifier_w_vn_nr, invitation_id):
         "A referee invitation has been cancelled."
     )
     invitation.submission.add_event_for_eic(
-        "Referee invitation for %s has been cancelled." % invitation.last_name
+        "Referee invitation for %s has been cancelled." % invitation.referee.full_name
     )
 
     return HTMXResponse("Invitation cancelled", tag="success")
@@ -1984,7 +1977,7 @@ def _hx_report_intended_delivery_form(request, invitation_id):
     """
     invitation = get_object_or_404(RefereeInvitation, pk=invitation_id)
 
-    is_invited_referee = invitation.profile == request.user.contributor.profile
+    is_invited_referee = invitation.referee == request.user.contributor.profile
     is_editor = invitation.submission.editor_in_charge == request.user.contributor
     if not (is_invited_referee or is_editor):
         return HTMXPermissionsDenied(
@@ -2187,7 +2180,7 @@ def communication(request, identifier_w_vn_nr, comtype, referee_id=None):
             raise PermissionDenied
 
         submissions_qs = Submission.objects.filter(
-            referee_invitations__referee__user=request.user
+            referee_invitations__referee=request.user.contributor.profile
         )
         referee = request.user.contributor
     elif author == "S":
@@ -2428,7 +2421,7 @@ def submit_report(request, identifier_w_vn_nr):
     is_author = check_verified_author(submission, request.user)
     is_author_unchecked = check_unverified_author(submission, request.user)
     invitation = submission.referee_invitations.filter(
-        fulfilled=False, cancelled=False, referee__user=request.user
+        fulfilled=False, cancelled=False, referee=request.user.contributor.profile
     ).first()
 
     errormessage = None
diff --git a/scipost_django/templates/email/eic/referee_response.html b/scipost_django/templates/email/eic/referee_response.html
index a0296d4d1..32175d251 100644
--- a/scipost_django/templates/email/eic/referee_response.html
+++ b/scipost_django/templates/email/eic/referee_response.html
@@ -1,11 +1,8 @@
 <p>Dear {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }},</p>
 
 <p>
-    Referee {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.get_title_display }} {{ invitation.first_name }} {{ invitation.last_name }}{% endif %} has {% if invitation.accepted %}accepted{% else %}declined (due to reason: {{ invitation.get_refusal_reason_display }}){% endif %} to referee Submission
+    Referee {{ invitation.referee.formal_name }} has {% if invitation.accepted %}accepted{% else %}declined (due to reason: {{ invitation.get_refusal_reason_display }}{% if invitation.other_refusal_reason %}, {{ invitation.other_refusal_reason }}{% endif %}){% endif %} to referee Submission
 </p>
-{% if invitation.other_refusal_reason %}
-  <p>Their "other" refusal reason states: {{ invitation.other_refusal_reason }}</p>
-{% endif %}
 <p>
   {{ invitation.submission.title }}
   <br/>
diff --git a/scipost_django/templates/email/eic/referee_unresponsive.html b/scipost_django/templates/email/eic/referee_unresponsive.html
index c8e3e3b39..e4eafc060 100644
--- a/scipost_django/templates/email/eic/referee_unresponsive.html
+++ b/scipost_django/templates/email/eic/referee_unresponsive.html
@@ -1,8 +1,8 @@
 <p>
-  Dear {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.last_name }},
+  Dear {{ invitation.submission.editor_in_charge.profile.formal_name }},
 </p>
 <p>
-  Referee {{ invitation.get_title_display }} {{ invitation.last_name }}, whom you invited to referee
+  Referee {{ invitation.referee.full_name }}, whom you invited to referee
   Submission
   <br><br>
   <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">{{ invitation.submission.title }}</a>
diff --git a/scipost_django/templates/email/referees/confirmation_invitation_response.html b/scipost_django/templates/email/referees/confirmation_invitation_response.html
index 663e56127..024258163 100644
--- a/scipost_django/templates/email/referees/confirmation_invitation_response.html
+++ b/scipost_django/templates/email/referees/confirmation_invitation_response.html
@@ -1,5 +1,5 @@
 <p>
-  Dear {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.profile.get_title_display }} {{ invitation.profile.last_name }}{% endif %},
+  Dear {{ invitation.referee.formal_name }},
 </p>
 
 <p>
diff --git a/scipost_django/templates/email/referees/inform_referee_manuscript_withdrawn.html b/scipost_django/templates/email/referees/inform_referee_manuscript_withdrawn.html
index d1478b73a..4cfcc4fb5 100644
--- a/scipost_django/templates/email/referees/inform_referee_manuscript_withdrawn.html
+++ b/scipost_django/templates/email/referees/inform_referee_manuscript_withdrawn.html
@@ -1,5 +1,5 @@
 <p>
-  Dear {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.profile.get_title_display }} {{ invitation.profile.last_name }}{% endif %}
+  Dear {{ invitation.referee.formal_name }}
 </p>
 
 <p>
diff --git a/scipost_django/templates/email/referees/invite_contributor_to_referee.html b/scipost_django/templates/email/referees/invite_contributor_to_referee.html
index a69a980b4..3f36aed6c 100644
--- a/scipost_django/templates/email/referees/invite_contributor_to_referee.html
+++ b/scipost_django/templates/email/referees/invite_contributor_to_referee.html
@@ -1,8 +1,8 @@
 <p>
-  Dear {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.profile.get_title_display }} {{ invitation.profile.last_name }}{% endif %},
+  Dear {{ invitation.referee.formal_name }},
 </p>
 <p>
-  We have received a Submission to {% if invitation.submission.submitted_to.doi_label == "MigPol" %}Migration Politics{% else %}SciPost{% endif %} which, in view of your expertise and On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.first_name }} {{ invitation.submission.editor_in_charge.user.last_name }}, we would like to invite you to referee:
+  We have received a Submission to {% if invitation.submission.submitted_to.doi_label == "MigPol" %}Migration Politics{% else %}SciPost{% endif %} which, in view of your expertise and On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.profile.formal_name }}, we would like to invite you to referee:
   <br />
   <br />
   <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">{{ invitation.submission.title }}</a>
diff --git a/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder1.html b/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder1.html
index dfa5ea727..046ac6856 100644
--- a/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder1.html
+++ b/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder1.html
@@ -1,11 +1,11 @@
 <h3>Re: refereeing invitation. First automatic reminder</h3>
 
 <p>
-  Dear {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.profile.get_title_display }} {{ invitation.profile.last_name }}{% endif %},
+  Dear {{ invitation.referee.formal_name }},
 </p>
 
 <p>
-  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.first_name }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
+  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.formal_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
   <br />
   <br />
   <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">{{ invitation.submission.title }}</a>
diff --git a/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder2.html b/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder2.html
index 96f961a08..dcc866af1 100644
--- a/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder2.html
+++ b/scipost_django/templates/email/referees/invite_contributor_to_referee_reminder2.html
@@ -1,11 +1,11 @@
 <h3>Re: refereeing invitation. Second (and last) automatic reminder</h3>
 
 <p>
-  Dear {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.profile.get_title_display }} {{ invitation.profile.last_name }}{% endif %},
+  Dear {{ invitation.referee.formal_name }},
 </p>
 
 <p>
-  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.first_name }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
+  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.formal_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
   <br />
   <br />
   <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">{{ invitation.submission.title }}</a>
diff --git a/scipost_django/templates/email/referees/invite_unregistered_to_referee.html b/scipost_django/templates/email/referees/invite_unregistered_to_referee.html
index 11448f1c4..4401fd99f 100644
--- a/scipost_django/templates/email/referees/invite_unregistered_to_referee.html
+++ b/scipost_django/templates/email/referees/invite_unregistered_to_referee.html
@@ -1,6 +1,6 @@
-<p>Dear {{ invitation.get_title_display }} {{ invitation.last_name }},</p>
+<p>Dear {{ invitation.referee.formal_name }},</p>
 <p>
-  On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.first_name }} {{ invitation.submission.editor_in_charge.user.last_name }}, we would like to invite you to referee a Submission to {{ invitation.submission.submitted_to }}, namely
+  On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.formal_name }}, we would like to invite you to referee a Submission to {{ invitation.submission.submitted_to }}, namely
   <br />
   <br />
   <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">{{ invitation.submission.title }}</a>
diff --git a/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder1.html b/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder1.html
index adbcb628d..3bab33e70 100644
--- a/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder1.html
+++ b/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder1.html
@@ -1,7 +1,7 @@
 <h3>Re: refereeing invitation. First automatic reminder</h3>
-<p>Dear {{ invitation.get_title_display }} {{ invitation.last_name }},</p>
+<p>Dear {{ invitation.referee.formal_name }},</p>
 <p>
-  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.first_name }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
+  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.formal_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
   <br />
   <br />
   <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">{{ invitation.submission.title }}</a>
diff --git a/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder2.html b/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder2.html
index 2ff96e9a7..06b295413 100644
--- a/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder2.html
+++ b/scipost_django/templates/email/referees/invite_unregistered_to_referee_reminder2.html
@@ -1,7 +1,7 @@
 <h3>Re: refereeing invitation. Second (and last) automatic reminder</h3>
-<p>Dear {{ invitation.get_title_display }} {{ invitation.last_name }},</p>
+<p>Dear {{ invitation.referee.formal_name }},</p>
 <p>
-  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.first_name }} {{ invitation.submission.editor_in_charge.user.last_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
+  Recently, On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.formal_name }}, we invited you to consider refereeing a Submission to {{ invitation.submission.submitted_to }}, namely
   <br />
   <br />
   <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">{{ invitation.submission.title }}</a>
diff --git a/scipost_django/templates/email/referees/reinvite_contributor_to_referee.html b/scipost_django/templates/email/referees/reinvite_contributor_to_referee.html
index 8c13f3af9..858e1a519 100644
--- a/scipost_django/templates/email/referees/reinvite_contributor_to_referee.html
+++ b/scipost_django/templates/email/referees/reinvite_contributor_to_referee.html
@@ -1,4 +1,4 @@
-<p>Dear {{ invitation.get_title_display }} {{ invitation.last_name }},</p>
+<p>Dear {{ invitation.referee.formal_name}},</p>
 
 <p>
   The authors of submission
@@ -11,7 +11,7 @@
   (<a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">see on SciPost.org</a>)
 </p>
 <p>
-  have submitted a new (revised) version of their manuscript to SciPost. On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.get_title_display }} {{ invitation.submission.editor_in_charge.user.first_name }} {{ invitation.submission.editor_in_charge.user.last_name }}, we would like to invite you to review this new version.
+  have submitted a new (revised) version of their manuscript to SciPost. On behalf of the Editor-in-charge {{ invitation.submission.editor_in_charge.profile.formal_name }}, we would like to invite you to review this new version.
 </p>
 <p>
   Your report can be submitted by clicking on the <a href="https://{{ domain }}{% url 'submissions:submit_report' identifier_w_vn_nr=invitation.submission.preprint.identifier_w_vn_nr %}">Contribute a Report</a> link on the <a href="https://{{ domain }}{{ invitation.submission.get_absolute_url }}">Submission's Page</a>{% if invitation.submission.reporting_deadline %} before the reporting deadline (currently set at {{ invitation.submission.reporting_deadline|date:'N j, Y' }}){% endif %}.
diff --git a/scipost_django/templates/email/referees/remind_referee_deadline_1week.html b/scipost_django/templates/email/referees/remind_referee_deadline_1week.html
index cad83b52a..a608e0a1f 100644
--- a/scipost_django/templates/email/referees/remind_referee_deadline_1week.html
+++ b/scipost_django/templates/email/referees/remind_referee_deadline_1week.html
@@ -1,5 +1,5 @@
 <p>
-  Dear {% if invitation.referee %}{{ invitation.referee.profile.get_title_display }} {{ invitation.referee.user.last_name }}{% else %}{{ invitation.profile.get_title_display }} {{ invitation.profile.last_name }}{% endif %},
+  Dear {{ invitation.referee.formal_name }},
 </p>
 <p>
   This is a simple email to remind you of the approaching deadline (in one week) to send a Report on
-- 
GitLab