Fixed #31486 -- Deprecated passing unsaved objects to related filters.

Co-Authored-By: Hasan Ramezani <hasan.r67@gmail.com>
This commit is contained in:
Albert Defler 2022-02-05 20:00:20 +00:00 committed by Mariusz Felisiak
parent 11cc227344
commit 2b6a3baebe
4 changed files with 46 additions and 1 deletions

View File

@ -1,3 +1,5 @@
import warnings
from django.db.models.lookups import ( from django.db.models.lookups import (
Exact, Exact,
GreaterThan, GreaterThan,
@ -7,6 +9,7 @@ from django.db.models.lookups import (
LessThan, LessThan,
LessThanOrEqual, LessThanOrEqual,
) )
from django.utils.deprecation import RemovedInDjango50Warning
class MultiColSource: class MultiColSource:
@ -40,6 +43,15 @@ def get_normalized_value(value, lhs):
from django.db.models import Model from django.db.models import Model
if isinstance(value, Model): if isinstance(value, Model):
if value.pk is None:
# When the deprecation ends, replace with:
# raise ValueError(
# "Model instances passed to related filters must be saved."
# )
warnings.warn(
"Passing unsaved model instances to related filters is deprecated.",
RemovedInDjango50Warning,
)
value_list = [] value_list = []
sources = lhs.output_field.path_infos[-1].target_fields sources = lhs.output_field.path_infos[-1].target_fields
for source in sources: for source in sources:

View File

@ -85,6 +85,8 @@ details on these changes.
objects without providing the ``chunk_size`` argument will no longer be objects without providing the ``chunk_size`` argument will no longer be
allowed. allowed.
* Passing unsaved model instances to related filters will no longer be allowed.
.. _deprecation-removed-in-4.1: .. _deprecation-removed-in-4.1:
4.1 4.1

View File

@ -484,6 +484,9 @@ Miscellaneous
versions, no prefetching was done. Providing a value for ``chunk_size`` versions, no prefetching was done. Providing a value for ``chunk_size``
signifies that the additional query per chunk needed to prefetch is desired. signifies that the additional query per chunk needed to prefetch is desired.
* Passing unsaved model instances to related filters is deprecated. In Django
5.0, the exception will be raised.
Features removed in 4.1 Features removed in 4.1
======================= =======================

View File

@ -12,7 +12,8 @@ from django.db.models.expressions import RawSQL
from django.db.models.sql.constants import LOUTER from django.db.models.sql.constants import LOUTER
from django.db.models.sql.where import NothingNode, WhereNode from django.db.models.sql.where import NothingNode, WhereNode
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
from django.test.utils import CaptureQueriesContext from django.test.utils import CaptureQueriesContext, ignore_warnings
from django.utils.deprecation import RemovedInDjango50Warning
from .models import ( from .models import (
FK1, FK1,
@ -1899,6 +1900,19 @@ class Queries5Tests(TestCase):
self.assertEqual(Ranking.objects.filter(author__in=authors).get(), self.rank3) self.assertEqual(Ranking.objects.filter(author__in=authors).get(), self.rank3)
self.assertEqual(authors.count(), 1) self.assertEqual(authors.count(), 1)
def test_filter_unsaved_object(self):
# These tests will catch ValueError in Django 5.0 when passing unsaved
# model instances to related filters becomes forbidden.
# msg = "Model instances passed to related filters must be saved."
msg = "Passing unsaved model instances to related filters is deprecated."
company = Company.objects.create(name="Django")
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
Employment.objects.filter(employer=Company(name="unsaved"))
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
Employment.objects.filter(employer__in=[company, Company(name="unsaved")])
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
StaffUser.objects.filter(staff=Staff(name="unsaved"))
class SelectRelatedTests(TestCase): class SelectRelatedTests(TestCase):
def test_tickets_3045_3288(self): def test_tickets_3045_3288(self):
@ -3211,6 +3225,7 @@ class ExcludeTests(TestCase):
[self.j1, self.j2], [self.j1, self.j2],
) )
@ignore_warnings(category=RemovedInDjango50Warning)
def test_exclude_unsaved_o2o_object(self): def test_exclude_unsaved_o2o_object(self):
jack = Staff.objects.create(name="jack") jack = Staff.objects.create(name="jack")
jack_staff = StaffUser.objects.create(staff=jack) jack_staff = StaffUser.objects.create(staff=jack)
@ -3221,6 +3236,19 @@ class ExcludeTests(TestCase):
StaffUser.objects.exclude(staff=unsaved_object), [jack_staff] StaffUser.objects.exclude(staff=unsaved_object), [jack_staff]
) )
def test_exclude_unsaved_object(self):
# These tests will catch ValueError in Django 5.0 when passing unsaved
# model instances to related filters becomes forbidden.
# msg = "Model instances passed to related filters must be saved."
company = Company.objects.create(name="Django")
msg = "Passing unsaved model instances to related filters is deprecated."
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
Employment.objects.exclude(employer=Company(name="unsaved"))
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
Employment.objects.exclude(employer__in=[company, Company(name="unsaved")])
with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
StaffUser.objects.exclude(staff=Staff(name="unsaved"))
class ExcludeTest17600(TestCase): class ExcludeTest17600(TestCase):
""" """