From 33b23d38da819e81df3695e421bab406a99f264f Mon Sep 17 00:00:00 2001
From: "J.-S. Caux" <J.S.Caux@uva.nl>
Date: Sat, 8 May 2021 20:32:06 +0200
Subject: [PATCH] Add affiliate journal manager permissions and facilities

---
 affiliates/forms.py                           | 20 ++++++++++
 .../migrations/0004_auto_20210508_2012.py     | 17 +++++++++
 affiliates/models/journal.py                  |  5 ++-
 .../affiliates/affiliatejournal_detail.html   | 32 ++++++++++++++++
 affiliates/urls.py                            | 10 +++++
 affiliates/views.py                           | 38 ++++++++++++++++++-
 6 files changed, 119 insertions(+), 3 deletions(-)
 create mode 100644 affiliates/forms.py
 create mode 100644 affiliates/migrations/0004_auto_20210508_2012.py

diff --git a/affiliates/forms.py b/affiliates/forms.py
new file mode 100644
index 000000000..0839871ea
--- /dev/null
+++ b/affiliates/forms.py
@@ -0,0 +1,20 @@
+__copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
+__license__ = "AGPL v3"
+
+
+from django import forms
+from django.contrib.auth.models import User
+
+from dal import autocomplete
+
+
+class AddAffiliateJournalManagerForm(forms.Form):
+    user = forms.ModelChoiceField(
+        queryset=User.objects.all(),
+        widget=autocomplete.ModelSelect2(
+            url='/user-autocomplete',
+            attrs={'data-html': True}
+        ),
+        label='',
+        required=True
+    )
diff --git a/affiliates/migrations/0004_auto_20210508_2012.py b/affiliates/migrations/0004_auto_20210508_2012.py
new file mode 100644
index 000000000..c8752a1d9
--- /dev/null
+++ b/affiliates/migrations/0004_auto_20210508_2012.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.2.16 on 2021-05-08 18:12
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('affiliates', '0003_auto_20210508_1653'),
+    ]
+
+    operations = [
+        migrations.AlterModelOptions(
+            name='affiliatejournal',
+            options={'ordering': ['publisher', 'name'], 'permissions': (('manage_journal_content', 'Manage Journal content'),)},
+        ),
+    ]
diff --git a/affiliates/models/journal.py b/affiliates/models/journal.py
index 989f257e6..aaf90d242 100644
--- a/affiliates/models/journal.py
+++ b/affiliates/models/journal.py
@@ -22,8 +22,6 @@ class AffiliateJournal(models.Model):
         max_length=256
     )
 
-    # Note that the short name can be just as long as the full name. This is because not all
-    # journals have abbreviated names in Crossref, and instead return the full journal name.
     short_name = models.CharField(
         max_length=256,
         default=""
@@ -40,6 +38,9 @@ class AffiliateJournal(models.Model):
             'publisher',
             'name'
         ]
+        permissions = (
+            ('manage_journal_content', 'Manage Journal content'),
+        )
 
     def __str__(self):
         return self.name
diff --git a/affiliates/templates/affiliates/affiliatejournal_detail.html b/affiliates/templates/affiliates/affiliatejournal_detail.html
index 809358b39..5b0b65826 100644
--- a/affiliates/templates/affiliates/affiliatejournal_detail.html
+++ b/affiliates/templates/affiliates/affiliatejournal_detail.html
@@ -12,6 +12,34 @@
 
   <h2 class="highlight">Affiliate Journal: {{ object }}</h2>
 
+  {% if perms.affiliates.can_edit_affiliatedjournal %}
+    <h3 class="highlight">Journal managers</h3>
+    <div class="row">
+      <div class="col-lg-6">
+	<h4>Add manager</h4>
+	<form action="{% url 'affiliates:journal_add_manager' slug=object.slug %}" method="post">
+	  {% csrf_token %}
+	  {{ add_manager_form }}
+	  <input type="submit" value="Submit" class="btn btn-primary">
+	</form>
+      </div>
+      <div class="col-lg-6">
+	<h4>Current list of managers</h4>
+	<ul>
+	  {% for manager in journal_managers.all %}
+	    <li>{{ manager.last_name }}, {{ manager.first_name }}&emsp;
+	      <a href="{% url 'affiliates:journal_remove_manager' slug=object.slug user_id=manager.pk %}">
+		<span class="text-danger">{% include 'bi/x-square-fill.html' %}</span>
+	      </a>
+	    </li>
+	  {% empty %}
+	    <li>No managers found</li>
+	  {% endfor %}
+	</ul>
+      </div>
+    </div>
+  {% endif %}
+
   <h3 class="highlight">Publications</h3>
   <table class="table">
     {% for pub in object.publications.all %}
