We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Adopt generic class based views for User management
| Part of the Epic https://github.com/wagtail/wagtail/issues/8365 |
|-----------------------------------------------|

### Is your proposal related to a problem?

* Wagtail 2.12 added generic class based views for use across Wagtail's admin.
* Many parts of the Wagtail admin are not yet migrated to this new approach, which makes it harder to share UI logic and styling/layouts.
* This issue specifically covers the user management (listing users, editing/deleting/creating users) views.

### Describe the solution you'd like

* Migrate the function views in [`wagtail/users/views/users.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/users/views/users.py) to class based views.
* Ideally this would adopt the [Wagtail Model Viewset approach](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/viewsets/model.py) as this would make the most sense, if not possible using discrete model generic class based views would be ok.
* The templates for the views should also extend their generic model class view counterparts.
* See a reference implementation already adopted in the Groups (listing/editing etc) views. [`wagtail/users/views/groups.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/users/views/groups.py)

### Describe alternatives you've considered

* Leave as is, however this will make it harder to share UI elements and layout between similar listing views.

### Additional context

* This is part of a larger epic work to adopt class based views throughout wagtail - https://github.com/wagtail/wagtail/issues/8365
* Generic class based model views can be found in [`wagtail/admin/views/generic/models.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/views/generic/models.py)
* Generic class based model view templates can be found in [`wagtail/admin/templates/wagtailadmin/generic`](https://github.com/wagtail/wagtail/tree/main/wagtail/admin/templates/wagtailadmin/generic)
* A more recent set up of locales using the model viewset can be found in 7c86c4e14f8a1be15c519b88935c19cd563e43d9

#### Screenshots

##### Users Index

<img width="1448" alt="Screen Shot 2022-06-05 at 7 59 47 pm" src="https://user-images.githubusercontent.com/1396140/172045236-f5847f9c-794a-4e3e-b2ad-6ca85ccbefe0.png">

##### Create user

<img width="1172" alt="Screen Shot 2022-06-05 at 8 01 26 pm" src="https://user-images.githubusercontent.com/1396140/172045261-6b0af777-23c2-41d9-b7b2-c425b01f3ea4.png">

##### Delete user

<img width="1448" alt="Screen Shot 2022-06-05 at 8 00 07 pm" src="https://user-images.githubusercontent.com/1396140/172045295-db494e2d-6744-492a-b29e-e9d16b53571d.png">

##### Bulk Actions pages (note: may not be in scope)

<img width="1448" alt="Screen Shot 2022-06-05 at 8 00 27 pm" src="https://user-images.githubusercontent.com/1396140/172045327-9474d4bc-d65e-4f4f-aa55-beb9d1ef16bc.png">

<img width="1450" alt="Screen Shot 2022-06-05 at 8 00 16 pm" src="https://user-images.githubusercontent.com/1396140/172045330-b2fc7cd4-56aa-4538-9e18-767837ed4758.png">

<img width="1448" alt="Screen Shot 2022-06-05 at 8 00 44 pm" src="https://user-images.githubusercontent.com/1396140/172045323-113f842b-a52f-4ff3-a123-5228a7658a19.png">



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `wagtail/users/urls/users.py`
Content:
```
1 from django.urls import path
2 
3 from wagtail.users.views import users
4 
5 app_name = "wagtailusers_users"
6 urlpatterns = [
7     path("", users.Index.as_view(), name="index"),
8     path("add/", users.Create.as_view(), name="add"),
9     path("<str:user_id>/", users.edit, name="edit"),
10     path("<str:user_id>/delete/", users.delete, name="delete"),
11 ]
12
```

Path: `wagtail/users/views/users.py`
Content:
```
1 from django.conf import settings
2 from django.contrib.auth import get_user_model, update_session_auth_hash
3 from django.contrib.auth.models import Group
4 from django.core.exceptions import PermissionDenied
5 from django.db import transaction
6 from django.db.models import Q
7 from django.shortcuts import get_object_or_404, redirect
8 from django.template.response import TemplateResponse
9 from django.urls import reverse
10 from django.utils.translation import gettext as _
11 from django.utils.translation import gettext_lazy
12 
13 from wagtail import hooks
14 from wagtail.admin import messages
15 from wagtail.admin.auth import permission_required
16 from wagtail.admin.views.generic import CreateView, IndexView
17 from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME
18 from wagtail.log_actions import log
19 from wagtail.permission_policies import ModelPermissionPolicy
20 from wagtail.users.forms import UserCreationForm, UserEditForm
21 from wagtail.users.utils import user_can_delete_user
22 from wagtail.utils.loading import get_custom_form
23 
24 User = get_user_model()
25 
26 # Typically we would check the permission 'auth.change_user' (and 'auth.add_user' /
27 # 'auth.delete_user') for user management actions, but this may vary according to
28 # the AUTH_USER_MODEL setting
29 add_user_perm = "{0}.add_{1}".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower())
30 change_user_perm = "{0}.change_{1}".format(
31     AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()
32 )
33 delete_user_perm = "{0}.delete_{1}".format(
34     AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()
35 )
36 
37 
38 def get_user_creation_form():
39     form_setting = "WAGTAIL_USER_CREATION_FORM"
40     if hasattr(settings, form_setting):
41         return get_custom_form(form_setting)
42     else:
43         return UserCreationForm
44 
45 
46 def get_user_edit_form():
47     form_setting = "WAGTAIL_USER_EDIT_FORM"
48     if hasattr(settings, form_setting):
49         return get_custom_form(form_setting)
50     else:
51         return UserEditForm
52 
53 
54 def get_users_filter_query(q, model_fields):
55     conditions = Q()
56 
57     for term in q.split():
58         if "username" in model_fields:
59             conditions |= Q(username__icontains=term)
60 
61         if "first_name" in model_fields:
62             conditions |= Q(first_name__icontains=term)
63 
64         if "last_name" in model_fields:
65             conditions |= Q(last_name__icontains=term)
66 
67         if "email" in model_fields:
68             conditions |= Q(email__icontains=term)
69 
70     return conditions
71 
72 
73 class Index(IndexView):
74     """
75     Lists the users for management within the admin.
76     """
77 
78     any_permission_required = ["add", "change", "delete"]
79     permission_policy = ModelPermissionPolicy(User)
80     model = User
81     context_object_name = "users"
82     index_url_name = "wagtailusers_users:index"
83     add_url_name = "wagtailusers_users:add"
84     edit_url_name = "wagtailusers_users:edit"
85     default_ordering = "name"
86     paginate_by = 20
87     template_name = None
88     is_searchable = True
89     page_title = gettext_lazy("Users")
90 
91     def setup(self, request, *args, **kwargs):
92         super().setup(request, *args, **kwargs)
93         setattr(self, "template_name", self.get_template())
94         self.group = get_object_or_404(Group, id=args[0]) if args else None
95         self.group_filter = Q(groups=self.group) if self.group else Q()
96         self.model_fields = [f.name for f in User._meta.get_fields()]
97 
98     def get_valid_orderings(self):
99         return ["name", "username"]
100 
101     def get_queryset(self):
102         if self.is_searching:
103             conditions = get_users_filter_query(self.search_query, self.model_fields)
104             users = User.objects.filter(self.group_filter & conditions)
105         else:
106             users = User.objects.filter(self.group_filter)
107 
108         if self.locale:
109             users = users.filter(locale=self.locale)
110 
111         if "last_name" in self.model_fields and "first_name" in self.model_fields:
112             users = users.order_by("last_name", "first_name")
113 
114         if self.get_ordering() == "username":
115             users = users.order_by(User.USERNAME_FIELD)
116 
117         return users
118 
119     def get_template(self):
120         if self.request.headers.get("x-requested-with") == "XMLHttpRequest":
121             return "wagtailusers/users/results.html"
122         else:
123             return "wagtailusers/users/index.html"
124 
125     def get_context_data(self, *args, object_list=None, **kwargs):
126         context_data = super().get_context_data(
127             *args, object_list=object_list, **kwargs
128         )
129         context_data["ordering"] = self.get_ordering()
130         context_data["group"] = self.group
131         if self.request.headers.get("x-requested-with") == "XMLHttpRequest":
132             return context_data
133 
134         context_data.update(
135             {
136                 "app_label": User._meta.app_label,
137                 "model_name": User._meta.model_name,
138             }
139         )
140         return context_data
141 
142 
143 class Create(CreateView):
144     """
145     Provide the ability to create a user within the admin.
146     """
147 
148     permission_policy = ModelPermissionPolicy(User)
149     permission_required = "add"
150     form_class = get_user_creation_form()
151     template_name = "wagtailusers/users/create.html"
152     add_url_name = "wagtailusers_users:add"
153     index_url_name = "wagtailusers_users:index"
154     edit_url_name = "wagtailusers_users:edit"
155     success_message = "User '{0}' created."
156     page_title = gettext_lazy("Add user")
157 
158     def run_before_hook(self):
159         return self.run_hook(
160             "before_create_user",
161             self.request,
162         )
163 
164     def run_after_hook(self):
165         return self.run_hook(
166             "after_create_user",
167             self.request,
168             self.object,
169         )
170 
171     def get_add_url(self):
172         return None
173 
174     def get_success_buttons(self):
175         return [
176             messages.button(
177                 reverse(self.edit_url_name, args=(self.object.pk,)), _("Edit")
178             )
179         ]
180 
181 
182 @permission_required(change_user_perm)
183 def edit(request, user_id):
184     user = get_object_or_404(User, pk=user_id)
185     can_delete = user_can_delete_user(request.user, user)
186     editing_self = request.user == user
187 
188     for fn in hooks.get_hooks("before_edit_user"):
189         result = fn(request, user)
190         if hasattr(result, "status_code"):
191             return result
192     if request.method == "POST":
193         form = get_user_edit_form()(
194             request.POST, request.FILES, instance=user, editing_self=editing_self
195         )
196         if form.is_valid():
197             with transaction.atomic():
198                 user = form.save()
199                 log(user, "wagtail.edit")
200 
201             if user == request.user and "password1" in form.changed_data:
202                 # User is changing their own password; need to update their session hash
203                 update_session_auth_hash(request, user)
204 
205             messages.success(
206                 request,
207                 _("User '{0}' updated.").format(user),
208                 buttons=[
209                     messages.button(
210                         reverse("wagtailusers_users:edit", args=(user.pk,)), _("Edit")
211                     )
212                 ],
213             )
214             for fn in hooks.get_hooks("after_edit_user"):
215                 result = fn(request, user)
216                 if hasattr(result, "status_code"):
217                     return result
218             return redirect("wagtailusers_users:index")
219         else:
220             messages.error(request, _("The user could not be saved due to errors."))
221     else:
222         form = get_user_edit_form()(instance=user, editing_self=editing_self)
223 
224     return TemplateResponse(
225         request,
226         "wagtailusers/users/edit.html",
227         {
228             "user": user,
229             "form": form,
230             "can_delete": can_delete,
231         },
232     )
233 
234 
235 @permission_required(delete_user_perm)
236 def delete(request, user_id):
237     user = get_object_or_404(User, pk=user_id)
238 
239     if not user_can_delete_user(request.user, user):
240         raise PermissionDenied
241 
242     for fn in hooks.get_hooks("before_delete_user"):
243         result = fn(request, user)
244         if hasattr(result, "status_code"):
245             return result
246     if request.method == "POST":
247         with transaction.atomic():
248             log(user, "wagtail.delete")
249             user.delete()
250         messages.success(request, _("User '{0}' deleted.").format(user))
251         for fn in hooks.get_hooks("after_delete_user"):
252             result = fn(request, user)
253             if hasattr(result, "status_code"):
254                 return result
255         return redirect("wagtailusers_users:index")
256 
257     return TemplateResponse(
258         request,
259         "wagtailusers/users/confirm_delete.html",
260         {
261             "user": user,
262         },
263     )
264
```
--- 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/wagtail/users/urls/users.py b/wagtail/users/urls/users.py
--- a/wagtail/users/urls/users.py
+++ b/wagtail/users/urls/users.py
@@ -7,5 +7,5 @@
     path("", users.Index.as_view(), name="index"),
     path("add/", users.Create.as_view(), name="add"),
     path("<str:user_id>/", users.edit, name="edit"),
-    path("<str:user_id>/delete/", users.delete, name="delete"),
+    path("<str:pk>/delete/", users.Delete.as_view(), name="delete"),
 ]
diff --git a/wagtail/users/views/users.py b/wagtail/users/views/users.py
--- a/wagtail/users/views/users.py
+++ b/wagtail/users/views/users.py
@@ -13,7 +13,7 @@
 from wagtail import hooks
 from wagtail.admin import messages
 from wagtail.admin.auth import permission_required
-from wagtail.admin.views.generic import CreateView, IndexView
+from wagtail.admin.views.generic import CreateView, DeleteView, IndexView
 from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME
 from wagtail.log_actions import log
 from wagtail.permission_policies import ModelPermissionPolicy
@@ -232,32 +232,37 @@
     )
 
 
-@permission_required(delete_user_perm)
-def delete(request, user_id):
-    user = get_object_or_404(User, pk=user_id)
+class Delete(DeleteView):
+    """
+    Provide the ability to delete a user within the admin.
+    """
 
-    if not user_can_delete_user(request.user, user):
-        raise PermissionDenied
+    permission_policy = ModelPermissionPolicy(User)
+    permission_required = "delete"
+    model = User
+    template_name = "wagtailusers/users/confirm_delete.html"
+    delete_url_name = "wagtailusers_users:delete"
+    index_url_name = "wagtailusers_users:index"
+    page_title = gettext_lazy("Delete user")
+    context_object_name = "user"
+    success_message = _("User '{0}' deleted.")
 
-    for fn in hooks.get_hooks("before_delete_user"):
-        result = fn(request, user)
-        if hasattr(result, "status_code"):
-            return result
-    if request.method == "POST":
-        with transaction.atomic():
-            log(user, "wagtail.delete")
-            user.delete()
-        messages.success(request, _("User '{0}' deleted.").format(user))
-        for fn in hooks.get_hooks("after_delete_user"):
-            result = fn(request, user)
-            if hasattr(result, "status_code"):
-                return result
-        return redirect("wagtailusers_users:index")
+    def dispatch(self, request, *args, **kwargs):
+        self.object = self.get_object()
+        if not user_can_delete_user(self.request.user, self.object):
+            raise PermissionDenied
+        return super().dispatch(request, *args, **kwargs)
 
-    return TemplateResponse(
-        request,
-        "wagtailusers/users/confirm_delete.html",
-        {
-            "user": user,
-        },
-    )
+    def run_before_hook(self):
+        return self.run_hook(
+            "before_delete_user",
+            self.request,
+            self.object,
+        )
+
+    def run_after_hook(self):
+        return self.run_hook(
+            "after_delete_user",
+            self.request,
+            self.object,
+        )

{"golden_diff": "diff --git a/wagtail/users/urls/users.py b/wagtail/users/urls/users.py\n--- a/wagtail/users/urls/users.py\n+++ b/wagtail/users/urls/users.py\n@@ -7,5 +7,5 @@\n     path(\"\", users.Index.as_view(), name=\"index\"),\n     path(\"add/\", users.Create.as_view(), name=\"add\"),\n     path(\"<str:user_id>/\", users.edit, name=\"edit\"),\n-    path(\"<str:user_id>/delete/\", users.delete, name=\"delete\"),\n+    path(\"<str:pk>/delete/\", users.Delete.as_view(), name=\"delete\"),\n ]\ndiff --git a/wagtail/users/views/users.py b/wagtail/users/views/users.py\n--- a/wagtail/users/views/users.py\n+++ b/wagtail/users/views/users.py\n@@ -13,7 +13,7 @@\n from wagtail import hooks\n from wagtail.admin import messages\n from wagtail.admin.auth import permission_required\n-from wagtail.admin.views.generic import CreateView, IndexView\n+from wagtail.admin.views.generic import CreateView, DeleteView, IndexView\n from wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME\n from wagtail.log_actions import log\n from wagtail.permission_policies import ModelPermissionPolicy\n@@ -232,32 +232,37 @@\n     )\n \n \n-@permission_required(delete_user_perm)\n-def delete(request, user_id):\n-    user = get_object_or_404(User, pk=user_id)\n+class Delete(DeleteView):\n+    \"\"\"\n+    Provide the ability to delete a user within the admin.\n+    \"\"\"\n \n-    if not user_can_delete_user(request.user, user):\n-        raise PermissionDenied\n+    permission_policy = ModelPermissionPolicy(User)\n+    permission_required = \"delete\"\n+    model = User\n+    template_name = \"wagtailusers/users/confirm_delete.html\"\n+    delete_url_name = \"wagtailusers_users:delete\"\n+    index_url_name = \"wagtailusers_users:index\"\n+    page_title = gettext_lazy(\"Delete user\")\n+    context_object_name = \"user\"\n+    success_message = _(\"User '{0}' deleted.\")\n \n-    for fn in hooks.get_hooks(\"before_delete_user\"):\n-        result = fn(request, user)\n-        if hasattr(result, \"status_code\"):\n-            return result\n-    if request.method == \"POST\":\n-        with transaction.atomic():\n-            log(user, \"wagtail.delete\")\n-            user.delete()\n-        messages.success(request, _(\"User '{0}' deleted.\").format(user))\n-        for fn in hooks.get_hooks(\"after_delete_user\"):\n-            result = fn(request, user)\n-            if hasattr(result, \"status_code\"):\n-                return result\n-        return redirect(\"wagtailusers_users:index\")\n+    def dispatch(self, request, *args, **kwargs):\n+        self.object = self.get_object()\n+        if not user_can_delete_user(self.request.user, self.object):\n+            raise PermissionDenied\n+        return super().dispatch(request, *args, **kwargs)\n \n-    return TemplateResponse(\n-        request,\n-        \"wagtailusers/users/confirm_delete.html\",\n-        {\n-            \"user\": user,\n-        },\n-    )\n+    def run_before_hook(self):\n+        return self.run_hook(\n+            \"before_delete_user\",\n+            self.request,\n+            self.object,\n+        )\n+\n+    def run_after_hook(self):\n+        return self.run_hook(\n+            \"after_delete_user\",\n+            self.request,\n+            self.object,\n+        )\n", "issue": "Adopt generic class based views for User management\n| Part of the Epic https://github.com/wagtail/wagtail/issues/8365 |\r\n|-----------------------------------------------|\r\n\r\n### Is your proposal related to a problem?\r\n\r\n* Wagtail 2.12 added generic class based views for use across Wagtail's admin.\r\n* Many parts of the Wagtail admin are not yet migrated to this new approach, which makes it harder to share UI logic and styling/layouts.\r\n* This issue specifically covers the user management (listing users, editing/deleting/creating users) views.\r\n\r\n### Describe the solution you'd like\r\n\r\n* Migrate the function views in [`wagtail/users/views/users.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/users/views/users.py) to class based views.\r\n* Ideally this would adopt the [Wagtail Model Viewset approach](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/viewsets/model.py) as this would make the most sense, if not possible using discrete model generic class based views would be ok.\r\n* The templates for the views should also extend their generic model class view counterparts.\r\n* See a reference implementation already adopted in the Groups (listing/editing etc) views. [`wagtail/users/views/groups.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/users/views/groups.py)\r\n\r\n### Describe alternatives you've considered\r\n\r\n* Leave as is, however this will make it harder to share UI elements and layout between similar listing views.\r\n\r\n### Additional context\r\n\r\n* This is part of a larger epic work to adopt class based views throughout wagtail - https://github.com/wagtail/wagtail/issues/8365\r\n* Generic class based model views can be found in [`wagtail/admin/views/generic/models.py`](https://github.com/wagtail/wagtail/blob/main/wagtail/admin/views/generic/models.py)\r\n* Generic class based model view templates can be found in [`wagtail/admin/templates/wagtailadmin/generic`](https://github.com/wagtail/wagtail/tree/main/wagtail/admin/templates/wagtailadmin/generic)\r\n* A more recent set up of locales using the model viewset can be found in 7c86c4e14f8a1be15c519b88935c19cd563e43d9\r\n\r\n#### Screenshots\r\n\r\n##### Users Index\r\n\r\n<img width=\"1448\" alt=\"Screen Shot 2022-06-05 at 7 59 47 pm\" src=\"https://user-images.githubusercontent.com/1396140/172045236-f5847f9c-794a-4e3e-b2ad-6ca85ccbefe0.png\">\r\n\r\n##### Create user\r\n\r\n<img width=\"1172\" alt=\"Screen Shot 2022-06-05 at 8 01 26 pm\" src=\"https://user-images.githubusercontent.com/1396140/172045261-6b0af777-23c2-41d9-b7b2-c425b01f3ea4.png\">\r\n\r\n##### Delete user\r\n\r\n<img width=\"1448\" alt=\"Screen Shot 2022-06-05 at 8 00 07 pm\" src=\"https://user-images.githubusercontent.com/1396140/172045295-db494e2d-6744-492a-b29e-e9d16b53571d.png\">\r\n\r\n##### Bulk Actions pages (note: may not be in scope)\r\n\r\n<img width=\"1448\" alt=\"Screen Shot 2022-06-05 at 8 00 27 pm\" src=\"https://user-images.githubusercontent.com/1396140/172045327-9474d4bc-d65e-4f4f-aa55-beb9d1ef16bc.png\">\r\n\r\n<img width=\"1450\" alt=\"Screen Shot 2022-06-05 at 8 00 16 pm\" src=\"https://user-images.githubusercontent.com/1396140/172045330-b2fc7cd4-56aa-4538-9e18-767837ed4758.png\">\r\n\r\n<img width=\"1448\" alt=\"Screen Shot 2022-06-05 at 8 00 44 pm\" src=\"https://user-images.githubusercontent.com/1396140/172045323-113f842b-a52f-4ff3-a123-5228a7658a19.png\">\r\n\r\n\n", "before_files": [{"content": "from django.urls import path\n\nfrom wagtail.users.views import users\n\napp_name = \"wagtailusers_users\"\nurlpatterns = [\n    path(\"\", users.Index.as_view(), name=\"index\"),\n    path(\"add/\", users.Create.as_view(), name=\"add\"),\n    path(\"<str:user_id>/\", users.edit, name=\"edit\"),\n    path(\"<str:user_id>/delete/\", users.delete, name=\"delete\"),\n]\n", "path": "wagtail/users/urls/users.py"}, {"content": "from django.conf import settings\nfrom django.contrib.auth import get_user_model, update_session_auth_hash\nfrom django.contrib.auth.models import Group\nfrom django.core.exceptions import PermissionDenied\nfrom django.db import transaction\nfrom django.db.models import Q\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import gettext_lazy\n\nfrom wagtail import hooks\nfrom wagtail.admin import messages\nfrom wagtail.admin.auth import permission_required\nfrom wagtail.admin.views.generic import CreateView, IndexView\nfrom wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME\nfrom wagtail.log_actions import log\nfrom wagtail.permission_policies import ModelPermissionPolicy\nfrom wagtail.users.forms import UserCreationForm, UserEditForm\nfrom wagtail.users.utils import user_can_delete_user\nfrom wagtail.utils.loading import get_custom_form\n\nUser = get_user_model()\n\n# Typically we would check the permission 'auth.change_user' (and 'auth.add_user' /\n# 'auth.delete_user') for user management actions, but this may vary according to\n# the AUTH_USER_MODEL setting\nadd_user_perm = \"{0}.add_{1}\".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower())\nchange_user_perm = \"{0}.change_{1}\".format(\n    AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()\n)\ndelete_user_perm = \"{0}.delete_{1}\".format(\n    AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()\n)\n\n\ndef get_user_creation_form():\n    form_setting = \"WAGTAIL_USER_CREATION_FORM\"\n    if hasattr(settings, form_setting):\n        return get_custom_form(form_setting)\n    else:\n        return UserCreationForm\n\n\ndef get_user_edit_form():\n    form_setting = \"WAGTAIL_USER_EDIT_FORM\"\n    if hasattr(settings, form_setting):\n        return get_custom_form(form_setting)\n    else:\n        return UserEditForm\n\n\ndef get_users_filter_query(q, model_fields):\n    conditions = Q()\n\n    for term in q.split():\n        if \"username\" in model_fields:\n            conditions |= Q(username__icontains=term)\n\n        if \"first_name\" in model_fields:\n            conditions |= Q(first_name__icontains=term)\n\n        if \"last_name\" in model_fields:\n            conditions |= Q(last_name__icontains=term)\n\n        if \"email\" in model_fields:\n            conditions |= Q(email__icontains=term)\n\n    return conditions\n\n\nclass Index(IndexView):\n    \"\"\"\n    Lists the users for management within the admin.\n    \"\"\"\n\n    any_permission_required = [\"add\", \"change\", \"delete\"]\n    permission_policy = ModelPermissionPolicy(User)\n    model = User\n    context_object_name = \"users\"\n    index_url_name = \"wagtailusers_users:index\"\n    add_url_name = \"wagtailusers_users:add\"\n    edit_url_name = \"wagtailusers_users:edit\"\n    default_ordering = \"name\"\n    paginate_by = 20\n    template_name = None\n    is_searchable = True\n    page_title = gettext_lazy(\"Users\")\n\n    def setup(self, request, *args, **kwargs):\n        super().setup(request, *args, **kwargs)\n        setattr(self, \"template_name\", self.get_template())\n        self.group = get_object_or_404(Group, id=args[0]) if args else None\n        self.group_filter = Q(groups=self.group) if self.group else Q()\n        self.model_fields = [f.name for f in User._meta.get_fields()]\n\n    def get_valid_orderings(self):\n        return [\"name\", \"username\"]\n\n    def get_queryset(self):\n        if self.is_searching:\n            conditions = get_users_filter_query(self.search_query, self.model_fields)\n            users = User.objects.filter(self.group_filter & conditions)\n        else:\n            users = User.objects.filter(self.group_filter)\n\n        if self.locale:\n            users = users.filter(locale=self.locale)\n\n        if \"last_name\" in self.model_fields and \"first_name\" in self.model_fields:\n            users = users.order_by(\"last_name\", \"first_name\")\n\n        if self.get_ordering() == \"username\":\n            users = users.order_by(User.USERNAME_FIELD)\n\n        return users\n\n    def get_template(self):\n        if self.request.headers.get(\"x-requested-with\") == \"XMLHttpRequest\":\n            return \"wagtailusers/users/results.html\"\n        else:\n            return \"wagtailusers/users/index.html\"\n\n    def get_context_data(self, *args, object_list=None, **kwargs):\n        context_data = super().get_context_data(\n            *args, object_list=object_list, **kwargs\n        )\n        context_data[\"ordering\"] = self.get_ordering()\n        context_data[\"group\"] = self.group\n        if self.request.headers.get(\"x-requested-with\") == \"XMLHttpRequest\":\n            return context_data\n\n        context_data.update(\n            {\n                \"app_label\": User._meta.app_label,\n                \"model_name\": User._meta.model_name,\n            }\n        )\n        return context_data\n\n\nclass Create(CreateView):\n    \"\"\"\n    Provide the ability to create a user within the admin.\n    \"\"\"\n\n    permission_policy = ModelPermissionPolicy(User)\n    permission_required = \"add\"\n    form_class = get_user_creation_form()\n    template_name = \"wagtailusers/users/create.html\"\n    add_url_name = \"wagtailusers_users:add\"\n    index_url_name = \"wagtailusers_users:index\"\n    edit_url_name = \"wagtailusers_users:edit\"\n    success_message = \"User '{0}' created.\"\n    page_title = gettext_lazy(\"Add user\")\n\n    def run_before_hook(self):\n        return self.run_hook(\n            \"before_create_user\",\n            self.request,\n        )\n\n    def run_after_hook(self):\n        return self.run_hook(\n            \"after_create_user\",\n            self.request,\n            self.object,\n        )\n\n    def get_add_url(self):\n        return None\n\n    def get_success_buttons(self):\n        return [\n            messages.button(\n                reverse(self.edit_url_name, args=(self.object.pk,)), _(\"Edit\")\n            )\n        ]\n\n\n@permission_required(change_user_perm)\ndef edit(request, user_id):\n    user = get_object_or_404(User, pk=user_id)\n    can_delete = user_can_delete_user(request.user, user)\n    editing_self = request.user == user\n\n    for fn in hooks.get_hooks(\"before_edit_user\"):\n        result = fn(request, user)\n        if hasattr(result, \"status_code\"):\n            return result\n    if request.method == \"POST\":\n        form = get_user_edit_form()(\n            request.POST, request.FILES, instance=user, editing_self=editing_self\n        )\n        if form.is_valid():\n            with transaction.atomic():\n                user = form.save()\n                log(user, \"wagtail.edit\")\n\n            if user == request.user and \"password1\" in form.changed_data:\n                # User is changing their own password; need to update their session hash\n                update_session_auth_hash(request, user)\n\n            messages.success(\n                request,\n                _(\"User '{0}' updated.\").format(user),\n                buttons=[\n                    messages.button(\n                        reverse(\"wagtailusers_users:edit\", args=(user.pk,)), _(\"Edit\")\n                    )\n                ],\n            )\n            for fn in hooks.get_hooks(\"after_edit_user\"):\n                result = fn(request, user)\n                if hasattr(result, \"status_code\"):\n                    return result\n            return redirect(\"wagtailusers_users:index\")\n        else:\n            messages.error(request, _(\"The user could not be saved due to errors.\"))\n    else:\n        form = get_user_edit_form()(instance=user, editing_self=editing_self)\n\n    return TemplateResponse(\n        request,\n        \"wagtailusers/users/edit.html\",\n        {\n            \"user\": user,\n            \"form\": form,\n            \"can_delete\": can_delete,\n        },\n    )\n\n\n@permission_required(delete_user_perm)\ndef delete(request, user_id):\n    user = get_object_or_404(User, pk=user_id)\n\n    if not user_can_delete_user(request.user, user):\n        raise PermissionDenied\n\n    for fn in hooks.get_hooks(\"before_delete_user\"):\n        result = fn(request, user)\n        if hasattr(result, \"status_code\"):\n            return result\n    if request.method == \"POST\":\n        with transaction.atomic():\n            log(user, \"wagtail.delete\")\n            user.delete()\n        messages.success(request, _(\"User '{0}' deleted.\").format(user))\n        for fn in hooks.get_hooks(\"after_delete_user\"):\n            result = fn(request, user)\n            if hasattr(result, \"status_code\"):\n                return result\n        return redirect(\"wagtailusers_users:index\")\n\n    return TemplateResponse(\n        request,\n        \"wagtailusers/users/confirm_delete.html\",\n        {\n            \"user\": user,\n        },\n    )\n", "path": "wagtail/users/views/users.py"}], "after_files": [{"content": "from django.urls import path\n\nfrom wagtail.users.views import users\n\napp_name = \"wagtailusers_users\"\nurlpatterns = [\n    path(\"\", users.Index.as_view(), name=\"index\"),\n    path(\"add/\", users.Create.as_view(), name=\"add\"),\n    path(\"<str:user_id>/\", users.edit, name=\"edit\"),\n    path(\"<str:pk>/delete/\", users.Delete.as_view(), name=\"delete\"),\n]\n", "path": "wagtail/users/urls/users.py"}, {"content": "from django.conf import settings\nfrom django.contrib.auth import get_user_model, update_session_auth_hash\nfrom django.contrib.auth.models import Group\nfrom django.core.exceptions import PermissionDenied\nfrom django.db import transaction\nfrom django.db.models import Q\nfrom django.shortcuts import get_object_or_404, redirect\nfrom django.template.response import TemplateResponse\nfrom django.urls import reverse\nfrom django.utils.translation import gettext as _\nfrom django.utils.translation import gettext_lazy\n\nfrom wagtail import hooks\nfrom wagtail.admin import messages\nfrom wagtail.admin.auth import permission_required\nfrom wagtail.admin.views.generic import CreateView, DeleteView, IndexView\nfrom wagtail.compat import AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME\nfrom wagtail.log_actions import log\nfrom wagtail.permission_policies import ModelPermissionPolicy\nfrom wagtail.users.forms import UserCreationForm, UserEditForm\nfrom wagtail.users.utils import user_can_delete_user\nfrom wagtail.utils.loading import get_custom_form\n\nUser = get_user_model()\n\n# Typically we would check the permission 'auth.change_user' (and 'auth.add_user' /\n# 'auth.delete_user') for user management actions, but this may vary according to\n# the AUTH_USER_MODEL setting\nadd_user_perm = \"{0}.add_{1}\".format(AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower())\nchange_user_perm = \"{0}.change_{1}\".format(\n    AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()\n)\ndelete_user_perm = \"{0}.delete_{1}\".format(\n    AUTH_USER_APP_LABEL, AUTH_USER_MODEL_NAME.lower()\n)\n\n\ndef get_user_creation_form():\n    form_setting = \"WAGTAIL_USER_CREATION_FORM\"\n    if hasattr(settings, form_setting):\n        return get_custom_form(form_setting)\n    else:\n        return UserCreationForm\n\n\ndef get_user_edit_form():\n    form_setting = \"WAGTAIL_USER_EDIT_FORM\"\n    if hasattr(settings, form_setting):\n        return get_custom_form(form_setting)\n    else:\n        return UserEditForm\n\n\ndef get_users_filter_query(q, model_fields):\n    conditions = Q()\n\n    for term in q.split():\n        if \"username\" in model_fields:\n            conditions |= Q(username__icontains=term)\n\n        if \"first_name\" in model_fields:\n            conditions |= Q(first_name__icontains=term)\n\n        if \"last_name\" in model_fields:\n            conditions |= Q(last_name__icontains=term)\n\n        if \"email\" in model_fields:\n            conditions |= Q(email__icontains=term)\n\n    return conditions\n\n\nclass Index(IndexView):\n    \"\"\"\n    Lists the users for management within the admin.\n    \"\"\"\n\n    any_permission_required = [\"add\", \"change\", \"delete\"]\n    permission_policy = ModelPermissionPolicy(User)\n    model = User\n    context_object_name = \"users\"\n    index_url_name = \"wagtailusers_users:index\"\n    add_url_name = \"wagtailusers_users:add\"\n    edit_url_name = \"wagtailusers_users:edit\"\n    default_ordering = \"name\"\n    paginate_by = 20\n    template_name = None\n    is_searchable = True\n    page_title = gettext_lazy(\"Users\")\n\n    def setup(self, request, *args, **kwargs):\n        super().setup(request, *args, **kwargs)\n        setattr(self, \"template_name\", self.get_template())\n        self.group = get_object_or_404(Group, id=args[0]) if args else None\n        self.group_filter = Q(groups=self.group) if self.group else Q()\n        self.model_fields = [f.name for f in User._meta.get_fields()]\n\n    def get_valid_orderings(self):\n        return [\"name\", \"username\"]\n\n    def get_queryset(self):\n        if self.is_searching:\n            conditions = get_users_filter_query(self.search_query, self.model_fields)\n            users = User.objects.filter(self.group_filter & conditions)\n        else:\n            users = User.objects.filter(self.group_filter)\n\n        if self.locale:\n            users = users.filter(locale=self.locale)\n\n        if \"last_name\" in self.model_fields and \"first_name\" in self.model_fields:\n            users = users.order_by(\"last_name\", \"first_name\")\n\n        if self.get_ordering() == \"username\":\n            users = users.order_by(User.USERNAME_FIELD)\n\n        return users\n\n    def get_template(self):\n        if self.request.headers.get(\"x-requested-with\") == \"XMLHttpRequest\":\n            return \"wagtailusers/users/results.html\"\n        else:\n            return \"wagtailusers/users/index.html\"\n\n    def get_context_data(self, *args, object_list=None, **kwargs):\n        context_data = super().get_context_data(\n            *args, object_list=object_list, **kwargs\n        )\n        context_data[\"ordering\"] = self.get_ordering()\n        context_data[\"group\"] = self.group\n        if self.request.headers.get(\"x-requested-with\") == \"XMLHttpRequest\":\n            return context_data\n\n        context_data.update(\n            {\n                \"app_label\": User._meta.app_label,\n                \"model_name\": User._meta.model_name,\n            }\n        )\n        return context_data\n\n\nclass Create(CreateView):\n    \"\"\"\n    Provide the ability to create a user within the admin.\n    \"\"\"\n\n    permission_policy = ModelPermissionPolicy(User)\n    permission_required = \"add\"\n    form_class = get_user_creation_form()\n    template_name = \"wagtailusers/users/create.html\"\n    add_url_name = \"wagtailusers_users:add\"\n    index_url_name = \"wagtailusers_users:index\"\n    edit_url_name = \"wagtailusers_users:edit\"\n    success_message = \"User '{0}' created.\"\n    page_title = gettext_lazy(\"Add user\")\n\n    def run_before_hook(self):\n        return self.run_hook(\n            \"before_create_user\",\n            self.request,\n        )\n\n    def run_after_hook(self):\n        return self.run_hook(\n            \"after_create_user\",\n            self.request,\n            self.object,\n        )\n\n    def get_add_url(self):\n        return None\n\n    def get_success_buttons(self):\n        return [\n            messages.button(\n                reverse(self.edit_url_name, args=(self.object.pk,)), _(\"Edit\")\n            )\n        ]\n\n\n@permission_required(change_user_perm)\ndef edit(request, user_id):\n    user = get_object_or_404(User, pk=user_id)\n    can_delete = user_can_delete_user(request.user, user)\n    editing_self = request.user == user\n\n    for fn in hooks.get_hooks(\"before_edit_user\"):\n        result = fn(request, user)\n        if hasattr(result, \"status_code\"):\n            return result\n    if request.method == \"POST\":\n        form = get_user_edit_form()(\n            request.POST, request.FILES, instance=user, editing_self=editing_self\n        )\n        if form.is_valid():\n            with transaction.atomic():\n                user = form.save()\n                log(user, \"wagtail.edit\")\n\n            if user == request.user and \"password1\" in form.changed_data:\n                # User is changing their own password; need to update their session hash\n                update_session_auth_hash(request, user)\n\n            messages.success(\n                request,\n                _(\"User '{0}' updated.\").format(user),\n                buttons=[\n                    messages.button(\n                        reverse(\"wagtailusers_users:edit\", args=(user.pk,)), _(\"Edit\")\n                    )\n                ],\n            )\n            for fn in hooks.get_hooks(\"after_edit_user\"):\n                result = fn(request, user)\n                if hasattr(result, \"status_code\"):\n                    return result\n            return redirect(\"wagtailusers_users:index\")\n        else:\n            messages.error(request, _(\"The user could not be saved due to errors.\"))\n    else:\n        form = get_user_edit_form()(instance=user, editing_self=editing_self)\n\n    return TemplateResponse(\n        request,\n        \"wagtailusers/users/edit.html\",\n        {\n            \"user\": user,\n            \"form\": form,\n            \"can_delete\": can_delete,\n        },\n    )\n\n\nclass Delete(DeleteView):\n    \"\"\"\n    Provide the ability to delete a user within the admin.\n    \"\"\"\n\n    permission_policy = ModelPermissionPolicy(User)\n    permission_required = \"delete\"\n    model = User\n    template_name = \"wagtailusers/users/confirm_delete.html\"\n    delete_url_name = \"wagtailusers_users:delete\"\n    index_url_name = \"wagtailusers_users:index\"\n    page_title = gettext_lazy(\"Delete user\")\n    context_object_name = \"user\"\n    success_message = _(\"User '{0}' deleted.\")\n\n    def dispatch(self, request, *args, **kwargs):\n        self.object = self.get_object()\n        if not user_can_delete_user(self.request.user, self.object):\n            raise PermissionDenied\n        return super().dispatch(request, *args, **kwargs)\n\n    def run_before_hook(self):\n        return self.run_hook(\n            \"before_delete_user\",\n            self.request,\n            self.object,\n        )\n\n    def run_after_hook(self):\n        return self.run_hook(\n            \"after_delete_user\",\n            self.request,\n            self.object,\n        )\n", "path": "wagtail/users/views/users.py"}]}