mirror of
https://github.com/django/django.git
synced 2024-12-23 01:25:58 +00:00
Fixed #33316 -- Added pagination to admin history view.
This commit is contained in:
parent
ff0b81b56b
commit
ac5cc6cf01
@ -1927,6 +1927,7 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
def history_view(self, request, object_id, extra_context=None):
|
def history_view(self, request, object_id, extra_context=None):
|
||||||
"The 'history' admin view for this model."
|
"The 'history' admin view for this model."
|
||||||
from django.contrib.admin.models import LogEntry
|
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.
|
# First check if the user can see this history.
|
||||||
model = self.model
|
model = self.model
|
||||||
@ -1945,11 +1946,19 @@ class ModelAdmin(BaseModelAdmin):
|
|||||||
content_type=get_content_type_for_model(model)
|
content_type=get_content_type_for_model(model)
|
||||||
).select_related().order_by('action_time')
|
).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 = {
|
context = {
|
||||||
**self.admin_site.each_context(request),
|
**self.admin_site.each_context(request),
|
||||||
'title': _('Change history: %s') % obj,
|
'title': _('Change history: %s') % obj,
|
||||||
'subtitle': None,
|
'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)),
|
'module_name': str(capfirst(opts.verbose_name_plural)),
|
||||||
'object': obj,
|
'object': obj,
|
||||||
'opts': opts,
|
'opts': opts,
|
||||||
|
@ -774,14 +774,21 @@ a.deletelink:focus, a.deletelink:hover {
|
|||||||
|
|
||||||
/* OBJECT HISTORY */
|
/* OBJECT HISTORY */
|
||||||
|
|
||||||
table#change-history {
|
#change-history table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
table#change-history tbody th {
|
#change-history table tbody th {
|
||||||
width: 16em;
|
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 */
|
/* PAGE STRUCTURE */
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
|
@ -13,10 +13,10 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div id="content-main">
|
<div id="content-main">
|
||||||
<div class="module">
|
<div id="change-history" class="module">
|
||||||
|
|
||||||
{% if action_list %}
|
{% if action_list %}
|
||||||
<table id="change-history">
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">{% translate 'Date/time' %}</th>
|
<th scope="col">{% translate 'Date/time' %}</th>
|
||||||
@ -34,6 +34,20 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
<p class="paginator">
|
||||||
|
{% if pagination_required %}
|
||||||
|
{% for i in page_range %}
|
||||||
|
{% if i == action_list.paginator.ELLIPSIS %}
|
||||||
|
{{ action_list.paginator.ELLIPSIS }}
|
||||||
|
{% elif i == action_list.number %}
|
||||||
|
<span class="this-page">{{ i }}</span>
|
||||||
|
{% else %}
|
||||||
|
<a href="?{{ page_var }}={{ i }}" {% if i == action_list.paginator.num_pages %} class="end" {% endif %}>{{ i }}</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{{ action_list.paginator.count }} {% if action_list.paginator.count == 1 %}{% translate "entry" %}{% else %}{% translate "entries" %}{% endif %}
|
||||||
|
</p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>{% translate 'This object doesn’t have a change history. It probably wasn’t added via this admin site.' %}</p>
|
<p>{% translate 'This object doesn’t have a change history. It probably wasn’t added via this admin site.' %}</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -2016,6 +2016,10 @@ Other methods
|
|||||||
Django view for the page that shows the modification history for a given
|
Django view for the page that shows the modification history for a given
|
||||||
model instance.
|
model instance.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.1
|
||||||
|
|
||||||
|
Pagination was added.
|
||||||
|
|
||||||
Unlike the hook-type ``ModelAdmin`` methods detailed in the previous section,
|
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
|
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
|
the admin application URL dispatching handler to render the pages that deal
|
||||||
|
@ -58,6 +58,9 @@ Minor features
|
|||||||
subclasses can now control the query string value separator when filtering
|
subclasses can now control the query string value separator when filtering
|
||||||
for multiple values using the ``__in`` lookup.
|
for multiple values using the ``__in`` lookup.
|
||||||
|
|
||||||
|
* The admin :meth:`history view <django.contrib.admin.ModelAdmin.history_view>`
|
||||||
|
is now paginated.
|
||||||
|
|
||||||
:mod:`django.contrib.admindocs`
|
:mod:`django.contrib.admindocs`
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
@ -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.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.test import TestCase, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -43,3 +46,52 @@ class AdminHistoryViewTests(TestCase):
|
|||||||
'nolabel_form_field and not_a_form_field. '
|
'nolabel_form_field and not_a_form_field. '
|
||||||
'Changed City verbose_name for city “%s”.' % city
|
'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)
|
||||||
|
Loading…
Reference in New Issue
Block a user