From 387f8dca0488db38468a123243b74e8e96e694d0 Mon Sep 17 00:00:00 2001
From: Jorran de Wit <jorrandewit@outlook.com>
Date: Tue, 20 Feb 2018 11:39:40 +0100
Subject: [PATCH] Do more

---
 journals/templatetags/lookup.py               |   2 +-
 mails/forms.py                                | 142 +----
 mails/mixins.py                               | 152 +++++-
 .../registration_invitation.html              |   4 +-
 mails/utils.py                                |  49 +-
 scipost/models.py                             |  36 +-
 .../personal_page/editorial_actions.html      |  11 +-
 scipost/urls.py                               |  32 +-
 scipost/utils.py                              | 491 ------------------
 scipost/views.py                              | 254 ---------
 submissions/templatetags/lookup.py            |   2 +-
 submissions/views.py                          |  29 +-
 12 files changed, 196 insertions(+), 1008 deletions(-)

diff --git a/journals/templatetags/lookup.py b/journals/templatetags/lookup.py
index bdfeb24b0..9323d78aa 100644
--- a/journals/templatetags/lookup.py
+++ b/journals/templatetags/lookup.py
@@ -28,4 +28,4 @@ class PublicationLookup(LookupChannel):
         Right now only used for draft registration invitations. May be extended in the
         future for other purposes as well.
         """
-        return request.user.has_perm('can_draft_registration_invitations')
+        return request.user.has_perm('can_create_registration_invitations')
diff --git a/mails/forms.py b/mails/forms.py
index dbc6b5959..b69d0d037 100644
--- a/mails/forms.py
+++ b/mails/forms.py
@@ -1,68 +1,10 @@
-import re
-import json
-import inspect
-from html2text import HTML2Text
-
 from django import forms
-from django.core.mail import EmailMultiAlternatives
-from django.contrib.auth import get_user_model
-from django.conf import settings
-from django.template import loader
-
-from scipost.models import Contributor
 
-# from .mixins import MailUtilsMixin
+from .mixins import MailUtilsMixin
 from .widgets import SummernoteEditor
 
 
-class MailUtilsMixin:
-    mail_fields = None
-    mail_template = ''
-
-    def __init__(self, *args, **kwargs):
-        self.mail_code = kwargs.pop('mail_code')
-        self.instance = kwargs.pop('instance', None)
-
-        # Gather meta data
-        json_location = '%s/mails/templates/mail_templates/%s.json' % (settings.BASE_DIR,
-                                                                       self.mail_code)
-        try:
-            self.mail_data = json.loads(open(json_location).read())
-        except OSError:
-            raise NotImplementedError(('You did not create a valid .html and .json file '
-                                       'for mail_code: %s' % self.mail_code))
-
-        # Save central object/instance
-        self.object = self.get_object(**kwargs)
-
-        # Digest the templates
-        mail_template = loader.get_template('mail_templates/%s.html' % self.mail_code)
-        if self.instance and self.mail_data.get('context_object'):
-            kwargs[self.mail_data['context_object']] = self.instance
-        self.mail_template = mail_template.render(kwargs)
-
-        # Gather Recipients data
-        self.recipient = ''
-        if self.object:
-            recipient = self.object
-            for attr in self.mail_data.get('to_address').split('.'):
-                recipient = getattr(recipient, attr)
-                if inspect.ismethod(recipient):
-                    recipient = recipient()
-            self.recipient = recipient
-        super().__init__(*args, **kwargs)
-
-    def get_object(self, **kwargs):
-        if self.object:
-            return self.object
-        if self.instance:
-            return self.instance
-
-        if self.mail_data.get('context_object'):
-            return kwargs.get(self.mail_data['context_object'], None)
-
-
-class EmailTemplateForm(MailUtilsMixin, forms.Form):
+class EmailTemplateForm(forms.Form, MailUtilsMixin):
     subject = forms.CharField(max_length=250, label="Subject*")
     text = forms.CharField(widget=SummernoteEditor, label="Text*")
     extra_recipient = forms.EmailField(label="Optional: bcc this email to", required=False)
@@ -81,7 +23,7 @@ class EmailTemplateForm(MailUtilsMixin, forms.Form):
             data = None
         super().__init__(data, *args, **kwargs)
 
-        if not self.recipient:
+        if not self.original_recipient:
             self.fields['extra_recipient'].label = "Send this email to"
             self.fields['extra_recipient'].required = True
 
@@ -91,84 +33,26 @@ class EmailTemplateForm(MailUtilsMixin, forms.Form):
 
     def save_data(self):
         # Get text and html
-        html_message = self.cleaned_data['text']
-        handler = HTML2Text()
-        message = handler.handle(html_message)
+        self.html_message = self.cleaned_data['text']
+        self.subject = self.cleaned_data['subject']
+        self.validate_message()
 
         # Get recipients list. Try to send through BCC to prevent privacy issues!
-        bcc_list = []
-        if self.mail_data.get('bcc_to', False) and self.object:
-            if re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to')):
-                bcc_list = [self.mail_data.get('bcc_to')]
-            else:
-                bcc_to = self.object
-                for attr in self.mail_data.get('bcc_to').split('.'):
-                    bcc_to = getattr(bcc_to, attr)
-
-                if not isinstance(bcc_to, list):
-                    bcc_list = [bcc_to]
-                else:
-                    bcc_list = bcc_to
-        elif re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to', '')):
-            bcc_list = [self.mail_data.get('bcc_to')]
-
-        if self.cleaned_data.get('extra_recipient') and self.recipient:
-            bcc_list.append(self.cleaned_data.get('extra_recipient'))
-        elif self.cleaned_data.get('extra_recipient') and not self.recipient:
-            self.recipient = [self.cleaned_data.get('extra_recipient')]
-        elif not self.recipient:
+        if self.cleaned_data.get('extra_recipient') and self.original_recipient:
+            self.bcc_list.append(self.cleaned_data.get('extra_recipient'))
+        elif self.cleaned_data.get('extra_recipient') and not self.original_recipient:
+            self.original_recipient = [self.cleaned_data.get('extra_recipient')]
+        elif not self.original_recipient:
             self.add_error('extra_recipient', 'Please fill the bcc field to send the mail.')
 
-        # Check the send list
-        if isinstance(self.recipient, list):
-            recipients = self.recipient
-        elif not isinstance(self.recipient, str):
-            try:
-                recipients = list(self.recipient)
-            except TypeError:
-                recipients = [self.recipient]
-        else:
-            recipients = [self.recipient]
-        recipients = list(recipients)
-
-        # Check if email needs to be taken from instance
-        _recipients = []
-        for recipient in recipients:
-            if isinstance(recipient, Contributor):
-                _recipients.append(recipient.user.email)
-            elif isinstance(recipient, get_user_model()):
-                _recipients.append(recipient.email)
-            elif isinstance(recipient, str):
-                _recipients.append(recipient)
-
-        self.mail_fields = {
-            'subject': self.cleaned_data['subject'],
-            'message': message,
-            'html_message': html_message,
-            'recipients': _recipients,
-            'bcc_list': bcc_list,
-        }
+        self.validate_recipients()
+        self.save_mail_data()
 
     def clean(self):
         data = super().clean()
         self.save_data()
         return data
 
-    def send(self):
-        # Send the mail
-        email = EmailMultiAlternatives(
-            self.mail_fields['subject'],
-            self.mail_fields['message'],
-            '%s <%s>' % (self.mail_data.get('from_address_name', 'SciPost'),
-                         self.mail_data.get('from_address', 'no-reply@scipost.org')),  # From
-            self.mail_fields['recipients'],  # To
-            bcc=self.mail_fields['bcc_list'],
-            reply_to=[self.mail_data.get('from_address', 'no-reply@scipost.org')])
-        email.attach_alternative(self.mail_fields['html_message'], 'text/html')
-        email.send(fail_silently=False)
-        if self.object and hasattr(self.object, 'mail_sent'):
-            self.object.mail_sent()
-
 
 class HiddenDataForm(forms.Form):
     def __init__(self, form, *args, **kwargs):
diff --git a/mails/mixins.py b/mails/mixins.py
index 0d0fd1b8e..029097f24 100644
--- a/mails/mixins.py
+++ b/mails/mixins.py
@@ -1,6 +1,18 @@
+import re
+import json
+import inspect
+from html2text import HTML2Text
+
+from django.core.mail import EmailMultiAlternatives
 from django.contrib import messages
+from django.contrib.auth import get_user_model
+from django.conf import settings
+from django.template import loader
+
+from scipost.models import Contributor
 
-from .forms import EmailTemplateForm, HiddenDataForm
+
+from . import forms
 
 
 class MailEditorMixin:
@@ -37,15 +49,15 @@ class MailEditorMixin:
         self.object = self.get_object()
         form = self.get_form()
         if form.is_valid():
-            self.mail_form = EmailTemplateForm(request.POST or None,
-                                               mail_code=self.mail_code,
-                                               instance=self.object)
+            self.mail_form = forms.EmailTemplateForm(request.POST or None,
+                                                     mail_code=self.mail_code,
+                                                     instance=self.object)
             if self.mail_form.is_valid():
                 return self.form_valid(form)
 
             return self.render_to_response(
                 self.get_context_data(form=self.mail_form,
-                                      transfer_data_form=HiddenDataForm(form)))
+                                      transfer_data_form=forms.HiddenDataForm(form)))
         else:
             return self.form_invalid(form)
 
@@ -70,3 +82,133 @@ class MailUtilsMixin:
     """
     This mixin takes care of inserting the default data into the Utils or Form.
     """
+    object = None
+    mail_fields = {}
+    mail_template = ''
+    html_message = ''
+    message = ''
+
+    def __init__(self, *args, **kwargs):
+        self.mail_code = kwargs.pop('mail_code')
+        self.instance = kwargs.pop('instance', None)
+
+        # Gather meta data
+        json_location = '%s/mails/templates/mail_templates/%s.json' % (settings.BASE_DIR,
+                                                                       self.mail_code)
+        try:
+            self.mail_data = json.loads(open(json_location).read())
+        except OSError:
+            raise NotImplementedError(('You did not create a valid .html and .json file '
+                                       'for mail_code: %s' % self.mail_code))
+
+        # Save central object/instance
+        self.object = self.get_object(**kwargs)
+
+        # Digest the templates
+        mail_template = loader.get_template('mail_templates/%s.html' % self.mail_code)
+        if self.instance and self.mail_data.get('context_object'):
+            kwargs[self.mail_data['context_object']] = self.instance
+        self.mail_template = mail_template.render(kwargs)
+
+        # Gather Recipients data
+        self.original_recipient = ''
+        if self.object:
+            recipient = self.object
+            for attr in self.mail_data.get('to_address').split('.'):
+                recipient = getattr(recipient, attr)
+                if inspect.ismethod(recipient):
+                    recipient = recipient()
+            self.original_recipient = recipient
+
+        self.subject = self.mail_data['subject']
+
+
+    def validate_recipients(self):
+        # Get recipients list. Try to send through BCC to prevent privacy issues!
+        self.bcc_list = []
+        if self.mail_data.get('bcc_to', False) and self.object:
+            if re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to')):
+                self.bcc_list = [self.mail_data.get('bcc_to')]
+            else:
+                bcc_to = self.object
+                for attr in self.mail_data.get('bcc_to').split('.'):
+                    bcc_to = getattr(bcc_to, attr)
+
+                if not isinstance(bcc_to, list):
+                    self.bcc_list = [bcc_to]
+                else:
+                    self.bcc_list = bcc_to
+        elif re.match("[^@]+@[^@]+\.[^@]+", self.mail_data.get('bcc_to', '')):
+            self.bcc_list = [self.mail_data.get('bcc_to')]
+
+        # Check the send list
+        if isinstance(self.original_recipient, list):
+            recipients = self.original_recipient
+        elif not isinstance(self.original_recipient, str):
+            try:
+                recipients = list(self.original_recipient)
+            except TypeError:
+                recipients = [self.original_recipient]
+        else:
+            recipients = [self.original_recipient]
+        recipients = list(recipients)
+
+        # Check if email needs to be taken from an instance
+        _recipients = []
+        for recipient in recipients:
+            if isinstance(recipient, Contributor):
+                _recipients.append(recipient.user.email)
+            elif isinstance(recipient, get_user_model()):
+                _recipients.append(recipient.email)
+            elif isinstance(recipient, str):
+                _recipients.append(recipient)
+        self.recipients = _recipients
+
+    def validate_message(self):
+        if not self.html_message:
+            self.html_message = self.mail_template
+        handler = HTML2Text()
+        self.message = handler.handle(self.html_message)
+
+    def validate(self):
+        """
+        Ease workflow by called this wrapper validation method.
+
+        Only to be used when the default data is used, eg. not in the EmailTemplateForm.
+        """
+        self.validate_message()
+        self.validate_recipients()
+        self.save_mail_data()
+
+    def save_mail_data(self):
+        self.mail_fields = {
+            'subject': self.subject,
+            'message': self.message,
+            'html_message': self.html_message,
+            'recipients': self.recipients,
+            'bcc_list': self.bcc_list,
+        }
+
+    def get_object(self, **kwargs):
+        if self.object:
+            return self.object
+        if self.instance:
+            return self.instance
+
+        if self.mail_data.get('context_object'):
+            return kwargs.get(self.mail_data['context_object'], None)
+
+    def send(self):
+        # Send the mail
+        email = EmailMultiAlternatives(
+            self.mail_fields['subject'],
+            self.mail_fields['message'],
+            '%s <%s>' % (self.mail_data.get('from_address_name', 'SciPost'),
+                         self.mail_data.get('from_address', 'no-reply@scipost.org')),  # From
+            self.mail_fields['recipients'],  # To
+            bcc=self.mail_fields['bcc_list'],
+            reply_to=[self.mail_data.get('from_address', 'no-reply@scipost.org')])
+        email.attach_alternative(self.mail_fields['html_message'], 'text/html')
+        email.send(fail_silently=False)
+        if self.object and hasattr(self.object, 'mail_sent'):
+            self.object.mail_sent()
diff --git a/mails/templates/mail_templates/registration_invitation.html b/mails/templates/mail_templates/registration_invitation.html
index b0b1349ae..fceaa5f99 100644
--- a/mails/templates/mail_templates/registration_invitation.html
+++ b/mails/templates/mail_templates/registration_invitation.html
@@ -8,7 +8,7 @@ Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }}
 <br><br>
 
 {% if invitation.personal_message %}
-    {{ personal_message|linebreaks }}
+    {{ invitation.personal_message|linebreaksbr }}
     <br>
 {% endif %}
 
@@ -39,6 +39,7 @@ Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }}
     </p>
 
 {% elif invitation.invitation_type == 'C' %}
+    {# "Regular" invite #}
     {% if invitation.citation_notifications.for_publications %}
         <p>
             Your work has been cited in
@@ -120,6 +121,7 @@ Dear {% if invitation.message_style == 'F' %}{{ invitation.get_title_display }}
         {{ invitation.invited_by.contributor.get_title_display }} {{ invitation.invited_by.first_name }} {{ invitation.invited_by.last_name }}
     </p>
 {% elif invitation.invitation_type == 'F' %}
+    {# Fellow invite #}
     <p>
         You will perhaps have already heard about SciPost, a publication
         portal established by and for professional scientists.
diff --git a/mails/utils.py b/mails/utils.py
index 59750638d..ee0d910d6 100644
--- a/mails/utils.py
+++ b/mails/utils.py
@@ -1,49 +1,14 @@
-from django.core.mail import EmailMultiAlternatives
-from django.template import loader
+from . import mixins
 
 
-class DirectMailUtil(object):
+class DirectMailUtil(mixins.MailUtilsMixin):
     """
     Same templates and json files as the form EmailTemplateForm, but this will directly send
     the mails out, without intercepting and showing the mail editor to the user.
     """
-    mail_sender = 'no-reply@scipost.org'
-    mail_sender_title = ''
 
-    @classmethod
-    def load(cls, _dict, request=None):
-        cls._context = _dict
-        cls._context['request'] = request
-        for var_name in _dict:
-            setattr(cls, var_name, _dict[var_name])
-
-    def _send_mail(cls, template_name, recipients, subject, extra_bcc=None, extra_context={}):
-        """
-        Call this method from a classmethod to send emails.
-        The template will have context variables defined appended from the `load` method.
-
-        Arguments:
-        template_name -- The .html template to use in the mail. The name be used to get the
-                         following two templates:
-                            `email/<template_name>.txt` (non-HTML)
-                            `email/<template_name>.html`
-        recipients -- List of mailaddresses to send to mail to.
-        subject -- The subject of the mail.
-        """
-        template = loader.get_template('email/%s.txt' % template_name)
-        html_template = loader.get_template('email/%s.html' % template_name)
-        cls._context.update(extra_context)
-        message = template.render(cls._context)
-        html_message = html_template.render(cls._context)
-        bcc_list = [cls.mail_sender]
-        if extra_bcc:
-            bcc_list += extra_bcc
-        email = EmailMultiAlternatives(
-            'SciPost: ' + subject,  # message,
-            message,
-            '%s <%s>' % (cls.mail_sender_title, cls.mail_sender),
-            recipients,
-            bcc=bcc_list,
-            reply_to=[cls.mail_sender])
-        email.attach_alternative(html_message, 'text/html')
-        email.send(fail_silently=False)
+    def __init__(self, mail_code, instance, *args, **kwargs):
+        kwargs['mail_code'] = mail_code
+        kwargs['instance'] = instance
+        super().__init__(*args, **kwargs)
+        self.validate()
diff --git a/scipost/models.py b/scipost/models.py
index 3f6779ee9..46e448359 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -189,7 +189,7 @@ class DraftInvitation(models.Model):
 
 class RegistrationInvitation(models.Model):
     """
