From a9caeeb27db0482497f4f2403bb79f21b3da1443 Mon Sep 17 00:00:00 2001
From: "J.-S. Caux" <J.S.Caux@uva.nl>
Date: Thu, 29 Dec 2016 11:07:35 +0100
Subject: [PATCH] DO MIGRATE; improve draft invitations, add
 CitationNotification

---
 .../migrations/0027_citationnotification.py   | 30 ++++++++
 ...emove_citationnotification_date_drafted.py | 19 +++++
 scipost/models.py                             | 21 ++++-
 .../scipost/citation_notifications.html       | 29 +++++++
 .../templates/scipost/edit_draft_reg_inv.html | 56 ++++++++++++++
 scipost/templates/scipost/personal_page.html  |  6 ++
 .../scipost/registration_invitations.html     | 16 ++++
 scipost/templatetags/scipost_extras.py        |  8 ++
 scipost/urls.py                               | 10 +++
 scipost/utils.py                              | 60 +++++++++++++++
 scipost/views.py                              | 76 +++++++++++++++++++
 11 files changed, 330 insertions(+), 1 deletion(-)
 create mode 100644 scipost/migrations/0027_citationnotification.py
 create mode 100644 scipost/migrations/0028_remove_citationnotification_date_drafted.py
 create mode 100644 scipost/templates/scipost/citation_notifications.html
 create mode 100644 scipost/templates/scipost/edit_draft_reg_inv.html

diff --git a/scipost/migrations/0027_citationnotification.py b/scipost/migrations/0027_citationnotification.py
new file mode 100644
index 000000000..1c0a8e27c
--- /dev/null
+++ b/scipost/migrations/0027_citationnotification.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2016-12-29 09:47
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('submissions', '0028_auto_20161212_1931'),
+        ('journals', '0006_publication_citedby'),
+        ('scipost', '0026_draftinvitation'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='CitationNotification',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('date_drafted', models.DateTimeField(default=django.utils.timezone.now)),
+                ('processed', models.BooleanField(default=False)),
+                ('cited_in_publication', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='journals.Publication')),
+                ('cited_in_submission', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='submissions.Submission')),
+                ('contributor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='scipost.Contributor')),
+            ],
+        ),
+    ]
diff --git a/scipost/migrations/0028_remove_citationnotification_date_drafted.py b/scipost/migrations/0028_remove_citationnotification_date_drafted.py
new file mode 100644
index 000000000..79b4fa512
--- /dev/null
+++ b/scipost/migrations/0028_remove_citationnotification_date_drafted.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.3 on 2016-12-29 09:47
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('scipost', '0027_citationnotification'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='citationnotification',
+            name='date_drafted',
+        ),
+    ]
diff --git a/scipost/models.py b/scipost/models.py
index bc1395d9a..e03f1efce 100644
--- a/scipost/models.py
+++ b/scipost/models.py
@@ -258,7 +258,7 @@ class Contributor(models.Model):
 
     def public_info_as_table(self):
         """Prints out all publicly-accessible info as a table."""
