We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
"My sources" page and sidebar should be ordered by date updated
How are sources supposed to show up in "My sources"? I am an editor for some sources but they don't seem to be in "my sources", so what determines this?


--- 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/templatetags/helper_tags.py`
Content:
```
1 import calendar
2 from typing import Union, Optional
3 from django import template
4 from main_app.models import Source
5 from articles.models import Article
6 from django.utils.safestring import mark_safe
7 from django.urls import reverse
8 from django.core.paginator import Paginator
9 from django.db.models import Q
10 
11 
12 register = template.Library()
13 
14 
15 @register.simple_tag(takes_context=False)
16 def recent_articles():
17     """
18     Generates a html unordered list of recent articles for display on the homepage
19 
20     Used in:
21         templates/flatpages/default.html
22     """
23     articles = Article.objects.order_by("-date_created")[:5]
24     list_item_template = '<li style="padding-bottom: 0.5em;"><a href="{url}">{title}</a><br><small>{date}</small></li>'
25     list_items = [
26         list_item_template.format(
27             url=a.get_absolute_url(),
28             title=a.title,
29             date=a.date_created.strftime("%A %B %-d, %Y"),
30         )
31         for a in articles
32     ]
33     list_items_string = "".join(list_items)
34     recent_articles_string = "<ul>{lis}</ul>".format(lis=list_items_string)
35     return mark_safe(recent_articles_string)
36 
37 
38 @register.filter(name="month_to_string")
39 def month_to_string(value: Optional[Union[str, int]]) -> Optional[Union[str, int]]:
40     """
41     Converts month number to textual representation, 3 letters (Jan, Mar, etc)
42 
43     used in:
44         main_app/templates/feast_detail.html
45         main_app/templates/feast_list.html
46     """
47     if type(value) == int and value in range(1, 13):
48         return calendar.month_abbr[value]
49     else:
50         return value
51 
52 
53 @register.simple_tag(takes_context=True)
54 def url_add_get_params(context, **kwargs):
55     """
56     accounts for the situations where there may be two paginations in one page
57 
58     Used in:
59         main_app/templates/pagination.html
60         main_app/templates/user_source_list.html
61     """
62     query = context["request"].GET.copy()
63     if "page" in kwargs:
64         query.pop("page", None)
65     if "page2" in kwargs:
66         query.pop("page2", None)
67     query.update(kwargs)
68     return query.urlencode()
69 
70 
71 @register.simple_tag(takes_context=False)
72 def source_links():
73     """
74     Generates a series of html option tags linking to sources in Cantus Dabase, for display on the homepage
75 
76     Used in:
77         templates/flatpages/default.html
78     """
79     sources = (
80         Source.objects.filter(published=True, segment__id=4063)
81         .exclude(siglum=None)
82         .values("siglum", "id")
83         .order_by("siglum")
84     )
85     options = ""
86     for source in sources:
87         option_str = (
88             f"<option value=source/{source['id']}>{source['siglum']}</option>\n"
89         )
90         options += option_str
91 
92     return mark_safe(options)
93 
94 
95 @register.filter
96 def classname(obj):
97     """
98     Returns the name of the object's class
99     A use-case is: {% if object|classname == "Notation" %}
100 
101     Used in:
102         main_app/templates/content_overview.html
103     """
104     return obj.__class__.__name__
105 
106 
107 @register.filter
108 def admin_url_name(class_name, action):
109     """
110     Accepts the name of a class in "main_app", and an action (either "change" or "delete") as arguments.
111     Returns the name of the URL for changing/deleting an object in the admin interface.
112 
113     Used in:
114         main_app/templates/content_overview.html
115     """
116     class_name = class_name.lower()
117     action = action.lower()
118 
119     return f"admin:main_app_{class_name}_{action}"
120 
121 
122 @register.filter(name="has_group")
123 def has_group(user, group_name):
124     """
125     Used in:
126         templates/base.html
127     """
128     return user.groups.filter(name=group_name).exists()
129 
130 
131 @register.simple_tag(takes_context=True)
132 def get_user_source_pagination(context):
133     user_created_sources = (
134         Source.objects.filter(
135             Q(current_editors=context["user"]) | Q(created_by=context["user"])
136         )
137         .order_by("-date_created")
138         .distinct()
139     )
140     paginator = Paginator(user_created_sources, 6)
141     page_number = context["request"].GET.get("page")
142     user_sources_page_obj = paginator.get_page(page_number)
143     return user_sources_page_obj
144 
145 
146 @register.simple_tag(takes_context=True)
147 def get_user_created_source_pagination(context):
148     user_created_sources = (
149         Source.objects.filter(created_by=context["user"])
150         .order_by("-date_created")
151         .distinct()
152     )
153     paginator = Paginator(user_created_sources, 6)
154     page_number = context["request"].GET.get("page2")
155     user_created_sources_page_obj = paginator.get_page(page_number)
156     return user_created_sources_page_obj
157
```

Path: `django/cantusdb_project/main_app/views/user.py`
Content:
```
1 from django.urls import reverse
2 from django.db.models.aggregates import Count
3 from django.views.generic import DetailView
4 from django.contrib.auth import get_user_model, login as auth_login
5 from main_app.models import Source
6 from django.views.generic import ListView
7 from django.contrib.auth.mixins import LoginRequiredMixin
8 from django.db.models import Q
9 from django.core.paginator import Paginator
10 from django.contrib.auth.views import LogoutView, LoginView
11 from django.contrib import messages
12 from extra_views import SearchableListMixin
13 from django.http import HttpResponseRedirect
14 from django.core.exceptions import PermissionDenied
15 from main_app.permissions import user_can_view_user_detail
16 
17 
18 class UserDetailView(DetailView):
19     """Detail view for User model
20 
21     Accessed by /users/<pk>
22     """
23 
24     model = get_user_model()
25     context_object_name = "user"
26     template_name = "user_detail.html"
27 
28     def get_context_data(self, **kwargs):
29         user = self.get_object()
30         # to begin, if the person viewing the site is not logged in,
31         # they should only be able to view the detail pages of indexers,
32         # and not the detail pages of run-of-the-mill users
33         viewing_user = self.request.user
34         if not user_can_view_user_detail(viewing_user, user):
35             raise PermissionDenied()
36 
37         context = super().get_context_data(**kwargs)
38         display_unpublished = viewing_user.is_authenticated
39         sort_by_siglum = lambda source: source.siglum
40         if display_unpublished:
41             context["inventoried_sources"] = sorted(
42                 user.inventoried_sources.all(), key=sort_by_siglum
43             )
44             context["full_text_sources"] = sorted(
45                 user.entered_full_text_for_sources.all(), key=sort_by_siglum
46             )
47             context["melody_sources"] = sorted(
48                 user.entered_melody_for_sources.all(), key=sort_by_siglum
49             )
50             context["proofread_sources"] = sorted(
51                 user.proofread_sources.all(), key=sort_by_siglum
52             )
53             context["edited_sources"] = sorted(
54                 user.edited_sources.all(), key=sort_by_siglum
55             )
56         else:
57             context["inventoried_sources"] = sorted(
58                 user.inventoried_sources.all().filter(published=True),
59                 key=sort_by_siglum,
60             )
61             context["full_text_sources"] = sorted(
62                 user.entered_full_text_for_sources.all().filter(published=True),
63                 key=sort_by_siglum,
64             )
65             context["melody_sources"] = sorted(
66                 user.entered_melody_for_sources.all().filter(published=True),
67                 key=sort_by_siglum,
68             )
69             context["proofread_sources"] = sorted(
70                 user.proofread_sources.all().filter(published=True), key=sort_by_siglum
71             )
72             context["edited_sources"] = sorted(
73                 user.edited_sources.all().filter(published=True), key=sort_by_siglum
74             )
75 
76         return context
77 
78 
79 class UserSourceListView(LoginRequiredMixin, ListView):
80     model = Source
81     context_object_name = "sources"
82     template_name = "user_source_list.html"
83 
84     def get_context_data(self, **kwargs):
85         context = super().get_context_data(**kwargs)
86 
87         my_sources = (
88             Source.objects.filter(
89                 Q(current_editors=self.request.user)
90                 | Q(created_by=self.request.user)
91                 # | Q(inventoried_by=self.request.user)
92                 # | Q(full_text_entered_by=self.request.user)
93                 # | Q(melodies_entered_by=self.request.user)
94                 # | Q(proofreaders=self.request.user)
95                 # | Q(other_editors=self.request.user)
96             )
97             .order_by("-date_created")
98             .distinct()
99         )
100 
101         user_sources_paginator = Paginator(my_sources, 10)
102         user_sources_page_num = self.request.GET.get("page")
103         user_sources_page_obj = user_sources_paginator.get_page(user_sources_page_num)
104 
105         user_created_sources = (
106             Source.objects.filter(created_by=self.request.user)
107             .order_by("-date_created")
108             .distinct()
109         )
110         user_created_paginator = Paginator(user_created_sources, 6)
111         user_created_page_num = self.request.GET.get("page2")
112         user_created_page_obj = user_created_paginator.get_page(user_created_page_num)
113 
114         context["page_obj"] = user_sources_page_obj
115         context["user_created_sources_page_obj"] = user_created_page_obj
116         return context
117 
118 
119 class CustomLogoutView(LogoutView):
120     def get_next_page(self):
121         next_page = super().get_next_page()
122         messages.success(self.request, "You have successfully logged out!")
123         return next_page
124 
125 
126 class UserListView(LoginRequiredMixin, SearchableListMixin, ListView):
127     """A list of all User objects
128 
129     This view is equivalent to the user list view on the old Cantus.
130     This includes all User objects on the old Cantus.
131     When passed a `?q=<query>` argument in the GET request, it will filter users
132     based on the fields defined in `search_fields` with the `icontains` lookup.
133 
134     Accessed by /users/
135     """
136 
137     model = get_user_model()
138     ordering = "full_name"
139     search_fields = ["full_name", "institution", "city", "country"]
140     paginate_by = 100
141     template_name = "user_list.html"
142     context_object_name = "users"
143 
144 
145 class IndexerListView(SearchableListMixin, ListView):
146     """A list of User objects shown to the public
147 
148     This view replaces the indexer list view on the old Cantus.
149     The indexers are considered a subset of all User objects, the subset shown to the public.
150     This includes the User objects corresponding to Indexer objects on the old Cantus.
151     When passed a `?q=<query>` argument in the GET request, it will filter users
152     based on the fields defined in `search_fields` with the `icontains` lookup.
153 
154     Accessed by /indexers/
155     """
156 
157     model = get_user_model()
158     ordering = "full_name"
159     search_fields = ["full_name", "institution", "city", "country"]
160     paginate_by = 100
161     template_name = "indexer_list.html"
162     context_object_name = "indexers"
163 
164     def get_queryset(self):
165         all_users = super().get_queryset()
166         indexers = all_users.filter(is_indexer=True)
167         display_unpublished = self.request.user.is_authenticated
168         if display_unpublished:
169             indexers = indexers.annotate(source_count=Count("inventoried_sources"))
170             # display those who have at least one source
171             return indexers.filter(source_count__gte=1)
172         else:
173             indexers = indexers.annotate(
174                 source_count=Count(
175                     "inventoried_sources", filter=Q(inventoried_sources__published=True)
176                 )
177             )
178             # display those who have at least one published source
179             return indexers.filter(source_count__gte=1)
180
```
--- 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/templatetags/helper_tags.py b/django/cantusdb_project/main_app/templatetags/helper_tags.py
--- a/django/cantusdb_project/main_app/templatetags/helper_tags.py
+++ b/django/cantusdb_project/main_app/templatetags/helper_tags.py
@@ -134,7 +134,7 @@
         Source.objects.filter(
             Q(current_editors=context["user"]) | Q(created_by=context["user"])
         )
-        .order_by("-date_created")
+        .order_by("-date_updated")
         .distinct()
     )
     paginator = Paginator(user_created_sources, 6)
diff --git a/django/cantusdb_project/main_app/views/user.py b/django/cantusdb_project/main_app/views/user.py
--- a/django/cantusdb_project/main_app/views/user.py
+++ b/django/cantusdb_project/main_app/views/user.py
@@ -104,7 +104,7 @@
 
         user_created_sources = (
             Source.objects.filter(created_by=self.request.user)
-            .order_by("-date_created")
+            .order_by("-date_updated")
             .distinct()
         )
         user_created_paginator = Paginator(user_created_sources, 6)

{"golden_diff": "diff --git a/django/cantusdb_project/main_app/templatetags/helper_tags.py b/django/cantusdb_project/main_app/templatetags/helper_tags.py\n--- a/django/cantusdb_project/main_app/templatetags/helper_tags.py\n+++ b/django/cantusdb_project/main_app/templatetags/helper_tags.py\n@@ -134,7 +134,7 @@\n         Source.objects.filter(\n             Q(current_editors=context[\"user\"]) | Q(created_by=context[\"user\"])\n         )\n-        .order_by(\"-date_created\")\n+        .order_by(\"-date_updated\")\n         .distinct()\n     )\n     paginator = Paginator(user_created_sources, 6)\ndiff --git a/django/cantusdb_project/main_app/views/user.py b/django/cantusdb_project/main_app/views/user.py\n--- a/django/cantusdb_project/main_app/views/user.py\n+++ b/django/cantusdb_project/main_app/views/user.py\n@@ -104,7 +104,7 @@\n \n         user_created_sources = (\n             Source.objects.filter(created_by=self.request.user)\n-            .order_by(\"-date_created\")\n+            .order_by(\"-date_updated\")\n             .distinct()\n         )\n         user_created_paginator = Paginator(user_created_sources, 6)\n", "issue": "\"My sources\" page and sidebar should be ordered by date updated\nHow are sources supposed to show up in \"My sources\"? I am an editor for some sources but they don't seem to be in \"my sources\", so what determines this?\r\n\n", "before_files": [{"content": "import calendar\nfrom typing import Union, Optional\nfrom django import template\nfrom main_app.models import Source\nfrom articles.models import Article\nfrom django.utils.safestring import mark_safe\nfrom django.urls import reverse\nfrom django.core.paginator import Paginator\nfrom django.db.models import Q\n\n\nregister = template.Library()\n\n\n@register.simple_tag(takes_context=False)\ndef recent_articles():\n    \"\"\"\n    Generates a html unordered list of recent articles for display on the homepage\n\n    Used in:\n        templates/flatpages/default.html\n    \"\"\"\n    articles = Article.objects.order_by(\"-date_created\")[:5]\n    list_item_template = '<li style=\"padding-bottom: 0.5em;\"><a href=\"{url}\">{title}</a><br><small>{date}</small></li>'\n    list_items = [\n        list_item_template.format(\n            url=a.get_absolute_url(),\n            title=a.title,\n            date=a.date_created.strftime(\"%A %B %-d, %Y\"),\n        )\n        for a in articles\n    ]\n    list_items_string = \"\".join(list_items)\n    recent_articles_string = \"<ul>{lis}</ul>\".format(lis=list_items_string)\n    return mark_safe(recent_articles_string)\n\n\n@register.filter(name=\"month_to_string\")\ndef month_to_string(value: Optional[Union[str, int]]) -> Optional[Union[str, int]]:\n    \"\"\"\n    Converts month number to textual representation, 3 letters (Jan, Mar, etc)\n\n    used in:\n        main_app/templates/feast_detail.html\n        main_app/templates/feast_list.html\n    \"\"\"\n    if type(value) == int and value in range(1, 13):\n        return calendar.month_abbr[value]\n    else:\n        return value\n\n\n@register.simple_tag(takes_context=True)\ndef url_add_get_params(context, **kwargs):\n    \"\"\"\n    accounts for the situations where there may be two paginations in one page\n\n    Used in:\n        main_app/templates/pagination.html\n        main_app/templates/user_source_list.html\n    \"\"\"\n    query = context[\"request\"].GET.copy()\n    if \"page\" in kwargs:\n        query.pop(\"page\", None)\n    if \"page2\" in kwargs:\n        query.pop(\"page2\", None)\n    query.update(kwargs)\n    return query.urlencode()\n\n\n@register.simple_tag(takes_context=False)\ndef source_links():\n    \"\"\"\n    Generates a series of html option tags linking to sources in Cantus Dabase, for display on the homepage\n\n    Used in:\n        templates/flatpages/default.html\n    \"\"\"\n    sources = (\n        Source.objects.filter(published=True, segment__id=4063)\n        .exclude(siglum=None)\n        .values(\"siglum\", \"id\")\n        .order_by(\"siglum\")\n    )\n    options = \"\"\n    for source in sources:\n        option_str = (\n            f\"<option value=source/{source['id']}>{source['siglum']}</option>\\n\"\n        )\n        options += option_str\n\n    return mark_safe(options)\n\n\n@register.filter\ndef classname(obj):\n    \"\"\"\n    Returns the name of the object's class\n    A use-case is: {% if object|classname == \"Notation\" %}\n\n    Used in:\n        main_app/templates/content_overview.html\n    \"\"\"\n    return obj.__class__.__name__\n\n\n@register.filter\ndef admin_url_name(class_name, action):\n    \"\"\"\n    Accepts the name of a class in \"main_app\", and an action (either \"change\" or \"delete\") as arguments.\n    Returns the name of the URL for changing/deleting an object in the admin interface.\n\n    Used in:\n        main_app/templates/content_overview.html\n    \"\"\"\n    class_name = class_name.lower()\n    action = action.lower()\n\n    return f\"admin:main_app_{class_name}_{action}\"\n\n\n@register.filter(name=\"has_group\")\ndef has_group(user, group_name):\n    \"\"\"\n    Used in:\n        templates/base.html\n    \"\"\"\n    return user.groups.filter(name=group_name).exists()\n\n\n@register.simple_tag(takes_context=True)\ndef get_user_source_pagination(context):\n    user_created_sources = (\n        Source.objects.filter(\n            Q(current_editors=context[\"user\"]) | Q(created_by=context[\"user\"])\n        )\n        .order_by(\"-date_created\")\n        .distinct()\n    )\n    paginator = Paginator(user_created_sources, 6)\n    page_number = context[\"request\"].GET.get(\"page\")\n    user_sources_page_obj = paginator.get_page(page_number)\n    return user_sources_page_obj\n\n\n@register.simple_tag(takes_context=True)\ndef get_user_created_source_pagination(context):\n    user_created_sources = (\n        Source.objects.filter(created_by=context[\"user\"])\n        .order_by(\"-date_created\")\n        .distinct()\n    )\n    paginator = Paginator(user_created_sources, 6)\n    page_number = context[\"request\"].GET.get(\"page2\")\n    user_created_sources_page_obj = paginator.get_page(page_number)\n    return user_created_sources_page_obj\n", "path": "django/cantusdb_project/main_app/templatetags/helper_tags.py"}, {"content": "from django.urls import reverse\nfrom django.db.models.aggregates import Count\nfrom django.views.generic import DetailView\nfrom django.contrib.auth import get_user_model, login as auth_login\nfrom main_app.models import Source\nfrom django.views.generic import ListView\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.db.models import Q\nfrom django.core.paginator import Paginator\nfrom django.contrib.auth.views import LogoutView, LoginView\nfrom django.contrib import messages\nfrom extra_views import SearchableListMixin\nfrom django.http import HttpResponseRedirect\nfrom django.core.exceptions import PermissionDenied\nfrom main_app.permissions import user_can_view_user_detail\n\n\nclass UserDetailView(DetailView):\n    \"\"\"Detail view for User model\n\n    Accessed by /users/<pk>\n    \"\"\"\n\n    model = get_user_model()\n    context_object_name = \"user\"\n    template_name = \"user_detail.html\"\n\n    def get_context_data(self, **kwargs):\n        user = self.get_object()\n        # to begin, if the person viewing the site is not logged in,\n        # they should only be able to view the detail pages of indexers,\n        # and not the detail pages of run-of-the-mill users\n        viewing_user = self.request.user\n        if not user_can_view_user_detail(viewing_user, user):\n            raise PermissionDenied()\n\n        context = super().get_context_data(**kwargs)\n        display_unpublished = viewing_user.is_authenticated\n        sort_by_siglum = lambda source: source.siglum\n        if display_unpublished:\n            context[\"inventoried_sources\"] = sorted(\n                user.inventoried_sources.all(), key=sort_by_siglum\n            )\n            context[\"full_text_sources\"] = sorted(\n                user.entered_full_text_for_sources.all(), key=sort_by_siglum\n            )\n            context[\"melody_sources\"] = sorted(\n                user.entered_melody_for_sources.all(), key=sort_by_siglum\n            )\n            context[\"proofread_sources\"] = sorted(\n                user.proofread_sources.all(), key=sort_by_siglum\n            )\n            context[\"edited_sources\"] = sorted(\n                user.edited_sources.all(), key=sort_by_siglum\n            )\n        else:\n            context[\"inventoried_sources\"] = sorted(\n                user.inventoried_sources.all().filter(published=True),\n                key=sort_by_siglum,\n            )\n            context[\"full_text_sources\"] = sorted(\n                user.entered_full_text_for_sources.all().filter(published=True),\n                key=sort_by_siglum,\n            )\n            context[\"melody_sources\"] = sorted(\n                user.entered_melody_for_sources.all().filter(published=True),\n                key=sort_by_siglum,\n            )\n            context[\"proofread_sources\"] = sorted(\n                user.proofread_sources.all().filter(published=True), key=sort_by_siglum\n            )\n            context[\"edited_sources\"] = sorted(\n                user.edited_sources.all().filter(published=True), key=sort_by_siglum\n            )\n\n        return context\n\n\nclass UserSourceListView(LoginRequiredMixin, ListView):\n    model = Source\n    context_object_name = \"sources\"\n    template_name = \"user_source_list.html\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        my_sources = (\n            Source.objects.filter(\n                Q(current_editors=self.request.user)\n                | Q(created_by=self.request.user)\n                # | Q(inventoried_by=self.request.user)\n                # | Q(full_text_entered_by=self.request.user)\n                # | Q(melodies_entered_by=self.request.user)\n                # | Q(proofreaders=self.request.user)\n                # | Q(other_editors=self.request.user)\n            )\n            .order_by(\"-date_created\")\n            .distinct()\n        )\n\n        user_sources_paginator = Paginator(my_sources, 10)\n        user_sources_page_num = self.request.GET.get(\"page\")\n        user_sources_page_obj = user_sources_paginator.get_page(user_sources_page_num)\n\n        user_created_sources = (\n            Source.objects.filter(created_by=self.request.user)\n            .order_by(\"-date_created\")\n            .distinct()\n        )\n        user_created_paginator = Paginator(user_created_sources, 6)\n        user_created_page_num = self.request.GET.get(\"page2\")\n        user_created_page_obj = user_created_paginator.get_page(user_created_page_num)\n\n        context[\"page_obj\"] = user_sources_page_obj\n        context[\"user_created_sources_page_obj\"] = user_created_page_obj\n        return context\n\n\nclass CustomLogoutView(LogoutView):\n    def get_next_page(self):\n        next_page = super().get_next_page()\n        messages.success(self.request, \"You have successfully logged out!\")\n        return next_page\n\n\nclass UserListView(LoginRequiredMixin, SearchableListMixin, ListView):\n    \"\"\"A list of all User objects\n\n    This view is equivalent to the user list view on the old Cantus.\n    This includes all User objects on the old Cantus.\n    When passed a `?q=<query>` argument in the GET request, it will filter users\n    based on the fields defined in `search_fields` with the `icontains` lookup.\n\n    Accessed by /users/\n    \"\"\"\n\n    model = get_user_model()\n    ordering = \"full_name\"\n    search_fields = [\"full_name\", \"institution\", \"city\", \"country\"]\n    paginate_by = 100\n    template_name = \"user_list.html\"\n    context_object_name = \"users\"\n\n\nclass IndexerListView(SearchableListMixin, ListView):\n    \"\"\"A list of User objects shown to the public\n\n    This view replaces the indexer list view on the old Cantus.\n    The indexers are considered a subset of all User objects, the subset shown to the public.\n    This includes the User objects corresponding to Indexer objects on the old Cantus.\n    When passed a `?q=<query>` argument in the GET request, it will filter users\n    based on the fields defined in `search_fields` with the `icontains` lookup.\n\n    Accessed by /indexers/\n    \"\"\"\n\n    model = get_user_model()\n    ordering = \"full_name\"\n    search_fields = [\"full_name\", \"institution\", \"city\", \"country\"]\n    paginate_by = 100\n    template_name = \"indexer_list.html\"\n    context_object_name = \"indexers\"\n\n    def get_queryset(self):\n        all_users = super().get_queryset()\n        indexers = all_users.filter(is_indexer=True)\n        display_unpublished = self.request.user.is_authenticated\n        if display_unpublished:\n            indexers = indexers.annotate(source_count=Count(\"inventoried_sources\"))\n            # display those who have at least one source\n            return indexers.filter(source_count__gte=1)\n        else:\n            indexers = indexers.annotate(\n                source_count=Count(\n                    \"inventoried_sources\", filter=Q(inventoried_sources__published=True)\n                )\n            )\n            # display those who have at least one published source\n            return indexers.filter(source_count__gte=1)\n", "path": "django/cantusdb_project/main_app/views/user.py"}], "after_files": [{"content": "import calendar\nfrom typing import Union, Optional\nfrom django import template\nfrom main_app.models import Source\nfrom articles.models import Article\nfrom django.utils.safestring import mark_safe\nfrom django.urls import reverse\nfrom django.core.paginator import Paginator\nfrom django.db.models import Q\n\n\nregister = template.Library()\n\n\n@register.simple_tag(takes_context=False)\ndef recent_articles():\n    \"\"\"\n    Generates a html unordered list of recent articles for display on the homepage\n\n    Used in:\n        templates/flatpages/default.html\n    \"\"\"\n    articles = Article.objects.order_by(\"-date_created\")[:5]\n    list_item_template = '<li style=\"padding-bottom: 0.5em;\"><a href=\"{url}\">{title}</a><br><small>{date}</small></li>'\n    list_items = [\n        list_item_template.format(\n            url=a.get_absolute_url(),\n            title=a.title,\n            date=a.date_created.strftime(\"%A %B %-d, %Y\"),\n        )\n        for a in articles\n    ]\n    list_items_string = \"\".join(list_items)\n    recent_articles_string = \"<ul>{lis}</ul>\".format(lis=list_items_string)\n    return mark_safe(recent_articles_string)\n\n\n@register.filter(name=\"month_to_string\")\ndef month_to_string(value: Optional[Union[str, int]]) -> Optional[Union[str, int]]:\n    \"\"\"\n    Converts month number to textual representation, 3 letters (Jan, Mar, etc)\n\n    used in:\n        main_app/templates/feast_detail.html\n        main_app/templates/feast_list.html\n    \"\"\"\n    if type(value) == int and value in range(1, 13):\n        return calendar.month_abbr[value]\n    else:\n        return value\n\n\n@register.simple_tag(takes_context=True)\ndef url_add_get_params(context, **kwargs):\n    \"\"\"\n    accounts for the situations where there may be two paginations in one page\n\n    Used in:\n        main_app/templates/pagination.html\n        main_app/templates/user_source_list.html\n    \"\"\"\n    query = context[\"request\"].GET.copy()\n    if \"page\" in kwargs:\n        query.pop(\"page\", None)\n    if \"page2\" in kwargs:\n        query.pop(\"page2\", None)\n    query.update(kwargs)\n    return query.urlencode()\n\n\n@register.simple_tag(takes_context=False)\ndef source_links():\n    \"\"\"\n    Generates a series of html option tags linking to sources in Cantus Dabase, for display on the homepage\n\n    Used in:\n        templates/flatpages/default.html\n    \"\"\"\n    sources = (\n        Source.objects.filter(published=True, segment__id=4063)\n        .exclude(siglum=None)\n        .values(\"siglum\", \"id\")\n        .order_by(\"siglum\")\n    )\n    options = \"\"\n    for source in sources:\n        option_str = (\n            f\"<option value=source/{source['id']}>{source['siglum']}</option>\\n\"\n        )\n        options += option_str\n\n    return mark_safe(options)\n\n\n@register.filter\ndef classname(obj):\n    \"\"\"\n    Returns the name of the object's class\n    A use-case is: {% if object|classname == \"Notation\" %}\n\n    Used in:\n        main_app/templates/content_overview.html\n    \"\"\"\n    return obj.__class__.__name__\n\n\n@register.filter\ndef admin_url_name(class_name, action):\n    \"\"\"\n    Accepts the name of a class in \"main_app\", and an action (either \"change\" or \"delete\") as arguments.\n    Returns the name of the URL for changing/deleting an object in the admin interface.\n\n    Used in:\n        main_app/templates/content_overview.html\n    \"\"\"\n    class_name = class_name.lower()\n    action = action.lower()\n\n    return f\"admin:main_app_{class_name}_{action}\"\n\n\n@register.filter(name=\"has_group\")\ndef has_group(user, group_name):\n    \"\"\"\n    Used in:\n        templates/base.html\n    \"\"\"\n    return user.groups.filter(name=group_name).exists()\n\n\n@register.simple_tag(takes_context=True)\ndef get_user_source_pagination(context):\n    user_created_sources = (\n        Source.objects.filter(\n            Q(current_editors=context[\"user\"]) | Q(created_by=context[\"user\"])\n        )\n        .order_by(\"-date_updated\")\n        .distinct()\n    )\n    paginator = Paginator(user_created_sources, 6)\n    page_number = context[\"request\"].GET.get(\"page\")\n    user_sources_page_obj = paginator.get_page(page_number)\n    return user_sources_page_obj\n\n\n@register.simple_tag(takes_context=True)\ndef get_user_created_source_pagination(context):\n    user_created_sources = (\n        Source.objects.filter(created_by=context[\"user\"])\n        .order_by(\"-date_created\")\n        .distinct()\n    )\n    paginator = Paginator(user_created_sources, 6)\n    page_number = context[\"request\"].GET.get(\"page2\")\n    user_created_sources_page_obj = paginator.get_page(page_number)\n    return user_created_sources_page_obj\n", "path": "django/cantusdb_project/main_app/templatetags/helper_tags.py"}, {"content": "from django.urls import reverse\nfrom django.db.models.aggregates import Count\nfrom django.views.generic import DetailView\nfrom django.contrib.auth import get_user_model, login as auth_login\nfrom main_app.models import Source\nfrom django.views.generic import ListView\nfrom django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.db.models import Q\nfrom django.core.paginator import Paginator\nfrom django.contrib.auth.views import LogoutView, LoginView\nfrom django.contrib import messages\nfrom extra_views import SearchableListMixin\nfrom django.http import HttpResponseRedirect\nfrom django.core.exceptions import PermissionDenied\nfrom main_app.permissions import user_can_view_user_detail\n\n\nclass UserDetailView(DetailView):\n    \"\"\"Detail view for User model\n\n    Accessed by /users/<pk>\n    \"\"\"\n\n    model = get_user_model()\n    context_object_name = \"user\"\n    template_name = \"user_detail.html\"\n\n    def get_context_data(self, **kwargs):\n        user = self.get_object()\n        # to begin, if the person viewing the site is not logged in,\n        # they should only be able to view the detail pages of indexers,\n        # and not the detail pages of run-of-the-mill users\n        viewing_user = self.request.user\n        if not user_can_view_user_detail(viewing_user, user):\n            raise PermissionDenied()\n\n        context = super().get_context_data(**kwargs)\n        display_unpublished = viewing_user.is_authenticated\n        sort_by_siglum = lambda source: source.siglum\n        if display_unpublished:\n            context[\"inventoried_sources\"] = sorted(\n                user.inventoried_sources.all(), key=sort_by_siglum\n            )\n            context[\"full_text_sources\"] = sorted(\n                user.entered_full_text_for_sources.all(), key=sort_by_siglum\n            )\n            context[\"melody_sources\"] = sorted(\n                user.entered_melody_for_sources.all(), key=sort_by_siglum\n            )\n            context[\"proofread_sources\"] = sorted(\n                user.proofread_sources.all(), key=sort_by_siglum\n            )\n            context[\"edited_sources\"] = sorted(\n                user.edited_sources.all(), key=sort_by_siglum\n            )\n        else:\n            context[\"inventoried_sources\"] = sorted(\n                user.inventoried_sources.all().filter(published=True),\n                key=sort_by_siglum,\n            )\n            context[\"full_text_sources\"] = sorted(\n                user.entered_full_text_for_sources.all().filter(published=True),\n                key=sort_by_siglum,\n            )\n            context[\"melody_sources\"] = sorted(\n                user.entered_melody_for_sources.all().filter(published=True),\n                key=sort_by_siglum,\n            )\n            context[\"proofread_sources\"] = sorted(\n                user.proofread_sources.all().filter(published=True), key=sort_by_siglum\n            )\n            context[\"edited_sources\"] = sorted(\n                user.edited_sources.all().filter(published=True), key=sort_by_siglum\n            )\n\n        return context\n\n\nclass UserSourceListView(LoginRequiredMixin, ListView):\n    model = Source\n    context_object_name = \"sources\"\n    template_name = \"user_source_list.html\"\n\n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        my_sources = (\n            Source.objects.filter(\n                Q(current_editors=self.request.user)\n                | Q(created_by=self.request.user)\n                # | Q(inventoried_by=self.request.user)\n                # | Q(full_text_entered_by=self.request.user)\n                # | Q(melodies_entered_by=self.request.user)\n                # | Q(proofreaders=self.request.user)\n                # | Q(other_editors=self.request.user)\n            )\n            .order_by(\"-date_created\")\n            .distinct()\n        )\n\n        user_sources_paginator = Paginator(my_sources, 10)\n        user_sources_page_num = self.request.GET.get(\"page\")\n        user_sources_page_obj = user_sources_paginator.get_page(user_sources_page_num)\n\n        user_created_sources = (\n            Source.objects.filter(created_by=self.request.user)\n            .order_by(\"-date_updated\")\n            .distinct()\n        )\n        user_created_paginator = Paginator(user_created_sources, 6)\n        user_created_page_num = self.request.GET.get(\"page2\")\n        user_created_page_obj = user_created_paginator.get_page(user_created_page_num)\n\n        context[\"page_obj\"] = user_sources_page_obj\n        context[\"user_created_sources_page_obj\"] = user_created_page_obj\n        return context\n\n\nclass CustomLogoutView(LogoutView):\n    def get_next_page(self):\n        next_page = super().get_next_page()\n        messages.success(self.request, \"You have successfully logged out!\")\n        return next_page\n\n\nclass UserListView(LoginRequiredMixin, SearchableListMixin, ListView):\n    \"\"\"A list of all User objects\n\n    This view is equivalent to the user list view on the old Cantus.\n    This includes all User objects on the old Cantus.\n    When passed a `?q=<query>` argument in the GET request, it will filter users\n    based on the fields defined in `search_fields` with the `icontains` lookup.\n\n    Accessed by /users/\n    \"\"\"\n\n    model = get_user_model()\n    ordering = \"full_name\"\n    search_fields = [\"full_name\", \"institution\", \"city\", \"country\"]\n    paginate_by = 100\n    template_name = \"user_list.html\"\n    context_object_name = \"users\"\n\n\nclass IndexerListView(SearchableListMixin, ListView):\n    \"\"\"A list of User objects shown to the public\n\n    This view replaces the indexer list view on the old Cantus.\n    The indexers are considered a subset of all User objects, the subset shown to the public.\n    This includes the User objects corresponding to Indexer objects on the old Cantus.\n    When passed a `?q=<query>` argument in the GET request, it will filter users\n    based on the fields defined in `search_fields` with the `icontains` lookup.\n\n    Accessed by /indexers/\n    \"\"\"\n\n    model = get_user_model()\n    ordering = \"full_name\"\n    search_fields = [\"full_name\", \"institution\", \"city\", \"country\"]\n    paginate_by = 100\n    template_name = \"indexer_list.html\"\n    context_object_name = \"indexers\"\n\n    def get_queryset(self):\n        all_users = super().get_queryset()\n        indexers = all_users.filter(is_indexer=True)\n        display_unpublished = self.request.user.is_authenticated\n        if display_unpublished:\n            indexers = indexers.annotate(source_count=Count(\"inventoried_sources\"))\n            # display those who have at least one source\n            return indexers.filter(source_count__gte=1)\n        else:\n            indexers = indexers.annotate(\n                source_count=Count(\n                    \"inventoried_sources\", filter=Q(inventoried_sources__published=True)\n                )\n            )\n            # display those who have at least one published source\n            return indexers.filter(source_count__gte=1)\n", "path": "django/cantusdb_project/main_app/views/user.py"}]}