diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 9f904a26b49..ea80a1cdd7b 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -106,8 +106,7 @@ class ForwardManyToOneDescriptor(object): def get_queryset(self, **hints): related_model = self.field.remote_field.model - if (not related_model._meta.base_manager_name and - getattr(related_model._default_manager, 'use_for_related_fields', False)): + if getattr(related_model._default_manager, 'use_for_related_fields', False): if not getattr(related_model._default_manager, 'silence_use_for_related_fields_deprecation', False): warnings.warn( "use_for_related_fields is deprecated, instead " @@ -292,8 +291,7 @@ class ReverseOneToOneDescriptor(object): def get_queryset(self, **hints): related_model = self.related.related_model - if (not related_model._meta.base_manager_name and - getattr(related_model._default_manager, 'use_for_related_fields', False)): + if getattr(related_model._default_manager, 'use_for_related_fields', False): if not getattr(related_model._default_manager, 'silence_use_for_related_fields_deprecation', False): warnings.warn( "use_for_related_fields is deprecated, instead " diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index c56ac6efc60..b7b2813b314 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -1091,6 +1091,12 @@ class to override the manager from the concrete model, or you'll set the model's ``Meta.manager_inheritance_from_future=True`` option to opt-in to the new inheritance behavior. +During the deprecation period, ``use_for_related_fields`` will be honored and +raise a warning, even if a ``base_manager_name`` is set. This allows +third-party code to preserve legacy behavior while transitioning to the new +API. The warning can be silenced by setting +``silence_use_for_related_fields_deprecation=True`` on the manager. + Miscellaneous ------------- diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py index 7c1223a5f12..feaca1cba05 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -334,11 +334,26 @@ class TestManagerDeprecations(TestCase): self.assertEqual(len(warns), 0) def test_use_for_related_fields_for_many_to_one(self): + # Common objects + class MyManagerQuerySet(models.QuerySet): + pass + + class MyLegacyManagerQuerySet(models.QuerySet): + pass + class MyManager(models.Manager): + def get_queryset(self): + return MyManagerQuerySet(model=self.model, using=self._db, hints=self._hints) + + class MyLegacyManager(models.Manager): use_for_related_fields = True + def get_queryset(self): + return MyLegacyManagerQuerySet(model=self.model, using=self._db, hints=self._hints) + + # With legacy config there should be a deprecation warning class MyRelModel(models.Model): - objects = MyManager() + objects = MyLegacyManager() class MyModel(models.Model): fk = models.ForeignKey(MyRelModel, on_delete=models.DO_NOTHING) @@ -375,16 +390,61 @@ class TestManagerDeprecations(TestCase): pass self.assertEqual(len(warns), 0) + # When mixing base_manager_name and use_for_related_fields, there + # should be warnings. + class MyRelModel3(models.Model): + my_base_manager = MyManager() + my_default_manager = MyLegacyManager() + + class Meta: + base_manager_name = 'my_base_manager' + default_manager_name = 'my_default_manager' + + class MyModel3(models.Model): + fk = models.ForeignKey(MyRelModel3, on_delete=models.DO_NOTHING) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always', RemovedInDjango20Warning) + try: + MyModel3(fk_id=42).fk + except DatabaseError: + pass + self.assertEqual(len(warns), 1) + self.assertEqual( + str(warns[0].message), + "use_for_related_fields is deprecated, " + "instead set Meta.base_manager_name on " + "'managers_regress.MyRelModel3'.", + ) + with warnings.catch_warnings(record=True): + warnings.simplefilter('always', RemovedInDjango20Warning) + self.assertIsInstance(MyModel3.fk.get_queryset(), MyLegacyManagerQuerySet) + def test_use_for_related_fields_for_one_to_one(self): + # Common objects + class MyManagerQuerySet(models.QuerySet): + pass + + class MyLegacyManagerQuerySet(models.QuerySet): + pass + class MyManager(models.Manager): + def get_queryset(self): + return MyManagerQuerySet(model=self.model, using=self._db, hints=self._hints) + + class MyLegacyManager(models.Manager): use_for_related_fields = True + def get_queryset(self): + return MyLegacyManagerQuerySet(model=self.model, using=self._db, hints=self._hints) + + # With legacy config there should be a deprecation warning class MyRelModel(models.Model): - objects = MyManager() + objects = MyLegacyManager() class MyModel(models.Model): o2o = models.OneToOneField(MyRelModel, on_delete=models.DO_NOTHING) - objects = MyManager() + objects = MyLegacyManager() with warnings.catch_warnings(record=True) as warns: warnings.simplefilter('always', RemovedInDjango20Warning) @@ -440,6 +500,37 @@ class TestManagerDeprecations(TestCase): pass self.assertEqual(len(warns), 0) + # When mixing base_manager_name and use_for_related_fields, there + # should be warnings. + class MyRelModel3(models.Model): + my_base_manager = MyManager() + my_default_manager = MyLegacyManager() + + class Meta: + base_manager_name = 'my_base_manager' + default_manager_name = 'my_default_manager' + + class MyModel3(models.Model): + o2o = models.OneToOneField(MyRelModel3, on_delete=models.DO_NOTHING) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always', RemovedInDjango20Warning) + try: + MyModel3(o2o_id=42).o2o + except DatabaseError: + pass + + self.assertEqual(len(warns), 1) + self.assertEqual( + str(warns[0].message), + "use_for_related_fields is deprecated, " + "instead set Meta.base_manager_name on " + "'managers_regress.MyRelModel3'.", + ) + with warnings.catch_warnings(record=True): + warnings.simplefilter('always', RemovedInDjango20Warning) + self.assertIsInstance(MyModel3.o2o.get_queryset(), MyLegacyManagerQuerySet) + def test_legacy_objects_is_created(self): class ConcreteParentWithoutManager(models.Model): pass