mirror of
https://github.com/django/django.git
synced 2025-08-03 18:38:50 +00:00
Fixed #10506, #13793, #14891, #25201 -- Introduced new APIs to specify models' default and base managers.
This deprecates use_for_related_fields. Old API: class CustomManager(models.Model): use_for_related_fields = True class Model(models.Model): custom_manager = CustomManager() New API: class Model(models.Model): custom_manager = CustomManager() class Meta: base_manager_name = 'custom_manager' Refs #20932, #25897. Thanks Carl Meyer for the guidance throughout this work. Thanks Tim Graham for writing the docs.
This commit is contained in:
parent
3a47d42fa3
commit
ed0ff913c6
18 changed files with 815 additions and 226 deletions
|
@ -1,9 +1,13 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import warnings
|
||||
|
||||
from django.db import models
|
||||
from django.db.utils import DatabaseError
|
||||
from django.template import Context, Template
|
||||
from django.test import TestCase, override_settings
|
||||
from django.test.utils import isolate_apps
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_text
|
||||
|
||||
from .models import (
|
||||
|
@ -160,3 +164,385 @@ class ManagersRegressionTests(TestCase):
|
|||
related = RelatedModel.objects.create(exact=False)
|
||||
relation = related.test_fk.create()
|
||||
self.assertEqual(related.test_fk.get(), relation)
|
||||
|
||||
|
||||
@isolate_apps('managers_regress')
|
||||
class TestManagerInheritance(TestCase):
|
||||
def test_implicit_inheritance(self):
|
||||
class CustomManager(models.Manager):
|
||||
pass
|
||||
|
||||
class AbstractModel(models.Model):
|
||||
custom_manager = CustomManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class PlainModel(models.Model):
|
||||
custom_manager = CustomManager()
|
||||
|
||||
self.assertIsInstance(PlainModel._base_manager, models.Manager)
|
||||
self.assertIsInstance(PlainModel._default_manager, CustomManager)
|
||||
|
||||
class ModelWithAbstractParent(AbstractModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
|
||||
self.assertIsInstance(ModelWithAbstractParent._base_manager, models.Manager)
|
||||
self.assertIsInstance(ModelWithAbstractParent._default_manager, CustomManager)
|
||||
|
||||
class ProxyModel(PlainModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
proxy = True
|
||||
|
||||
self.assertIsInstance(ProxyModel._base_manager, models.Manager)
|
||||
self.assertIsInstance(ProxyModel._default_manager, CustomManager)
|
||||
|
||||
class MTIModel(PlainModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
|
||||
self.assertIsInstance(MTIModel._base_manager, models.Manager)
|
||||
self.assertIsInstance(MTIModel._default_manager, CustomManager)
|
||||
|
||||
def test_default_manager_inheritance(self):
|
||||
class CustomManager(models.Manager):
|
||||
pass
|
||||
|
||||
class AbstractModel(models.Model):
|
||||
another_manager = models.Manager()
|
||||
custom_manager = CustomManager()
|
||||
|
||||
class Meta:
|
||||
default_manager_name = 'custom_manager'
|
||||
abstract = True
|
||||
|
||||
class PlainModel(models.Model):
|
||||
another_manager = models.Manager()
|
||||
custom_manager = CustomManager()
|
||||
|
||||
class Meta:
|
||||
default_manager_name = 'custom_manager'
|
||||
|
||||
self.assertIsInstance(PlainModel._default_manager, CustomManager)
|
||||
|
||||
class ModelWithAbstractParent(AbstractModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
|
||||
self.assertIsInstance(ModelWithAbstractParent._default_manager, CustomManager)
|
||||
|
||||
class ProxyModel(PlainModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
proxy = True
|
||||
|
||||
self.assertIsInstance(ProxyModel._default_manager, CustomManager)
|
||||
|
||||
class MTIModel(PlainModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
|
||||
self.assertIsInstance(MTIModel._default_manager, CustomManager)
|
||||
|
||||
def test_base_manager_inheritance(self):
|
||||
class CustomManager(models.Manager):
|
||||
pass
|
||||
|
||||
class AbstractModel(models.Model):
|
||||
another_manager = models.Manager()
|
||||
custom_manager = CustomManager()
|
||||
|
||||
class Meta:
|
||||
base_manager_name = 'custom_manager'
|
||||
abstract = True
|
||||
|
||||
class PlainModel(models.Model):
|
||||
another_manager = models.Manager()
|
||||
custom_manager = CustomManager()
|
||||
|
||||
class Meta:
|
||||
base_manager_name = 'custom_manager'
|
||||
|
||||
self.assertIsInstance(PlainModel._base_manager, CustomManager)
|
||||
|
||||
class ModelWithAbstractParent(AbstractModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
|
||||
self.assertIsInstance(ModelWithAbstractParent._base_manager, CustomManager)
|
||||
|
||||
class ProxyModel(PlainModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
proxy = True
|
||||
|
||||
self.assertIsInstance(ProxyModel._base_manager, CustomManager)
|
||||
|
||||
class MTIModel(PlainModel):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
|
||||
self.assertIsInstance(MTIModel._base_manager, CustomManager)
|
||||
|
||||
|
||||
@isolate_apps('managers_regress')
|
||||
class TestManagerDeprecations(TestCase):
|
||||
def test_use_for_related_fields_on_geomanager(self):
|
||||
from django.contrib.gis.db.models import GeoManager
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = GeoManager()
|
||||
|
||||
# Shouldn't issue any warnings, since GeoManager itself will be
|
||||
# deprecated at the same time as use_for_related_fields, there
|
||||
# is no point annoying users with this deprecation.
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
MyModel._base_manager
|
||||
self.assertEqual(len(warns), 0)
|
||||
|
||||
def test_use_for_related_fields_for_base_manager(self):
|
||||
class MyManager(models.Manager):
|
||||
use_for_related_fields = True
|
||||
|
||||
class MyModel(models.Model):
|
||||
objects = MyManager()
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
MyModel._base_manager
|
||||
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.MyModel'.",
|
||||
)
|
||||
|
||||
# With the new base_manager_name API there shouldn't be any warnings.
|
||||
class MyModel2(models.Model):
|
||||
objects = MyManager()
|
||||
|
||||
class Meta:
|
||||
base_manager_name = 'objects'
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
MyModel2._base_manager
|
||||
self.assertEqual(len(warns), 0)
|
||||
|
||||
def test_use_for_related_fields_for_many_to_one(self):
|
||||
class MyManager(models.Manager):
|
||||
use_for_related_fields = True
|
||||
|
||||
class MyRelModel(models.Model):
|
||||
objects = MyManager()
|
||||
|
||||
class MyModel(models.Model):
|
||||
fk = models.ForeignKey(MyRelModel, on_delete=models.DO_NOTHING)
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
try:
|
||||
MyModel(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.MyRelModel'.",
|
||||
)
|
||||
|
||||
# With the new base_manager_name API there shouldn't be any warnings.
|
||||
class MyRelModel2(models.Model):
|
||||
objects = MyManager()
|
||||
|
||||
class Meta:
|
||||
base_manager_name = 'objects'
|
||||
|
||||
class MyModel2(models.Model):
|
||||
fk = models.ForeignKey(MyRelModel2, on_delete=models.DO_NOTHING)
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
try:
|
||||
MyModel2(fk_id=42).fk
|
||||
except DatabaseError:
|
||||
pass
|
||||
self.assertEqual(len(warns), 0)
|
||||
|
||||
def test_use_for_related_fields_for_one_to_one(self):
|
||||
class MyManager(models.Manager):
|
||||
use_for_related_fields = True
|
||||
|
||||
class MyRelModel(models.Model):
|
||||
objects = MyManager()
|
||||
|
||||
class MyModel(models.Model):
|
||||
o2o = models.OneToOneField(MyRelModel, on_delete=models.DO_NOTHING)
|
||||
objects = MyManager()
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
try:
|
||||
MyModel(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.MyRelModel'.",
|
||||
)
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
try:
|
||||
MyRelModel(pk=42).mymodel
|
||||
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.MyModel'.",
|
||||
)
|
||||
|
||||
# With the new base_manager_name API there shouldn't be any warnings.
|
||||
class MyRelModel2(models.Model):
|
||||
objects = MyManager()
|
||||
|
||||
class Meta:
|
||||
base_manager_name = 'objects'
|
||||
|
||||
class MyModel2(models.Model):
|
||||
o2o = models.OneToOneField(MyRelModel2, on_delete=models.DO_NOTHING)
|
||||
objects = MyManager()
|
||||
|
||||
class Meta:
|
||||
base_manager_name = 'objects'
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
try:
|
||||
MyModel2(o2o_id=42).o2o
|
||||
except DatabaseError:
|
||||
pass
|
||||
try:
|
||||
MyRelModel2(pk=42).mymodel2
|
||||
except DatabaseError:
|
||||
pass
|
||||
self.assertEqual(len(warns), 0)
|
||||
|
||||
def test_legacy_objects_is_created(self):
|
||||
class ConcreteParentWithoutManager(models.Model):
|
||||
pass
|
||||
|
||||
class ConcreteParentWithManager(models.Model):
|
||||
default = models.Manager()
|
||||
|
||||
class AbstractParent(models.Model):
|
||||
default = models.Manager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
# Shouldn't complain since the inherited manager
|
||||
# is basically the same that would have been created.
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
|
||||
class MyModel(ConcreteParentWithoutManager):
|
||||
pass
|
||||
self.assertEqual(len(warns), 0)
|
||||
|
||||
# Should create 'objects' (set as default) and warn that
|
||||
# it will no longer be the case in the future.
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
|
||||
class MyModel2(ConcreteParentWithManager):
|
||||
pass
|
||||
self.assertEqual(len(warns), 1)
|
||||
self.assertEqual(
|
||||
str(warns[0].message),
|
||||
"Managers from concrete parents will soon qualify as default "
|
||||
"managers. As a result, the 'objects' manager won't be created "
|
||||
"(or recreated) automatically anymore on "
|
||||
"'managers_regress.MyModel2' and 'default' declared on "
|
||||
"'managers_regress.ConcreteParentWithManager' will be promoted "
|
||||
"to default manager. You can declare explicitly "
|
||||
"`objects = models.Manager()` on 'MyModel2' to keep things the "
|
||||
"way they are or you can switch to the new behavior right away "
|
||||
"by setting `Meta.manager_inheritance_from_future` to `True`.",
|
||||
)
|
||||
|
||||
self.assertIs(MyModel2.objects, MyModel2._default_manager)
|
||||
|
||||
# When there is a local manager we shouldn't get any warning
|
||||
# and 'objects' shouldn't be created.
|
||||
class MyModel3(ConcreteParentWithManager):
|
||||
default = models.Manager()
|
||||
self.assertIs(MyModel3.default, MyModel3._default_manager)
|
||||
self.assertIsNone(getattr(MyModel3, 'objects', None))
|
||||
|
||||
# When there is an inherited manager we shouldn't get any warning
|
||||
# and 'objects' shouldn't be created.
|
||||
class MyModel4(AbstractParent, ConcreteParentWithManager):
|
||||
pass
|
||||
self.assertIs(MyModel4.default, MyModel4._default_manager)
|
||||
self.assertIsNone(getattr(MyModel4, 'objects', None))
|
||||
|
||||
# With `manager_inheritance_from_future = True` 'objects'
|
||||
# shouldn't be created.
|
||||
class MyModel5(ConcreteParentWithManager):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
self.assertIs(MyModel5.default, MyModel5._default_manager)
|
||||
self.assertIsNone(getattr(MyModel5, 'objects', None))
|
||||
|
||||
def test_legacy_default_manager_promotion(self):
|
||||
class ConcreteParent(models.Model):
|
||||
concrete = models.Manager()
|
||||
|
||||
class AbstractParent(models.Model):
|
||||
abstract = models.Manager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
with warnings.catch_warnings(record=True) as warns:
|
||||
warnings.simplefilter('always', RemovedInDjango20Warning)
|
||||
|
||||
class MyModel(ConcreteParent, AbstractParent):
|
||||
pass
|
||||
self.assertEqual(len(warns), 1)
|
||||
self.assertEqual(
|
||||
str(warns[0].message),
|
||||
"Managers from concrete parents will soon qualify as default "
|
||||
"managers if they appear before any other managers in the "
|
||||
"MRO. As a result, 'abstract' declared on "
|
||||
"'managers_regress.AbstractParent' will no longer be the "
|
||||
"default manager for 'managers_regress.MyModel' in favor of "
|
||||
"'concrete' declared on 'managers_regress.ConcreteParent'. "
|
||||
"You can redeclare 'abstract' on 'MyModel' to keep things the "
|
||||
"way they are or you can switch to the new behavior right "
|
||||
"away by setting `Meta.manager_inheritance_from_future` to "
|
||||
"`True`.",
|
||||
)
|
||||
self.assertIs(MyModel.abstract, MyModel._default_manager)
|
||||
|
||||
class MyModel2(ConcreteParent, AbstractParent):
|
||||
abstract = models.Manager()
|
||||
self.assertIs(MyModel2.abstract, MyModel2._default_manager)
|
||||
|
||||
class MyModel3(ConcreteParent, AbstractParent):
|
||||
class Meta:
|
||||
manager_inheritance_from_future = True
|
||||
self.assertIs(MyModel3.concrete, MyModel3._default_manager)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue