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' %} 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