From ac5cc6cf01463d90aa333d5f6f046c311019827b Mon Sep 17 00:00:00 2001 From: mgaligniana Date: Tue, 14 Dec 2021 03:03:36 -0300 Subject: [PATCH] Fixed #33316 -- Added pagination to admin history view. --- django/contrib/admin/options.py | 11 +++- .../contrib/admin/static/admin/css/base.css | 11 +++- .../admin/templates/admin/object_history.html | 18 ++++++- docs/ref/contrib/admin/index.txt | 4 ++ docs/releases/4.1.txt | 3 ++ tests/admin_views/test_history_view.py | 54 ++++++++++++++++++- 6 files changed, 95 insertions(+), 6 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index c5968a79ed..100e481229 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1927,6 +1927,7 @@ class ModelAdmin(BaseModelAdmin): def history_view(self, request, object_id, extra_context=None): "The 'history' admin view for this model." from django.contrib.admin.models import LogEntry + from django.contrib.admin.views.main import PAGE_VAR # First check if the user can see this history. model = self.model @@ -1945,11 +1946,19 @@ class ModelAdmin(BaseModelAdmin): content_type=get_content_type_for_model(model) ).select_related().order_by('action_time') + paginator = self.get_paginator(request, action_list, 100) + page_number = request.GET.get(PAGE_VAR, 1) + page_obj = paginator.get_page(page_number) + page_range = paginator.get_elided_page_range(page_obj.number) + context = { **self.admin_site.each_context(request), 'title': _('Change history: %s') % obj, 'subtitle': None, - 'action_list': action_list, + 'action_list': page_obj, + 'page_range': page_range, + 'page_var': PAGE_VAR, + 'pagination_required': paginator.count > 100, 'module_name': str(capfirst(opts.verbose_name_plural)), 'object': obj, 'opts': opts, diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index d2e5bb6cfa..c44a265a96 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -774,14 +774,21 @@ a.deletelink:focus, a.deletelink:hover { /* OBJECT HISTORY */ -table#change-history { +#change-history table { width: 100%; } -table#change-history tbody th { +#change-history table tbody th { width: 16em; } +#change-history .paginator { + color: var(--body-quiet-color); + border-bottom: 1px solid var(--hairline-color); + background: var(--body-bg); + overflow: hidden; +} + /* PAGE STRUCTURE */ #container { diff --git a/django/contrib/admin/templates/admin/object_history.html b/django/contrib/admin/templates/admin/object_history.html index 3015f36c4a..91dad45026 100644 --- a/django/contrib/admin/templates/admin/object_history.html +++ b/django/contrib/admin/templates/admin/object_history.html @@ -13,10 +13,10 @@ {% block content %}
-
+
{% if action_list %} - +
@@ -34,6 +34,20 @@ {% endfor %}
{% translate 'Date/time' %}
+

+ {% if pagination_required %} + {% for i in page_range %} + {% if i == action_list.paginator.ELLIPSIS %} + {{ action_list.paginator.ELLIPSIS }} + {% elif i == action_list.number %} + {{ i }} + {% else %} + {{ i }} + {% endif %} + {% endfor %} + {% endif %} + {{ action_list.paginator.count }} {% if action_list.paginator.count == 1 %}{% translate "entry" %}{% else %}{% translate "entries" %}{% endif %} +

{% else %}

{% translate 'This object doesn’t have a change history. It probably wasn’t added via this admin site.' %}

{% endif %} diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index ac8f44b765..7b97ee5638 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -2016,6 +2016,10 @@ Other methods Django view for the page that shows the modification history for a given model instance. + .. versionchanged:: 4.1 + + Pagination was added. + Unlike the hook-type ``ModelAdmin`` methods detailed in the previous section, these five methods are in reality designed to be invoked as Django views from the admin application URL dispatching handler to render the pages that deal diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 5f184d2349..ee21c63a38 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -58,6 +58,9 @@ Minor features subclasses can now control the query string value separator when filtering for multiple values using the ``__in`` lookup. +* The admin :meth:`history view ` + is now paginated. + :mod:`django.contrib.admindocs` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/admin_views/test_history_view.py b/tests/admin_views/test_history_view.py index 277990cc92..c8d678476f 100644 --- a/tests/admin_views/test_history_view.py +++ b/tests/admin_views/test_history_view.py @@ -1,5 +1,8 @@ -from django.contrib.admin.models import LogEntry +from django.contrib.admin.models import CHANGE, LogEntry +from django.contrib.admin.tests import AdminSeleniumTestCase from django.contrib.auth.models import User +from django.contrib.contenttypes.models import ContentType +from django.core.paginator import Paginator from django.test import TestCase, override_settings from django.urls import reverse @@ -43,3 +46,52 @@ class AdminHistoryViewTests(TestCase): 'nolabel_form_field and not_a_form_field. ' 'Changed City verbose_name for city “%s”.' % city ) + + +@override_settings(ROOT_URLCONF='admin_views.urls') +class SeleniumTests(AdminSeleniumTestCase): + available_apps = ['admin_views'] + AdminSeleniumTestCase.available_apps + + def setUp(self): + self.superuser = User.objects.create_superuser( + username='super', password='secret', email='super@example.com', + ) + content_type_pk = ContentType.objects.get_for_model(User).pk + for i in range(1, 1101): + LogEntry.objects.log_action( + self.superuser.pk, + content_type_pk, + self.superuser.pk, + repr(self.superuser), + CHANGE, + change_message=f'Changed something {i}', + ) + self.admin_login( + username='super', password='secret', login_url=reverse('admin:index'), + ) + + def test_pagination(self): + from selenium.webdriver.common.by import By + + user_history_url = reverse('admin:auth_user_history', args=(self.superuser.pk,)) + self.selenium.get(self.live_server_url + user_history_url) + + paginator = self.selenium.find_element(By.CSS_SELECTOR, '.paginator') + self.assertTrue(paginator.is_displayed()) + self.assertIn('%s entries' % LogEntry.objects.count(), paginator.text) + self.assertIn(str(Paginator.ELLIPSIS), paginator.text) + # The current page. + current_page_link = self.selenium.find_element(By.CSS_SELECTOR, 'span.this-page') + self.assertEqual(current_page_link.text, '1') + # The last page. + last_page_link = self.selenium.find_element(By.CSS_SELECTOR, '.end') + self.assertTrue(last_page_link.text, '20') + # Select the second page. + pages = paginator.find_elements(By.TAG_NAME, 'a') + second_page_link = pages[0] + self.assertEqual(second_page_link.text, '2') + second_page_link.click() + self.assertIn('?p=2', self.selenium.current_url) + rows = self.selenium.find_elements(By.CSS_SELECTOR, '#change-history tbody tr') + self.assertIn('Changed something 101', rows[0].text) + self.assertIn('Changed something 200', rows[-1].text)