We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Source Edit Page: sidebar: source navigation links not displayed
breaking this issue out from #483. 

OldCantus - https://cantus.uwaterloo.ca/node/711311/edit:
![Screen Shot 2022-12-16 at 2 23 09 PM](https://user-images.githubusercontent.com/58090591/208173403-56dcb32a-ec87-4639-88cf-d9bbc74dc192.png)

NewCantus - http://206.12.88.113/edit-source/123653:
![Screen Shot 2022-12-16 at 2 23 47 PM](https://user-images.githubusercontent.com/58090591/208173695-676814b9-4306-4ffc-b187-ab8f9f624914.png)

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `django/cantusdb_project/main_app/views/source.py`
Content:
```
1 from django.views.generic import DetailView, ListView, CreateView, UpdateView
2 from django.db.models import Q, Prefetch
3 from main_app.models import Source, Provenance, Century
4 from main_app.forms import SourceCreateForm, SourceEditForm
5 from django.contrib import messages
6 from django.urls import reverse
7 from django.contrib.auth.mixins import LoginRequiredMixin
8 from django.http import HttpResponseRedirect
9 from django.contrib.auth.mixins import UserPassesTestMixin
10 from django.core.exceptions import PermissionDenied
11 from django.shortcuts import get_object_or_404
12 from main_app.views.chant import get_feast_selector_options
13 
14 
15 class SourceDetailView(DetailView):
16     model = Source
17     context_object_name = "source"
18     template_name = "source_detail.html"
19 
20     def get_context_data(self, **kwargs):
21         source = self.get_object()
22         display_unpublished = self.request.user.is_authenticated
23         if (source.published is False) and (not display_unpublished):
24             raise PermissionDenied()
25 
26         context = super().get_context_data(**kwargs)
27 
28         if source.segment and source.segment.id == 4064:
29             # if this is a sequence source
30             context["sequences"] = source.sequence_set.order_by("s_sequence")
31             context["folios"] = (
32                 source.sequence_set.values_list("folio", flat=True)
33                 .distinct()
34                 .order_by("folio")
35             )
36         else:
37             # if this is a chant source
38             folios = (
39                 source.chant_set.values_list("folio", flat=True)
40                 .distinct()
41                 .order_by("folio")
42             )
43             context["folios"] = folios
44             # the options for the feast selector on the right, only chant sources have this
45             context["feasts_with_folios"] = get_feast_selector_options(source, folios)
46         return context
47 
48 
49 class SourceListView(ListView):
50     paginate_by = 100
51     context_object_name = "sources"
52     template_name = "source_list.html"
53 
54     def get_context_data(self, **kwargs):
55         context = super().get_context_data(**kwargs)
56         context["provenances"] = (
57             Provenance.objects.all().order_by("name").values("id", "name")
58         )
59         context["centuries"] = (
60             Century.objects.all().order_by("name").values("id", "name")
61         )
62         return context
63 
64     def get_queryset(self):
65         # use select_related() for foreign keys to reduce DB queries
66         queryset = Source.objects.select_related(
67             "rism_siglum", "segment", "provenance"
68         ).order_by("siglum")
69 
70         display_unpublished = self.request.user.is_authenticated
71         if display_unpublished:
72             q_obj_filter = Q()
73         else:
74             q_obj_filter = Q(published=True)
75 
76         if self.request.GET.get("century"):
77             century_name = Century.objects.get(id=self.request.GET.get("century")).name
78             q_obj_filter &= Q(century__name__icontains=century_name)
79 
80         if self.request.GET.get("provenance"):
81             provenance_id = int(self.request.GET.get("provenance"))
82             q_obj_filter &= Q(provenance__id=provenance_id)
83         if self.request.GET.get("segment"):
84             segment_id = int(self.request.GET.get("segment"))
85             q_obj_filter &= Q(segment__id=segment_id)
86         if self.request.GET.get("fullSource") in ["true", "false"]:
87             full_source_str = self.request.GET.get("fullSource")
88             if full_source_str == "true":
89                 full_source_q = Q(full_source=True) | Q(full_source=None)
90                 q_obj_filter &= full_source_q
91             else:
92                 q_obj_filter &= Q(full_source=False)
93 
94         if self.request.GET.get("general"):
95             # Strip spaces at the beginning and end. Then make list of terms split on spaces
96             general_search_terms = self.request.GET.get("general").strip(" ").split(" ")
97             # We need a Q Object for each field we're gonna look into
98             title_q = Q()
99             siglum_q = Q()
100             rism_siglum_q = Q()
101             description_q = Q()
102             # it seems that old cantus don't look into title and provenance for the general search terms
103             # cantus.uwaterloo.ca/source/123901 this source cannot be found by searching its provenance 'Kremsmünster' in the general search field
104             # provenance_q = Q()
105             summary_q = Q()
106 
107             # For each term, add it to the Q object of each field with an OR operation.
108             # We split the terms so that the words can be separated in the actual
109             # field, allowing for a more flexible search, and a field needs
110             # to match only one of the terms
111             for term in general_search_terms:
112                 title_q |= Q(title__icontains=term)
113                 siglum_q |= Q(siglum__icontains=term)
114                 rism_siglum_q |= Q(rism_siglum__name__icontains=term) | Q(
115                     rism_siglum__description__icontains=term
116                 )
117                 description_q |= Q(description__icontains=term)
118                 summary_q |= Q(summary__icontains=term)
119                 # provenance_q |= Q(provenance__name__icontains=term)
120             # All the Q objects are put together with OR.
121             # The end result is that at least one term has to match in at least one
122             # field
123             # general_search_q = (
124             #     title_q | siglum_q | rism_siglum_q | description_q | provenance_q
125             # )
126             general_search_q = (
127                 title_q | siglum_q | rism_siglum_q | description_q | summary_q
128             )
129             q_obj_filter &= general_search_q
130 
131         # For the indexing notes search we follow the same procedure as above but with
132         # different fields
133         if self.request.GET.get("indexing"):
134             # Make list of terms split on spaces
135             indexing_search_terms = self.request.GET.get("indexing").split(" ")
136             # We need a Q Object for each field we're gonna look into
137             inventoried_by_q = Q()
138             full_text_entered_by_q = Q()
139             melodies_entered_by_q = Q()
140             proofreaders_q = Q()
141             other_editors_q = Q()
142             indexing_notes_q = Q()
143             # For each term, add it to the Q object of each field with an OR operation.
144             # We split the terms so that the words can be separated in the actual
145             # field, allowing for a more flexible search, and a field needs
146             # to match only one of the terms
147             for term in indexing_search_terms:
148                 inventoried_by_q |= Q(inventoried_by__full_name__icontains=term)
149                 full_text_entered_by_q |= Q(
150                     full_text_entered_by__full_name__icontains=term
151                 )
152                 melodies_entered_by_q |= Q(
153                     melodies_entered_by__full_name__icontains=term
154                 )
155                 proofreaders_q |= Q(proofreaders__full_name__icontains=term)
156                 other_editors_q |= Q(other_editors__full_name__icontains=term)
157                 indexing_notes_q |= Q(indexing_notes__icontains=term)
158             # All the Q objects are put together with OR.
159             # The end result is that at least one term has to match in at least one
160             # field
161             indexing_search_q = (
162                 inventoried_by_q
163                 | full_text_entered_by_q
164                 | melodies_entered_by_q
165                 | proofreaders_q
166                 | other_editors_q
167                 | indexing_notes_q
168             )
169             q_obj_filter &= indexing_search_q
170 
171         return queryset.filter(q_obj_filter).prefetch_related(
172             Prefetch("century", queryset=Century.objects.all().order_by("id"))
173         )
174 
175 
176 class SourceCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):
177     model = Source
178     template_name = "source_create_form.html"
179     form_class = SourceCreateForm
180 
181     def test_func(self):
182         user = self.request.user
183         # checks if the user is allowed to create sources
184         is_authorized = user.groups.filter(
185             Q(name="project manager") | Q(name="editor") | Q(name="contributor")
186         ).exists()
187 
188         if is_authorized:
189             return True
190         else:
191             return False
192 
193     def get_success_url(self):
194         return reverse("source-create")
195 
196     def form_valid(self, form):
197         form.instance.created_by = self.request.user
198         source = form.save()
199 
200         # assign this source to the "current_editors"
201         current_editors = source.current_editors.all()
202 
203         for editor in current_editors:
204             editor.sources_user_can_edit.add(source)
205 
206         messages.success(
207             self.request,
208             "Source created successfully!",
209         )
210 
211         return HttpResponseRedirect(self.get_success_url())
212 
213 
214 class SourceEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
215     template_name = "source_edit.html"
216     model = Source
217     form_class = SourceEditForm
218     pk_url_kwarg = "source_id"
219 
220     def test_func(self):
221         user = self.request.user
222         source_id = self.kwargs.get(self.pk_url_kwarg)
223         source = get_object_or_404(Source, id=source_id)
224 
225         assigned_to_source = user.sources_user_can_edit.filter(id=source_id)
226 
227         # checks if the user is a project manager
228         is_project_manager = user.groups.filter(name="project manager").exists()
229         # checks if the user is an editor
230         is_editor = user.groups.filter(name="editor").exists()
231         # checks if the user is a contributor
232         is_contributor = user.groups.filter(name="contributor").exists()
233 
234         if (
235             (is_project_manager)
236             or (is_editor and assigned_to_source)
237             or (is_editor and source.created_by == user)
238             or (is_contributor and source.created_by == user)
239         ):
240             return True
241         else:
242             return False
243 
244     def form_valid(self, form):
245         form.instance.last_updated_by = self.request.user
246 
247         # remove this source from the old "current_editors"
248         # assign this source to the new "current_editors"
249 
250         old_current_editors = list(
251             Source.objects.get(id=form.instance.id).current_editors.all()
252         )
253         new_current_editors = form.cleaned_data["current_editors"]
254         source = form.save()
255 
256         for old_editor in old_current_editors:
257             old_editor.sources_user_can_edit.remove(source)
258 
259         for new_editor in new_current_editors:
260             new_editor.sources_user_can_edit.add(source)
261 
262         return HttpResponseRedirect(self.get_success_url())
263
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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
@@ -211,7 +211,9 @@
         return HttpResponseRedirect(self.get_success_url())
 
 
-class SourceEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
+class SourceEditView(
+    LoginRequiredMixin, UserPassesTestMixin, UpdateView, SourceDetailView
+):
     template_name = "source_edit.html"
     model = Source
     form_class = SourceEditForm

{"golden_diff": "diff --git a/django/cantusdb_project/main_app/views/source.py b/django/cantusdb_project/main_app/views/source.py\n--- a/django/cantusdb_project/main_app/views/source.py\n+++ b/django/cantusdb_project/main_app/views/source.py\n@@ -211,7 +211,9 @@\n         return HttpResponseRedirect(self.get_success_url())\n \n \n-class SourceEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):\n+class SourceEditView(\n+    LoginRequiredMixin, UserPassesTestMixin, UpdateView, SourceDetailView\n+):\n     template_name = \"source_edit.html\"\n     model = Source\n     form_class = SourceEditForm\n", "issue": "Source Edit Page: sidebar: source navigation links not displayed\nbreaking this issue out from #483. \r\n\r\nOldCantus - https://cantus.uwaterloo.ca/node/711311/edit:\r\n![Screen Shot 2022-12-16 at 2 23 09 PM](https://user-images.githubusercontent.com/58090591/208173403-56dcb32a-ec87-4639-88cf-d9bbc74dc192.png)\r\n\r\nNewCantus - http://206.12.88.113/edit-source/123653:\r\n![Screen Shot 2022-12-16 at 2 23 47 PM](https://user-images.githubusercontent.com/58090591/208173695-676814b9-4306-4ffc-b187-ab8f9f624914.png)\n", "before_files": [{"content": "from django.views.generic import DetailView, ListView, CreateView, UpdateView\nfrom django.db.models import Q, Prefetch\nfrom main_app.models import Source, Provenance, Century\nfrom main_app.forms import SourceCreateForm, SourceEditForm\nfrom django.contrib import messages\nfrom django.urls import reverse\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.http import HttpResponseRedirect\nfrom django.contrib.auth.mixins import UserPassesTestMixin\nfrom django.core.exceptions import PermissionDenied\nfrom django.shortcuts import get_object_or_404\nfrom main_app.views.chant import get_feast_selector_options\n\n\nclass SourceDetailView(DetailView):\n    model = Source\n    context_object_name = \"source\"\n    template_name = \"source_detail.html\"\n\n    def get_context_data(self, **kwargs):\n        source = self.get_object()\n        display_unpublished = self.request.user.is_authenticated\n        if (source.published is False) and (not display_unpublished):\n            raise PermissionDenied()\n\n        context = super().get_context_data(**kwargs)\n\n        if source.segment and source.segment.id == 4064:\n            # if this is a sequence source\n            context[\"sequences\"] = source.sequence_set.order_by(\"s_sequence\")\n            context[\"folios\"] = (\n                source.sequence_set.values_list(\"folio\", flat=True)\n                .distinct()\n                .order_by(\"folio\")\n            )\n        else:\n            # if this is a chant source\n            folios = (\n                source.chant_set.values_list(\"folio\", flat=True)\n                .distinct()\n                .order_by(\"folio\")\n            )\n            context[\"folios\"] = folios\n            # the options for the feast selector on the right, only chant sources have this\n            context[\"feasts_with_folios\"] = get_feast_selector_options(source, folios)\n        return context\n\n\nclass SourceListView(ListView):\n    paginate_by = 100\n    context_object_name = \"sources\"\n    template_name = \"source_list.html\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context[\"provenances\"] = (\n            Provenance.objects.all().order_by(\"name\").values(\"id\", \"name\")\n        )\n        context[\"centuries\"] = (\n            Century.objects.all().order_by(\"name\").values(\"id\", \"name\")\n        )\n        return context\n\n    def get_queryset(self):\n        # use select_related() for foreign keys to reduce DB queries\n        queryset = Source.objects.select_related(\n            \"rism_siglum\", \"segment\", \"provenance\"\n        ).order_by(\"siglum\")\n\n        display_unpublished = self.request.user.is_authenticated\n        if display_unpublished:\n            q_obj_filter = Q()\n        else:\n            q_obj_filter = Q(published=True)\n\n        if self.request.GET.get(\"century\"):\n            century_name = Century.objects.get(id=self.request.GET.get(\"century\")).name\n            q_obj_filter &= Q(century__name__icontains=century_name)\n\n        if self.request.GET.get(\"provenance\"):\n            provenance_id = int(self.request.GET.get(\"provenance\"))\n            q_obj_filter &= Q(provenance__id=provenance_id)\n        if self.request.GET.get(\"segment\"):\n            segment_id = int(self.request.GET.get(\"segment\"))\n            q_obj_filter &= Q(segment__id=segment_id)\n        if self.request.GET.get(\"fullSource\") in [\"true\", \"false\"]:\n            full_source_str = self.request.GET.get(\"fullSource\")\n            if full_source_str == \"true\":\n                full_source_q = Q(full_source=True) | Q(full_source=None)\n                q_obj_filter &= full_source_q\n            else:\n                q_obj_filter &= Q(full_source=False)\n\n        if self.request.GET.get(\"general\"):\n            # Strip spaces at the beginning and end. Then make list of terms split on spaces\n            general_search_terms = self.request.GET.get(\"general\").strip(\" \").split(\" \")\n            # We need a Q Object for each field we're gonna look into\n            title_q = Q()\n            siglum_q = Q()\n            rism_siglum_q = Q()\n            description_q = Q()\n            # it seems that old cantus don't look into title and provenance for the general search terms\n            # cantus.uwaterloo.ca/source/123901 this source cannot be found by searching its provenance 'Kremsm\u00fcnster' in the general search field\n            # provenance_q = Q()\n            summary_q = Q()\n\n            # For each term, add it to the Q object of each field with an OR operation.\n            # We split the terms so that the words can be separated in the actual\n            # field, allowing for a more flexible search, and a field needs\n            # to match only one of the terms\n            for term in general_search_terms:\n                title_q |= Q(title__icontains=term)\n                siglum_q |= Q(siglum__icontains=term)\n                rism_siglum_q |= Q(rism_siglum__name__icontains=term) | Q(\n                    rism_siglum__description__icontains=term\n                )\n                description_q |= Q(description__icontains=term)\n                summary_q |= Q(summary__icontains=term)\n                # provenance_q |= Q(provenance__name__icontains=term)\n            # All the Q objects are put together with OR.\n            # The end result is that at least one term has to match in at least one\n            # field\n            # general_search_q = (\n            #     title_q | siglum_q | rism_siglum_q | description_q | provenance_q\n            # )\n            general_search_q = (\n                title_q | siglum_q | rism_siglum_q | description_q | summary_q\n            )\n            q_obj_filter &= general_search_q\n\n        # For the indexing notes search we follow the same procedure as above but with\n        # different fields\n        if self.request.GET.get(\"indexing\"):\n            # Make list of terms split on spaces\n            indexing_search_terms = self.request.GET.get(\"indexing\").split(\" \")\n            # We need a Q Object for each field we're gonna look into\n            inventoried_by_q = Q()\n            full_text_entered_by_q = Q()\n            melodies_entered_by_q = Q()\n            proofreaders_q = Q()\n            other_editors_q = Q()\n            indexing_notes_q = Q()\n            # For each term, add it to the Q object of each field with an OR operation.\n            # We split the terms so that the words can be separated in the actual\n            # field, allowing for a more flexible search, and a field needs\n            # to match only one of the terms\n            for term in indexing_search_terms:\n                inventoried_by_q |= Q(inventoried_by__full_name__icontains=term)\n                full_text_entered_by_q |= Q(\n                    full_text_entered_by__full_name__icontains=term\n                )\n                melodies_entered_by_q |= Q(\n                    melodies_entered_by__full_name__icontains=term\n                )\n                proofreaders_q |= Q(proofreaders__full_name__icontains=term)\n                other_editors_q |= Q(other_editors__full_name__icontains=term)\n                indexing_notes_q |= Q(indexing_notes__icontains=term)\n            # All the Q objects are put together with OR.\n            # The end result is that at least one term has to match in at least one\n            # field\n            indexing_search_q = (\n                inventoried_by_q\n                | full_text_entered_by_q\n                | melodies_entered_by_q\n                | proofreaders_q\n                | other_editors_q\n                | indexing_notes_q\n            )\n            q_obj_filter &= indexing_search_q\n\n        return queryset.filter(q_obj_filter).prefetch_related(\n            Prefetch(\"century\", queryset=Century.objects.all().order_by(\"id\"))\n        )\n\n\nclass SourceCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):\n    model = Source\n    template_name = \"source_create_form.html\"\n    form_class = SourceCreateForm\n\n    def test_func(self):\n        user = self.request.user\n        # checks if the user is allowed to create sources\n        is_authorized = user.groups.filter(\n            Q(name=\"project manager\") | Q(name=\"editor\") | Q(name=\"contributor\")\n        ).exists()\n\n        if is_authorized:\n            return True\n        else:\n            return False\n\n    def get_success_url(self):\n        return reverse(\"source-create\")\n\n    def form_valid(self, form):\n        form.instance.created_by = self.request.user\n        source = form.save()\n\n        # assign this source to the \"current_editors\"\n        current_editors = source.current_editors.all()\n\n        for editor in current_editors:\n            editor.sources_user_can_edit.add(source)\n\n        messages.success(\n            self.request,\n            \"Source created successfully!\",\n        )\n\n        return HttpResponseRedirect(self.get_success_url())\n\n\nclass SourceEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):\n    template_name = \"source_edit.html\"\n    model = Source\n    form_class = SourceEditForm\n    pk_url_kwarg = \"source_id\"\n\n    def test_func(self):\n        user = self.request.user\n        source_id = self.kwargs.get(self.pk_url_kwarg)\n        source = get_object_or_404(Source, id=source_id)\n\n        assigned_to_source = user.sources_user_can_edit.filter(id=source_id)\n\n        # checks if the user is a project manager\n        is_project_manager = user.groups.filter(name=\"project manager\").exists()\n        # checks if the user is an editor\n        is_editor = user.groups.filter(name=\"editor\").exists()\n        # checks if the user is a contributor\n        is_contributor = user.groups.filter(name=\"contributor\").exists()\n\n        if (\n            (is_project_manager)\n            or (is_editor and assigned_to_source)\n            or (is_editor and source.created_by == user)\n            or (is_contributor and source.created_by == user)\n        ):\n            return True\n        else:\n            return False\n\n    def form_valid(self, form):\n        form.instance.last_updated_by = self.request.user\n\n        # remove this source from the old \"current_editors\"\n        # assign this source to the new \"current_editors\"\n\n        old_current_editors = list(\n            Source.objects.get(id=form.instance.id).current_editors.all()\n        )\n        new_current_editors = form.cleaned_data[\"current_editors\"]\n        source = form.save()\n\n        for old_editor in old_current_editors:\n            old_editor.sources_user_can_edit.remove(source)\n\n        for new_editor in new_current_editors:\n            new_editor.sources_user_can_edit.add(source)\n\n        return HttpResponseRedirect(self.get_success_url())\n", "path": "django/cantusdb_project/main_app/views/source.py"}], "after_files": [{"content": "from django.views.generic import DetailView, ListView, CreateView, UpdateView\nfrom django.db.models import Q, Prefetch\nfrom main_app.models import Source, Provenance, Century\nfrom main_app.forms import SourceCreateForm, SourceEditForm\nfrom django.contrib import messages\nfrom django.urls import reverse\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.http import HttpResponseRedirect\nfrom django.contrib.auth.mixins import UserPassesTestMixin\nfrom django.core.exceptions import PermissionDenied\nfrom django.shortcuts import get_object_or_404\nfrom main_app.views.chant import get_feast_selector_options\n\n\nclass SourceDetailView(DetailView):\n    model = Source\n    context_object_name = \"source\"\n    template_name = \"source_detail.html\"\n\n    def get_context_data(self, **kwargs):\n        source = self.get_object()\n        display_unpublished = self.request.user.is_authenticated\n        if (source.published is False) and (not display_unpublished):\n            raise PermissionDenied()\n\n        context = super().get_context_data(**kwargs)\n\n        if source.segment and source.segment.id == 4064:\n            # if this is a sequence source\n            context[\"sequences\"] = source.sequence_set.order_by(\"s_sequence\")\n            context[\"folios\"] = (\n                source.sequence_set.values_list(\"folio\", flat=True)\n                .distinct()\n                .order_by(\"folio\")\n            )\n        else:\n            # if this is a chant source\n            folios = (\n                source.chant_set.values_list(\"folio\", flat=True)\n                .distinct()\n                .order_by(\"folio\")\n            )\n            context[\"folios\"] = folios\n            # the options for the feast selector on the right, only chant sources have this\n            context[\"feasts_with_folios\"] = get_feast_selector_options(source, folios)\n        return context\n\n\nclass SourceListView(ListView):\n    paginate_by = 100\n    context_object_name = \"sources\"\n    template_name = \"source_list.html\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n        context[\"provenances\"] = (\n            Provenance.objects.all().order_by(\"name\").values(\"id\", \"name\")\n        )\n        context[\"centuries\"] = (\n            Century.objects.all().order_by(\"name\").values(\"id\", \"name\")\n        )\n        return context\n\n    def get_queryset(self):\n        # use select_related() for foreign keys to reduce DB queries\n        queryset = Source.objects.select_related(\n            \"rism_siglum\", \"segment\", \"provenance\"\n        ).order_by(\"siglum\")\n\n        display_unpublished = self.request.user.is_authenticated\n        if display_unpublished:\n            q_obj_filter = Q()\n        else:\n            q_obj_filter = Q(published=True)\n\n        if self.request.GET.get(\"century\"):\n            century_name = Century.objects.get(id=self.request.GET.get(\"century\")).name\n            q_obj_filter &= Q(century__name__icontains=century_name)\n\n        if self.request.GET.get(\"provenance\"):\n            provenance_id = int(self.request.GET.get(\"provenance\"))\n            q_obj_filter &= Q(provenance__id=provenance_id)\n        if self.request.GET.get(\"segment\"):\n            segment_id = int(self.request.GET.get(\"segment\"))\n            q_obj_filter &= Q(segment__id=segment_id)\n        if self.request.GET.get(\"fullSource\") in [\"true\", \"false\"]:\n            full_source_str = self.request.GET.get(\"fullSource\")\n            if full_source_str == \"true\":\n                full_source_q = Q(full_source=True) | Q(full_source=None)\n                q_obj_filter &= full_source_q\n            else:\n                q_obj_filter &= Q(full_source=False)\n\n        if self.request.GET.get(\"general\"):\n            # Strip spaces at the beginning and end. Then make list of terms split on spaces\n            general_search_terms = self.request.GET.get(\"general\").strip(\" \").split(\" \")\n            # We need a Q Object for each field we're gonna look into\n            title_q = Q()\n            siglum_q = Q()\n            rism_siglum_q = Q()\n            description_q = Q()\n            # it seems that old cantus don't look into title and provenance for the general search terms\n            # cantus.uwaterloo.ca/source/123901 this source cannot be found by searching its provenance 'Kremsm\u00fcnster' in the general search field\n            # provenance_q = Q()\n            summary_q = Q()\n\n            # For each term, add it to the Q object of each field with an OR operation.\n            # We split the terms so that the words can be separated in the actual\n            # field, allowing for a more flexible search, and a field needs\n            # to match only one of the terms\n            for term in general_search_terms:\n                title_q |= Q(title__icontains=term)\n                siglum_q |= Q(siglum__icontains=term)\n                rism_siglum_q |= Q(rism_siglum__name__icontains=term) | Q(\n                    rism_siglum__description__icontains=term\n                )\n                description_q |= Q(description__icontains=term)\n                summary_q |= Q(summary__icontains=term)\n                # provenance_q |= Q(provenance__name__icontains=term)\n            # All the Q objects are put together with OR.\n            # The end result is that at least one term has to match in at least one\n            # field\n            # general_search_q = (\n            #     title_q | siglum_q | rism_siglum_q | description_q | provenance_q\n            # )\n            general_search_q = (\n                title_q | siglum_q | rism_siglum_q | description_q | summary_q\n            )\n            q_obj_filter &= general_search_q\n\n        # For the indexing notes search we follow the same procedure as above but with\n        # different fields\n        if self.request.GET.get(\"indexing\"):\n            # Make list of terms split on spaces\n            indexing_search_terms = self.request.GET.get(\"indexing\").split(\" \")\n            # We need a Q Object for each field we're gonna look into\n            inventoried_by_q = Q()\n            full_text_entered_by_q = Q()\n            melodies_entered_by_q = Q()\n            proofreaders_q = Q()\n            other_editors_q = Q()\n            indexing_notes_q = Q()\n            # For each term, add it to the Q object of each field with an OR operation.\n            # We split the terms so that the words can be separated in the actual\n            # field, allowing for a more flexible search, and a field needs\n            # to match only one of the terms\n            for term in indexing_search_terms:\n                inventoried_by_q |= Q(inventoried_by__full_name__icontains=term)\n                full_text_entered_by_q |= Q(\n                    full_text_entered_by__full_name__icontains=term\n                )\n                melodies_entered_by_q |= Q(\n                    melodies_entered_by__full_name__icontains=term\n                )\n                proofreaders_q |= Q(proofreaders__full_name__icontains=term)\n                other_editors_q |= Q(other_editors__full_name__icontains=term)\n                indexing_notes_q |= Q(indexing_notes__icontains=term)\n            # All the Q objects are put together with OR.\n            # The end result is that at least one term has to match in at least one\n            # field\n            indexing_search_q = (\n                inventoried_by_q\n                | full_text_entered_by_q\n                | melodies_entered_by_q\n                | proofreaders_q\n                | other_editors_q\n                | indexing_notes_q\n            )\n            q_obj_filter &= indexing_search_q\n\n        return queryset.filter(q_obj_filter).prefetch_related(\n            Prefetch(\"century\", queryset=Century.objects.all().order_by(\"id\"))\n        )\n\n\nclass SourceCreateView(LoginRequiredMixin, UserPassesTestMixin, CreateView):\n    model = Source\n    template_name = \"source_create_form.html\"\n    form_class = SourceCreateForm\n\n    def test_func(self):\n        user = self.request.user\n        # checks if the user is allowed to create sources\n        is_authorized = user.groups.filter(\n            Q(name=\"project manager\") | Q(name=\"editor\") | Q(name=\"contributor\")\n        ).exists()\n\n        if is_authorized:\n            return True\n        else:\n            return False\n\n    def get_success_url(self):\n        return reverse(\"source-create\")\n\n    def form_valid(self, form):\n        form.instance.created_by = self.request.user\n        source = form.save()\n\n        # assign this source to the \"current_editors\"\n        current_editors = source.current_editors.all()\n\n        for editor in current_editors:\n            editor.sources_user_can_edit.add(source)\n\n        messages.success(\n            self.request,\n            \"Source created successfully!\",\n        )\n\n        return HttpResponseRedirect(self.get_success_url())\n\n\nclass SourceEditView(\n    LoginRequiredMixin, UserPassesTestMixin, UpdateView, SourceDetailView\n):\n    template_name = \"source_edit.html\"\n    model = Source\n    form_class = SourceEditForm\n    pk_url_kwarg = \"source_id\"\n\n    def test_func(self):\n        user = self.request.user\n        source_id = self.kwargs.get(self.pk_url_kwarg)\n        source = get_object_or_404(Source, id=source_id)\n\n        assigned_to_source = user.sources_user_can_edit.filter(id=source_id)\n\n        # checks if the user is a project manager\n        is_project_manager = user.groups.filter(name=\"project manager\").exists()\n        # checks if the user is an editor\n        is_editor = user.groups.filter(name=\"editor\").exists()\n        # checks if the user is a contributor\n        is_contributor = user.groups.filter(name=\"contributor\").exists()\n\n        if (\n            (is_project_manager)\n            or (is_editor and assigned_to_source)\n            or (is_editor and source.created_by == user)\n            or (is_contributor and source.created_by == user)\n        ):\n            return True\n        else:\n            return False\n\n    def form_valid(self, form):\n        form.instance.last_updated_by = self.request.user\n\n        # remove this source from the old \"current_editors\"\n        # assign this source to the new \"current_editors\"\n\n        old_current_editors = list(\n            Source.objects.get(id=form.instance.id).current_editors.all()\n        )\n        new_current_editors = form.cleaned_data[\"current_editors\"]\n        source = form.save()\n\n        for old_editor in old_current_editors:\n            old_editor.sources_user_can_edit.remove(source)\n\n        for new_editor in new_current_editors:\n            new_editor.sources_user_can_edit.add(source)\n\n        return HttpResponseRedirect(self.get_success_url())\n", "path": "django/cantusdb_project/main_app/views/source.py"}]}