Repository: DDMAL/CantusDB

Issue:
Source Detail page should not display link to "Manage source editors" to all logged-in users
Currently, in the Source Detail view, if a user is logged in, we display "add new chant", "full text & volpiano editor", "edit source description" and "manage source editors" links in the sidebar. We need to check that the user has permission to edit the source before displaying these links, otherwise, users will click through into 403 errors. An extra check will be required for the "manage source editors" link - only people with "staff status" should see this link, as it leads to the admin area.


Discussion:
I spent a little while working on this, and I think we'll need a helper tag/filter to determine whether or not a user should see these links: trying to do it in the template, I was stymied by not being able to use brackets to group logical operators: `{% if request.user|has_group:"editor" or request.user|has_group:"project manager" or request.user|has_group:"contributor" and source in request.user.sources_user_can_edit %}` evaluates as `(condition OR condition OR condition) AND condition` when I want it to evaluate as `condition OR condition OR (condition AND condition)`. Setting this aside for the moment - @lucasmarchd01, you're welcome to fix this if you'd like; otherwise I'll deal with it as soon as I get back.

Resolving patch:
diff --git a/django/cantusdb_project/main_app/views/source.py b/django/cantusdb_project/main_app/views/source.py
--- a/django/cantusdb_project/main_app/views/source.py
+++ b/django/cantusdb_project/main_app/views/source.py
@@ -9,7 +9,10 @@
 from django.contrib.auth.mixins import UserPassesTestMixin
 from django.core.exceptions import PermissionDenied
 from django.shortcuts import get_object_or_404
-from main_app.views.chant import get_feast_selector_options
+from main_app.views.chant import (
+    get_feast_selector_options,
+    user_can_edit_chants_in_source,
+)
 
 
 class SourceDetailView(DetailView):
@@ -19,6 +22,7 @@ class SourceDetailView(DetailView):
 
     def get_context_data(self, **kwargs):
         source = self.get_object()
+        user = self.request.user
         display_unpublished = self.request.user.is_authenticated
         if (source.published is False) and (not display_unpublished):
             raise PermissionDenied()
@@ -43,6 +47,9 @@ def get_context_data(self, **kwargs):
             context["folios"] = folios
             # the options for the feast selector on the right, only chant sources have this
             context["feasts_with_folios"] = get_feast_selector_options(source, folios)
+
+        context["user_can_edit_chants"] = user_can_edit_chants_in_source(user, source)
+        context["user_can_edit_source"] = user_can_edit_source(user, source)
         return context
 
 
@@ -246,24 +253,7 @@ def test_func(self):
         source_id = self.kwargs.get(self.pk_url_kwarg)
         source = get_object_or_404(Source, id=source_id)
 
-        assigned_to_source = user.sources_user_can_edit.filter(id=source_id)
-
-        # checks if the user is a project manager
-        is_project_manager = user.groups.filter(name="project manager").exists()
-        # checks if the user is an editor
-        is_editor = user.groups.filter(name="editor").exists()
-        # checks if the user is a contributor
-        is_contributor = user.groups.filter(name="contributor").exists()
-
-        if (
-            (is_project_manager)
-            or (is_editor and assigned_to_source)
-            or (is_editor and source.created_by == user)
-            or (is_contributor and source.created_by == user)
-        ):
-            return True
-        else:
-            return False
+        return user_can_edit_source(user, source)
 
     def form_valid(self, form):
         form.instance.last_updated_by = self.request.user
@@ -284,3 +274,25 @@ def form_valid(self, form):
             new_editor.sources_user_can_edit.add(source)
 
         return HttpResponseRedirect(self.get_success_url())
+
+
+def user_can_edit_source(user, source):
+    source_id = source.id
+    assigned_to_source = user.sources_user_can_edit.filter(id=source_id)
+
+    # checks if the user is a project manager
+    is_project_manager = user.groups.filter(name="project manager").exists()
+    # checks if the user is an editor
+    is_editor = user.groups.filter(name="editor").exists()
+    # checks if the user is a contributor
+    is_contributor = user.groups.filter(name="contributor").exists()
+
+    if (
+        (is_project_manager)
+        or (is_editor and assigned_to_source)
+        or (is_editor and source.created_by == user)
+        or (is_contributor and source.created_by == user)
+    ):
+        return True
+    else:
+        return False


Test patch: