From 34bd3ed944bf38792c631b55e581963d44d52284 Mon Sep 17 00:00:00 2001 From: farhan Date: Thu, 4 Sep 2025 17:05:03 +0500 Subject: [PATCH 1/5] Refs #36559, #35667 -- Used skip_file_prefixes in PartialTemplate.source warning. --- django/template/base.py | 4 +++- tests/template_tests/test_partials.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 37e7243d5c9b..b87291746b68 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -52,10 +52,12 @@ import inspect import logging +import os import re import warnings from enum import Enum +import django from django.template.context import BaseContext from django.utils.formats import localize from django.utils.html import conditional_escape @@ -327,7 +329,7 @@ def source(self): "PartialTemplate.source is only available when template " "debugging is enabled.", RuntimeWarning, - stacklevel=2, + skip_file_prefixes=(os.path.dirname(django.__file__),), ) return self.find_partial_source(template.source) diff --git a/tests/template_tests/test_partials.py b/tests/template_tests/test_partials.py index 9762436fdc42..984f0441c204 100644 --- a/tests/template_tests/test_partials.py +++ b/tests/template_tests/test_partials.py @@ -144,8 +144,9 @@ def test_template_source_warning(self): RuntimeWarning, "PartialTemplate.source is only available when template " "debugging is enabled.", - ): + ) as ctx: self.assertEqual(partial.template.source, "") + self.assertEqual(ctx.filename, __file__) class RobustPartialHandlingTests(TestCase): From 11c2c9ac17db1c04c6de302167d4b0a5539c90fd Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Thu, 4 Sep 2025 09:24:33 -0400 Subject: [PATCH 2/5] Refs #36481 -- Improved test coverage for invalid updates on reverse relations. --- tests/update/tests.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/update/tests.py b/tests/update/tests.py index bbff0e4ff40a..fdd0631142df 100644 --- a/tests/update/tests.py +++ b/tests/update/tests.py @@ -165,6 +165,14 @@ def test_update_m2m_field(self): with self.assertRaisesMessage(FieldError, msg): Bar.objects.update(m2m_foo="whatever") + def test_update_reverse_m2m_descriptor(self): + msg = ( + "Cannot update model field " + "(only non-relations and foreign keys permitted)." + ) + with self.assertRaisesMessage(FieldError, msg): + Foo.objects.update(m2m_foo="whatever") + def test_update_transformed_field(self): A.objects.create(x=5) A.objects.create(x=-6) From bad03eb108b029dad70cbd997f1fef221da3e415 Mon Sep 17 00:00:00 2001 From: Ryan P Kilby Date: Wed, 25 Jun 2025 22:54:50 -0700 Subject: [PATCH 3/5] Fixed #36481 -- Fixed QuerySet.update concrete fields check. FieldError is now emitted for invalid update calls involving reverse relations, where previously they failed with AttributeError. --- django/db/models/sql/subqueries.py | 5 +---- tests/composite_pk/test_update.py | 11 +++++++---- tests/update/models.py | 3 +++ tests/update/tests.py | 24 ++++++++++++++++++++++++ 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py index 2705114a5434..9936f7ff42ca 100644 --- a/django/db/models/sql/subqueries.py +++ b/django/db/models/sql/subqueries.py @@ -87,15 +87,12 @@ def add_update_values(self, values): values_seq = [] for name, val in values.items(): field = self.get_meta().get_field(name) - direct = ( - not (field.auto_created and not field.concrete) or not field.concrete - ) model = field.model._meta.concrete_model if field.name == "pk" and model._meta.is_composite_pk: raise FieldError( "Composite primary key fields must be updated individually." ) - if not direct or (field.is_relation and field.many_to_many): + if not field.concrete or (field.is_relation and field.many_to_many): raise FieldError( "Cannot update model field %r (only non-relations and " "foreign keys permitted)." % field diff --git a/tests/composite_pk/test_update.py b/tests/composite_pk/test_update.py index 697383b00795..8d786e8afb5a 100644 --- a/tests/composite_pk/test_update.py +++ b/tests/composite_pk/test_update.py @@ -169,13 +169,16 @@ def test_update_token_by_tenant_name(self): token_3 = Token.objects.get(pk=self.token_3.pk) self.assertEqual(token_3.secret, "bar") - def test_cant_update_to_unsaved_object(self): + def test_cant_update_relation(self): msg = ( - "Unsaved model instance cannot be used " - "in an ORM query." + "Cannot update model field (only non-relations and foreign keys permitted)" ) - with self.assertRaisesMessage(ValueError, msg): + with self.assertRaisesMessage(FieldError, msg): + Comment.objects.update(user=self.user_1) + + with self.assertRaisesMessage(FieldError, msg): Comment.objects.update(user=User()) def test_cant_update_pk_field(self): diff --git a/tests/update/models.py b/tests/update/models.py index d71fc887c7d3..3d93915c9f9e 100644 --- a/tests/update/models.py +++ b/tests/update/models.py @@ -41,6 +41,9 @@ class Foo(models.Model): class Bar(models.Model): foo = models.ForeignKey(Foo, models.CASCADE, to_field="target") + o2o_foo = models.OneToOneField( + Foo, models.CASCADE, related_name="o2o_bar", null=True + ) m2m_foo = models.ManyToManyField(Foo, related_name="m2m_foo") x = models.IntegerField(default=0) diff --git a/tests/update/tests.py b/tests/update/tests.py index fdd0631142df..af5939a2ef10 100644 --- a/tests/update/tests.py +++ b/tests/update/tests.py @@ -173,6 +173,30 @@ def test_update_reverse_m2m_descriptor(self): with self.assertRaisesMessage(FieldError, msg): Foo.objects.update(m2m_foo="whatever") + def test_update_reverse_fk_descriptor(self): + msg = ( + "Cannot update model field " + "(only non-relations and foreign keys permitted)." + ) + with self.assertRaisesMessage(FieldError, msg): + Foo.objects.update(bar="whatever") + + def test_update_reverse_o2o_descriptor(self): + msg = ( + "Cannot update model field " + "(only non-relations and foreign keys permitted)." + ) + with self.assertRaisesMessage(FieldError, msg): + Foo.objects.update(o2o_bar="whatever") + + def test_update_reverse_mti_parent_link_descriptor(self): + msg = ( + "Cannot update model field " + "(only non-relations and foreign keys permitted)." + ) + with self.assertRaisesMessage(FieldError, msg): + UniqueNumber.objects.update(uniquenumberchild="whatever") + def test_update_transformed_field(self): A.objects.create(x=5) A.objects.create(x=-6) From c595af65457e44221aa56cab25e1b5766d802b8f Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Thu, 28 Aug 2025 15:09:39 +0200 Subject: [PATCH 4/5] Fixed #36578, Refs #35791 -- Ensured inline delete icon scales and stays centered in the admin. Regression in 87ab54b488cb58d810939112f208bb37068710e0. Refs #35829. Thank you Natalia Bidart for the review. --- django/contrib/admin/static/admin/css/widgets.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/static/admin/css/widgets.css b/django/contrib/admin/static/admin/css/widgets.css index ea967018fb0c..d15ee5c09a1f 100644 --- a/django/contrib/admin/static/admin/css/widgets.css +++ b/django/contrib/admin/static/admin/css/widgets.css @@ -568,7 +568,8 @@ ul.timelist, .timelist li { .inline-deletelink { float: right; text-indent: -9999px; - background: url(../img/inline-delete.svg) 0 0 no-repeat; + background: url(../img/inline-delete.svg) center center no-repeat; + background-size: contain; width: 1.5rem; height: 1.5rem; border: 0px none; From 4e7a991c12a113229e0927974d3bf94ea04eecf6 Mon Sep 17 00:00:00 2001 From: Jake Howard Date: Thu, 4 Sep 2025 11:53:51 +0100 Subject: [PATCH 5/5] Refs #36588 -- Warned about using external templates in startapp/startproject commands. Clarified that custom templates provided via `--template` for `starapp` and `startproject` are used as-is, adding a warning that malicious or poorly constructed templates may introduce security issues. --- docs/ref/django-admin.txt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 245a8f387cd1..def46ceeccd1 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1319,6 +1319,15 @@ zip files, you can use a URL like: django-admin startapp --template=https://github.com/githubuser/django-app-template/archive/main.zip myapp +.. warning:: + + Templates provided via ``--template`` are used as is. Malicious or poorly + constructed templates may introduce security weaknesses or unintended + behavior. Compressed archives may also consume excessive resources during + extraction, potentially causing crashes or hangs. + + Contents of templates should be carefully inspected before use. + .. django-admin-option:: --extension EXTENSIONS, -e EXTENSIONS Specifies which file extensions in the app template should be rendered with the @@ -1412,7 +1421,10 @@ For example: .. django-admin-option:: --template TEMPLATE Specifies a directory, file path, or URL of a custom project template. See the -:option:`startapp --template` documentation for examples and usage. +:option:`startapp --template` documentation for examples and usage. The same +**security considerations** described for ``startapp`` templates apply here: +malicious or poorly constructed templates may introduce weaknesses or consume +excessive resources, and templates should be carefully inspected before use. .. django-admin-option:: --extension EXTENSIONS, -e EXTENSIONS