@@ -26,3 +54,7 @@
   </table>
 
 {% endblock content %}
+
+{% block footer_script %}
+  {{ add_manager_form.media }}
+{% endblock footer_script %}
diff --git a/affiliates/urls.py b/affiliates/urls.py
index 91ce17930..7edcc3664 100644
--- a/affiliates/urls.py
+++ b/affiliates/urls.py
@@ -22,6 +22,16 @@ urlpatterns = [
         views.AffiliateJournalDetailView.as_view(),
         name='journal_detail'
     ),
+    path( # /affiliations/journals/<slug>/add_manager
+        'journals/<slug:slug>/add_manager',
+        views.affiliatejournal_add_manager,
+        name='journal_add_manager'
+    ),
+    path( # /affiliations/journals/<slug>/remove_manager
+        'journals/<slug:slug>/remove_manager/<int:user_id>',
+        views.affiliatejournal_remove_manager,
+        name='journal_remove_manager'
+    ),
     path( # /affiliates/publications/<doi:doi>
         'publications/<doi:doi>',
         views.AffiliatePublicationDetailView.as_view(),
diff --git a/affiliates/views.py b/affiliates/views.py
index 0a6661eb4..d6cf6df23 100644
--- a/affiliates/views.py
+++ b/affiliates/views.py
@@ -2,11 +2,17 @@ __copyright__ = "Copyright © Stichting SciPost (SciPost Foundation)"
 __license__ = "AGPL v3"
 
 
-from django.shortcuts import render
+from django.contrib.auth.models import User
+from django.shortcuts import get_object_or_404, render, redirect
+from django.urls import reverse
 from django.views.generic.detail import DetailView
 from django.views.generic.list import ListView
 
+from guardian.decorators import permission_required_or_403
+from guardian.shortcuts import assign_perm, remove_perm, get_users_with_perms
+
 from .models import AffiliateJournal, AffiliatePublication
+from .forms import AddAffiliateJournalManagerForm
 
 
 class AffiliateJournalListView(ListView):
@@ -16,6 +22,36 @@ class AffiliateJournalListView(ListView):
 class AffiliateJournalDetailView(DetailView):
     model = AffiliateJournal
 
+    def get_context_data(self, *args, **kwargs):
+        context = super().get_context_data(*args, **kwargs)
+        context['journal_managers'] = get_users_with_perms(
+            self.object,
+            with_superusers=False
+        )
+        context['add_manager_form'] = AddAffiliateJournalManagerForm()
+        return context
+
+
+@permission_required_or_403('affiliates.change_affiliatejournal',
+                            (AffiliateJournal, 'slug', 'slug'))
+def affiliatejournal_add_manager(request, slug):
+    journal = get_object_or_404(AffiliateJournal, slug=slug)
+    form = AddAffiliateJournalManagerForm(request.POST or None)
+    if form.is_valid():
+        assign_perm('manage_journal_content',
+                    form.cleaned_data['user'], journal)
+    return redirect(reverse('affiliates:journal_detail',
+                            kwargs={'slug': slug}))
+
+@permission_required_or_403('affiliates.change_affiliatejournal',
+                            (AffiliateJournal, 'slug', 'slug'))
+def affiliatejournal_remove_manager(request, slug, user_id):
+    journal = get_object_or_404(AffiliateJournal, slug=slug)
+    user = get_object_or_404(User, pk=user_id)
+    remove_perm('manage_journal_content', user, journal)
+    return redirect(reverse('affiliates:journal_detail',
+                            kwargs={'slug': slug}))
+
 
 class AffiliatePublicationDetailView(DetailView):
     model = AffiliatePublication
-- 
GitLab