-    Invitation to particular persons for registration
+    Deprecated: Use the `invitations` app
     """
     title = models.CharField(max_length=4, choices=TITLE_CHOICES)
     first_name = models.CharField(max_length=30)
@@ -218,29 +218,11 @@ class RegistrationInvitation(models.Model):
     responded = models.BooleanField(default=False)
     declined = models.BooleanField(default=False)
 
-    # objects = RegistrationInvitationManager()
-    #
-    # class Meta:
-    #     ordering = ['last_name']
-    #
-    # def __str__(self):
-    #     return (self.first_name + ' ' + self.last_name
-    #             + ' on ' + self.date_sent.strftime("%Y-%m-%d"))
-    #
-    # def refresh_keys(self, force_new_key=False):
-    #     # Generate email activation key and link
-    #     if not self.invitation_key or force_new_key:
-    #         salt = ""
-    #         for i in range(5):
-    #             salt = 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)
-    #     self.save()
-
 
 class CitationNotification(models.Model):
+    """
+    Deprecated: Use the `invitations` app
+    """
     contributor = models.ForeignKey('scipost.Contributor', on_delete=models.CASCADE)
     cited_in_submission = models.ForeignKey('submissions.Submission',
                                             on_delete=models.CASCADE,
@@ -250,16 +232,6 @@ class CitationNotification(models.Model):
                                              blank=True, null=True)
     processed = models.BooleanField(default=False)
 
-    # def __str__(self):
-    #     text = str(self.contributor) + ', cited in '
-    #     if self.cited_in_submission:
-    #         text += self.cited_in_submission.arxiv_identifier_w_vn_nr
-    #     elif self.cited_in_publication:
-    #         text += self.cited_in_publication.citation()
-    #     if self.processed:
-    #         text += ' (processed)'
-    #     return text
-
 
 class AuthorshipClaim(models.Model):
     claimant = models.ForeignKey('scipost.Contributor',
diff --git a/scipost/templates/partials/scipost/personal_page/editorial_actions.html b/scipost/templates/partials/scipost/personal_page/editorial_actions.html
index 10fefa062..0996ec371 100644
--- a/scipost/templates/partials/scipost/personal_page/editorial_actions.html
+++ b/scipost/templates/partials/scipost/personal_page/editorial_actions.html
@@ -10,7 +10,7 @@
 </div>
 
 <div class="row">
-    {% if perms.scipost.can_vet_registration_requests or perms.scipost.can_draft_registration_invitations or perms.scipost.can_manage_registration_invitations %}
+    {% if perms.scipost.can_vet_registration_requests or perms.scipost.can_create_registration_invitations or perms.scipost.can_resend_registration_requests %}
     <div class="col-md-4">
         <h3>Registration actions</h3>
         <ul>
@@ -20,20 +20,15 @@
             {% if perms.scipost.can_resend_registration_requests %}
                 <li><a href="{% url 'scipost:registration_requests' %}">Awaiting validation</a> ({{ nr_reg_awaiting_validation }})</li>
             {% endif %}
-            {% if perms.scipost.can_draft_registration_invitations %}
-                <li><a href="{% url 'scipost:contributors_filter' %}">Contributors filter</a></li>
-                <li><a href="{% url 'scipost:draft_registration_invitation' %}">Draft a Registration Invitation</a></li>
+            {% if perms.scipost.can_create_registration_invitations %}
                 <li><a href="{% url 'invitations:list' %}">Manage Registration Invitations</a></li>
             {% endif %}
-            {% if perms.scipost.can_manage_registration_invitations %}
-                <li><a href="{% url 'scipost:registration_invitations' %}">Manage Registration Invitations</a></li>
-            {% endif %}
         </ul>
 
         {% if perms.scipost.can_manage_registration_invitations %}
             <h3>Notifications</h3>
             <ul>
-                <li><a href="{% url 'scipost:citation_notifications' %}">Manage citation notifications</a></li>
+                <li><a href="{% url 'invitations:citation_notification_list' %}">Manage citation notifications</a></li>
             </ul>
         {% endif %}
 
diff --git a/scipost/urls.py b/scipost/urls.py
index 421a4e2bc..9da0a19f2 100644
--- a/scipost/urls.py
+++ b/scipost/urls.py
@@ -84,38 +84,8 @@ urlpatterns = [
     url(r'^registration_requests/(?P<contributor_id>[0-9]+)/reset$',
         views.registration_requests_reset, name="registration_requests_reset"),
 
-    # # Invitations
-    # url(r'^registration_invitations$',
-    #     views.registration_invitation_list, name="registration_invitations"),
-    # url(r'^registration_invitations/(?P<invitation_id>[0-9]+)/renew$',
-    #     views.renew_registration_invitation, name="renew_registration_invitation"),
-    # url(r'^registration_invitations/(?P<draft_id>[0-9]+)$',
-    #     views.registration_invitations_form, name="registration_invitations_from_draft"),
-    # url(r'^registration_invitations/cleanup$', views.registration_invitations_cleanup,
-    #     name="registration_invitations_cleanup"),
-    # url(r'^registration_invitations/(?P<invitation_id>[0-9]+)/remove$',
-    #     views.remove_registration_invitation, name="remove_registration_invitation"),
-    # url(r'^registration_invitations/(?P<invitation_id>[0-9]+)/mark_declined$',
-    #     views.registration_invitation_mark_declined,
-    #     name="registration_invitation_mark_declined"),
-    #
-    # # Draft Invitations
-    # url(r'^registration_invitations/drafts$',
-    #     views.draft_registration_invitation, name="draft_registration_invitation"),
-    # url(r'^registration_invitations/drafts/(?P<draft_id>[0-9]+)$',
-    #     views.draft_registration_invitation_form, name="draft_registration_invitation_form"),
-    # url(r'^registration_invitations/drafts/(?P<draft_id>[0-9]+)/map_to_contributor/(?P<contributor_id>[0-9]+)$',
-    #     views.map_draft_reg_inv_to_contributor, name="map_draft_reg_inv_to_contributor"),
-    # url(r'^registration_invitations/drafts/(?P<draft_id>[0-9]+)/mark_processed$',
-    #     views.draft_registration_invitation_processed, name='mark_draft_inv_as_processed'),
-    # url(r'^contributors_filter$', views.contributors_filter, name="contributors_filter"),
-
-    # Registration invitations
+    # Registration invitations (Never change this route! Thank you.)
     url(r'^invitation/(?P<key>.+)$', views.invitation, name='invitation'),
-    # url(r'^citation_notifications$',
-    #     views.citation_notifications, name='citation_notifications'),
-    # url(r'^process_citation_notification/(?P<cn_id>[0-9]+)$',
-    #     views.process_citation_notification, name='process_citation_notification'),
 
     # Authentication
     url(r'^login/$', views.login_view, name='login'),
diff --git a/scipost/utils.py b/scipost/utils.py
index 350d1eabe..de045624e 100644
--- a/scipost/utils.py
+++ b/scipost/utils.py
@@ -1,17 +1,3 @@
-import datetime
-import hashlib
-import random
-import string
-
-from django.contrib.auth.models import User
-
-from django.core.mail import EmailMultiAlternatives
-from django.core.urlresolvers import reverse
-from django.template import Context, Template
-from django.utils import timezone
-
-from .models import DraftInvitation, RegistrationInvitation
-
 from common.utils import BaseMailUtil
 
 
@@ -106,480 +92,3 @@ class Utils(BaseMailUtil):
         cls._send_mail(cls, 'new_activation_link',
                        [cls._context['contributor'].user.email],
                        'new email activation link')
-
-    # @classmethod
-    # def send_registration_invitation_email(cls, renew=False):
-    #     signature = (cls.invitation.invited_by.get_title_display() + ' '
-    #                  + cls.invitation.invited_by.user.first_name + ' '
-    #                  + cls.invitation.invited_by.user.last_name)
-    #     if not renew:
-    #         # Generate email activation key and link
-    #         salt = ""
-    #         for i in range(5):
-    #             salt = salt + random.choice(string.ascii_letters)
-    #         salt = salt.encode('utf8')
-    #         invitationsalt = cls.invitation.last_name
-    #         invitationsalt = invitationsalt.encode('utf8')
-    #         cls.invitation.invitation_key = hashlib.sha1(salt+invitationsalt).hexdigest()
-    #     cls.invitation.key_expires = datetime.datetime.strftime(
-    #         datetime.datetime.now() + datetime.timedelta(days=365), "%Y-%m-%d %H:%M:%S")
-    #     if renew:
-    #         cls.invitation.nr_reminders += 1
-    #         cls.invitation.date_last_reminded = timezone.now()
-    #     cls.invitation.save()
-    #     email_text = ''
-    #     email_text_html = ''
-    #     email_context = {}
-    #     if renew:
-    #         email_text += ('Reminder: Invitation to SciPost\n'
-    #                        '-------------------------------\n\n')
-    #         email_text_html += ('<strong>Reminder: Invitation to SciPost</strong>'
-    #                             '<br/><hr/><br/>')
-    #     if cls.invitation.invitation_type == 'F':
-    #         email_text += 'RE: Invitation to join the Editorial College of SciPost\n\n'
-    #         email_text_html += ('<strong>RE: Invitation to join the Editorial College '
-    #                             'of SciPost</strong><br/><hr/><br/>')
-    #     email_text += 'Dear '
-    #     email_text_html += 'Dear '
-    #     if cls.invitation.message_style == 'F':
-    #         email_text += cls.invitation.get_title_display() + ' ' + cls.invitation.last_name
-    #         email_text_html += '{{ title }} {{ last_name }}'
-    #         email_context['title'] = cls.invitation.get_title_display()
-    #         email_context['last_name'] = cls.invitation.last_name
-    #     else:
-    #         email_text += cls.invitation.first_name
-    #         email_text_html += '{{ first_name }}'
-    #         email_context['first_name'] = cls.invitation.first_name
-    #     email_text += ',\n\n'
-    #     email_text_html += ',<br/>'
-    #     if len(cls.invitation.personal_message) > 3:
-    #         email_text += cls.invitation.personal_message + '\n\n'
-    #         email_text_html += '\n{{ personal_message|linebreaks }}<br/>\n'
-    #         email_context['personal_message'] = cls.invitation.personal_message
-    #
-    #     # This text to be put in C, ci invitations
-    #     summary_text = (
-    #         '\n\nIn summary, SciPost.org is a publication portal managed by '
-    #         'professional scientists, offering (among others) high-quality '
-    #         'Open Access journals with innovative forms of refereeing, and a '
-    #         'means of commenting on all existing literature. SciPost is established as '
-    #         'a not-for-profit foundation devoted to serving the interests of the '
-    #         'international scientific community.'
-    #         '\n\nThe site is anchored at https://scipost.org. Many further details '
-    #         'about SciPost, its principles, ideals and implementation can be found at '
-    #         'https://scipost.org/about and https://scipost.org/FAQ.'
-    #         '\n\nAs a professional academic, you can register at '
-    #         'https://scipost.org/register, enabling you to contribute to the site\'s '
-    #         'contents, for example by offering submissions, reports and comments.'
-    #         '\n\nFor your convenience, a partly pre-filled registration '
-    #         'form has been prepared for you at '
-    #         'https://scipost.org/invitation/' + cls.invitation.invitation_key
-    #         + ' (you can in any case still register at '
-    #         'https://scipost.org/register).\n\n'
-    #         'If you do develop sympathy for the initiative, besides participating in the '
-    #         'online platform, we would be very grateful if you considered submitting a '
-    #         'publication to one of the journals within the near future, in order to help '
-    #         'establish their reputation. We\'ll also be looking forward to your reaction, '
-    #         'comments and suggestions about the initiative, which we hope you will find '
-    #         'useful to your work as a professional scientist.'
-    #         '\n\nMany thanks in advance for taking a few minutes to look into it,'
-    #         '\n\nOn behalf of the SciPost Foundation,\n\n'
-    #         + signature + '\n'
-    #     )
-    #
-    #     summary_text_html = (
-    #         '\n<p>In summary, SciPost.org is a publication portal managed by '
-    #         'professional scientists, offering (among others) high-quality '
-    #         'Open Access journals with innovative forms of refereeing, and a '
-    #         'means of commenting on all existing literature. SciPost is established as '
-    #         'a not-for-profit foundation devoted to serving the interests of the '
-    #         'international scientific community.</p>'
-    #         '\n<p>The site is anchored at <a href="https://scipost.org">scipost.org</a>. '
-    #         'Many further details '
-    #         'about SciPost, its principles, ideals and implementation can be found at '
-    #         'the <a href="https://scipost.org/about">about</a> '
-    #         'and <a href="https://scipost.org/FAQ">FAQ</a> pages.</p>'
-    #         '<p>As a professional academic, you can register at the '
-    #         '<a href="https://scipost.org/register">registration page</a>, '
-    #         'enabling you to contribute to the site\'s '
-    #         'contents, for example by offering submissions, reports and comments.</p>'
-    #         '\n<p>For your convenience, a partly pre-filled '
-    #         '<a href="https://scipost.org/invitation/{{ invitation_key }}">registration form</a>'
-    #         ' has been prepared for you (you can in any case still register at the '
-    #         '<a href="https://scipost.org/register">registration page</a>).</p>'
-    #         '\n<p>If you do develop sympathy for the initiative, besides participating in the '
-    #         'online platform, we would be very grateful if you considered submitting a '
-    #         'publication to one of the journals within the near future, in order to help '
-    #         'establish their reputation. We\'ll also be looking forward to your reaction, '
-    #         'comments and suggestions about the initiative, which we hope you will find '
-    #         'useful to your work as a professional scientist.</p>'
-    #         '\n<p>Many thanks in advance for taking a few minutes to look into it,</p>'
-    #         '<p>On behalf of the SciPost Foundation,</p>'
-    #         '<p>' + signature + '</p>'
-    #     )
-    #     email_context['invitation_key'] = cls.invitation.invitation_key
-    #
-    #     if cls.invitation.invitation_type == 'R':
-    #         # Refereeing invitation
-    #         # Details of the submission to referee are already in the personal_message field
-    #         email_text += (
-    #             'We would hereby like to cordially invite you '
-    #             'to become a Contributor on SciPost '
-    #             '(this is required in order to deliver reports; '
-    #             'our records show that you are not yet registered); '
-    #             'for your convenience, we have prepared a pre-filled form for you at\n\n'
-    #             'https://scipost.org/invitation/' + cls.invitation.invitation_key + '\n\n'
-    #             'after which your registration will be activated, allowing you to contribute, '
-    #             'in particular by providing referee reports.\n\n'
-    #             'To ensure timely processing of the submission (out of respect for the authors), '
-    #             'we would appreciate a quick accept/decline '
-    #             'response from you, ideally within the next 2 days.\n\n'
-    #             'If you are not able to provide a Report, you can let us know by simply '
-    #             'navigating to \n\nhttps://scipost.org/submissions/decline_ref_invitation/'
-    #             + cls.invitation.invitation_key + '\n\n'
-    #             'If you are able to provide a Report, you can confirm this after registering '
-    #             'and logging in (you will automatically be prompted for a confirmation).\n\n'
-    #             'We very much hope that we can count on your expertise,\n\n'
-    #             'Many thanks in advance,\n\nThe SciPost Team')
-    #         email_text_html += (
-    #             '\n<p>We would hereby like to cordially invite you '
-    #             'to become a Contributor on SciPost '
-    #             '(this is required in order to deliver reports; '
-    #             'our records show that you are not yet registered); '
-    #             'for your convenience, we have prepared a pre-filled '
-    #             '<a href="https://scipost.org/invitation/{{ invitation_key }}">registration form</a> '
-    #             'for you. After activation of your registration, you will be allowed to contribute, '
-    #             'in particular by providing referee reports.</p>'
-    #             '<p>To ensure timely processing of the submission (out of respect for the authors), '
-    #             'we would appreciate a quick accept/decline '
-    #             'response from you, ideally within the next 2 days.</p>'
-    #             '<p>If you are <strong>not</strong> able to provide a Report, '
-    #             'you can let us know by simply '
-    #             '<a href="https://scipost.org/submissions/decline_ref_invitation/{{ invitation_key }}">'
-    #             'clicking here</a>.</p>'
-    #             '<p>If you are able to provide a Report, you can confirm this after registering '
-    #             'and logging in (you will automatically be prompted for a confirmation).</p>'
-    #             '<p>We very much hope that we can count on your expertise,</p>'
-    #             '<p>Many thanks in advance,</p>'
-    #             '<p>The SciPost Team</p>')
-    #
-    #         email_text += SCIPOST_SUMMARY_FOOTER
-    #         email_text_html += SCIPOST_SUMMARY_FOOTER_HTML
-    #         email_text_html += '<br/>' + EMAIL_FOOTER
-    #         html_template = Template(email_text_html)
-    #         html_version = html_template.render(Context(email_context))
-    #         emailmessage = EmailMultiAlternatives(
-    #             'SciPost: refereeing request (and registration invitation)', email_text,
-    #             'SciPost Refereeing <refereeing@scipost.org>',
-    #             [cls.invitation.email],
-    #             cc=[cls.invitation.invited_by.user.email],
-    #             bcc=['refereeing@scipost.org'],
-    #             reply_to=['refereeing@scipost.org'])
-    #         emailmessage.attach_alternative(html_version, 'text/html')
-    #
-    #     elif cls.invitation.invitation_type == 'ci':
-    #         # Has been cited in a Submission. Invite!
-    #         email_text += (
-    #             'Your work has been cited in a manuscript submitted to SciPost,'
-    #             '\n\n' + cls.invitation.cited_in_submission.title
-    #             + ' by ' + cls.invitation.cited_in_submission.author_list + '.\n\n'
-    #             'I would hereby like to use this opportunity to quickly introduce '
-    #             'you to the SciPost initiative, and to invite you to become an active '
-    #             'Contributor to the site. You might for example consider reporting or '
-    #             'commenting on the above submission before the refereeing deadline.')
-    #         email_text_html += (
-    #             '<p>Your work has been cited in a manuscript submitted to SciPost,</p>'
-    #             '<p>{{ sub_title }} <br>by {{ sub_author_list }},</p>'
-    #             '<p>which you can find online at the '
-    #             '<a href="https://scipost.org/submission/{{ arxiv_nr_w_vn_nr }}">'
-    #             'submission\'s page</a>.</p>'
-    #             '\n<p>I would hereby like to use this opportunity to quickly introduce '
-    #             'you to the SciPost initiative, and to invite you to become an active '
-    #             'Contributor to the site. You might for example consider reporting or '
-    #             'commenting on the above submission before the refereeing deadline.</p>')
-    #         email_context['sub_title'] = cls.invitation.cited_in_submission.title
-    #         email_context['sub_author_list'] = cls.invitation.cited_in_submission.author_list
-    #         email_context['arxiv_identifier_w_vn_nr'] = cls.invitation.cited_in_submission.arxiv_identifier_w_vn_nr
-    #
-    #         email_text += summary_text
-    #         email_text_html += summary_text_html
-    #         email_text_html += '<br/>' + EMAIL_FOOTER
-    #         html_template = Template(email_text_html)
-    #         html_version = html_template.render(Context(email_context))
-    #         emailmessage = EmailMultiAlternatives(
-    #             'SciPost: invitation', email_text,
-    #             'SciPost registration <registration@scipost.org>',
-    #             [cls.invitation.email],
-    #             cc=[cls.invitation.invited_by.user.email],
-    #             bcc=['registration@scipost.org'],
-    #             reply_to=['registration@scipost.org'])
-    #         emailmessage.attach_alternative(html_version, 'text/html')
-    #
-    #     elif cls.invitation.invitation_type == 'cp':
-    #         # Has been cited in a Publication. Invite!
-    #         email_text += (
-    #             'Your work has been cited in a paper published by SciPost,'
-    #             '\n\n' + cls.invitation.cited_in_publication.title
-    #             + '\nby ' + cls.invitation.cited_in_publication.author_list +
-    #             '\n\n(published as ' + cls.invitation.cited_in_publication.citation()
-    #             + ').\n\n'
-    #             'I would hereby like to use this opportunity to quickly introduce '
-    #             'you to the SciPost initiative, and to invite you to become an active '
-    #             'Contributor to the site.')
-    #         email_text_html += (
-    #             '<p>Your work has been cited in a paper published by SciPost,</p>'
-    #             '<p>{{ pub_title }}</p> <p>by {{ pub_author_list }}</p>'
-    #             '(published as <a href="https://scipost.org/{{ doi_label }}">{{ citation }}</a>).'
-    #             '</p>'
-    #             '\n<p>I would hereby like to use this opportunity to quickly introduce '
-    #             'you to the SciPost initiative, and to invite you to become an active '
-    #             'Contributor to the site.</p>')
-    #         email_context['pub_title'] = cls.invitation.cited_in_publication.title
-    #         email_context['pub_author_list'] = cls.invitation.cited_in_publication.author_list
-    #         email_context['doi_label'] = cls.invitation.cited_in_publication.doi_label
-    #         email_context['citation'] = cls.invitation.cited_in_publication.citation()
-    #         email_text += summary_text
-    #         email_text_html += summary_text_html
-    #         email_text_html += '<br/>' + EMAIL_FOOTER
-    #         html_template = Template(email_text_html)
-    #         html_version = html_template.render(Context(email_context))
-    #         emailmessage = EmailMultiAlternatives(
-    #             'SciPost: invitation', email_text,
-    #             'SciPost registration <registration@scipost.org>',
-    #             [cls.invitation.email],
-    #             cc=[cls.invitation.invited_by.user.email],
-    #             bcc=['registration@scipost.org'],
-    #             reply_to=['registration@scipost.org'])
-    #         emailmessage.attach_alternative(html_version, 'text/html')
-    #
-    #     elif cls.invitation.invitation_type == 'C':
-    #         email_text += ('I would hereby like to quickly introduce '
-    #                        'you to a scientific publishing initiative '
-    #                        'called SciPost, and to invite you to become an active Contributor.')
-    #         email_text += summary_text
-    #         email_text_html += (
-    #             '<p>I would hereby like to quickly introduce '
-    #             'you to a scientific publishing initiative '
-    #             'called SciPost, and to invite you to become an active Contributor.</p>')
-    #         email_text_html += summary_text_html + '<br/>' + EMAIL_FOOTER
-    #         html_template = Template(email_text_html)
-    #         html_version = html_template.render(Context(email_context))
-    #         emailmessage = EmailMultiAlternatives(
-    #             'SciPost: invitation', email_text,
-    #             'SciPost registration <registration@scipost.org>',
-    #             [cls.invitation.email],
-    #             cc=[cls.invitation.invited_by.user.email],
-    #             bcc=['registration@scipost.org'],
-    #             reply_to=['registration@scipost.org'])
-    #         emailmessage.attach_alternative(html_version, 'text/html')
-    #
-    #     elif cls.invitation.invitation_type == 'F':
-    #         email_text += (
-    #             '\nYou will perhaps have already heard about SciPost, a publication '
-    #             'portal established by and for professional scientists.\n'
-    #             '\nSciPost.org is legally based on a not-for-profit foundation and will '
-    #             'operate in perpetuity as a non-commercial entity at the exclusive service '
-    #             'of the academic sector, bringing a cost-slashing alternative to existing '
-    #             'practices.\n'
-    #             '\nSciPost offers a collection of two-way open '
-    #             'access (no subscription fees, no author fees) journals with extremely '
-    #             'stringent (peer-witnessed) refereeing, overseen by '
-    #             'our Editorial College (exclusively composed '
-    #             'of established, professionally practising scientists). The whole process is '
-    #             'designed to ensure the highest achievable scientific quality while making the '
-    #             'editorial workflow as light and efficient as possible.\n'
-    #             '\nTo go straight to the point, on behalf of the foundation '
-    #             'and in view of your professional expertise, I hereby would '
-    #             'like to invite you to become an Editorial Fellow and thus join the '
-    #             'Editorial College of SciPost Physics.\n\n'
-    #             'Please note that only well-known and respected senior academics are '
-    #             'being contacted for this purpose. Academic reputation and involvement '
-    #             'in the community are the most important criteria guiding our '
-    #             'considerations of who should belong to the Editorial College.\n'
-    #             '\nTo help you in considering this, it would be best if you were to take '
-    #             'the time to look at the website itself, which is anchored at scipost.org. '
-    #             'Besides looking around the side, you can also personally register '
-    #             '(to become a Contributor, without necessarily committing to membership '
-    #             'of the Editorial College, this to be discussed separately) by visiting '
-    #             'the following single-use link, containing a partly pre-filled form for '
-    #             'your convenience: \n\n'
-    #             'https://scipost.org/invitation/' + cls.invitation.invitation_key + '.\n'
-    #             '\nMany details about the initiative '
-    #             'can then be found at scipost.org/about and at scipost.org/FAQ. '
-    #             'Functioning of the College will proceed according to the by-laws set '
-    #             'out in scipost.org/EdCol_by-laws.\n\n'
-    #             'Since the success of this initiative is dependent on the involvement of '
-    #             'the very people it is meant to serve, we\'d be very grateful if you were '
-    #             'to give due consideration to this proposal. We would expect you to '
-    #             'commit just 2-4 hours per month to help perform Editorial duties; we will '
-    #             'adjust the number of Editorial Fellows to ensure this is the case. You '
-    #             'could try it out for 6 months or a year, and of course you could quit '
-    #             'any time you wished.\n\n'
-    #             'I\'d be happy to provide you with more information, should you require '
-    #             'it. In view of our development plans, I would be grateful if you could '
-    #             'react (by replying to this email) within the next two or three weeks, '
-    #             'if possible. I\'ll be looking forward to your reaction, your comments '
-    #             'and suggestions, be they positive or negative. If you need more time '
-    #             'to consider, that\'s also fine; just let me know.\n\n'
-    #             'On behalf of the SciPost Foundation,\n\n'
-    #             'Prof. dr Jean-Sébastien Caux\n---------------------------------------------'
-    #             '\nInstitute for Theoretial Physics\nUniversity of Amsterdam'
-    #             '\nScience Park 904\n1098 XH Amsterdam\nThe Netherlands'
-    #             '\n---------------------------------------------\n'
-    #             'tel.: +31 (0)20 5255775\nfax: +31 (0)20 5255778'
-    #             '\n---------------------------------------------')
-    #         email_text_html += (
-    #             '\n<p>You will perhaps have already heard about SciPost, a publication '
-    #             'portal established by and for professional scientists. '
-    #             '\n<p>SciPost.org is legally based on a not-for-profit foundation and will '
-    #             'operate in perpetuity as a non-commercial entity at the exclusive service '
-    #             'of the academic sector, bringing a cost-slashing alternative to existing '
-    #             'practices.</p>'
-    #             '<p>SciPost offers a collection of two-way open '
-    #             'access (no subscription fees, no author fees) journals with extremely '
-    #             'stringent (peer-witnessed) refereeing, overseen by '
-    #             'our Editorial College (exclusively composed '
-    #             'of established, professionally practising scientists). The whole process is '
-    #             'designed to ensure the highest achievable scientific quality while making the '
-    #             'editorial workflow as light and efficient as possible.</p>'
-    #             '\n<p>To go straight to the point, on behalf of the SciPost Foundation '
-    #             'and in view of your professional expertise, I hereby would '
-    #             'like to invite you to become an Editorial Fellow and thus join the '
-    #             'Editorial College of SciPost Physics.</p>'
-    #             '\n<p>Please note that only well-known and respected senior academics are '
-    #             'being contacted for this purpose. Academic reputation and involvement '
-    #             'in the community are the most important criteria guiding our '
-    #             'considerations of who should belong to the Editorial College.</p>'
-    #             '\n<p>To help you in considering this, it would be best if you were to take '
-    #             'the time to look at the website itself, which is anchored at scipost.org. '
-    #             'Besides looking around the site, you can also personally register '
-    #             '(to become a Contributor, without necessarily committing to membership '
-    #             'of the Editorial College, this to be discussed separately) by visiting '
-    #             'the following <a href="https://scipost.org/invitation/{{ invitation_key }}">'
-    #             'single-use link</a>, containing a partly pre-filled form for '
-    #             'your convenience.</p>'
-    #             '\n<p>Many details about the initiative '
-    #             'can then be found at scipost.org/about and at scipost.org/FAQ. '
-    #             'Functioning of the College will proceed according to the by-laws set '
-    #             'out in scipost.org/EdCol_by-laws.</p>'
-    #             '\n<p>Since the success of this initiative is dependent on the involvement of '
-    #             'the very people it is meant to serve, we\'d be very grateful if you were '
-    #             'to give due consideration to this proposal. We would expect you to '
-    #             'commit just 2-4 hours per month to help perform Editorial duties; we will '
-    #             'constantly adjust the number of Editorial Fellows to ensure this is the case. You '
-    #             'could try it out for 6 months or a year, and of course you could quit '
-    #             'any time you wished.</p>'
-    #             '\n<p>I\'d be happy to provide you with more information, should you require '
-    #             'it. In view of our development plans, I would be grateful if you could '
-    #             'react (by replying to this email) within the next two or three weeks, '
-    #             'if possible. I\'ll be looking forward to your reaction, your comments '
-    #             'and suggestions, be they positive or negative. If you need more time '
-    #             'to consider, that\'s also fine; just let me know.</p>'
-    #             '<p>On behalf of the SciPost Foundation,</p>'
-    #             '<br/>Prof. dr Jean-Sébastien Caux'
-    #             '<br/>---------------------------------------------'
-    #             '<br/>Institute for Theoretial Physics'
-    #             '<br/>University of Amsterdam'
-    #             '<br/>Science Park 904<br/>1098 XH Amsterdam<br/>The Netherlands'
-    #             '<br/>---------------------------------------------'
-    #             '<br/>tel.: +31 (0)20 5255775\nfax: +31 (0)20 5255778'
-    #             '<br/>---------------------------------------------\n')
-    #
-    #         email_text_html += '<br/>' + EMAIL_FOOTER
-    #         html_template = Template(email_text_html)
-    #         html_version = html_template.render(Context(email_context))
-    #         emailmessage = EmailMultiAlternatives(
-    #             'SciPost registration invitation', email_text,
-    #             'J-S Caux <jscaux@scipost.org>',
-    #             [cls.invitation.email],
-    #             cc=[cls.invitation.invited_by.user.email],
-    #             bcc=['registration@scipost.org'],
-    #             reply_to=['registration@scipost.org'])
-    #         emailmessage.attach_alternative(html_version, 'text/html')
-    #
-    #     # This function is now for all invitation types:
-    #     emailmessage.send(fail_silently=False)
-
-    @classmethod
-    def send_citation_notification_email(cls):
-        """
-        Requires loading the 'notification' attribute.
-        """
-        email_context = {}
-        email_text = ('Dear ' + cls.notification.contributor.get_title_display() +
-                      ' ' + cls.notification.contributor.user.last_name)
-        email_text_html = 'Dear {{ title }} {{ last_name }}'
-        email_context['title'] = cls.notification.contributor.get_title_display()
-        email_context['last_name'] = cls.notification.contributor.user.last_name
-        email_text += ',\n\n'
-        email_text_html += ',<br/>'
-        if cls.notification.cited_in_publication:
-            url_unsubscribe = reverse('scipost:unsubscribe',
-                                      args=[cls.notification.contributor.id,
-                                            cls.notification.contributor.activation_key])
-            email_text += (
-                'We would like to notify you that '
-                'your work has been cited in a paper published by SciPost,'
-                '\n\n' + cls.notification.cited_in_publication.title
-                + '\nby ' + cls.notification.cited_in_publication.author_list +
-                '\n\n(published as ' + cls.notification.cited_in_publication.citation() +
-                ').\n\nWe hope you will find this paper of interest to your own research.'
-                '\n\nBest regards,\n\nThe SciPost Team'
-                '\n\nDon\'t want to receive such emails? Unsubscribe by visiting '
-                + url_unsubscribe + '.')
-            email_text_html += (
-                '<p>We would like to notify you that '
-                'your work has been cited in a paper published by SciPost,</p>'
-                '<p>{{ title }}</p><p>by {{ pub_author_list }}</p>'
-                '<p>(published as <a href="https://scipost.org/{{ doi_label }}">'
-                '{{ citation }}</a>).</p>'
-                '<p>We hope you will find this paper of interest to your own research.</p>'
-                '<p>Best regards,</p><p>The SciPost Team</p><br/>'
-                + EMAIL_FOOTER + '<br/>'
-                '\n<p style="font-size: 10px;">Don\'t want to receive such emails? '
-                '<a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe)
-            email_context['title'] = cls.notification.cited_in_publication.title
-            email_context['pub_author_list'] = cls.notification.cited_in_publication.author_list
-            email_context['doi_label'] = cls.notification.cited_in_publication.doi_label
-            email_context['citation'] = cls.notification.cited_in_publication.citation()
-            email_context['key'] = cls.notification.contributor.activation_key
-            html_template = Template(email_text_html)
-            html_version = html_template.render(Context(email_context))
-        elif cls.notification.cited_in_submission:
-            url_unsubscribe = reverse('scipost:unsubscribe',
-                                      args=[cls.notification.contributor.id,
-                                            cls.notification.contributor.activation_key])
-            email_text += (
-                'Your work has been cited in a manuscript submitted to SciPost,'
-                '\n\n' + cls.notification.cited_in_submission.title
-                + ' by ' + cls.notification.cited_in_submission.author_list + '.\n\n'
-                'You might for example consider reporting or '
-                'commenting on the above submission before the refereeing deadline.\n\n'
-                'Best regards,\n\nThe SciPost Team'
-                '\n\nDon\'t want to receive such emails? Unsubscribe by visiting '
-                + url_unsubscribe + '.')
-            email_text_html += (
-                '<p>Your work has been cited in a manuscript submitted to SciPost,</p>'
-                '<p>{{ sub_title }} <br>by {{ sub_author_list }},</p>'
-                '<p>which you can find online at the '
-                '<a href="https://scipost.org/submission/{{ arxiv_nr_w_vn_nr }}">'
-                'submission\'s page</a>.</p>'
-                '<p>You might for example consider reporting or '
-                'commenting on the above submission before the refereeing deadline.</p>'
-                '<p>Best regards,</p><p>The SciPost Team</p><br/>'
-                + EMAIL_FOOTER + '<br/>'
-                '\n<p style="font-size: 10px;">Don\'t want to receive such emails? '
-                '<a href="%s">Unsubscribe</a>.</p>' % url_unsubscribe)
-            email_context['sub_title'] = cls.notification.cited_in_submission.title
-            email_context['sub_author_list'] = cls.notification.cited_in_submission.author_list
-            email_context['arxiv_identifier_w_vn_nr'] = cls.notification.cited_in_submission.arxiv_identifier_w_vn_nr
-            email_context['key'] = cls.notification.contributor.activation_key
-
-        emailmessage = EmailMultiAlternatives(
-            'SciPost: citation notification', email_text,
-            'SciPost admin <admin@scipost.org>',
-            [cls.notification.contributor.user.email],
-            bcc=['admin@scipost.org'],
-            reply_to=['admin@scipost.org'])
-        emailmessage.attach_alternative(html_version, 'text/html')
-        emailmessage.send(fail_silently=False)
diff --git a/scipost/views.py b/scipost/views.py
index bdcdfffc4..3097897d4 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -350,260 +350,6 @@ def registration_requests_reset(request, contributor_id):
     return redirect(reverse('scipost:registration_requests'))
 
 
-# @permission_required('scipost.can_draft_registration_invitations', return_403=True)
-# def draft_registration_invitation(request):
-#     """
-#     For officers to prefill registration invitations.
-#     This is similar to the registration_invitations method,
-#     which is used to complete the invitation process.
-#     """
-#     form = DraftInvitationForm(request.POST or None, current_user=request.user)
-#     if form.is_valid():
-#         invitation = form.save(commit=False)
-#         invitation.drafted_by = request.user.contributor
-#         invitation.save()
-#
-#         # Assign permission to 'drafter' to edit the draft afterwards
-#         assign_perm('comments.change_draftinvitation', request.user, invitation)
-#         messages.success(request, 'Draft invitation saved.')
-#         return redirect(reverse('scipost:draft_registration_invitation'))
-#
-#     existing_drafts = DraftInvitation.objects.filter(processed=False).order_by('last_name')
-#
-#     context = {
-#         'form': form,
-#         'existing_drafts': existing_drafts,
-#     }
-#     return render(request, 'scipost/draft_registration_invitation.html', context)
-
-
-# @permission_required('scipost.can_draft_registration_invitations', return_403=True)
-# def contributors_filter(request):
-#     """
-#     For Invitation Officers that use lists of scientists as a to-do. This
-#     view returns all entries of those lists with users that are certainly not registered
-#     or invitated.
-#     """
-#     names_found = names_not_found = invitations_found = None
-#     form = ContributorsFilterForm(request.POST or None)
-#     if form.is_valid():
-#         names_found, names_not_found, invitations_found = form.filter()
-#
-#     context = {
-#         'form': form,
-#         'names_found': names_found,
-#         'names_not_found': names_not_found,
-#         'invitations_found': invitations_found,
-#     }
-#     return render(request, 'scipost/contributors_filter.html', context)
-
-
-# @login_required
-# def draft_registration_invitation_form(request, draft_id):
-#     """
-#     Edit DraftInvitation instance. It's only possible to edit istances created by the User itself.
-#     """
-#     draft = get_object_or_404((get_objects_for_user(request.user, 'scipost.change_draftinvitation')
-#                                .filter(processed=False)),
-#                               id=draft_id)
-#
-#     draft_inv_form = DraftInvitationForm(request.POST or None, current_user=request.user,
-#                                          instance=draft)
-#     if draft_inv_form.is_valid():
-#         draft = draft_inv_form.save()
-#         messages.success(request, 'Draft invitation saved.')
-#         return redirect(reverse('scipost:registration_invitations'))
-#
-#     context = {'draft_inv_form': draft_inv_form}
-#     return render(request, 'scipost/draft_registration_invitation_form.html', context)
-
-
-# @permission_required('scipost.can_manage_registration_invitations', return_403=True)
-# def map_draft_reg_inv_to_contributor(request, draft_id, contributor_id):
-#     """
-#     If a draft invitation actually points to an already-registered
-#     Contributor, this method marks the draft invitation as processed
-#     and, if the draft invitation was for a citation type,
-#     creates an instance of CitationNotification.
-#     """
-#     draft = get_object_or_404(DraftInvitation, id=draft_id)
-#     contributor = get_object_or_404(Contributor, id=contributor_id)
-#     draft.processed = True
-#     draft.save()
-#     citation = CitationNotification(
-#         contributor=contributor,
-#         cited_in_submission=draft.cited_in_submission,
-#         cited_in_publication=draft.cited_in_publication,
-#         processed=False)
-#     citation.save()
-#     return redirect(reverse('scipost:registration_invitations'))
-
-
-# @permission_required('scipost.can_invite_fellows', return_403=True)
-# def registration_invitations_form(request, draft_id):
-#     draft = get_object_or_404(DraftInvitation, id=draft_id)
-#     initial = {
-#         'title': draft.title,
-#         'first_name': draft.first_name,
-#         'last_name': draft.last_name,
-#         'email': draft.email,
-#         'invitation_type': draft.invitation_type,
-#         'cited_in_submission': draft.cited_in_submission,
-#         'cited_in_publication': draft.cited_in_publication,
-#     }
-#     form = RegistrationInvitationForm(request.POST or None, initial=initial,
-#                                       current_user=request.user)
-#     mail_request = MailEditingSubView(request, mail_code='registration_invitation',
-#                                       invitation=draft)
-#     if form.is_valid():
-#         if mail_request.is_valid():
-#             invitation = form.save(commit=False)
-#             invitation.invited_by = request.user.contributor
-#             invitation.save()
-#             invitation.refresh_keys()
-#
-#             Utils.load({'invitation': invitation})
-#             Utils.send_registration_invitation_email()
-#             DraftInvitation.objects.filter(email=form.cleaned_data['email']).update(processed=True)
-#
-#             messages.success(request, 'Registration Invitation sent')
-#             return redirect(reverse('scipost:registration_invitations'))
-#         else:
-#             mail_request.add_form(form)
-#             return mail_request.return_render()
-#     context = {
-#         'form': form,
-#     }
-#     return render(request, 'scipost/registration_invitation_form.html', context)
-
-
-# @permission_required('scipost.can_invite_fellows', return_403=True)
-# def registration_invitation_list(request):
-#     """ Overview and tools for administrators """
-#     sent_reg_inv = RegistrationInvitation.objects.filter(responded=False, declined=False)
-#     sent_reg_inv_fellows = sent_reg_inv.filter(invitation_type='F').order_by('last_name')
-#     sent_reg_inv_contrib = sent_reg_inv.filter(invitation_type='C').order_by('last_name')
-#     sent_reg_inv_ref = sent_reg_inv.filter(invitation_type='R').order_by('last_name')
-#     sent_reg_inv_cited_sub = sent_reg_inv.filter(invitation_type='ci').order_by('last_name')
-#     sent_reg_inv_cited_pub = sent_reg_inv.filter(invitation_type='cp').order_by('last_name')
-#
-#     resp_reg_inv = RegistrationInvitation.objects.filter(responded=True, declined=False)
-#     resp_reg_inv_fellows = resp_reg_inv.filter(invitation_type='F').order_by('last_name')
-#     resp_reg_inv_contrib = resp_reg_inv.filter(invitation_type='C').order_by('last_name')
-#     resp_reg_inv_ref = resp_reg_inv.filter(invitation_type='R').order_by('last_name')
-#     resp_reg_inv_cited_sub = resp_reg_inv.filter(invitation_type='ci').order_by('last_name')
-#     resp_reg_inv_cited_pub = resp_reg_inv.filter(invitation_type='cp').order_by('last_name')
-#
-#     decl_reg_inv = RegistrationInvitation.objects.filter(responded=True, declined=True)
-#
-#     names_reg_contributors = Contributor.objects.active().order_by('user__last_name').values_list(
-#         'user__first_name', 'user__last_name')
-#     existing_drafts = DraftInvitation.objects.filter(
-#         processed=False).order_by('last_name', 'email')
-#
-#     context = {
-#         'sent_reg_inv_fellows': sent_reg_inv_fellows,
-#         'sent_reg_inv_contrib': sent_reg_inv_contrib,
-#         'sent_reg_inv_ref': sent_reg_inv_ref,
-#         'sent_reg_inv_cited_sub': sent_reg_inv_cited_sub,
-#         'sent_reg_inv_cited_pub': sent_reg_inv_cited_pub,
-#         'resp_reg_inv_fellows': resp_reg_inv_fellows,
-#         'resp_reg_inv_contrib': resp_reg_inv_contrib,
-#         'resp_reg_inv_ref': resp_reg_inv_ref,
-#         'resp_reg_inv_cited_sub': resp_reg_inv_cited_sub,
-#         'resp_reg_inv_cited_pub': resp_reg_inv_cited_pub,
-#         'decl_reg_inv': decl_reg_inv,
-#         'names_reg_contributors': names_reg_contributors,
-#         'existing_drafts': existing_drafts,
-#     }
-#     return render(request, 'scipost/registration_invitation_list.html', context)
-
-
-# @permission_required('scipost.can_manage_registration_invitations', return_403=True)
-# def registration_invitations_cleanup(request):
-#     """
-#     Compares the email addresses of invitations with those in the
-#     database of registered Contributors. Flags overlaps.
-#     """
-#     contributor_email_list = Contributor.objects.values_list('user__email', flat=True)
-#     invs_to_cleanup = RegistrationInvitation.objects.filter(
-#         responded=False, email__in=contributor_email_list)
-#     context = {'invs_to_cleanup': invs_to_cleanup}
-#     return render(request, 'scipost/registration_invitations_cleanup.html', context)
-
-
-# @permission_required('scipost.can_manage_registration_invitations', return_403=True)
-# def remove_registration_invitation(request, invitation_id):
-#     """
-#     Remove an invitation (called from registration_invitations_cleanup).
-#     """
-#     invitation = get_object_or_404(RegistrationInvitation, pk=invitation_id)
-#     invitation.delete()
-#     return redirect(reverse('scipost:registration_invitations_cleanup'))
-
-
-# @permission_required('scipost.can_invite_fellows', return_403=True)
-# def renew_registration_invitation(request, invitation_id):
-#     """
-#     Renew an invitation (called from registration_invitations).
-#     """
-#     invitation = get_object_or_404(RegistrationInvitation, pk=invitation_id)
-#
-#     # Utils.load({'invitation': invitation})
-#     # Utils.send_registration_invitation_email(True)
-#     mail_request = MailEditingSubView(request, mail_code='registration_invitation_renewal',
-#                                       invitation=invitation)
-#     if mail_request.is_valid():
-#         invitation.nr_reminders += 1
-#         invitation.date_last_reminded = timezone.now()
-#         invitation.save()
-#         invitation.refresh_keys()
-#         messages.success(request, 'Registration invitation has been sent.')
-#         mail_request.send()
-#         return redirect('scipost:registration_invitations')
-#     else:
-#         return mail_request.return_render()
-
-
-# @permission_required('scipost.can_manage_registration_invitations', return_403=True)
-# def registration_invitation_mark_declined(request, invitation_id):
-#     """
-#     Mark an invitation as declined (called from registration_invitation_list).
-#     """
-#     invitation = get_object_or_404(RegistrationInvitation, pk=invitation_id)
-#     invitation.responded = True
-#     invitation.declined = True
-#     invitation.save()
-#     return redirect(reverse('scipost:registration_invitations'))
-
-
-# @permission_required('scipost.can_manage_registration_invitations', return_403=True)
-# def citation_notifications(request):
-#     unprocessed_notifications = CitationNotification.objects.filter(
-#         processed=False).order_by('contributor__user__last_name')
-#     context = {'unprocessed_notifications': unprocessed_notifications, }
-#     return render(request, 'scipost/citation_notifications.html', context)
-
-
-# @permission_required('scipost.can_manage_registration_invitations', return_403=True)
-# def process_citation_notification(request, cn_id):
-#     notification = get_object_or_404(CitationNotification, id=cn_id)
-#     notification.processed = True
-#     notification.save()
-#     if notification.contributor.accepts_SciPost_emails:
-#         Utils.load({'notification': notification})
-#         Utils.send_citation_notification_email()
-#     return redirect(reverse('scipost:citation_notifications'))
-
-
-# @permission_required('scipost.can_manage_registration_invitations', return_403=True)
-# def draft_registration_invitation_processed(request, draft_id):
-#     draft = get_object_or_404(DraftInvitation, id=draft_id)
-#     draft.processed = True
-#     draft.save()
-#     return redirect(reverse('scipost:registration_invitations'))
-
-
 def login_view(request):
     """
     This view shows and processes a user's login session.
diff --git a/submissions/templatetags/lookup.py b/submissions/templatetags/lookup.py
index c2f7e5491..5f1f0e24f 100644
--- a/submissions/templatetags/lookup.py
+++ b/submissions/templatetags/lookup.py
@@ -27,4 +27,4 @@ class SubmissionLookup(LookupChannel):
         Right now only used for draft registration invitations. May be extended in the
         future for other purposes as well.
         """
