diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index e2be6a78fe..082dafc141 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -808,19 +808,19 @@ a.deletelink:focus, a.deletelink:hover { /* OBJECT TOOLS */ .object-tools { - font-size: 0.625rem; - font-weight: bold; - padding-left: 0; - float: right; - position: relative; - margin-top: -48px; + padding: 0; + overflow: hidden; + text-align: right; + margin: 0 0 15px; } .object-tools li { - display: block; - float: left; - margin-left: 5px; - height: 1rem; + display: inline-block; + height: auto; +} + +.object-tools li + li { + margin-left: 15px; } .object-tools a { diff --git a/django/contrib/admin/static/admin/css/responsive.css b/django/contrib/admin/static/admin/css/responsive.css index 7a2c321ceb..9aa895316c 100644 --- a/django/contrib/admin/static/admin/css/responsive.css +++ b/django/contrib/admin/static/admin/css/responsive.css @@ -442,19 +442,7 @@ input[type="submit"], button { } .object-tools { - float: none; - margin: 0 0 15px; - padding: 0; - overflow: hidden; - } - - .object-tools li { - height: auto; - margin-left: 0; - } - - .object-tools li + li { - margin-left: 15px; + text-align: left; } /* Forms */ diff --git a/django/contrib/admin/static/admin/css/responsive_rtl.css b/django/contrib/admin/static/admin/css/responsive_rtl.css index acad9e8b6c..b336bbfbe9 100644 --- a/django/contrib/admin/static/admin/css/responsive_rtl.css +++ b/django/contrib/admin/static/admin/css/responsive_rtl.css @@ -34,15 +34,6 @@ background-position: calc(100% - 8px) 9px; } - [dir="rtl"] .object-tools li { - float: right; - } - - [dir="rtl"] .object-tools li + li { - margin-left: 0; - margin-right: 15px; - } - [dir="rtl"] .dashboard .module table td a { padding-left: 0; padding-right: 16px; @@ -72,6 +63,11 @@ margin-left: 0; margin-right: 0; } + + [dir="rtl"] .object-tools { + text-align: right; + } + [dir="rtl"] .aligned .vCheckboxLabel { padding: 1px 5px 0 0; } diff --git a/django/contrib/admin/static/admin/css/rtl.css b/django/contrib/admin/static/admin/css/rtl.css index b824d7d0db..f484d55275 100644 --- a/django/contrib/admin/static/admin/css/rtl.css +++ b/django/contrib/admin/static/admin/css/rtl.css @@ -26,7 +26,12 @@ th { } .object-tools { - float: left; + text-align: left; +} + +.object-tools li + li { + margin-right: 15px; + margin-left: 0; } thead th:first-child, diff --git a/docs/intro/_images/admin04t.png b/docs/intro/_images/admin04t.png index e8f5ea8a6f..ef5e12b93c 100644 Binary files a/docs/intro/_images/admin04t.png and b/docs/intro/_images/admin04t.png differ diff --git a/docs/intro/_images/admin05t.png b/docs/intro/_images/admin05t.png index 427a26ba18..0007fae910 100644 Binary files a/docs/intro/_images/admin05t.png and b/docs/intro/_images/admin05t.png differ diff --git a/docs/intro/_images/admin12t.png b/docs/intro/_images/admin12t.png index c4a4192855..b79465a4ac 100644 Binary files a/docs/intro/_images/admin12t.png and b/docs/intro/_images/admin12t.png differ diff --git a/docs/intro/_images/admin13t.png b/docs/intro/_images/admin13t.png index ae39044608..1567d32393 100644 Binary files a/docs/intro/_images/admin13t.png and b/docs/intro/_images/admin13t.png differ diff --git a/docs/ref/contrib/admin/_images/actions-as-modeladmin-methods.png b/docs/ref/contrib/admin/_images/actions-as-modeladmin-methods.png index c8c67aa566..997a0e16c8 100644 Binary files a/docs/ref/contrib/admin/_images/actions-as-modeladmin-methods.png and b/docs/ref/contrib/admin/_images/actions-as-modeladmin-methods.png differ diff --git a/docs/ref/contrib/admin/_images/adding-actions-to-the-modeladmin.png b/docs/ref/contrib/admin/_images/adding-actions-to-the-modeladmin.png index e08edee6cf..1d539fe446 100644 Binary files a/docs/ref/contrib/admin/_images/adding-actions-to-the-modeladmin.png and b/docs/ref/contrib/admin/_images/adding-actions-to-the-modeladmin.png differ diff --git a/docs/ref/contrib/admin/_images/list_filter.png b/docs/ref/contrib/admin/_images/list_filter.png index 777034516d..bdb58f9f38 100644 Binary files a/docs/ref/contrib/admin/_images/list_filter.png and b/docs/ref/contrib/admin/_images/list_filter.png differ diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index a20130bb02..8f102dece3 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -973,6 +973,12 @@ class Restaurant(models.Model): city = models.ForeignKey(City, models.CASCADE) name = models.CharField(max_length=100) + class Meta: + verbose_name = ( + "very very very very very very very very very " + "loooooooooooooooooooooooooooooooooooooooooong name" + ) + def get_absolute_url(self): return "/dummy/%s/" % self.pk diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 00b5d5ab89..65241becc0 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -126,6 +126,7 @@ from .models import ( Song, State, Story, + Subscriber, SuperSecretHideout, SuperVillain, Telegram, @@ -6955,6 +6956,37 @@ class SeleniumTests(AdminSeleniumTestCase): with self.wait_page_loaded(): save_button.click() + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) + def test_long_header_with_object_tools_layout(self): + from selenium.webdriver.common.by import By + + self.admin_login( + username="super", password="secret", login_url=reverse("admin:index") + ) + s = Subscriber.objects.create(name="a " * 40, email="b " * 80) + self.selenium.get( + self.live_server_url + + reverse("admin:admin_views_subscriber_change", args=(s.pk,)) + ) + header = self.selenium.find_element(By.CSS_SELECTOR, "div#content h2") + self.assertGreater(len(header.text), 100) + object_tools = self.selenium.find_elements( + By.CSS_SELECTOR, "div#content ul.object-tools li" + ) + self.assertGreater(len(object_tools), 0) + self.take_screenshot("change_form") + + self.selenium.get( + self.live_server_url + reverse("admin:admin_views_restaurant_changelist") + ) + header = self.selenium.find_element(By.CSS_SELECTOR, "div#content h1") + self.assertGreater(len(header.text), 100) + object_tools = self.selenium.find_elements( + By.CSS_SELECTOR, "div#content ul.object-tools li" + ) + self.assertGreater(len(object_tools), 0) + self.take_screenshot("change_list") + @override_settings(ROOT_URLCONF="admin_views.urls") class ReadonlyTest(AdminFieldExtractionMixin, TestCase):