We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
currently missing a user list page
implement it similar to indexer list 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/urls.py`
Content:
```
1 from django.urls import path, include
2 from main_app.views import *
3 from main_app.views import views
4 from main_app.views.sequence import SequenceEditView
5 from main_app.views.source import SourceCreateView, SourceEditView
6 from main_app.views.chant import ChantEditVolpianoView
7 from django.contrib.auth import views as auth_views
8 from main_app.views.user import UserDetailView, UserSourceListView, CustomLogoutView
9 
10 urlpatterns = [
11     # static pages
12     path("index/", FullIndexView.as_view(), name="chant-index"),
13     path("contact/", views.contact_us, name="contact"),
14     # login/logout/user
15     path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name="login"),
16     path('logout/', CustomLogoutView.as_view(), name="logout"),
17     path("my-sources/", UserSourceListView.as_view(), name="my-sources"),
18     path("users/<int:user_id>", UserDetailView.as_view(), name="user-detail"),
19 
20     # chant
21     path("chants/", ChantListView.as_view(), name="chant-list"),
22     path("chant/<int:pk>", ChantDetailView.as_view(), name="chant-detail"),
23     path("chant-search/", ChantSearchView.as_view(), name="chant-search"),
24     path(
25         "chant-create/<int:source_pk>", ChantCreateView.as_view(), name="chant-create"
26     ),
27     path("chant-update/<int:pk>", ChantUpdateView.as_view(), name="chant-update"),
28     path(
29         "id/<str:cantus_id>", ChantByCantusIDView.as_view(), name="chant-by-cantus-id"
30     ),
31     path("chant-delete/<int:pk>", ChantDeleteView.as_view(), name="chant-delete"),
32     path(
33         "edit-volpiano/<int:source_id>", 
34         ChantEditVolpianoView.as_view(), 
35         name="source-edit-volpiano"
36     ),
37     # feast
38     path("feasts/", FeastListView.as_view(), name="feast-list"),
39     path("feast/<int:pk>", FeastDetailView.as_view(), name="feast-detail"),
40     # genre
41     path("genres/", GenreListView.as_view(), name="genre-list"),
42     path("genre/<int:pk>", GenreDetailView.as_view(), name="genre-detail"),
43     # indexer
44     path("indexers/", IndexerListView.as_view(), name="indexer-list"),
45     path("indexer/<int:pk>", IndexerDetailView.as_view(), name="indexer-detail"),
46     # office
47     path("offices/", OfficeListView.as_view(), name="office-list"),
48     path("office/<int:pk>", OfficeDetailView.as_view(), name="office-detail"),
49     # sequence
50     path("sequences/", SequenceListView.as_view(), name="sequence-list"),
51     path("sequence/<int:pk>", SequenceDetailView.as_view(), name="sequence-detail",),
52     path("edit-sequence/<int:sequence_id>", SequenceEditView.as_view(), name="sequence-edit",),
53     # source
54     path("sources/", SourceListView.as_view(), name="source-list"),
55     path("source/<int:pk>", SourceDetailView.as_view(), name="source-detail"),
56     path(
57         "source-create/", 
58         SourceCreateView.as_view(), 
59         name="source-create"
60     ),
61     path(
62         "edit-source/<int:source_id>", 
63         SourceEditView.as_view(), 
64         name="source-edit"
65     ),
66     # melody
67     path("melody/", MelodySearchView.as_view(), name="melody-search"),
68     path("ajax/melody/<str:cantus_id>", views.ajax_melody_list, name="ajax-melody"),
69     path("ajax/melody-search/", views.ajax_melody_search, name="ajax-melody-search",),
70     # json api
71     path("json-sources/", views.json_sources_export, name="json-sources-export"),
72     path("json-node/<str:id>", views.json_node_export, name="json-node-export"),
73     path("json-nextchants/<str:cantus_id>", views.json_nextchants, name="json-nextchants"),
74     path(
75         "json-melody/<str:cantus_id>",
76         views.json_melody_export,
77         name="json-melody-export",
78     ),
79     # misc search
80     path(
81         "chant-search-ms/<int:source_pk>",
82         ChantSearchMSView.as_view(),
83         name="chant-search-ms",
84     ),
85     path("ci-search/<str:search_term>", CISearchView.as_view(), name="ci-search"),
86     path(
87         "ajax/search-bar/<str:search_term>",
88         views.ajax_search_bar,
89         name="ajax-search-bar",
90     ),
91     # misc
92     path("content-statistics", views.items_count, name="items-count"),
93     path("csv/<str:source_id>", views.csv_export, name="csv-export"),
94     path(
95         "ajax/concordance/<str:cantus_id>",
96         views.ajax_concordance_list,
97         name="ajax-concordance",
98     ),
99 ]
100 
101 handler404 = 'main_app.views.views.handle404'
102
```

Path: `django/cantusdb_project/main_app/views/user.py`
Content:
```
1 from urllib import request
2 from django.views.generic import DetailView
3 from django.contrib.auth import get_user_model
4 from main_app.models import Source
5 from django.views.generic import ListView
6 from django.contrib.auth.mixins import LoginRequiredMixin
7 from django.db.models import Q
8 from django.core.paginator import Paginator
9 from django.contrib.auth.views import LogoutView
10 from django.contrib import messages
11 
12 class UserDetailView(DetailView):
13     """Detail view for User model
14 
15     Accessed by /users/<user_id>
16     """
17 
18     model = get_user_model()
19     context_object_name = "user"
20     template_name = "user_detail.html"
21     pk_url_kwarg = 'user_id'	
22 
23 class UserSourceListView(LoginRequiredMixin, ListView):
24     model = Source
25     context_object_name = "sources"
26     template_name = "user_source_list.html"
27     paginate_by = 100
28 
29     def get_queryset(self):
30         return Source.objects.filter(
31             Q(current_editors=self.request.user)
32             | Q(created_by=self.request.user)
33             # | Q(inventoried_by=self.request.user)
34             # | Q(full_text_entered_by=self.request.user)
35             # | Q(melodies_entered_by=self.request.user)
36             # | Q(proofreaders=self.request.user)
37             # | Q(other_editors=self.request.user) 
38         ).order_by("title")
39         
40     def get_context_data(self, **kwargs):
41         context = super().get_context_data(**kwargs)
42 
43         user_created_sources = Source.objects.filter(created_by=self.request.user)
44         paginator = Paginator(user_created_sources, 10)
45         page_number = self.request.GET.get('page2')
46         page_obj = paginator.get_page(page_number)
47 
48         context["user_created_sources_page_obj"] = page_obj
49         return context
50 
51 class CustomLogoutView(LogoutView):
52     def get_next_page(self):
53         next_page = super().get_next_page()
54         messages.success(
55             self.request, 
56             'You have successfully logged out!'
57         )
58         return next_page
59
```

Path: `django/cantusdb_project/users/models.py`
Content:
```
1 from django.db import models
2 from django.contrib.auth.models import AbstractUser
3 
4 
5 class User(AbstractUser):
6     institution = models.CharField(max_length=255, blank=True, null=True)
7     city = models.CharField(max_length=255, blank=True, null=True)
8     country = models.CharField(max_length=255, blank=True, null=True)
9     website = models.URLField(blank=True, null=True)
10     sources_user_can_edit = models.ManyToManyField("main_app.Source", related_name="users_who_can_edit_this_source", blank=True)
11     full_name = models.CharField(max_length=255, blank=True, null=True)
12 
13     @property
14     def name(self):
15         if self.full_name:
16             return self.full_name
17         elif self.first_name and self.last_name:
18             return f'{self.first_name} {self.last_name}'
19 
20     def __str__(self):
21         if self.name:
22             return self.name
23         else:
24             return self.username
```
--- 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/urls.py b/django/cantusdb_project/main_app/urls.py
--- a/django/cantusdb_project/main_app/urls.py
+++ b/django/cantusdb_project/main_app/urls.py
@@ -5,7 +5,7 @@
 from main_app.views.source import SourceCreateView, SourceEditView
 from main_app.views.chant import ChantEditVolpianoView
 from django.contrib.auth import views as auth_views
-from main_app.views.user import UserDetailView, UserSourceListView, CustomLogoutView
+from main_app.views.user import UserDetailView, UserSourceListView, CustomLogoutView, UserListView
 
 urlpatterns = [
     # static pages
@@ -15,7 +15,8 @@
     path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name="login"),
     path('logout/', CustomLogoutView.as_view(), name="logout"),
     path("my-sources/", UserSourceListView.as_view(), name="my-sources"),
-    path("users/<int:user_id>", UserDetailView.as_view(), name="user-detail"),
+    path("users/<int:pk>", UserDetailView.as_view(), name="user-detail"),
+    path("users/", UserListView.as_view(), name="user-list"),
 
     # chant
     path("chants/", ChantListView.as_view(), name="chant-list"),
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
@@ -8,17 +8,17 @@
 from django.core.paginator import Paginator
 from django.contrib.auth.views import LogoutView
 from django.contrib import messages
+from extra_views import SearchableListMixin
 
 class UserDetailView(DetailView):
     """Detail view for User model
 
-    Accessed by /users/<user_id>
+    Accessed by /users/<pk>
     """
 
     model = get_user_model()
     context_object_name = "user"
     template_name = "user_detail.html"
-    pk_url_kwarg = 'user_id'	
 
 class UserSourceListView(LoginRequiredMixin, ListView):
     model = Source
@@ -56,3 +56,18 @@
             'You have successfully logged out!'
         )
         return next_page
+
+class UserListView(LoginRequiredMixin, SearchableListMixin, ListView):
+    """Searchable List view for User model
+
+    Accessed by /users/
+
+    When passed a ``?q=<query>`` argument in the GET request, it will filter users
+    based on the fields defined in ``search_fields`` with the ``icontains`` lookup
+    """
+
+    model = get_user_model()
+    search_fields = ["first_name", "last_name", "institution", "city", "country"]
+    paginate_by = 100
+    template_name = "user_list.html"
+    context_object_name = "users"
diff --git a/django/cantusdb_project/users/models.py b/django/cantusdb_project/users/models.py
--- a/django/cantusdb_project/users/models.py
+++ b/django/cantusdb_project/users/models.py
@@ -1,5 +1,6 @@
 from django.db import models
 from django.contrib.auth.models import AbstractUser
+from django.urls.base import reverse
 
 
 class User(AbstractUser):
@@ -21,4 +22,9 @@
         if self.name:
             return self.name
         else:
-            return self.username
\ No newline at end of file
+            return self.username
+
+    def get_absolute_url(self) -> str:
+        """Get the absolute URL for an instance of a model."""
+        detail_name = self.__class__.__name__.lower() + "-detail"
+        return reverse(detail_name, kwargs={"pk": self.pk})

{"golden_diff": "diff --git a/django/cantusdb_project/main_app/urls.py b/django/cantusdb_project/main_app/urls.py\n--- a/django/cantusdb_project/main_app/urls.py\n+++ b/django/cantusdb_project/main_app/urls.py\n@@ -5,7 +5,7 @@\n from main_app.views.source import SourceCreateView, SourceEditView\n from main_app.views.chant import ChantEditVolpianoView\n from django.contrib.auth import views as auth_views\n-from main_app.views.user import UserDetailView, UserSourceListView, CustomLogoutView\n+from main_app.views.user import UserDetailView, UserSourceListView, CustomLogoutView, UserListView\n \n urlpatterns = [\n     # static pages\n@@ -15,7 +15,8 @@\n     path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name=\"login\"),\n     path('logout/', CustomLogoutView.as_view(), name=\"logout\"),\n     path(\"my-sources/\", UserSourceListView.as_view(), name=\"my-sources\"),\n-    path(\"users/<int:user_id>\", UserDetailView.as_view(), name=\"user-detail\"),\n+    path(\"users/<int:pk>\", UserDetailView.as_view(), name=\"user-detail\"),\n+    path(\"users/\", UserListView.as_view(), name=\"user-list\"),\n \n     # chant\n     path(\"chants/\", ChantListView.as_view(), name=\"chant-list\"),\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@@ -8,17 +8,17 @@\n from django.core.paginator import Paginator\n from django.contrib.auth.views import LogoutView\n from django.contrib import messages\n+from extra_views import SearchableListMixin\n \n class UserDetailView(DetailView):\n     \"\"\"Detail view for User model\n \n-    Accessed by /users/<user_id>\n+    Accessed by /users/<pk>\n     \"\"\"\n \n     model = get_user_model()\n     context_object_name = \"user\"\n     template_name = \"user_detail.html\"\n-    pk_url_kwarg = 'user_id'\t\n \n class UserSourceListView(LoginRequiredMixin, ListView):\n     model = Source\n@@ -56,3 +56,18 @@\n             'You have successfully logged out!'\n         )\n         return next_page\n+\n+class UserListView(LoginRequiredMixin, SearchableListMixin, ListView):\n+    \"\"\"Searchable List view for User model\n+\n+    Accessed by /users/\n+\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+\n+    model = get_user_model()\n+    search_fields = [\"first_name\", \"last_name\", \"institution\", \"city\", \"country\"]\n+    paginate_by = 100\n+    template_name = \"user_list.html\"\n+    context_object_name = \"users\"\ndiff --git a/django/cantusdb_project/users/models.py b/django/cantusdb_project/users/models.py\n--- a/django/cantusdb_project/users/models.py\n+++ b/django/cantusdb_project/users/models.py\n@@ -1,5 +1,6 @@\n from django.db import models\n from django.contrib.auth.models import AbstractUser\n+from django.urls.base import reverse\n \n \n class User(AbstractUser):\n@@ -21,4 +22,9 @@\n         if self.name:\n             return self.name\n         else:\n-            return self.username\n\\ No newline at end of file\n+            return self.username\n+\n+    def get_absolute_url(self) -> str:\n+        \"\"\"Get the absolute URL for an instance of a model.\"\"\"\n+        detail_name = self.__class__.__name__.lower() + \"-detail\"\n+        return reverse(detail_name, kwargs={\"pk\": self.pk})\n", "issue": "currently missing a user list page\nimplement it similar to indexer list page\n", "before_files": [{"content": "from django.urls import path, include\nfrom main_app.views import *\nfrom main_app.views import views\nfrom main_app.views.sequence import SequenceEditView\nfrom main_app.views.source import SourceCreateView, SourceEditView\nfrom main_app.views.chant import ChantEditVolpianoView\nfrom django.contrib.auth import views as auth_views\nfrom main_app.views.user import UserDetailView, UserSourceListView, CustomLogoutView\n\nurlpatterns = [\n    # static pages\n    path(\"index/\", FullIndexView.as_view(), name=\"chant-index\"),\n    path(\"contact/\", views.contact_us, name=\"contact\"),\n    # login/logout/user\n    path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name=\"login\"),\n    path('logout/', CustomLogoutView.as_view(), name=\"logout\"),\n    path(\"my-sources/\", UserSourceListView.as_view(), name=\"my-sources\"),\n    path(\"users/<int:user_id>\", UserDetailView.as_view(), name=\"user-detail\"),\n\n    # chant\n    path(\"chants/\", ChantListView.as_view(), name=\"chant-list\"),\n    path(\"chant/<int:pk>\", ChantDetailView.as_view(), name=\"chant-detail\"),\n    path(\"chant-search/\", ChantSearchView.as_view(), name=\"chant-search\"),\n    path(\n        \"chant-create/<int:source_pk>\", ChantCreateView.as_view(), name=\"chant-create\"\n    ),\n    path(\"chant-update/<int:pk>\", ChantUpdateView.as_view(), name=\"chant-update\"),\n    path(\n        \"id/<str:cantus_id>\", ChantByCantusIDView.as_view(), name=\"chant-by-cantus-id\"\n    ),\n    path(\"chant-delete/<int:pk>\", ChantDeleteView.as_view(), name=\"chant-delete\"),\n    path(\n        \"edit-volpiano/<int:source_id>\", \n        ChantEditVolpianoView.as_view(), \n        name=\"source-edit-volpiano\"\n    ),\n    # feast\n    path(\"feasts/\", FeastListView.as_view(), name=\"feast-list\"),\n    path(\"feast/<int:pk>\", FeastDetailView.as_view(), name=\"feast-detail\"),\n    # genre\n    path(\"genres/\", GenreListView.as_view(), name=\"genre-list\"),\n    path(\"genre/<int:pk>\", GenreDetailView.as_view(), name=\"genre-detail\"),\n    # indexer\n    path(\"indexers/\", IndexerListView.as_view(), name=\"indexer-list\"),\n    path(\"indexer/<int:pk>\", IndexerDetailView.as_view(), name=\"indexer-detail\"),\n    # office\n    path(\"offices/\", OfficeListView.as_view(), name=\"office-list\"),\n    path(\"office/<int:pk>\", OfficeDetailView.as_view(), name=\"office-detail\"),\n    # sequence\n    path(\"sequences/\", SequenceListView.as_view(), name=\"sequence-list\"),\n    path(\"sequence/<int:pk>\", SequenceDetailView.as_view(), name=\"sequence-detail\",),\n    path(\"edit-sequence/<int:sequence_id>\", SequenceEditView.as_view(), name=\"sequence-edit\",),\n    # source\n    path(\"sources/\", SourceListView.as_view(), name=\"source-list\"),\n    path(\"source/<int:pk>\", SourceDetailView.as_view(), name=\"source-detail\"),\n    path(\n        \"source-create/\", \n        SourceCreateView.as_view(), \n        name=\"source-create\"\n    ),\n    path(\n        \"edit-source/<int:source_id>\", \n        SourceEditView.as_view(), \n        name=\"source-edit\"\n    ),\n    # melody\n    path(\"melody/\", MelodySearchView.as_view(), name=\"melody-search\"),\n    path(\"ajax/melody/<str:cantus_id>\", views.ajax_melody_list, name=\"ajax-melody\"),\n    path(\"ajax/melody-search/\", views.ajax_melody_search, name=\"ajax-melody-search\",),\n    # json api\n    path(\"json-sources/\", views.json_sources_export, name=\"json-sources-export\"),\n    path(\"json-node/<str:id>\", views.json_node_export, name=\"json-node-export\"),\n    path(\"json-nextchants/<str:cantus_id>\", views.json_nextchants, name=\"json-nextchants\"),\n    path(\n        \"json-melody/<str:cantus_id>\",\n        views.json_melody_export,\n        name=\"json-melody-export\",\n    ),\n    # misc search\n    path(\n        \"chant-search-ms/<int:source_pk>\",\n        ChantSearchMSView.as_view(),\n        name=\"chant-search-ms\",\n    ),\n    path(\"ci-search/<str:search_term>\", CISearchView.as_view(), name=\"ci-search\"),\n    path(\n        \"ajax/search-bar/<str:search_term>\",\n        views.ajax_search_bar,\n        name=\"ajax-search-bar\",\n    ),\n    # misc\n    path(\"content-statistics\", views.items_count, name=\"items-count\"),\n    path(\"csv/<str:source_id>\", views.csv_export, name=\"csv-export\"),\n    path(\n        \"ajax/concordance/<str:cantus_id>\",\n        views.ajax_concordance_list,\n        name=\"ajax-concordance\",\n    ),\n]\n\nhandler404 = 'main_app.views.views.handle404'\n", "path": "django/cantusdb_project/main_app/urls.py"}, {"content": "from urllib import request\nfrom django.views.generic import DetailView\nfrom django.contrib.auth import get_user_model\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\nfrom django.contrib import messages\n\nclass UserDetailView(DetailView):\n    \"\"\"Detail view for User model\n\n    Accessed by /users/<user_id>\n    \"\"\"\n\n    model = get_user_model()\n    context_object_name = \"user\"\n    template_name = \"user_detail.html\"\n    pk_url_kwarg = 'user_id'\t\n\nclass UserSourceListView(LoginRequiredMixin, ListView):\n    model = Source\n    context_object_name = \"sources\"\n    template_name = \"user_source_list.html\"\n    paginate_by = 100\n\n    def get_queryset(self):\n        return 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        ).order_by(\"title\")\n        \n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        user_created_sources = Source.objects.filter(created_by=self.request.user)\n        paginator = Paginator(user_created_sources, 10)\n        page_number = self.request.GET.get('page2')\n        page_obj = paginator.get_page(page_number)\n\n        context[\"user_created_sources_page_obj\"] = page_obj\n        return context\n\nclass CustomLogoutView(LogoutView):\n    def get_next_page(self):\n        next_page = super().get_next_page()\n        messages.success(\n            self.request, \n            'You have successfully logged out!'\n        )\n        return next_page\n", "path": "django/cantusdb_project/main_app/views/user.py"}, {"content": "from django.db import models\nfrom django.contrib.auth.models import AbstractUser\n\n\nclass User(AbstractUser):\n    institution = models.CharField(max_length=255, blank=True, null=True)\n    city = models.CharField(max_length=255, blank=True, null=True)\n    country = models.CharField(max_length=255, blank=True, null=True)\n    website = models.URLField(blank=True, null=True)\n    sources_user_can_edit = models.ManyToManyField(\"main_app.Source\", related_name=\"users_who_can_edit_this_source\", blank=True)\n    full_name = models.CharField(max_length=255, blank=True, null=True)\n\n    @property\n    def name(self):\n        if self.full_name:\n            return self.full_name\n        elif self.first_name and self.last_name:\n            return f'{self.first_name} {self.last_name}'\n\n    def __str__(self):\n        if self.name:\n            return self.name\n        else:\n            return self.username", "path": "django/cantusdb_project/users/models.py"}], "after_files": [{"content": "from django.urls import path, include\nfrom main_app.views import *\nfrom main_app.views import views\nfrom main_app.views.sequence import SequenceEditView\nfrom main_app.views.source import SourceCreateView, SourceEditView\nfrom main_app.views.chant import ChantEditVolpianoView\nfrom django.contrib.auth import views as auth_views\nfrom main_app.views.user import UserDetailView, UserSourceListView, CustomLogoutView, UserListView\n\nurlpatterns = [\n    # static pages\n    path(\"index/\", FullIndexView.as_view(), name=\"chant-index\"),\n    path(\"contact/\", views.contact_us, name=\"contact\"),\n    # login/logout/user\n    path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name=\"login\"),\n    path('logout/', CustomLogoutView.as_view(), name=\"logout\"),\n    path(\"my-sources/\", UserSourceListView.as_view(), name=\"my-sources\"),\n    path(\"users/<int:pk>\", UserDetailView.as_view(), name=\"user-detail\"),\n    path(\"users/\", UserListView.as_view(), name=\"user-list\"),\n\n    # chant\n    path(\"chants/\", ChantListView.as_view(), name=\"chant-list\"),\n    path(\"chant/<int:pk>\", ChantDetailView.as_view(), name=\"chant-detail\"),\n    path(\"chant-search/\", ChantSearchView.as_view(), name=\"chant-search\"),\n    path(\n        \"chant-create/<int:source_pk>\", ChantCreateView.as_view(), name=\"chant-create\"\n    ),\n    path(\"chant-update/<int:pk>\", ChantUpdateView.as_view(), name=\"chant-update\"),\n    path(\n        \"id/<str:cantus_id>\", ChantByCantusIDView.as_view(), name=\"chant-by-cantus-id\"\n    ),\n    path(\"chant-delete/<int:pk>\", ChantDeleteView.as_view(), name=\"chant-delete\"),\n    path(\n        \"edit-volpiano/<int:source_id>\", \n        ChantEditVolpianoView.as_view(), \n        name=\"source-edit-volpiano\"\n    ),\n    # feast\n    path(\"feasts/\", FeastListView.as_view(), name=\"feast-list\"),\n    path(\"feast/<int:pk>\", FeastDetailView.as_view(), name=\"feast-detail\"),\n    # genre\n    path(\"genres/\", GenreListView.as_view(), name=\"genre-list\"),\n    path(\"genre/<int:pk>\", GenreDetailView.as_view(), name=\"genre-detail\"),\n    # indexer\n    path(\"indexers/\", IndexerListView.as_view(), name=\"indexer-list\"),\n    path(\"indexer/<int:pk>\", IndexerDetailView.as_view(), name=\"indexer-detail\"),\n    # office\n    path(\"offices/\", OfficeListView.as_view(), name=\"office-list\"),\n    path(\"office/<int:pk>\", OfficeDetailView.as_view(), name=\"office-detail\"),\n    # sequence\n    path(\"sequences/\", SequenceListView.as_view(), name=\"sequence-list\"),\n    path(\"sequence/<int:pk>\", SequenceDetailView.as_view(), name=\"sequence-detail\",),\n    path(\"edit-sequence/<int:sequence_id>\", SequenceEditView.as_view(), name=\"sequence-edit\",),\n    # source\n    path(\"sources/\", SourceListView.as_view(), name=\"source-list\"),\n    path(\"source/<int:pk>\", SourceDetailView.as_view(), name=\"source-detail\"),\n    path(\n        \"source-create/\", \n        SourceCreateView.as_view(), \n        name=\"source-create\"\n    ),\n    path(\n        \"edit-source/<int:source_id>\", \n        SourceEditView.as_view(), \n        name=\"source-edit\"\n    ),\n    # melody\n    path(\"melody/\", MelodySearchView.as_view(), name=\"melody-search\"),\n    path(\"ajax/melody/<str:cantus_id>\", views.ajax_melody_list, name=\"ajax-melody\"),\n    path(\"ajax/melody-search/\", views.ajax_melody_search, name=\"ajax-melody-search\",),\n    # json api\n    path(\"json-sources/\", views.json_sources_export, name=\"json-sources-export\"),\n    path(\"json-node/<str:id>\", views.json_node_export, name=\"json-node-export\"),\n    path(\"json-nextchants/<str:cantus_id>\", views.json_nextchants, name=\"json-nextchants\"),\n    path(\n        \"json-melody/<str:cantus_id>\",\n        views.json_melody_export,\n        name=\"json-melody-export\",\n    ),\n    # misc search\n    path(\n        \"chant-search-ms/<int:source_pk>\",\n        ChantSearchMSView.as_view(),\n        name=\"chant-search-ms\",\n    ),\n    path(\"ci-search/<str:search_term>\", CISearchView.as_view(), name=\"ci-search\"),\n    path(\n        \"ajax/search-bar/<str:search_term>\",\n        views.ajax_search_bar,\n        name=\"ajax-search-bar\",\n    ),\n    # misc\n    path(\"content-statistics\", views.items_count, name=\"items-count\"),\n    path(\"csv/<str:source_id>\", views.csv_export, name=\"csv-export\"),\n    path(\n        \"ajax/concordance/<str:cantus_id>\",\n        views.ajax_concordance_list,\n        name=\"ajax-concordance\",\n    ),\n]\n\nhandler404 = 'main_app.views.views.handle404'\n", "path": "django/cantusdb_project/main_app/urls.py"}, {"content": "from urllib import request\nfrom django.views.generic import DetailView\nfrom django.contrib.auth import get_user_model\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\nfrom django.contrib import messages\nfrom extra_views import SearchableListMixin\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\nclass UserSourceListView(LoginRequiredMixin, ListView):\n    model = Source\n    context_object_name = \"sources\"\n    template_name = \"user_source_list.html\"\n    paginate_by = 100\n\n    def get_queryset(self):\n        return 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        ).order_by(\"title\")\n        \n    def get_context_data(self, **kwargs):\n        context = super().get_context_data(**kwargs)\n\n        user_created_sources = Source.objects.filter(created_by=self.request.user)\n        paginator = Paginator(user_created_sources, 10)\n        page_number = self.request.GET.get('page2')\n        page_obj = paginator.get_page(page_number)\n\n        context[\"user_created_sources_page_obj\"] = page_obj\n        return context\n\nclass CustomLogoutView(LogoutView):\n    def get_next_page(self):\n        next_page = super().get_next_page()\n        messages.success(\n            self.request, \n            'You have successfully logged out!'\n        )\n        return next_page\n\nclass UserListView(LoginRequiredMixin, SearchableListMixin, ListView):\n    \"\"\"Searchable List view for User model\n\n    Accessed by /users/\n\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\n    model = get_user_model()\n    search_fields = [\"first_name\", \"last_name\", \"institution\", \"city\", \"country\"]\n    paginate_by = 100\n    template_name = \"user_list.html\"\n    context_object_name = \"users\"\n", "path": "django/cantusdb_project/main_app/views/user.py"}, {"content": "from django.db import models\nfrom django.contrib.auth.models import AbstractUser\nfrom django.urls.base import reverse\n\n\nclass User(AbstractUser):\n    institution = models.CharField(max_length=255, blank=True, null=True)\n    city = models.CharField(max_length=255, blank=True, null=True)\n    country = models.CharField(max_length=255, blank=True, null=True)\n    website = models.URLField(blank=True, null=True)\n    sources_user_can_edit = models.ManyToManyField(\"main_app.Source\", related_name=\"users_who_can_edit_this_source\", blank=True)\n    full_name = models.CharField(max_length=255, blank=True, null=True)\n\n    @property\n    def name(self):\n        if self.full_name:\n            return self.full_name\n        elif self.first_name and self.last_name:\n            return f'{self.first_name} {self.last_name}'\n\n    def __str__(self):\n        if self.name:\n            return self.name\n        else:\n            return self.username\n\n    def get_absolute_url(self) -> str:\n        \"\"\"Get the absolute URL for an instance of a model.\"\"\"\n        detail_name = self.__class__.__name__.lower() + \"-detail\"\n        return reverse(detail_name, kwargs={\"pk\": self.pk})\n", "path": "django/cantusdb_project/users/models.py"}]}