-        return request.user.has_perm('can_draft_registration_invitations')
+        return request.user.has_perm('can_create_registration_invitations')
diff --git a/submissions/views.py b/submissions/views.py
index 838d26c1f..3e38c390d 100644
--- a/submissions/views.py
+++ b/submissions/views.py
@@ -40,11 +40,11 @@ from mails.views import MailEditingSubView
 from scipost.forms import ModifyPersonalMessageForm, RemarkForm
 from scipost.mixins import PaginationMixin
 from scipost.models import Contributor, Remark
-from scipost.utils import Utils
 
 from comments.forms import CommentForm
 from invitations.constants import INVITATION_REFEREEING
 from invitations.models import RegistrationInvitation
+from mails.utils import DirectMailUtil
 from production.forms import ProofsDecisionForm
 from production.models import ProductionStream
 
@@ -770,14 +770,15 @@ def recruit_referee(request, arxiv_identifier_w_vn_nr):
             ref_invitation = ref_recruit_form.save(commit=False)
             ref_invitation.submission = submission
             ref_invitation.invited_by = request.user.contributor
-            ref_invitation.save()
 
             # Create and send a registration invitation
-            ref_inv_message_head = """
-                On behalf of the Editor-in-charge {eic_title} {eic_last_name}, we would
-                like to invite you to referee a Submission to {journal},
-                namely {sub_title} by {sub_author_list} (see https://scipost.org/{sub_url}).
-                """.format(
+            ref_inv_message_head = (
+                'On behalf of the Editor-in-charge {eic_title} {eic_last_name}, we would'
+                'like to invite you to referee a Submission to {journal}, namely'
+                '\n{sub_title}'
+                '\nby {sub_author_list}'
+                '\n(see https://scipost.org/{sub_url}).'
+                ).format(
                     eic_title=submission.editor_in_charge.get_title_display(),
                     eic_last_name=submission.editor_in_charge.user.last_name,
                     journal=submission.get_submitted_to_journal_display(),
@@ -790,18 +791,20 @@ def recruit_referee(request, arxiv_identifier_w_vn_nr):
                 last_name=ref_recruit_form.cleaned_data['last_name'],
                 email=ref_recruit_form.cleaned_data['email_address'],
                 invitation_type=INVITATION_REFEREEING,
-                invited_by=request.user.contributor,
+                created_by=request.user.contributor.user,
+                invited_by=request.user.contributor.user,
                 personal_message=ref_inv_message_head)
 
             reg_invitation.save()
-            Utils.load({'invitation': reg_invitation})
-            Utils.send_registration_invitation_email()
+            # Copy the key to the refereeing invitation
+            ref_invitation.invitation_key = reg_invitation.invitation_key
+            ref_invitation.save()
+            mail_sender = DirectMailUtil(mail_code='registration_invitation',
+                                         instance=reg_invitation)
+            mail_sender.send()
             submission.add_event_for_author('A referee has been invited.')
             submission.add_event_for_eic('%s has been recruited and invited as a referee.'
                                          % ref_recruit_form.cleaned_data['last_name'])
-            # Copy the key to the refereeing invitation:
-            ref_invitation.invitation_key = reg_invitation.invitation_key
-            ref_invitation.save()
 
     return redirect(reverse('submissions:editorial_page',
                             kwargs={'arxiv_identifier_w_vn_nr': arxiv_identifier_w_vn_nr}))
-- 
GitLab