Co-authored-by: Jarek Glowacki <jarekwg@gmail.com>
This commit is contained in:
parent
0c0240aba8
commit
225d96533a
|
@ -782,10 +782,6 @@ class Field(RegisterLookupMixin):
|
||||||
self.model = cls
|
self.model = cls
|
||||||
cls._meta.add_field(self, private=private_only)
|
cls._meta.add_field(self, private=private_only)
|
||||||
if self.column:
|
if self.column:
|
||||||
# Don't override classmethods with the descriptor. This means that
|
|
||||||
# if you have a classmethod and a field with the same name, then
|
|
||||||
# such fields can't be deferred (we don't have a check for this).
|
|
||||||
if not getattr(cls, self.attname, None):
|
|
||||||
setattr(cls, self.attname, self.descriptor_class(self))
|
setattr(cls, self.attname, self.descriptor_class(self))
|
||||||
if self.choices is not None:
|
if self.choices is not None:
|
||||||
# Don't override a get_FOO_display() method defined explicitly on
|
# Don't override a get_FOO_display() method defined explicitly on
|
||||||
|
|
|
@ -1457,6 +1457,13 @@ different database tables).
|
||||||
Django will raise a :exc:`~django.core.exceptions.FieldError` if you override
|
Django will raise a :exc:`~django.core.exceptions.FieldError` if you override
|
||||||
any model field in any ancestor model.
|
any model field in any ancestor model.
|
||||||
|
|
||||||
|
Note that because of the way fields are resolved during class definition, model
|
||||||
|
fields inherited from multiple abstract parent models are resolved in a strict
|
||||||
|
depth-first order. This contrasts with standard Python MRO, which is resolved
|
||||||
|
breadth-first in cases of diamond shaped inheritance. This difference only
|
||||||
|
affects complex model hierarchies, which (as per the advice above) you should
|
||||||
|
try to avoid.
|
||||||
|
|
||||||
Organizing models in a package
|
Organizing models in a package
|
||||||
==============================
|
==============================
|
||||||
|
|
||||||
|
|
|
@ -314,6 +314,12 @@ class CheckFrameworkReservedNamesTests(SimpleTestCase):
|
||||||
obj=ModelWithAttributeCalledCheck,
|
obj=ModelWithAttributeCalledCheck,
|
||||||
id='models.E020'
|
id='models.E020'
|
||||||
),
|
),
|
||||||
|
Error(
|
||||||
|
"The 'ModelWithFieldCalledCheck.check()' class method is "
|
||||||
|
"currently overridden by %r." % ModelWithFieldCalledCheck.check,
|
||||||
|
obj=ModelWithFieldCalledCheck,
|
||||||
|
id='models.E020'
|
||||||
|
),
|
||||||
Error(
|
Error(
|
||||||
"The 'ModelWithRelatedManagerCalledCheck.check()' class method is "
|
"The 'ModelWithRelatedManagerCalledCheck.check()' class method is "
|
||||||
"currently overridden by %r." % ModelWithRelatedManagerCalledCheck.check,
|
"currently overridden by %r." % ModelWithRelatedManagerCalledCheck.check,
|
||||||
|
|
|
@ -44,3 +44,16 @@ class RefreshPrimaryProxy(Primary):
|
||||||
if fields.intersection(deferred_fields):
|
if fields.intersection(deferred_fields):
|
||||||
fields = fields.union(deferred_fields)
|
fields = fields.union(deferred_fields)
|
||||||
super().refresh_from_db(using, fields, **kwargs)
|
super().refresh_from_db(using, fields, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class ShadowParent(models.Model):
|
||||||
|
"""
|
||||||
|
ShadowParent declares a scalar, rather than a field. When this is
|
||||||
|
overridden, the field value, rather than the scalar value must still be
|
||||||
|
used when the field is deferred.
|
||||||
|
"""
|
||||||
|
name = 'aphrodite'
|
||||||
|
|
||||||
|
|
||||||
|
class ShadowChild(ShadowParent):
|
||||||
|
name = models.CharField(default='adonis', max_length=6)
|
||||||
|
|
|
@ -3,6 +3,7 @@ from django.test import TestCase
|
||||||
|
|
||||||
from .models import (
|
from .models import (
|
||||||
BigChild, Child, ChildProxy, Primary, RefreshPrimaryProxy, Secondary,
|
BigChild, Child, ChildProxy, Primary, RefreshPrimaryProxy, Secondary,
|
||||||
|
ShadowChild,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -165,6 +166,11 @@ class DeferTests(AssertionMixin, TestCase):
|
||||||
self.assertEqual(obj.name, "c1")
|
self.assertEqual(obj.name, "c1")
|
||||||
self.assertEqual(obj.value, "foo")
|
self.assertEqual(obj.value, "foo")
|
||||||
|
|
||||||
|
def test_defer_of_overridden_scalar(self):
|
||||||
|
ShadowChild.objects.create()
|
||||||
|
obj = ShadowChild.objects.defer('name').get()
|
||||||
|
self.assertEqual(obj.name, 'adonis')
|
||||||
|
|
||||||
|
|
||||||
class BigChildDeferTests(AssertionMixin, TestCase):
|
class BigChildDeferTests(AssertionMixin, TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1212,9 +1212,8 @@ class OtherModelTests(SimpleTestCase):
|
||||||
class Model(models.Model):
|
class Model(models.Model):
|
||||||
fk = models.ForeignKey('self', models.CASCADE)
|
fk = models.ForeignKey('self', models.CASCADE)
|
||||||
|
|
||||||
@property
|
# Override related field accessor.
|
||||||
def fk_id(self):
|
Model.fk_id = property(lambda self: 'ERROR')
|
||||||
pass
|
|
||||||
|
|
||||||
self.assertEqual(Model.check(), [
|
self.assertEqual(Model.check(), [
|
||||||
Error(
|
Error(
|
||||||
|
|
|
@ -34,7 +34,12 @@ class AbstractInheritanceTests(SimpleTestCase):
|
||||||
self.assertEqual(DerivedChild._meta.get_field('name').max_length, 50)
|
self.assertEqual(DerivedChild._meta.get_field('name').max_length, 50)
|
||||||
self.assertEqual(DerivedGrandChild._meta.get_field('name').max_length, 50)
|
self.assertEqual(DerivedGrandChild._meta.get_field('name').max_length, 50)
|
||||||
|
|
||||||
def test_multiple_inheritance_cannot_shadow_inherited_field(self):
|
def test_multiple_inheritance_allows_inherited_field(self):
|
||||||
|
"""
|
||||||
|
Single layer multiple inheritance is as expected, deriving the
|
||||||
|
inherited field from the first base.
|
||||||
|
"""
|
||||||
|
|
||||||
class ParentA(models.Model):
|
class ParentA(models.Model):
|
||||||
name = models.CharField(max_length=255)
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
@ -50,14 +55,46 @@ class AbstractInheritanceTests(SimpleTestCase):
|
||||||
class Child(ParentA, ParentB):
|
class Child(ParentA, ParentB):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.assertEqual(Child.check(), [
|
self.assertEqual(Child.check(), [])
|
||||||
Error(
|
inherited_field = Child._meta.get_field('name')
|
||||||
"The field 'name' clashes with the field 'name' from model "
|
self.assertTrue(isinstance(inherited_field, models.CharField))
|
||||||
"'model_inheritance.child'.",
|
self.assertEqual(inherited_field.max_length, 255)
|
||||||
obj=Child._meta.get_field('name'),
|
|
||||||
id='models.E006',
|
def test_diamond_shaped_multiple_inheritance_is_depth_first(self):
|
||||||
),
|
"""
|
||||||
])
|
In contrast to standard Python MRO, resolution of inherited fields is
|
||||||
|
strictly depth-first, rather than breadth-first in diamond-shaped cases.
|
||||||
|
|
||||||
|
This is because a copy of the parent field descriptor is placed onto
|
||||||
|
the model class in ModelBase.__new__(), rather than the attribute
|
||||||
|
lookup going via bases. (It only **looks** like inheritance.)
|
||||||
|
|
||||||
|
Here, Child inherits name from Root, rather than ParentB.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Root(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
class ParentA(Root):
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
class ParentB(Root):
|
||||||
|
name = models.IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
|
||||||
|
class Child(ParentA, ParentB):
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.assertEqual(Child.check(), [])
|
||||||
|
inherited_field = Child._meta.get_field('name')
|
||||||
|
self.assertTrue(isinstance(inherited_field, models.CharField))
|
||||||
|
self.assertEqual(inherited_field.max_length, 255)
|
||||||
|
|
||||||
def test_target_field_may_be_pushed_down(self):
|
def test_target_field_may_be_pushed_down(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -2,6 +2,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.query_utils import DeferredAttribute
|
||||||
from django.test import SimpleTestCase, TestCase
|
from django.test import SimpleTestCase, TestCase
|
||||||
from django.test.utils import CaptureQueriesContext, isolate_apps
|
from django.test.utils import CaptureQueriesContext, isolate_apps
|
||||||
|
|
||||||
|
@ -222,6 +223,36 @@ class ModelInheritanceTests(TestCase):
|
||||||
self.assertIs(models.QuerySet[Post, Post], models.QuerySet)
|
self.assertIs(models.QuerySet[Post, Post], models.QuerySet)
|
||||||
self.assertIs(models.QuerySet[Post, int, str], models.QuerySet)
|
self.assertIs(models.QuerySet[Post, int, str], models.QuerySet)
|
||||||
|
|
||||||
|
def test_shadow_parent_attribute_with_field(self):
|
||||||
|
class ScalarParent(models.Model):
|
||||||
|
foo = 1
|
||||||
|
|
||||||
|
class ScalarOverride(ScalarParent):
|
||||||
|
foo = models.IntegerField()
|
||||||
|
|
||||||
|
self.assertEqual(type(ScalarOverride.foo), DeferredAttribute)
|
||||||
|
|
||||||
|
def test_shadow_parent_property_with_field(self):
|
||||||
|
class PropertyParent(models.Model):
|
||||||
|
@property
|
||||||
|
def foo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PropertyOverride(PropertyParent):
|
||||||
|
foo = models.IntegerField()
|
||||||
|
|
||||||
|
self.assertEqual(type(PropertyOverride.foo), DeferredAttribute)
|
||||||
|
|
||||||
|
def test_shadow_parent_method_with_field(self):
|
||||||
|
class MethodParent(models.Model):
|
||||||
|
def foo(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MethodOverride(MethodParent):
|
||||||
|
foo = models.IntegerField()
|
||||||
|
|
||||||
|
self.assertEqual(type(MethodOverride.foo), DeferredAttribute)
|
||||||
|
|
||||||
|
|
||||||
class ModelInheritanceDataTests(TestCase):
|
class ModelInheritanceDataTests(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
Loading…
Reference in New Issue