Source code for submissions.models.referee_invitation

__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
__license__ = "AGPL v3"


import datetime

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 ..constants import ASSIGNMENT_NULLBOOL, ASSIGNMENT_REFUSAL_REASONS
from ..managers import RefereeInvitationQuerySet


[docs]class RefereeInvitation(SubmissionRelatedObjectMixin, models.Model): """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. 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('profiles.Profile', on_delete=models.SET_NULL, blank=True, null=True) submission = models.ForeignKey('submissions.Submission', on_delete=models.CASCADE, related_name='referee_invitations') referee = models.ForeignKey('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 invitation_key = models.CharField(max_length=40, blank=True) date_invited = models.DateTimeField(default=timezone.now) invited_by = models.ForeignKey('scipost.Contributor', related_name='referee_invited_by', blank=True, null=True, on_delete=models.CASCADE) auto_reminders_allowed = models.BooleanField(default=True) nr_reminders = models.PositiveSmallIntegerField(default=0) date_last_reminded = models.DateTimeField(blank=True, null=True) accepted = models.NullBooleanField(choices=ASSIGNMENT_NULLBOOL, default=None) date_responded = models.DateTimeField(blank=True, null=True) refusal_reason = models.CharField(max_length=3, choices=ASSIGNMENT_REFUSAL_REASONS, blank=True, null=True) fulfilled = models.BooleanField(default=False) # True if a Report has been submitted cancelled = models.BooleanField(default=False) # True if EIC has deactivated invitation objects = RefereeInvitationQuerySet.as_manager() class Meta: ordering = ['cancelled', 'date_invited'] def __str__(self): """Summarize the RefereeInvitation's basic information.""" return (self.first_name + ' ' + self.last_name + ' to referee ' + self.submission.title[:30] + ' by ' + self.submission.author_list[:30] + ', invited on ' + self.date_invited.strftime('%Y-%m-%d'))
[docs] def get_absolute_url(self): """Return url of the invitation's processing page.""" 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 @property def notification_name(self): """Return string representation of this RefereeInvitation as shown in Notifications.""" return self.submission.preprint.identifier_w_vn_nr @property def related_report(self): """Return the Report that's been created for this invitation.""" return self.submission.reports.filter(author=self.referee).last() @property def needs_response(self): """Check if invitation has no response in more than three days.""" if not self.cancelled and self.accepted is None: if self.date_last_reminded: # No reponse in over three days since last reminder return timezone.now() - self.date_last_reminded > datetime.timedelta(days=3) # No reponse in over three days since original invite return timezone.now() - self.date_invited > datetime.timedelta(days=3) return False @property def needs_fulfillment_reminder(self): """Check if isn't fullfilled but deadline is closing in.""" if self.accepted and not self.cancelled and not self.fulfilled: # Refereeing deadline closing in/overdue, but invitation isn't fulfilled yet. return (self.submission.reporting_deadline - timezone.now()).days < 7 return False @property def is_overdue(self): """Check if isn't fullfilled but deadline has expired.""" if self.accepted and not self.cancelled and not self.fulfilled: # Refereeing deadline closing in/overdue, but invitation isn't fulfilled yet. return (self.submission.reporting_deadline - timezone.now()).days < 0 return False @property def needs_attention(self): """Check if invitation needs attention by the editor.""" return self.needs_response or self.needs_fulfillment_reminder @property def get_status_display(self): """Get status: a combination between different boolean fields.""" if self.cancelled: return 'Cancelled' if self.fulfilled: return 'Fulfilled' if self.accepted is None: return 'Awaiting response' elif self.accepted: return 'Accepted' else: return 'Declined ({})'.format(self.get_refusal_reason_display())
[docs] def reset_content(self): """Reset the invitation's information as a new invitation.""" self.nr_reminders = 0 self.date_last_reminded = None self.accepted = None self.refusal_reason = None self.fulfilled = False self.cancelled = False