mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Refs #28586 -- Copied fetch modes to related objects.
This change ensures that behavior and performance remain consistent when traversing relationships.
This commit is contained in:
committed by
Jacob Walls
parent
821619aa87
commit
6dc9b04018
@@ -201,11 +201,13 @@ class GenericForeignKeyDescriptor:
|
|||||||
for ct_id, fkeys in fk_dict.items():
|
for ct_id, fkeys in fk_dict.items():
|
||||||
if ct_id in custom_queryset_dict:
|
if ct_id in custom_queryset_dict:
|
||||||
# Return values from the custom queryset, if provided.
|
# Return values from the custom queryset, if provided.
|
||||||
ret_val.extend(custom_queryset_dict[ct_id].filter(pk__in=fkeys))
|
queryset = custom_queryset_dict[ct_id].filter(pk__in=fkeys)
|
||||||
else:
|
else:
|
||||||
instance = instance_dict[ct_id]
|
instance = instance_dict[ct_id]
|
||||||
ct = self.field.get_content_type(id=ct_id, using=instance._state.db)
|
ct = self.field.get_content_type(id=ct_id, using=instance._state.db)
|
||||||
ret_val.extend(ct.get_all_objects_for_this_type(pk__in=fkeys))
|
queryset = ct.get_all_objects_for_this_type(pk__in=fkeys)
|
||||||
|
|
||||||
|
ret_val.extend(queryset.fetch_mode(instances[0]._state.fetch_mode))
|
||||||
|
|
||||||
# For doing the join in Python, we have to match both the FK val and
|
# For doing the join in Python, we have to match both the FK val and
|
||||||
# the content type, so we use a callable that returns a (fk, class)
|
# the content type, so we use a callable that returns a (fk, class)
|
||||||
@@ -271,6 +273,8 @@ class GenericForeignKeyDescriptor:
|
|||||||
)
|
)
|
||||||
except ObjectDoesNotExist:
|
except ObjectDoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
|
rel_obj._state.fetch_mode = instance._state.fetch_mode
|
||||||
self.field.set_cached_value(instance, rel_obj)
|
self.field.set_cached_value(instance, rel_obj)
|
||||||
|
|
||||||
def fetch_many(self, instances):
|
def fetch_many(self, instances):
|
||||||
@@ -636,7 +640,11 @@ def create_generic_related_manager(superclass, rel):
|
|||||||
Filter the queryset for the instance this manager is bound to.
|
Filter the queryset for the instance this manager is bound to.
|
||||||
"""
|
"""
|
||||||
db = self._db or router.db_for_read(self.model, instance=self.instance)
|
db = self._db or router.db_for_read(self.model, instance=self.instance)
|
||||||
return queryset.using(db).filter(**self.core_filters)
|
return (
|
||||||
|
queryset.using(db)
|
||||||
|
.fetch_mode(self.instance._state.fetch_mode)
|
||||||
|
.filter(**self.core_filters)
|
||||||
|
)
|
||||||
|
|
||||||
def _remove_prefetched_objects(self):
|
def _remove_prefetched_objects(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ class ForwardManyToOneDescriptor:
|
|||||||
def get_queryset(self, *, instance):
|
def get_queryset(self, *, instance):
|
||||||
return self.field.remote_field.model._base_manager.db_manager(
|
return self.field.remote_field.model._base_manager.db_manager(
|
||||||
hints={"instance": instance}
|
hints={"instance": instance}
|
||||||
).all()
|
).fetch_mode(instance._state.fetch_mode)
|
||||||
|
|
||||||
def get_prefetch_querysets(self, instances, querysets=None):
|
def get_prefetch_querysets(self, instances, querysets=None):
|
||||||
if querysets and len(querysets) != 1:
|
if querysets and len(querysets) != 1:
|
||||||
@@ -398,6 +398,7 @@ class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor):
|
|||||||
obj = rel_model(**kwargs)
|
obj = rel_model(**kwargs)
|
||||||
obj._state.adding = instance._state.adding
|
obj._state.adding = instance._state.adding
|
||||||
obj._state.db = instance._state.db
|
obj._state.db = instance._state.db
|
||||||
|
obj._state.fetch_mode = instance._state.fetch_mode
|
||||||
return obj
|
return obj
|
||||||
return super().get_object(instance)
|
return super().get_object(instance)
|
||||||
|
|
||||||
@@ -462,7 +463,7 @@ class ReverseOneToOneDescriptor:
|
|||||||
def get_queryset(self, *, instance):
|
def get_queryset(self, *, instance):
|
||||||
return self.related.related_model._base_manager.db_manager(
|
return self.related.related_model._base_manager.db_manager(
|
||||||
hints={"instance": instance}
|
hints={"instance": instance}
|
||||||
).all()
|
).fetch_mode(instance._state.fetch_mode)
|
||||||
|
|
||||||
def get_prefetch_querysets(self, instances, querysets=None):
|
def get_prefetch_querysets(self, instances, querysets=None):
|
||||||
if querysets and len(querysets) != 1:
|
if querysets and len(querysets) != 1:
|
||||||
@@ -740,6 +741,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
|||||||
queryset._add_hints(instance=self.instance)
|
queryset._add_hints(instance=self.instance)
|
||||||
if self._db:
|
if self._db:
|
||||||
queryset = queryset.using(self._db)
|
queryset = queryset.using(self._db)
|
||||||
|
queryset._fetch_mode = self.instance._state.fetch_mode
|
||||||
queryset._defer_next_filter = True
|
queryset._defer_next_filter = True
|
||||||
queryset = queryset.filter(**self.core_filters)
|
queryset = queryset.filter(**self.core_filters)
|
||||||
for field in self.field.foreign_related_fields:
|
for field in self.field.foreign_related_fields:
|
||||||
@@ -1141,6 +1143,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
|
|||||||
queryset._add_hints(instance=self.instance)
|
queryset._add_hints(instance=self.instance)
|
||||||
if self._db:
|
if self._db:
|
||||||
queryset = queryset.using(self._db)
|
queryset = queryset.using(self._db)
|
||||||
|
queryset._fetch_mode = self.instance._state.fetch_mode
|
||||||
queryset._defer_next_filter = True
|
queryset._defer_next_filter = True
|
||||||
return queryset._next_is_sticky().filter(**self.core_filters)
|
return queryset._next_is_sticky().filter(**self.core_filters)
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ class ModelIterable(BaseIterable):
|
|||||||
queryset = self.queryset
|
queryset = self.queryset
|
||||||
db = queryset.db
|
db = queryset.db
|
||||||
compiler = queryset.query.get_compiler(using=db)
|
compiler = queryset.query.get_compiler(using=db)
|
||||||
|
fetch_mode = queryset._fetch_mode
|
||||||
# Execute the query. This will also fill compiler.select, klass_info,
|
# Execute the query. This will also fill compiler.select, klass_info,
|
||||||
# and annotations.
|
# and annotations.
|
||||||
results = compiler.execute_sql(
|
results = compiler.execute_sql(
|
||||||
@@ -106,7 +107,7 @@ class ModelIterable(BaseIterable):
|
|||||||
init_list = [
|
init_list = [
|
||||||
f[0].target.attname for f in select[model_fields_start:model_fields_end]
|
f[0].target.attname for f in select[model_fields_start:model_fields_end]
|
||||||
]
|
]
|
||||||
related_populators = get_related_populators(klass_info, select, db)
|
related_populators = get_related_populators(klass_info, select, db, fetch_mode)
|
||||||
known_related_objects = [
|
known_related_objects = [
|
||||||
(
|
(
|
||||||
field,
|
field,
|
||||||
@@ -124,7 +125,6 @@ class ModelIterable(BaseIterable):
|
|||||||
)
|
)
|
||||||
for field, related_objs in queryset._known_related_objects.items()
|
for field, related_objs in queryset._known_related_objects.items()
|
||||||
]
|
]
|
||||||
fetch_mode = queryset._fetch_mode
|
|
||||||
peers = []
|
peers = []
|
||||||
for row in compiler.results_iter(results):
|
for row in compiler.results_iter(results):
|
||||||
obj = model_cls.from_db(
|
obj = model_cls.from_db(
|
||||||
@@ -2787,8 +2787,9 @@ class RelatedPopulator:
|
|||||||
model instance.
|
model instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, klass_info, select, db):
|
def __init__(self, klass_info, select, db, fetch_mode):
|
||||||
self.db = db
|
self.db = db
|
||||||
|
self.fetch_mode = fetch_mode
|
||||||
# Pre-compute needed attributes. The attributes are:
|
# Pre-compute needed attributes. The attributes are:
|
||||||
# - model_cls: the possibly deferred model class to instantiate
|
# - model_cls: the possibly deferred model class to instantiate
|
||||||
# - either:
|
# - either:
|
||||||
@@ -2841,7 +2842,9 @@ class RelatedPopulator:
|
|||||||
# relationship. Therefore checking for a single member of the primary
|
# relationship. Therefore checking for a single member of the primary
|
||||||
# key is enough to determine if the referenced object exists or not.
|
# key is enough to determine if the referenced object exists or not.
|
||||||
self.pk_idx = self.init_list.index(self.model_cls._meta.pk_fields[0].attname)
|
self.pk_idx = self.init_list.index(self.model_cls._meta.pk_fields[0].attname)
|
||||||
self.related_populators = get_related_populators(klass_info, select, self.db)
|
self.related_populators = get_related_populators(
|
||||||
|
klass_info, select, self.db, fetch_mode
|
||||||
|
)
|
||||||
self.local_setter = klass_info["local_setter"]
|
self.local_setter = klass_info["local_setter"]
|
||||||
self.remote_setter = klass_info["remote_setter"]
|
self.remote_setter = klass_info["remote_setter"]
|
||||||
|
|
||||||
@@ -2853,7 +2856,12 @@ class RelatedPopulator:
|
|||||||
if obj_data[self.pk_idx] is None:
|
if obj_data[self.pk_idx] is None:
|
||||||
obj = None
|
obj = None
|
||||||
else:
|
else:
|
||||||
obj = self.model_cls.from_db(self.db, self.init_list, obj_data)
|
obj = self.model_cls.from_db(
|
||||||
|
self.db,
|
||||||
|
self.init_list,
|
||||||
|
obj_data,
|
||||||
|
fetch_mode=self.fetch_mode,
|
||||||
|
)
|
||||||
for rel_iter in self.related_populators:
|
for rel_iter in self.related_populators:
|
||||||
rel_iter.populate(row, obj)
|
rel_iter.populate(row, obj)
|
||||||
self.local_setter(from_obj, obj)
|
self.local_setter(from_obj, obj)
|
||||||
@@ -2861,10 +2869,10 @@ class RelatedPopulator:
|
|||||||
self.remote_setter(obj, from_obj)
|
self.remote_setter(obj, from_obj)
|
||||||
|
|
||||||
|
|
||||||
def get_related_populators(klass_info, select, db):
|
def get_related_populators(klass_info, select, db, fetch_mode):
|
||||||
iterators = []
|
iterators = []
|
||||||
related_klass_infos = klass_info.get("related_klass_infos", [])
|
related_klass_infos = klass_info.get("related_klass_infos", [])
|
||||||
for rel_klass_info in related_klass_infos:
|
for rel_klass_info in related_klass_infos:
|
||||||
rel_cls = RelatedPopulator(rel_klass_info, select, db)
|
rel_cls = RelatedPopulator(rel_klass_info, select, db, fetch_mode)
|
||||||
iterators.append(rel_cls)
|
iterators.append(rel_cls)
|
||||||
return iterators
|
return iterators
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ Fetch modes apply to:
|
|||||||
* Fields deferred with :meth:`.QuerySet.defer` or :meth:`.QuerySet.only`
|
* Fields deferred with :meth:`.QuerySet.defer` or :meth:`.QuerySet.only`
|
||||||
* :ref:`generic-relations`
|
* :ref:`generic-relations`
|
||||||
|
|
||||||
|
Django copies the fetch mode of an instance to any related objects it fetches,
|
||||||
|
so the mode applies to a whole tree of relationships, not just the top-level
|
||||||
|
model in the initial ``QuerySet``. This copying is also done in related
|
||||||
|
managers, even though fetch modes don't affect such managers' queries.
|
||||||
|
|
||||||
Available modes
|
Available modes
|
||||||
===============
|
===============
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from operator import attrgetter
|
|||||||
|
|
||||||
from django.core.exceptions import FieldError, ValidationError
|
from django.core.exceptions import FieldError, ValidationError
|
||||||
from django.db import connection, models
|
from django.db import connection, models
|
||||||
|
from django.db.models import FETCH_PEERS
|
||||||
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
|
||||||
from django.test.utils import CaptureQueriesContext, isolate_apps
|
from django.test.utils import CaptureQueriesContext, isolate_apps
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
@@ -603,6 +604,42 @@ class MultiColumnFKTests(TestCase):
|
|||||||
[m4],
|
[m4],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_one(self):
|
||||||
|
person = Person.objects.fetch_mode(FETCH_PEERS).get(pk=self.bob.pk)
|
||||||
|
self.assertEqual(person._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
person.person_country._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_many(self):
|
||||||
|
people = list(Person.objects.fetch_mode(FETCH_PEERS))
|
||||||
|
person = people[0]
|
||||||
|
self.assertEqual(person._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
person.person_country._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_one(self):
|
||||||
|
country = Country.objects.fetch_mode(FETCH_PEERS).get(pk=self.usa.pk)
|
||||||
|
self.assertEqual(country._state.fetch_mode, FETCH_PEERS)
|
||||||
|
person = country.person_set.get(pk=self.bob.pk)
|
||||||
|
self.assertEqual(
|
||||||
|
person._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_many(self):
|
||||||
|
countries = list(Country.objects.fetch_mode(FETCH_PEERS))
|
||||||
|
country = countries[0]
|
||||||
|
self.assertEqual(country._state.fetch_mode, FETCH_PEERS)
|
||||||
|
person = country.person_set.earliest("pk")
|
||||||
|
self.assertEqual(
|
||||||
|
person._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestModelCheckTests(SimpleTestCase):
|
class TestModelCheckTests(SimpleTestCase):
|
||||||
@isolate_apps("foreign_object")
|
@isolate_apps("foreign_object")
|
||||||
|
|||||||
@@ -813,7 +813,6 @@ class GenericRelationsTests(TestCase):
|
|||||||
self.assertEqual(quartz_tag.content_object, self.quartz)
|
self.assertEqual(quartz_tag.content_object, self.quartz)
|
||||||
|
|
||||||
def test_fetch_mode_raise(self):
|
def test_fetch_mode_raise(self):
|
||||||
TaggedItem.objects.create(tag="lion", content_object=self.lion)
|
|
||||||
tag = TaggedItem.objects.fetch_mode(RAISE).get(tag="yellow")
|
tag = TaggedItem.objects.fetch_mode(RAISE).get(tag="yellow")
|
||||||
msg = "Fetching of TaggedItem.content_object blocked."
|
msg = "Fetching of TaggedItem.content_object blocked."
|
||||||
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
|
with self.assertRaisesMessage(FieldFetchBlocked, msg) as cm:
|
||||||
@@ -821,6 +820,37 @@ class GenericRelationsTests(TestCase):
|
|||||||
self.assertIsNone(cm.exception.__cause__)
|
self.assertIsNone(cm.exception.__cause__)
|
||||||
self.assertTrue(cm.exception.__suppress_context__)
|
self.assertTrue(cm.exception.__suppress_context__)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_one(self):
|
||||||
|
tag = TaggedItem.objects.fetch_mode(FETCH_PEERS).get(tag="yellow")
|
||||||
|
self.assertEqual(tag.content_object, self.lion)
|
||||||
|
self.assertEqual(
|
||||||
|
tag.content_object._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_many(self):
|
||||||
|
tags = list(TaggedItem.objects.fetch_mode(FETCH_PEERS).order_by("tag"))
|
||||||
|
tag = [t for t in tags if t.tag == "yellow"][0]
|
||||||
|
self.assertEqual(tag.content_object, self.lion)
|
||||||
|
self.assertEqual(
|
||||||
|
tag.content_object._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_one(self):
|
||||||
|
animal = Animal.objects.fetch_mode(FETCH_PEERS).get(pk=self.lion.pk)
|
||||||
|
self.assertEqual(animal._state.fetch_mode, FETCH_PEERS)
|
||||||
|
tag = animal.tags.get(tag="yellow")
|
||||||
|
self.assertEqual(tag._state.fetch_mode, FETCH_PEERS)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_many(self):
|
||||||
|
animals = list(Animal.objects.fetch_mode(FETCH_PEERS))
|
||||||
|
animal = animals[0]
|
||||||
|
self.assertEqual(animal._state.fetch_mode, FETCH_PEERS)
|
||||||
|
tags = list(animal.tags.all())
|
||||||
|
tag = tags[0]
|
||||||
|
self.assertEqual(tag._state.fetch_mode, FETCH_PEERS)
|
||||||
|
|
||||||
|
|
||||||
class ProxyRelatedModelTest(TestCase):
|
class ProxyRelatedModelTest(TestCase):
|
||||||
def test_default_behavior(self):
|
def test_default_behavior(self):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
from django.db import connection, transaction
|
from django.db import connection, transaction
|
||||||
|
from django.db.models import FETCH_PEERS
|
||||||
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -589,6 +590,46 @@ class ManyToManyTests(TestCase):
|
|||||||
querysets=[Publication.objects.all(), Publication.objects.all()],
|
querysets=[Publication.objects.all(), Publication.objects.all()],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_one(self):
|
||||||
|
a = Article.objects.fetch_mode(FETCH_PEERS).get(pk=self.a1.pk)
|
||||||
|
self.assertEqual(a._state.fetch_mode, FETCH_PEERS)
|
||||||
|
p = a.publications.earliest("pk")
|
||||||
|
self.assertEqual(
|
||||||
|
p._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_many(self):
|
||||||
|
articles = list(Article.objects.fetch_mode(FETCH_PEERS))
|
||||||
|
a = articles[0]
|
||||||
|
self.assertEqual(a._state.fetch_mode, FETCH_PEERS)
|
||||||
|
publications = list(a.publications.all())
|
||||||
|
p = publications[0]
|
||||||
|
self.assertEqual(
|
||||||
|
p._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_one(self):
|
||||||
|
p1 = Publication.objects.fetch_mode(FETCH_PEERS).get(pk=self.p1.pk)
|
||||||
|
self.assertEqual(p1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
a = p1.article_set.earliest("pk")
|
||||||
|
self.assertEqual(
|
||||||
|
a._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_many(self):
|
||||||
|
publications = list(Publication.objects.fetch_mode(FETCH_PEERS))
|
||||||
|
p = publications[0]
|
||||||
|
self.assertEqual(p._state.fetch_mode, FETCH_PEERS)
|
||||||
|
articles = list(p.article_set.all())
|
||||||
|
a = articles[0]
|
||||||
|
self.assertEqual(
|
||||||
|
a._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ManyToManyQueryTests(TestCase):
|
class ManyToManyQueryTests(TestCase):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -941,3 +941,52 @@ class ManyToOneTests(TestCase):
|
|||||||
a.reporter
|
a.reporter
|
||||||
self.assertIsNone(cm.exception.__cause__)
|
self.assertIsNone(cm.exception.__cause__)
|
||||||
self.assertTrue(cm.exception.__suppress_context__)
|
self.assertTrue(cm.exception.__suppress_context__)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_one(self):
|
||||||
|
a1 = Article.objects.fetch_mode(FETCH_PEERS).get()
|
||||||
|
self.assertEqual(a1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
a1.reporter._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_many(self):
|
||||||
|
Article.objects.create(
|
||||||
|
headline="This is another test",
|
||||||
|
pub_date=datetime.date(2005, 7, 27),
|
||||||
|
reporter=self.r2,
|
||||||
|
)
|
||||||
|
a1, a2 = Article.objects.fetch_mode(FETCH_PEERS)
|
||||||
|
self.assertEqual(a1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
a1.reporter._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_one(self):
|
||||||
|
r1 = Reporter.objects.fetch_mode(FETCH_PEERS).get(pk=self.r.pk)
|
||||||
|
self.assertEqual(r1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
article = r1.article_set.get()
|
||||||
|
self.assertEqual(
|
||||||
|
article._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_many(self):
|
||||||
|
Article.objects.create(
|
||||||
|
headline="This is another test",
|
||||||
|
pub_date=datetime.date(2005, 7, 27),
|
||||||
|
reporter=self.r2,
|
||||||
|
)
|
||||||
|
r1, r2 = Reporter.objects.fetch_mode(FETCH_PEERS)
|
||||||
|
self.assertEqual(r1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
a1 = r1.article_set.get()
|
||||||
|
self.assertEqual(
|
||||||
|
a1._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
a2 = r2.article_set.get()
|
||||||
|
self.assertEqual(
|
||||||
|
a2._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from operator import attrgetter
|
|||||||
from unittest import expectedFailure
|
from unittest import expectedFailure
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.db.models import FETCH_PEERS
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -600,6 +601,22 @@ class ModelInheritanceTest(TestCase):
|
|||||||
self.assertEqual(restaurant.place_ptr.restaurant, restaurant)
|
self.assertEqual(restaurant.place_ptr.restaurant, restaurant)
|
||||||
self.assertEqual(restaurant.italianrestaurant, italian_restaurant)
|
self.assertEqual(restaurant.italianrestaurant, italian_restaurant)
|
||||||
|
|
||||||
|
def test_parent_access_copies_fetch_mode(self):
|
||||||
|
italian_restaurant = ItalianRestaurant.objects.create(
|
||||||
|
name="Mom's Spaghetti",
|
||||||
|
address="2131 Woodward Ave",
|
||||||
|
serves_hot_dogs=False,
|
||||||
|
serves_pizza=False,
|
||||||
|
serves_gnocchi=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# No queries are made when accessing the parent objects.
|
||||||
|
italian_restaurant = ItalianRestaurant.objects.fetch_mode(FETCH_PEERS).get(
|
||||||
|
pk=italian_restaurant.pk
|
||||||
|
)
|
||||||
|
restaurant = italian_restaurant.restaurant_ptr
|
||||||
|
self.assertEqual(restaurant._state.fetch_mode, FETCH_PEERS)
|
||||||
|
|
||||||
def test_id_field_update_on_ancestor_change(self):
|
def test_id_field_update_on_ancestor_change(self):
|
||||||
place1 = Place.objects.create(name="House of Pasta", address="944 Fullerton")
|
place1 = Place.objects.create(name="House of Pasta", address="944 Fullerton")
|
||||||
place2 = Place.objects.create(name="House of Pizza", address="954 Fullerton")
|
place2 = Place.objects.create(name="House of Pizza", address="954 Fullerton")
|
||||||
|
|||||||
@@ -657,3 +657,41 @@ class OneToOneTests(TestCase):
|
|||||||
p.restaurant
|
p.restaurant
|
||||||
self.assertIsNone(cm.exception.__cause__)
|
self.assertIsNone(cm.exception.__cause__)
|
||||||
self.assertTrue(cm.exception.__suppress_context__)
|
self.assertTrue(cm.exception.__suppress_context__)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_one(self):
|
||||||
|
r1 = Restaurant.objects.fetch_mode(FETCH_PEERS).get(pk=self.r1.pk)
|
||||||
|
self.assertEqual(r1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
r1.place._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_forward_fetching_many(self):
|
||||||
|
Restaurant.objects.create(
|
||||||
|
place=self.p2, serves_hot_dogs=True, serves_pizza=False
|
||||||
|
)
|
||||||
|
r1, r2 = Restaurant.objects.fetch_mode(FETCH_PEERS)
|
||||||
|
self.assertEqual(r1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
r1.place._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_one(self):
|
||||||
|
p1 = Place.objects.fetch_mode(FETCH_PEERS).get(pk=self.p1.pk)
|
||||||
|
self.assertEqual(p1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
p1.restaurant._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_reverse_fetching_many(self):
|
||||||
|
Restaurant.objects.create(
|
||||||
|
place=self.p2, serves_hot_dogs=True, serves_pizza=False
|
||||||
|
)
|
||||||
|
p1, p2 = Place.objects.fetch_mode(FETCH_PEERS)
|
||||||
|
self.assertEqual(p1._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
p1.restaurant._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ from unittest import mock
|
|||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.db import NotSupportedError, connection
|
from django.db import NotSupportedError, connection
|
||||||
from django.db.models import F, Prefetch, QuerySet, prefetch_related_objects
|
from django.db.models import (
|
||||||
|
FETCH_PEERS,
|
||||||
|
F,
|
||||||
|
Prefetch,
|
||||||
|
QuerySet,
|
||||||
|
prefetch_related_objects,
|
||||||
|
)
|
||||||
from django.db.models.fetch_modes import RAISE
|
from django.db.models.fetch_modes import RAISE
|
||||||
from django.db.models.query import get_prefetcher
|
from django.db.models.query import get_prefetcher
|
||||||
from django.db.models.sql import Query
|
from django.db.models.sql import Query
|
||||||
@@ -108,6 +114,28 @@ class PrefetchRelatedTests(TestDataMixin, TestCase):
|
|||||||
normal_books = [a.first_book for a in Author.objects.all()]
|
normal_books = [a.first_book for a in Author.objects.all()]
|
||||||
self.assertEqual(books, normal_books)
|
self.assertEqual(books, normal_books)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_fetching_one(self):
|
||||||
|
author = (
|
||||||
|
Author.objects.fetch_mode(FETCH_PEERS)
|
||||||
|
.prefetch_related("first_book")
|
||||||
|
.get(pk=self.author1.pk)
|
||||||
|
)
|
||||||
|
self.assertEqual(author._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
author.first_book._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_fetching_many(self):
|
||||||
|
authors = list(
|
||||||
|
Author.objects.fetch_mode(FETCH_PEERS).prefetch_related("first_book")
|
||||||
|
)
|
||||||
|
self.assertEqual(authors[0]._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
authors[0].first_book._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
def test_fetch_mode_raise(self):
|
def test_fetch_mode_raise(self):
|
||||||
authors = list(Author.objects.fetch_mode(RAISE).prefetch_related("first_book"))
|
authors = list(Author.objects.fetch_mode(RAISE).prefetch_related("first_book"))
|
||||||
authors[0].first_book # No exception, already loaded
|
authors[0].first_book # No exception, already loaded
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from django.core.exceptions import FieldError
|
from django.core.exceptions import FieldError
|
||||||
|
from django.db.models import FETCH_PEERS
|
||||||
from django.test import SimpleTestCase, TestCase
|
from django.test import SimpleTestCase, TestCase
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
@@ -210,6 +211,37 @@ class SelectRelatedTests(TestCase):
|
|||||||
with self.assertRaisesMessage(TypeError, message):
|
with self.assertRaisesMessage(TypeError, message):
|
||||||
list(Species.objects.values_list("name").select_related("genus"))
|
list(Species.objects.values_list("name").select_related("genus"))
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_fetching_one(self):
|
||||||
|
fly = (
|
||||||
|
Species.objects.fetch_mode(FETCH_PEERS)
|
||||||
|
.select_related("genus__family")
|
||||||
|
.get(name="melanogaster")
|
||||||
|
)
|
||||||
|
self.assertEqual(fly._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
fly.genus._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
fly.genus.family._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_fetch_mode_copied_fetching_many(self):
|
||||||
|
specieses = list(
|
||||||
|
Species.objects.fetch_mode(FETCH_PEERS).select_related("genus__family")
|
||||||
|
)
|
||||||
|
species = specieses[0]
|
||||||
|
self.assertEqual(species._state.fetch_mode, FETCH_PEERS)
|
||||||
|
self.assertEqual(
|
||||||
|
species.genus._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
species.genus.family._state.fetch_mode,
|
||||||
|
FETCH_PEERS,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SelectRelatedValidationTests(SimpleTestCase):
|
class SelectRelatedValidationTests(SimpleTestCase):
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user