diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py index c8870924e7..47a25608fe 100644 --- a/django/contrib/admin/sites.py +++ b/django/contrib/admin/sites.py @@ -526,16 +526,14 @@ class AdminSite: "models": [model_dict], } - if label: - return app_dict.get(label) return app_dict - def get_app_list(self, request): + def get_app_list(self, request, app_label=None): """ Return a sorted list of all the installed apps that have been registered in this site. """ - app_dict = self._build_app_dict(request) + app_dict = self._build_app_dict(request, app_label) # Sort the apps alphabetically. app_list = sorted(app_dict.values(), key=lambda x: x["name"].lower()) @@ -568,16 +566,16 @@ class AdminSite: ) def app_index(self, request, app_label, extra_context=None): - app_dict = self._build_app_dict(request, app_label) - if not app_dict: + app_list = self.get_app_list(request, app_label) + + if not app_list: raise Http404("The requested admin page does not exist.") - # Sort the models alphabetically within each app. - app_dict["models"].sort(key=lambda x: x["name"]) + context = { **self.each_context(request), - "title": _("%(app)s administration") % {"app": app_dict["name"]}, + "title": _("%(app)s administration") % {"app": app_list[0]["name"]}, "subtitle": None, - "app_list": [app_dict], + "app_list": app_list, "app_label": app_label, **(extra_context or {}), } diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index ab8da5a729..199a864566 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -2896,6 +2896,39 @@ Templates can override or extend base admin templates as described in The ``model`` variable for each model was added. +.. method:: AdminSite.get_app_list(request, app_label=None) + + Returns a list of applications from the :doc:`application registry + ` available for the current user. You can optionally + pass an ``app_label`` argument to get details for a single app. Each entry + in the list is a dictionary representing an application with the following + keys: + + * ``app_label``: the application label + * ``app_url``: the URL of the application index in the admin + * ``has_module_perms``: a boolean indicating if displaying and accessing of + the module's index page is permitted for the current user + * ``models``: a list of the models available in the application + * ``name``: name of the application + + Each model is a dictionary with the following keys: + + * ``model``: the model class + * ``object_name``: class name of the model + * ``name``: plural name of the model + * ``perms``: a ``dict`` tracking ``add``, ``change``, ``delete``, and + ``view`` permissions + * ``admin_url``: admin changelist URL for the model + * ``add_url``: admin URL to add a new model instance + + Lists of applications and models are sorted alphabetically by their names. + You can override this method to change the default order on the admin index + page. + + .. versionchanged:: 4.1 + + The ``app_label`` argument was added. + .. method:: AdminSite.has_permission(request) Returns ``True`` if the user for the given ``HttpRequest`` has permission diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 885c9329c5..39dd2faba9 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -63,6 +63,9 @@ Minor features * Related widget wrappers now have a link to object's change form. +* The :meth:`.AdminSite.get_app_list` method now allows changing the order of + apps and models on the admin index page. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_views/customadmin.py b/tests/admin_views/customadmin.py index f9e1f6fe1a..e3429ec4bc 100644 --- a/tests/admin_views/customadmin.py +++ b/tests/admin_views/customadmin.py @@ -35,6 +35,14 @@ class Admin2(admin.AdminSite): def password_change(self, request, extra_context=None): return super().password_change(request, {"spam": "eggs"}) + def get_app_list(self, request, app_label=None): + app_list = super().get_app_list(request, app_label=app_label) + # Reverse order of apps and models. + app_list = list(reversed(app_list)) + for app in app_list: + app["models"].sort(key=lambda x: x["name"], reverse=True) + return app_list + class UserLimitedAdmin(UserAdmin): # used for testing password change on a user not in queryset diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index acad97923a..185c1bff26 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1358,6 +1358,17 @@ class AdminViewBasicTest(AdminViewBasicTestCase): models = [model["name"] for model in response.context["app_list"][0]["models"]] self.assertSequenceEqual(models, sorted(models)) + def test_app_index_context_reordered(self): + self.client.force_login(self.superuser) + response = self.client.get(reverse("admin2:app_list", args=("admin_views",))) + self.assertContains( + response, + "Admin_Views administration | Django site admin", + ) + # Models are in reverse order. + models = [model["name"] for model in response.context["app_list"][0]["models"]] + self.assertSequenceEqual(models, sorted(models, reverse=True)) + def test_change_view_subtitle_per_object(self): response = self.client.get( reverse("admin:admin_views_article_change", args=(self.a1.pk,)),