From 9482a4116cc50961e8093151c87ba0719bcb33be Mon Sep 17 00:00:00 2001 From: Antoliny0919 Date: Tue, 10 Feb 2026 19:47:52 +0900 Subject: [PATCH 1/5] Fixed #36873 -- Aligned and normalized stroke width for non-multiple select icons in related-widget-wrapper. --- django/contrib/admin/static/admin/css/forms.css | 8 ++++++++ django/contrib/admin/static/admin/css/widgets.css | 4 ++++ django/contrib/admin/static/admin/img/icon-deletelink.svg | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/static/admin/css/forms.css b/django/contrib/admin/static/admin/css/forms.css index 6249e55772b0..5d2c1d2018f4 100644 --- a/django/contrib/admin/static/admin/css/forms.css +++ b/django/contrib/admin/static/admin/css/forms.css @@ -516,6 +516,14 @@ form .related-widget-wrapper ul { padding-left: 0; } +form .related-widget-wrapper a.change-related img { + transform: scale(0.85); +} + +form .related-widget-wrapper a.view-related img { + transform: scale(0.95); +} + .clearable-file-input input { margin-top: 0; } diff --git a/django/contrib/admin/static/admin/css/widgets.css b/django/contrib/admin/static/admin/css/widgets.css index 43271f8c0841..c0de045c6802 100644 --- a/django/contrib/admin/static/admin/css/widgets.css +++ b/django/contrib/admin/static/admin/css/widgets.css @@ -589,6 +589,10 @@ ul.timelist, .timelist li { margin-bottom: 5px; } +.related-widget-wrapper:has(select:not([multiple])) { + align-items: center; +} + .related-widget-wrapper-link { opacity: .6; filter: grayscale(1); diff --git a/django/contrib/admin/static/admin/img/icon-deletelink.svg b/django/contrib/admin/static/admin/img/icon-deletelink.svg index eac19d7507ad..9336c51ad1a9 100644 --- a/django/contrib/admin/static/admin/img/icon-deletelink.svg +++ b/django/contrib/admin/static/admin/img/icon-deletelink.svg @@ -6,6 +6,6 @@ Icon Family: classic Icon Style: solid --> - + From 69d47f921979aba5ad5d876bff015b55121fc49c Mon Sep 17 00:00:00 2001 From: Antoliny0919 Date: Tue, 24 Feb 2026 09:13:38 +0900 Subject: [PATCH 2/5] Refs #36873 -- Fixed changelink background image Y position. --- django/contrib/admin/static/admin/css/base.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index ee67fe7cd501..8cb965984cf6 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -807,7 +807,7 @@ ol.breadcrumbs a:focus, ol.breadcrumbs a:hover { .changelink, .inlinechangelink { padding-left: 16px; - background: url(../img/icon-changelink.svg) 0 1px no-repeat; + background: url(../img/icon-changelink.svg) 0 2px no-repeat; } .deletelink { From 169152f8f5ac586e19779faf9943086fe0f416e9 Mon Sep 17 00:00:00 2001 From: AbhimanyuGit2507 Date: Sat, 14 Feb 2026 01:59:03 +0530 Subject: [PATCH 3/5] Fixed #36839 -- Warned when model renames encounter conflicts from stale ContentTypes. --- .../contenttypes/management/__init__.py | 12 +++++- tests/contenttypes_tests/test_operations.py | 42 ++++++++++++------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/django/contrib/contenttypes/management/__init__.py b/django/contrib/contenttypes/management/__init__.py index 929e44f390db..55b08870d83e 100644 --- a/django/contrib/contenttypes/management/__init__.py +++ b/django/contrib/contenttypes/management/__init__.py @@ -1,3 +1,5 @@ +import warnings + from django.apps import apps as global_apps from django.db import DEFAULT_DB_ALIAS, IntegrityError, migrations, router, transaction @@ -28,8 +30,14 @@ def _rename(self, apps, schema_editor, old_model, new_model): content_type.save(using=db, update_fields={"model"}) except IntegrityError: # Gracefully fallback if a stale content type causes a - # conflict as remove_stale_contenttypes will take care of - # asking the user what should be done next. + # conflict. Warn the user so they can run the + # remove_stale_contenttypes management command. + warnings.warn( + f"Could not rename content type '{self.app_label}.{old_model}' " + f"to '{new_model}' due to an existing conflicting content type. " + "Run 'remove_stale_contenttypes' to clean up stale entries.", + RuntimeWarning, + ) content_type.model = old_model else: # Clear the cache as the `get_by_natural_key()` call will cache diff --git a/tests/contenttypes_tests/test_operations.py b/tests/contenttypes_tests/test_operations.py index d44648d9fe6e..9f6506640ad6 100644 --- a/tests/contenttypes_tests/test_operations.py +++ b/tests/contenttypes_tests/test_operations.py @@ -154,13 +154,19 @@ def test_missing_content_type_rename_ignore(self): def test_content_type_rename_conflict(self): ContentType.objects.create(app_label="contenttypes_tests", model="foo") ContentType.objects.create(app_label="contenttypes_tests", model="renamedfoo") - call_command( - "migrate", - "contenttypes_tests", - database="default", - interactive=False, - verbosity=0, - ) + msg = ( + "Could not rename content type 'contenttypes_tests.foo' to " + "'renamedfoo' due to an existing conflicting content type. " + "Run 'remove_stale_contenttypes' to clean up stale entries." + ) + with self.assertWarnsMessage(RuntimeWarning, msg): + call_command( + "migrate", + "contenttypes_tests", + database="default", + interactive=False, + verbosity=0, + ) self.assertTrue( ContentType.objects.filter( app_label="contenttypes_tests", model="foo" @@ -171,14 +177,20 @@ def test_content_type_rename_conflict(self): app_label="contenttypes_tests", model="renamedfoo" ).exists() ) - call_command( - "migrate", - "contenttypes_tests", - "zero", - database="default", - interactive=False, - verbosity=0, - ) + msg = ( + "Could not rename content type 'contenttypes_tests.renamedfoo' to " + "'foo' due to an existing conflicting content type. " + "Run 'remove_stale_contenttypes' to clean up stale entries." + ) + with self.assertWarnsMessage(RuntimeWarning, msg): + call_command( + "migrate", + "contenttypes_tests", + "zero", + database="default", + interactive=False, + verbosity=0, + ) self.assertTrue( ContentType.objects.filter( app_label="contenttypes_tests", model="foo" From 07d6fa5a95b79a70cf4ba4b725ce37e488ceb7ca Mon Sep 17 00:00:00 2001 From: Amar <100243770+aadeina@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:00:39 +0000 Subject: [PATCH 4/5] Fixed #36948 -- Fixed breadcrumb text overlap at small widths in admin. Visual regression in bc03f1064e10fa247a46d4e8a98ba9b26aa4790d. --- django/contrib/admin/static/admin/css/base.css | 1 - 1 file changed, 1 deletion(-) diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index 8cb965984cf6..ec2ad6ab9370 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -767,7 +767,6 @@ ol.breadcrumbs li { display: inline-block; font-size: 0.875rem; padding: 0; - line-height: 0; } ol.breadcrumbs li:not([aria-current="page"])::after { From bbc6818bc12f14c1764a7eb68556018195f56b59 Mon Sep 17 00:00:00 2001 From: Natalia <124304+nessita@users.noreply.github.com> Date: Wed, 25 Feb 2026 10:37:38 -0300 Subject: [PATCH 5/5] Fixed #36944 -- Removed MAX_LENGTH_HTML and related 5M chars limit references from HTML truncation docs. --- django/utils/text.py | 6 ------ docs/ref/templates/builtins.txt | 6 ++---- tests/utils_tests/test_text.py | 36 --------------------------------- 3 files changed, 2 insertions(+), 46 deletions(-) diff --git a/django/utils/text.py b/django/utils/text.py index ef4baa935bf2..cfe6ceca9e4b 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -185,14 +185,8 @@ def process(self, data): class Truncator(SimpleLazyObject): """ An object used to truncate text, either by characters or words. - - When truncating HTML text (either chars or words), input will be limited to - at most `MAX_LENGTH_HTML` characters. """ - # 5 million characters are approximately 4000 text pages or 3 web pages. - MAX_LENGTH_HTML = 5_000_000 - def __init__(self, text): super().__init__(lambda: str(text)) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 9eee51beba97..94ec4a6be5f4 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2860,8 +2860,7 @@ Newlines in the HTML content will be preserved. .. admonition:: Size of input string Processing large, potentially malformed HTML strings can be - resource-intensive and impact service performance. ``truncatechars_html`` - limits input to the first five million characters. + resource-intensive and impact service performance. .. templatefilter:: truncatewords @@ -2908,8 +2907,7 @@ Newlines in the HTML content will be preserved. .. admonition:: Size of input string Processing large, potentially malformed HTML strings can be - resource-intensive and impact service performance. ``truncatewords_html`` - limits input to the first five million characters. + resource-intensive and impact service performance. .. templatefilter:: unordered_list diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py index 11c01874cb5d..50e205a25449 100644 --- a/tests/utils_tests/test_text.py +++ b/tests/utils_tests/test_text.py @@ -1,6 +1,5 @@ import json import sys -from unittest.mock import patch from django.core.exceptions import SuspiciousFileOperation from django.test import SimpleTestCase @@ -136,23 +135,6 @@ def test_truncate_chars_html(self): truncator = text.Truncator("foo

