1
0
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:
Adam Johnson
2025-04-14 15:12:28 +01:00
committed by Jacob Walls
parent 821619aa87
commit 6dc9b04018
12 changed files with 310 additions and 14 deletions

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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
=============== ===============

View File

@@ -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")

View File

@@ -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):

View File

@@ -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):
""" """

View File

@@ -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,
)

View File

@@ -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")

View File

@@ -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,
)

View File

@@ -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

View File

@@ -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):
""" """