Repository: DDMAL/CantusDB

Issue:
Links to unpublished sources should not appear on Provenance detail pages
Example: visit http://206.12.93.196/provenance/3665 (while logged out), click on first link. We get a 403 Forbidden error, since the source is unpublished.

Unpublished sources should not be listed on the Provenance Detail page.

Credit to @zhannaklimanova and her link checker script for catching this bug!


Discussion:
just out of curiosity--how complicated an exercise would it be to have a page like this one list the *names* of all the sources, but only have the published ones be *links*?
> just out of curiosity--how complicated an exercise would it be to have a page like this one list the _names_ of all the sources, but only have the published ones be _links_?

This would be easy!
oooo. I may confer about this...

Resolving patch:
diff --git a/django/cantusdb_project/main_app/views/century.py b/django/cantusdb_project/main_app/views/century.py
--- a/django/cantusdb_project/main_app/views/century.py
+++ b/django/cantusdb_project/main_app/views/century.py
@@ -16,6 +16,6 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
         sources = Source.objects.filter(century=century)
         if not display_unpublished:
             sources = sources.filter(published=True)
-        sources = sources.only("title", "id")
+        sources = sources.only("title", "id", "siglum")
         context["sources"] = sources
         return context
diff --git a/django/cantusdb_project/main_app/views/provenance.py b/django/cantusdb_project/main_app/views/provenance.py
--- a/django/cantusdb_project/main_app/views/provenance.py
+++ b/django/cantusdb_project/main_app/views/provenance.py
@@ -1,8 +1,21 @@
 from django.views.generic import DetailView
-from main_app.models import Provenance
+from main_app.models import Provenance, Source
+from typing import Any
 
 
 class ProvenanceDetailView(DetailView):
     model = Provenance
     context_object_name = "provenance"
     template_name = "provenance_detail.html"
+
+    def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
+        context = super().get_context_data(**kwargs)
+        provenance = self.get_object()
+        user = self.request.user
+        display_unpublished = user.is_authenticated
+        sources = Source.objects.filter(provenance=provenance)
+        if not display_unpublished:
+            sources = sources.filter(published=True)
+        sources = sources.only("title", "id", "siglum")
+        context["sources"] = sources
+        return context


Test patch:
diff --git a/django/cantusdb_project/main_app/tests/test_views.py b/django/cantusdb_project/main_app/tests/test_views.py
--- a/django/cantusdb_project/main_app/tests/test_views.py
+++ b/django/cantusdb_project/main_app/tests/test_views.py
@@ -2747,6 +2747,31 @@ def test_url_and_templates(self):
         self.assertTemplateUsed(response, "base.html")
         self.assertTemplateUsed(response, "provenance_detail.html")
 
+    def test_listed_sources(self):
+        provenance = make_fake_provenance()
+        provenance_sources = [
+            make_fake_source(provenance=provenance, published=True) for _ in range(5)
+        ]
+        response = self.client.get(reverse("provenance-detail", args=[provenance.id]))
+        returned_sources = response.context["sources"]
+        for source in provenance_sources:
+            self.assertIn(source, returned_sources)
+
+    def test_unpublished_sources_not_listed(self):
+        provenance = make_fake_provenance()
+        published_sources = [
+            make_fake_source(provenance=provenance, published=True) for _ in range(5)
+        ]
+        unpublished_sources = [
+            make_fake_source(provenance=provenance, published=False) for _ in range(5)
+        ]
+        response = self.client.get(reverse("provenance-detail", args=[provenance.id]))
+        returned_sources = response.context["sources"]
+        for source in published_sources:
+            self.assertIn(source, returned_sources)
+        for source in unpublished_sources:
+            self.assertNotIn(source, returned_sources)
+
 
 class SequenceListViewTest(TestCase):
     def setUp(self):