") self.assertEqual("foo

", truncator.chars(5, html=True)) - @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000) - def test_truncate_chars_html_size_limit(self): - max_len = text.Truncator.MAX_LENGTH_HTML - bigger_len = text.Truncator.MAX_LENGTH_HTML + 1 - valid_html = "

Joel is a slug

" # 14 chars - perf_test_values = [ - ("", ""), - ("", "

"), - ("&" * bigger_len, ""), - ("_X<<<<<<<<<<<>", "_X<<<<<<<…"), - (valid_html * bigger_len, "

Joel is a…

"), # 10 chars - ] - for value, expected in perf_test_values: - with self.subTest(value=value): - truncator = text.Truncator(value) - self.assertEqual(expected, truncator.chars(10, html=True)) - def test_truncate_chars_html_with_newline_inside_tag(self): truncator = text.Truncator( '

The quick brown fox jumped over ' @@ -329,24 +311,6 @@ def test_truncate_html_words(self): self.assertEqual(truncator.words(3, html=True), "hello ><…") self.assertEqual(truncator.words(4, html=True), "hello >< world") - @patch("django.utils.text.Truncator.MAX_LENGTH_HTML", 10_000) - def test_truncate_words_html_size_limit(self): - max_len = text.Truncator.MAX_LENGTH_HTML - bigger_len = text.Truncator.MAX_LENGTH_HTML + 1 - valid_html = "

Joel is a slug

" # 4 words - perf_test_values = [ - ("", ""), - ("", "

"), - ("&" * max_len, ""), - ("&" * bigger_len, ""), - ("_X<<<<<<<<<<<>", "_X<<<<<<<<<<<>"), - (valid_html * bigger_len, valid_html * 12 + "

Joel is…

"), # 50 words - ] - for value, expected in perf_test_values: - with self.subTest(value=value): - truncator = text.Truncator(value) - self.assertEqual(expected, truncator.words(50, html=True)) - def test_wrap(self): digits = "1234 67 9" self.assertEqual(text.wrap(digits, 100), "1234 67 9")