1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Fixed #35837 -- Added missing alters_data=True to QuerySet and UserManager methods.

Thank you to Jason Chambers for the report and to Mariusz Felisiak for the review.
This commit is contained in:
Sarah Boyce 2024-10-14 13:59:00 +02:00
parent 03c0a3de72
commit 611bf6c2e2
4 changed files with 70 additions and 1 deletions

View File

@ -174,11 +174,15 @@ class UserManager(BaseUserManager):
extra_fields.setdefault("is_superuser", False) extra_fields.setdefault("is_superuser", False)
return self._create_user(username, email, password, **extra_fields) return self._create_user(username, email, password, **extra_fields)
create_user.alters_data = True
async def acreate_user(self, username, email=None, password=None, **extra_fields): async def acreate_user(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", False) extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_superuser", False) extra_fields.setdefault("is_superuser", False)
return await self._acreate_user(username, email, password, **extra_fields) return await self._acreate_user(username, email, password, **extra_fields)
acreate_user.alters_data = True
def create_superuser(self, username, email=None, password=None, **extra_fields): def create_superuser(self, username, email=None, password=None, **extra_fields):
extra_fields.setdefault("is_staff", True) extra_fields.setdefault("is_staff", True)
extra_fields.setdefault("is_superuser", True) extra_fields.setdefault("is_superuser", True)
@ -190,6 +194,8 @@ class UserManager(BaseUserManager):
return self._create_user(username, email, password, **extra_fields) return self._create_user(username, email, password, **extra_fields)
create_superuser.alters_data = True
async def acreate_superuser( async def acreate_superuser(
self, username, email=None, password=None, **extra_fields self, username, email=None, password=None, **extra_fields
): ):
@ -203,6 +209,8 @@ class UserManager(BaseUserManager):
return await self._acreate_user(username, email, password, **extra_fields) return await self._acreate_user(username, email, password, **extra_fields)
acreate_superuser.alters_data = True
def with_perm( def with_perm(
self, perm, is_active=True, include_superusers=True, backend=None, obj=None self, perm, is_active=True, include_superusers=True, backend=None, obj=None
): ):

View File

@ -660,9 +660,13 @@ class QuerySet(AltersData):
obj.save(force_insert=True, using=self.db) obj.save(force_insert=True, using=self.db)
return obj return obj
create.alters_data = True
async def acreate(self, **kwargs): async def acreate(self, **kwargs):
return await sync_to_async(self.create)(**kwargs) return await sync_to_async(self.create)(**kwargs)
acreate.alters_data = True
def _prepare_for_bulk_create(self, objs): def _prepare_for_bulk_create(self, objs):
from django.db.models.expressions import DatabaseDefault from django.db.models.expressions import DatabaseDefault
@ -835,6 +839,8 @@ class QuerySet(AltersData):
return objs return objs
bulk_create.alters_data = True
async def abulk_create( async def abulk_create(
self, self,
objs, objs,
@ -853,6 +859,8 @@ class QuerySet(AltersData):
unique_fields=unique_fields, unique_fields=unique_fields,
) )
abulk_create.alters_data = True
def bulk_update(self, objs, fields, batch_size=None): def bulk_update(self, objs, fields, batch_size=None):
""" """
Update the given fields in each of the given objects in the database. Update the given fields in each of the given objects in the database.
@ -941,12 +949,16 @@ class QuerySet(AltersData):
pass pass
raise raise
get_or_create.alters_data = True
async def aget_or_create(self, defaults=None, **kwargs): async def aget_or_create(self, defaults=None, **kwargs):
return await sync_to_async(self.get_or_create)( return await sync_to_async(self.get_or_create)(
defaults=defaults, defaults=defaults,
**kwargs, **kwargs,
) )
aget_or_create.alters_data = True
def update_or_create(self, defaults=None, create_defaults=None, **kwargs): def update_or_create(self, defaults=None, create_defaults=None, **kwargs):
""" """
Look up an object with the given kwargs, updating one with defaults Look up an object with the given kwargs, updating one with defaults
@ -992,6 +1004,8 @@ class QuerySet(AltersData):
obj.save(using=self.db) obj.save(using=self.db)
return obj, False return obj, False
update_or_create.alters_data = True
async def aupdate_or_create(self, defaults=None, create_defaults=None, **kwargs): async def aupdate_or_create(self, defaults=None, create_defaults=None, **kwargs):
return await sync_to_async(self.update_or_create)( return await sync_to_async(self.update_or_create)(
defaults=defaults, defaults=defaults,
@ -999,6 +1013,8 @@ class QuerySet(AltersData):
**kwargs, **kwargs,
) )
aupdate_or_create.alters_data = True
def _extract_model_params(self, defaults, **kwargs): def _extract_model_params(self, defaults, **kwargs):
""" """
Prepare `params` for creating a model instance based on the given Prepare `params` for creating a model instance based on the given

View File

@ -392,6 +392,22 @@ Miscellaneous
* The :func:`~django.template.context_processors.debug` context processor is no * The :func:`~django.template.context_processors.debug` context processor is no
longer included in the default project template. longer included in the default project template.
* The following methods now have ``alters_data=True`` set to prevent side
effects when :ref:`rendering a template context <alters-data-description>`:
* :meth:`.UserManager.create_user`
* :meth:`.UserManager.acreate_user`
* :meth:`.UserManager.create_superuser`
* :meth:`.UserManager.acreate_superuser`
* :meth:`.QuerySet.create`
* :meth:`.QuerySet.acreate`
* :meth:`.QuerySet.bulk_create`
* :meth:`.QuerySet.abulk_create`
* :meth:`.QuerySet.get_or_create`
* :meth:`.QuerySet.aget_or_create`
* :meth:`.QuerySet.update_or_create`
* :meth:`.QuerySet.aupdate_or_create`
.. _deprecated-features-5.2: .. _deprecated-features-5.2:
Features deprecated in 5.2 Features deprecated in 5.2

View File

@ -1,8 +1,9 @@
from pathlib import Path from pathlib import Path
from unittest import mock, skipIf from unittest import mock, skipIf
from django.contrib.auth.models import User
from django.template import TemplateSyntaxError from django.template import TemplateSyntaxError
from django.test import RequestFactory from django.test import RequestFactory, TestCase
from .test_dummy import TemplateStringsTests from .test_dummy import TemplateStringsTests
@ -135,3 +136,31 @@ class Jinja2Tests(TemplateStringsTests):
self.assertEqual(len(debug["source_lines"]), 0) self.assertEqual(len(debug["source_lines"]), 0)
self.assertTrue(debug["name"].endswith("nonexistent.html")) self.assertTrue(debug["name"].endswith("nonexistent.html"))
self.assertIn("message", debug) self.assertIn("message", debug)
@skipIf(jinja2 is None, "this test requires jinja2")
class Jinja2SandboxTests(TestCase):
engine_class = Jinja2
backend_name = "jinja2"
options = {"environment": "jinja2.sandbox.SandboxedEnvironment"}
@classmethod
def setUpClass(cls):
super().setUpClass()
params = {
"DIRS": [],
"APP_DIRS": True,
"NAME": cls.backend_name,
"OPTIONS": cls.options,
}
cls.engine = cls.engine_class(params)
def test_set_alters_data(self):
template = self.engine.from_string(
"{% set test = User.objects.create_superuser("
"username='evil', email='a@b.com', password='xxx') %}"
"{{ test }}"
)
with self.assertRaises(jinja2.exceptions.SecurityError):
template.render(context={"User": User})
self.assertEqual(User.objects.count(), 0)