Source code for invitations.models

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


import datetime
import hashlib
import random
import string

from django.db import models, IntegrityError
from django.conf import settings
from django.utils import timezone

from . import constants
from .managers import RegistrationInvitationQuerySet, CitationNotificationQuerySet

from scipost.constants import TITLE_CHOICES


[docs]class RegistrationInvitation(models.Model): """ Invitation to particular persons for registration """ profile = models.ForeignKey( "profiles.Profile", on_delete=models.SET_NULL, blank=True, null=True ) title = models.CharField(max_length=4, choices=TITLE_CHOICES) first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=150) email = models.EmailField() status = models.CharField( max_length=8, choices=constants.REGISTATION_INVITATION_STATUSES, default=constants.STATUS_DRAFT, ) # Text content message_style = models.CharField( max_length=1, choices=constants.INVITATION_STYLE, default=constants.INVITATION_FORMAL, ) personal_message = models.TextField(blank=True) invited_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, blank=True, null=True, related_name="invitations_sent", ) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="invitations_created", ) # Related to objects invitation_type = models.CharField( max_length=2, choices=constants.INVITATION_TYPE, default=constants.INVITATION_CONTRIBUTOR, ) # Response keys invitation_key = models.CharField(max_length=40, unique=True) key_expires = models.DateTimeField(default=timezone.now) # Timestamps date_sent_first = models.DateTimeField(null=True, blank=True) date_sent_last = models.DateTimeField(null=True, blank=True) times_sent = models.PositiveSmallIntegerField(default=0) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) objects = RegistrationInvitationQuerySet.as_manager() class Meta: ordering = ["last_name"] def __init__(self, *args, **kwargs): response = super().__init__(*args, **kwargs) self.refresh_keys() return response def __str__(self): return "{} {} on {}".format( self.first_name, self.last_name, self.created.strftime("%Y-%m-%d") )
[docs] def refresh_keys(self, force_new_key=False): # Generate email activation key and link if not self.invitation_key or force_new_key: # TODO: Replace this all by the `secrets` package available from python 3.6(!) salt = "" for i in range(5): salt += random.choice(string.ascii_letters) salt = salt.encode("utf8") invitationsalt = self.last_name.encode("utf8") self.invitation_key = hashlib.sha1(salt + invitationsalt).hexdigest() self.key_expires = timezone.now() + datetime.timedelta(days=365)
[docs] def mail_sent(self, user=None): """ Update instance fields as if a new invitation mail has been sent out. """ if self.status == constants.STATUS_DRAFT: self.status = constants.STATUS_SENT if not self.date_sent_first: self.date_sent_first = timezone.now() self.date_sent_last = timezone.now() self.invited_by = user or self.created_by self.times_sent += 1 self.citation_notifications.update(processed=True) self.save()
@property def has_responded(self): return self.status in [constants.STATUS_DECLINED, constants.STATUS_REGISTERED]
[docs]class CitationNotification(models.Model): invitation = models.ForeignKey( "invitations.RegistrationInvitation", on_delete=models.CASCADE, null=True, blank=True, ) contributor = models.ForeignKey( "scipost.Contributor", on_delete=models.CASCADE, null=True, blank=True, related_name="+", ) # Content submission = models.ForeignKey( "submissions.Submission", null=True, blank=True, on_delete=models.CASCADE, related_name="+", ) publication = models.ForeignKey( "journals.Publication", null=True, blank=True, on_delete=models.CASCADE, related_name="+", ) processed = models.BooleanField(default=False) # Meta info created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="notifications_created", ) date_sent = models.DateTimeField(null=True, blank=True) created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) objects = CitationNotificationQuerySet.as_manager() class Meta: default_related_name = "citation_notifications" unique_together = ( ("invitation", "submission"), ("invitation", "publication"), ("contributor", "submission"), ("contributor", "publication"), ) def __str__(self): _str = "Citation for " if self.invitation: _str += " Invitation ({} {})".format( self.invitation.first_name, self.invitation.last_name, ) elif self.contributor: _str += " Contributor ({})".format(self.contributor) _str += " on " if self.submission: _str += "Submission ({})".format( self.submission.preprint.identifier_w_vn_nr ) elif self.publication: _str += "Publication ({})".format(self.publication.doi_label) return _str
[docs] def save(self, *args, **kwargs): if not self.submission and not self.publication: raise IntegrityError( ( "CitationNotification needs to be related to either a " "Submission or Publication object." ) ) return super().save(*args, **kwargs)
[docs] def mail_sent(self): """ Update instance fields as if a new citation notification mail has been sent out. """ self.processed = True if not self.date_sent: # Don't overwrite by accident... self.date_sent = timezone.now() self.save()
[docs] def related_notifications(self): return CitationNotification.objects.unprocessed().filter( models.Q(contributor=self.contributor) | models.Q(invitation=self.invitation) )
@property def email(self): if self.invitation: return self.invitation.email elif self.contributor: return self.contributor.user.email @property def last_name(self): if self.invitation: return self.invitation.last_name elif self.contributor: return self.contributor.user.last_name @property def get_title(self): if self.invitation: return self.invitation.get_title_display() elif self.contributor and self.contributor.profile: return self.contributor.profile.get_title_display()