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