Repository: wagtail/wagtail

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">




Discussion:
Can you please elaborate using example or Why you think so, especially use case example?
Thanks @faltunik - I will aim to flesh this out soon. 

Just preparing some issues relating to the GSoC UX Unification project and the upcoming Wagtail space sprints. 

See umbrella issue for context as to what this is about. https://github.com/wagtail/wagtail/issues/8365
Updated.

Resolving patch:
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 @@ def edit(request, user_id):
     )
 
 
-@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,
+        )


Test patch: