We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
should editors get automatic proofreader access to their own sources?
It seems weird to me that on sources you (an editor) create, you can always access the Volpiano editor (assuming the source is non-empty!) but you have to be added as an editor to access the proofreading form. 
<img width="969" alt="image" src="https://user-images.githubusercontent.com/67451875/209043248-1a0a8e13-0196-498d-a835-081fddc3ee13.png">
<img width="666" alt="image" src="https://user-images.githubusercontent.com/67451875/209043188-94b4b649-c1e7-41cc-9692-fd2a6947c28c.png">

If there were something preventing you from proofreading your own source as an intentional part of the workflow, this might be sort of useful (?) but as it is it just adds an extra step to get permissions you should in theory already have. 

New source created doesn't show up in home page
I just created a new source from my account. It doesn't show up immediately on the home page in the My Sources sidebar (even after refreshing the page and re-logging in). It does however appear in the database as well as in the my-sources page.

--- 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
@@ -199,6 +199,7 @@
 
         # assign this source to the "current_editors"
         current_editors = source.current_editors.all()
+        self.request.user.sources_user_can_edit.add(source)
 
         for editor in current_editors:
             editor.sources_user_can_edit.add(source)

{"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@@ -199,6 +199,7 @@\n \n         # assign this source to the \"current_editors\"\n         current_editors = source.current_editors.all()\n+        self.request.user.sources_user_can_edit.add(source)\n \n         for editor in current_editors:\n             editor.sources_user_can_edit.add(source)\n", "issue": "should editors get automatic proofreader access to their own sources?\nIt seems weird to me that on sources you (an editor) create, you can always access the Volpiano editor (assuming the source is non-empty!) but you have to be added as an editor to access the proofreading form. \r\n<img width=\"969\" alt=\"image\" src=\"https://user-images.githubusercontent.com/67451875/209043248-1a0a8e13-0196-498d-a835-081fddc3ee13.png\">\r\n<img width=\"666\" alt=\"image\" src=\"https://user-images.githubusercontent.com/67451875/209043188-94b4b649-c1e7-41cc-9692-fd2a6947c28c.png\">\r\n\r\nIf there were something preventing you from proofreading your own source as an intentional part of the workflow, this might be sort of useful (?) but as it is it just adds an extra step to get permissions you should in theory already have. \r\n\nNew source created doesn't show up in home page\nI just created a new source from my account. It doesn't show up immediately on the home page in the My Sources sidebar (even after refreshing the page and re-logging in). It does however appear in the database as well as in the my-sources page.\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        self.request.user.sources_user_can_edit.add(source)\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"}]}