From fadd74d25f2c5ff96fbece1e5cc18f5ce7e60079 Mon Sep 17 00:00:00 2001
From: Sage Abdullah <sage.abdullah@torchbox.com>
Date: Thu, 2 Feb 2023 14:39:53 +0000
Subject: [PATCH] Extract generic UsageView from snippets UsageView

---
 wagtail/admin/views/generic/__init__.py       |  1 +
 wagtail/admin/views/generic/models.py         |  1 +
 wagtail/admin/views/generic/usage.py          | 71 +++++++++++++++++++
 .../wagtailsnippets/snippets/usage.html       | 45 +-----------
 wagtail/snippets/views/snippets.py            | 69 +-----------------
 5 files changed, 78 insertions(+), 109 deletions(-)
 create mode 100644 wagtail/admin/views/generic/usage.py

diff --git a/wagtail/admin/views/generic/__init__.py b/wagtail/admin/views/generic/__init__.py
index 944f74fbcd..0c78491184 100644
--- a/wagtail/admin/views/generic/__init__.py
+++ b/wagtail/admin/views/generic/__init__.py
@@ -18,3 +18,4 @@ from .models import (  # noqa
     UnpublishView,
 )
 from .permissions import PermissionCheckedMixin  # noqa
+from .usage import UsageView  # noqa
diff --git a/wagtail/admin/views/generic/models.py b/wagtail/admin/views/generic/models.py
index 907d252690..17ab4da205 100644
--- a/wagtail/admin/views/generic/models.py
+++ b/wagtail/admin/views/generic/models.py
@@ -356,6 +356,7 @@ class IndexView(
         context["search_form"] = self.search_form
         context["is_searching"] = self.is_searching
         context["query_string"] = self.search_query
+        context["model_opts"] = self.model and self.model._meta
         return context
 
 
diff --git a/wagtail/admin/views/generic/usage.py b/wagtail/admin/views/generic/usage.py
new file mode 100644
index 0000000000..c3dede85c9
--- /dev/null
+++ b/wagtail/admin/views/generic/usage.py
@@ -0,0 +1,71 @@
+from django.utils.translation import gettext as _
+from django.utils.translation import gettext_lazy
+
+from wagtail.admin.admin_url_finder import AdminURLFinder
+from wagtail.admin.ui import tables
+from wagtail.admin.utils import get_latest_str
+from wagtail.admin.views.generic import BaseObjectMixin, IndexView
+from wagtail.models import DraftStateMixin
+
+
+class TitleColumn(tables.TitleColumn):
+    def get_link_attrs(self, instance, parent_context):
+        return {"title": instance["edit_link_title"]}
+
+
+class UsageView(BaseObjectMixin, IndexView):
+    paginate_by = 20
+    is_searchable = False
+    page_title = gettext_lazy("Usage of")
+
+    def get_object(self):
+        object = super().get_object()
+        if isinstance(object, DraftStateMixin):
+            return object.get_latest_revision_as_object()
+        return object
+
+    def get_page_subtitle(self):
+        return get_latest_str(self.object)
+
+    def get_queryset(self):
+        return self.object.get_usage()
+
+    def get_columns(self):
+        return [
+            TitleColumn(
+                "name",
+                label=_("Name"),
+                accessor="label",
+                get_url=lambda r: r["edit_url"],
+            ),
+            tables.ReferencesColumn(
+                "field",
+                label=_("Field"),
+                accessor="references",
+                get_url=lambda r: r["edit_url"],
+            ),
+        ]
+
+    def get_table(self, object_list, **kwargs):
+        url_finder = AdminURLFinder(self.request.user)
+        results = []
+        for object, references in object_list:
+            row = {"object": object, "references": references}
+            row["edit_url"] = url_finder.get_edit_url(object)
+            if row["edit_url"] is None:
+                row["label"] = _("(Private %(object)s)") % {
+                    "object": object._meta.verbose_name
+                }
+                row["edit_link_title"] = None
+            else:
+                row["label"] = str(object)
+                row["edit_link_title"] = _("Edit this %(object)s") % {
+                    "object": object._meta.verbose_name
+                }
+            results.append(row)
+        return super().get_table(results, **kwargs)
+
+    def get_context_data(self, *args, object_list=None, **kwargs):
+        return super().get_context_data(
+            *args, object_list=object_list, object=self.object, **kwargs
+        )
diff --git a/wagtail/snippets/templates/wagtailsnippets/snippets/usage.html b/wagtail/snippets/templates/wagtailsnippets/snippets/usage.html
index 6a3193ed64..f84f2eea14 100644
--- a/wagtail/snippets/templates/wagtailsnippets/snippets/usage.html
+++ b/wagtail/snippets/templates/wagtailsnippets/snippets/usage.html
@@ -1,47 +1,6 @@
-{% extends "wagtailadmin/base.html" %}
-{% load i18n %}
-{% block titletag %}{% blocktrans trimmed with title=object %}Usage of {{ title }}{% endblocktrans %}{% endblock %}
-{% block bodyclass %}model-{{ model_opts.model_name }}{% endblock %}
+{% extends "wagtailadmin/generic/index.html" %}
 
 {% block content %}
     {% include 'wagtailsnippets/snippets/headers/usage_header.html' %}
-
-    {% trans "Usage of" as usage_str %}
-    {% include "wagtailadmin/shared/header.html" with title=usage_str subtitle=object %}
-
-    <div class="nice-padding">
-        <table class="listing">
-            <thead>
-                <tr>
-                    <th class="title">{% trans "Title" %}</th>
-                    <th>{% trans "Field" %}</th>
-                </tr>
-            </thead>
-            <tbody>
-                {% for label, edit_url, edit_link_title, references in results %}
-                    <tr>
-                        <td class="title">
-                            <div class="title-wrapper">
-                                {% if edit_url %}<a href="{{ edit_url }}" title="{{ edit_link_title }}">{% endif %}
-                                {{ label }}
-                                {% if edit_url %}</a>{% endif %}
-                            </div>
-                        </td>
-                        <td>
-                            <ul>
-                                {% for reference in references %}
-                                    <li>
-                                        {% if edit_url %}<a href="{{ edit_url }}#content-path-{{ reference.content_path }}">{% endif %}
-                                        {{ reference.describe_source_field }}
-                                        {% if edit_url %}</a>{% endif %}
-                                    </li>
-                                {% endfor %}
-                            </ul>
-                        </td>
-                    </tr>
-                {% endfor %}
-            </tbody>
-        </table>
-    </div>
-    {% include "wagtailadmin/shared/pagination_nav.html" with items=page_obj %}
+    {{ block.super }}
 {% endblock %}
diff --git a/wagtail/snippets/views/snippets.py b/wagtail/snippets/views/snippets.py
index cb9988810d..17757ae561 100644
--- a/wagtail/snippets/views/snippets.py
+++ b/wagtail/snippets/views/snippets.py
@@ -13,7 +13,7 @@ from django.utils.text import capfirst
 from django.utils.translation import gettext as _
 from django.utils.translation import gettext_lazy, ngettext
 
-from wagtail.admin.admin_url_finder import AdminURLFinder, register_admin_url_finder
+from wagtail.admin.admin_url_finder import register_admin_url_finder
 from wagtail.admin.filters import DateRangePickerWidget, WagtailFilterSet
 from wagtail.admin.panels import get_edit_handler
 from wagtail.admin.ui.tables import (
@@ -447,74 +447,11 @@ class DeleteView(generic.DeleteView):
         return context
 
 
-class UsageView(generic.IndexView):
+class UsageView(generic.UsageView):
     view_name = "usage"
     template_name = "wagtailsnippets/snippets/usage.html"
-    paginate_by = 20
-    page_kwarg = "p"
-    is_searchable = False
     permission_required = "change"
-
-    def setup(self, request, *args, pk, **kwargs):
-        super().setup(request, *args, **kwargs)
-        self.pk = pk
-        self.object = self.get_object()
-
-    def get_object(self):
-        object = get_object_or_404(self.model, pk=unquote(self.pk))
-        if isinstance(object, DraftStateMixin):
-            return object.get_latest_revision_as_object()
-        return object
-
-    def get_queryset(self):
-        return self.object.get_usage()
-
-    def paginate_queryset(self, queryset, page_size):
-        paginator = self.get_paginator(
-            queryset,
-            page_size,
-            orphans=self.get_paginate_orphans(),
-            allow_empty_first_page=self.get_allow_empty(),
-        )
-
-        page_number = self.request.GET.get(self.page_kwarg)
-        page = paginator.get_page(page_number)
-
-        # Add edit URLs to each source object
-        url_finder = AdminURLFinder(self.request.user)
-        for object, references in page:
-            object.edit_url = url_finder.get_edit_url(object)
-
-        return (paginator, page, page.object_list, page.has_other_pages())
-
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-
-        # Add edit URLs to each source object
-        url_finder = AdminURLFinder(self.request.user)
-        results = []
-        for object, references in context.get("page_obj"):
-            edit_url = url_finder.get_edit_url(object)
-            if edit_url is None:
-                label = _("(Private %(object)s)") % {
-                    "object": object._meta.verbose_name
-                }
-                edit_link_title = None
-            else:
-                label = str(object)
-                edit_link_title = _("Edit this %(object)s") % {
-                    "object": object._meta.verbose_name
-                }
-            results.append((label, edit_url, edit_link_title, references))
-
-        context.update(
-            {
-                "object": self.object,
-                "results": results,
-                "model_opts": self.model._meta,
-            }
-        )
-        return context
+    header_icon = "snippet"
 
 
 def redirect_to_edit(request, app_label, model_name, pk):