-        
+
         template = Template('''
             <table>
             <tr><td>Title: </td><td>&nbsp;</td><td>{{ title }}</td></tr>
@@ -443,6 +443,25 @@ class RegistrationInvitation(models.Model):
                 + ' on ' + self.date_sent.strftime("%Y-%m-%d"))
 
 
+class CitationNotification(models.Model):
+    contributor = models.ForeignKey(Contributor, on_delete=models.CASCADE)
+    cited_in_submission = models.ForeignKey('submissions.Submission',
+                                            on_delete=models.CASCADE,
+                                            blank=True, null=True)
+    cited_in_publication = models.ForeignKey('journals.Publication',
+                                             on_delete=models.CASCADE,
+                                             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_nr_w_vn_nr
+        elif self.cited_in_publication:
+            text += self.cited_in_publication.citation()
+        if self.processed:
+            text += ' (processed)'
+        return text
 
 AUTHORSHIP_CLAIM_STATUS = (
     (1, 'accepted'),
diff --git a/scipost/templates/scipost/citation_notifications.html b/scipost/templates/scipost/citation_notifications.html
new file mode 100644
index 000000000..0cc98b155
--- /dev/null
+++ b/scipost/templates/scipost/citation_notifications.html
@@ -0,0 +1,29 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: citation notifications{% endblock pagetitle %}
+
+{% block bodysup %}
+
+
+<section>
+  <div class="flex-greybox">
+    <h1>Citation notifications to process</h1>
+  </div>
+
+  {% if errormessage %}
+  <h3 style="color: red;">{{ errormessage }}</h3>
+  {% endif %}
+
+  {% if unprocessed_notifications %}
+  <ul>
+    {% for un in unprocessed_notifications %}
+    <li>{{ un }} <a href="{% url 'scipost:process_citation_notification' cn_id=un.id %}">Process this notification</li>
+    {% endfor %}
+  </ul>
+  {% else %}
+  <h3>There are no citation notifications to process.</h3>
+  <p>Return to your <a href="{% url 'scipost:personal_page' %}">personal page</a>.</p>
+  {% endif %}
+
+
+{% endblock bodysup %}
diff --git a/scipost/templates/scipost/edit_draft_reg_inv.html b/scipost/templates/scipost/edit_draft_reg_inv.html
new file mode 100644
index 000000000..fd3e872cb
--- /dev/null
+++ b/scipost/templates/scipost/edit_draft_reg_inv.html
@@ -0,0 +1,56 @@
+{% extends 'scipost/base.html' %}
+
+{% block pagetitle %}: edit draft reg inv{% endblock pagetitle %}
+
+{% block bodysup %}
+
+<script>
+  $(document).ready(function(){
+
+  switch ($('select#id_invitation_type').val()) {
+  case "ci":
+  $("#div_id_cited_in_submission").show();
+  $("#div_id_cited_in_publication").hide();
+  break;
+  case "cp":
+  $("#div_id_cited_in_submission").hide();
+  $("#div_id_cited_in_publication").show();
+  break;
+  default:
+  $("#div_id_cited_in_submission").hide();
+  $("#div_id_cited_in_publication").hide();
+  }
+
+  $('select#id_invitation_type').on('change', function() {
+    switch ($('select#id_invitation_type').val()) {
+      case "ci":
+        $("#div_id_cited_in_submission").show();
+        $("#div_id_cited_in_publication").hide();
+        break;
+      case "cp":
+        $("#div_id_cited_in_submission").hide();
+        $("#div_id_cited_in_publication").show();
+        break;
+      default:
+        $("#div_id_cited_in_submission").hide();
+        $("#div_id_cited_in_publication").hide();
+    }
+   });
+  });
+</script>
+
+<section>
+  <div class="flex-greybox">
+    <h1>Edit a draft registration invitation</h1>
+  </div>
+  {% if errormessage %}
+    <h3 style="color: red;">{{ errormessage }}</h3>
+  {% endif %}
+  <form action="{% url 'scipost:edit_draft_reg_inv' draft_id=draft.id %}" method="post">
+    {% csrf_token %}
+    {% load crispy_forms_tags %}
+    {% crispy draft_inv_form %}
+  </form>
+</section>
+
+{% endblock bodysup %}
diff --git a/scipost/templates/scipost/personal_page.html b/scipost/templates/scipost/personal_page.html
index 0d218ed6d..d2645698f 100644
--- a/scipost/templates/scipost/personal_page.html
+++ b/scipost/templates/scipost/personal_page.html
@@ -244,6 +244,12 @@
       <li><a href="{% url 'scipost:registration_invitations' %}">Manage Registration Invitations</a></li>
       {% endif %}
     </ul>
+    <h3>Notifications</h3>
+    <ul>
+      {% if perms.scipost.can_manage_registration_invitations %}
+      <li><a href="{% url 'scipost:citation_notifications' %}">Manage citation notifications</a></li>
+      {% endif %}
+    </ul>
     <h3>Email communications</h3>
     <ul>
       {% if perms.scipost.can_email_group_members %}
diff --git a/scipost/templates/scipost/registration_invitations.html b/scipost/templates/scipost/registration_invitations.html
index f74909628..0b5b8cb50 100644
--- a/scipost/templates/scipost/registration_invitations.html
+++ b/scipost/templates/scipost/registration_invitations.html
@@ -9,8 +9,19 @@
 <script>
   $(document).ready(function(){
 
+  switch ($('select#id_invitation_type').val()) {
+  case "ci":
+  $("#div_id_cited_in_submission").show();
+  $("#div_id_cited_in_publication").hide();
+  break;
+  case "cp":
+  $("#div_id_cited_in_submission").hide();
+  $("#div_id_cited_in_publication").show();
+  break;
+  default:
   $("#div_id_cited_in_submission").hide();
   $("#div_id_cited_in_publication").hide();
+  }
 
   $('select#id_invitation_type').on('change', function() {
     switch ($('select#id_invitation_type').val()) {
@@ -67,7 +78,12 @@
       <td>{{ draft.date_drafted }} </td>
       <td>{{ draft.invitation_type }}</td>
       <td>{{ draft.drafted_by.user.last_name }}</td>
+      <td><a href="{% url 'scipost:edit_draft_reg_inv' draft_id=draft.id %}">Edit</a></td>
       <td><a href="{% url 'scipost:registration_invitations_from_draft' draft_id=draft.id %}">Process</a></td>
+      <td><a href="{% url 'scipost:mark_draft_inv_as_processed' draft_id=draft.id %}">Mark as processed</a></td>
+      {% for ac in draft|associated_contributors %}
+      <td><a href="{% url 'scipost:map_draft_reg_inv_to_contributor' draft_id=draft.id contributor_id=ac.id %}">Map to {{ ac.user.first_name }} {{ ac.user.last_name }}</a></td>
+      {% endfor %}
     </tr>
     {% endfor %}
   </table>
diff --git a/scipost/templatetags/scipost_extras.py b/scipost/templatetags/scipost_extras.py
index 2c7c70788..3a34f87e7 100644
--- a/scipost/templatetags/scipost_extras.py
+++ b/scipost/templatetags/scipost_extras.py
@@ -1,6 +1,8 @@
 from django import template
 from django.contrib.auth.models import Group
 
+from scipost.models import Contributor
+
 register = template.Library()
 
 
@@ -21,3 +23,9 @@ def sort_by(queryset, order):
 def is_in_group(user, group_name):
     group = Group.objects.get(name=group_name)
     return True if group in user.groups.all() else False
+
+
+@register.filter(name='associated_contributors')
+def associated_contributors(draft):
+    return Contributor.objects.filter(
+        user__last_name__icontains=draft.last_name)
diff --git a/scipost/urls.py b/scipost/urls.py
index 0b33f90cb..8791b63d1 100644
--- a/scipost/urls.py
+++ b/scipost/urls.py
@@ -67,6 +67,10 @@ urlpatterns = [
         views.registration_invitations, name="registration_invitations"),
     url(r'^draft_registration_invitation$',
         views.draft_registration_invitation, name="draft_registration_invitation"),
+    url(r'^edit_draft_reg_inv/(?P<draft_id>[0-9]+)$',
+        views.edit_draft_reg_inv, name="edit_draft_reg_inv"),
+    url(r'^map_draft_reg_inv_to_contributor/(?P<draft_id>[0-9]+)/(?P<contributor_id>[0-9]+)$',
+        views.map_draft_reg_inv_to_contributor, name="map_draft_reg_inv_to_contributor"),
     url(r'^registration_invitations_cleanup$',
         views.registration_invitations_cleanup,
         name="registration_invitations_cleanup"),
@@ -90,6 +94,12 @@ urlpatterns = [
     url(r'^accept_invitation_error$',
         TemplateView.as_view(template_name='scipost/accept_invitation_error.html'),
         name='accept_invitation_error'),
+    url(r'^mark_draft_inv_as_processed/(?P<draft_id>[0-9]+)$',
+        views.mark_draft_inv_as_processed, name='mark_draft_inv_as_processed'),
+    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 d263d7ced..369f05f86 100644
--- a/scipost/utils.py
+++ b/scipost/utils.py
@@ -653,3 +653,63 @@ class Utils(object):
 
         # 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 = Context({})
+        email_text = ('Dear ' + title_dict[cls.notification.contributor.title] +
+                      ' ' + cls.notification.contributor.user.last_name)
+        email_text_html = 'Dear {{ title }} {{ last_name }}'
+        email_context['title'] = title_dict[cls.notification.contributor.title]
+        email_context['last_name'] = cls.notification.contributor.user.last_name
+        email_text +=  ',\n\n'
+        email_text_html += ',<br/>'
+        if cls.notification.cited_in_publication:
+            email_text += (
+                '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\n')
+            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><br/>' + EMAIL_FOOTER)
+            email_context['pub_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()
+            html_template = Template(email_text_html)
+            html_version = html_template.render(email_context)
+        elif cls.notification.cited_in_submission:
+            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.')
+            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>')
+            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
+
+        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 6c38ae2a5..e88be053c 100644
--- a/scipost/views.py
+++ b/scipost/views.py
@@ -576,11 +576,58 @@ def draft_registration_invitation(request):
     return render(request, 'scipost/draft_registration_invitation.html', context)
 
 
+@permission_required('scipost.can_manage_registration_invitations', return_403=True)
+def edit_draft_reg_inv(request, draft_id):
+    draft = get_object_or_404(DraftInvitation, id=draft_id)
+    errormessage = ''
+    if request.method == 'POST':
+        draft_inv_form = DraftInvitationForm(request.POST)
+        if draft_inv_form.is_valid():
+            draft.title = draft_inv_form.cleaned_data['title']
+            draft.first_name = draft_inv_form.cleaned_data['first_name']
+            draft.last_name = draft_inv_form.cleaned_data['last_name']
+            draft.email = draft_inv_form.cleaned_data['email']
+            draft.save()
+            return redirect(reverse('scipost:registration_invitations'))
+        else:
+            errormessage = 'The form is invalidly filled'
+    else:
+        draft_inv_form = DraftInvitationForm(instance=draft)
+    context = {'draft_inv_form': draft_inv_form,
+               'draft': draft,
+               'errormessage': errormessage,}
+    return render(request, 'scipost/edit_draft_reg_inv.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)
+    errormessage = ''
+    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_manage_registration_invitations', return_403=True)
 def registration_invitations(request, draft_id=None):
     """ Overview and tools for administrators """
     # List invitations sent; send new ones
     errormessage = ''
+    associated_contributors = None
     if request.method == 'POST':
         # Send invitation from form information
         reg_inv_form = RegistrationInvitationForm(request.POST)
@@ -622,6 +669,8 @@ def registration_invitations(request, draft_id=None):
         initial = {}
         if draft_id:
             draft = get_object_or_404(DraftInvitation, id=draft_id)
+            associated_contributors = Contributor.objects.filter(
+                user__last_name__icontains=draft.last_name)
             initial = {'title': draft.title,
                        'first_name': draft.first_name,
                        'last_name': draft.last_name,
@@ -687,6 +736,7 @@ def registration_invitations(request, draft_id=None):
                'decl_reg_inv': decl_reg_inv,
                'names_reg_contributors': names_reg_contributors,
                'existing_drafts': existing_drafts,
+               'associated_contributors': associated_contributors,
     }
     return render(request, 'scipost/registration_invitations.html', context)
 
@@ -768,6 +818,32 @@ def mark_reg_inv_as_declined(request, invitation_id):
     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()
+    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 mark_draft_inv_as_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):
     redirect_to = request.POST.get('next',
                                    request.GET.get('next', reverse('scipost:personal_page')))
-- 
GitLab