Fixed #30427, Fixed #16176 -- Corrected setting descriptor in Field.contribute_to_class().

Co-authored-by: Jarek Glowacki <jarekwg@gmail.com>
This commit is contained in:
Carlton Gibson 2021-06-09 16:55:22 +02:00
parent 0c0240aba8
commit 225d96533a
8 changed files with 112 additions and 17 deletions

View File

@ -782,11 +782,7 @@ 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 setattr(cls, self.attname, self.descriptor_class(self))
# 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))
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
# this class, but don't check methods derived from inheritance, to # this class, but don't check methods derived from inheritance, to

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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