1. from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
    
  2. from django.contrib.contenttypes.models import ContentType
    
  3. from django.core.checks import Error
    
  4. from django.core.exceptions import FieldDoesNotExist, FieldError
    
  5. from django.db import models
    
  6. from django.test import SimpleTestCase
    
  7. from django.test.utils import isolate_apps
    
  8. 
    
  9. 
    
  10. @isolate_apps("model_inheritance")
    
  11. class AbstractInheritanceTests(SimpleTestCase):
    
  12.     def test_single_parent(self):
    
  13.         class AbstractBase(models.Model):
    
  14.             name = models.CharField(max_length=30)
    
  15. 
    
  16.             class Meta:
    
  17.                 abstract = True
    
  18. 
    
  19.         class AbstractDescendant(AbstractBase):
    
  20.             name = models.CharField(max_length=50)
    
  21. 
    
  22.             class Meta:
    
  23.                 abstract = True
    
  24. 
    
  25.         class DerivedChild(AbstractBase):
    
  26.             name = models.CharField(max_length=50)
    
  27. 
    
  28.         class DerivedGrandChild(AbstractDescendant):
    
  29.             pass
    
  30. 
    
  31.         self.assertEqual(AbstractDescendant._meta.get_field("name").max_length, 50)
    
  32.         self.assertEqual(DerivedChild._meta.get_field("name").max_length, 50)
    
  33.         self.assertEqual(DerivedGrandChild._meta.get_field("name").max_length, 50)
    
  34. 
    
  35.     def test_multiple_inheritance_allows_inherited_field(self):
    
  36.         """
    
  37.         Single layer multiple inheritance is as expected, deriving the
    
  38.         inherited field from the first base.
    
  39.         """
    
  40. 
    
  41.         class ParentA(models.Model):
    
  42.             name = models.CharField(max_length=255)
    
  43. 
    
  44.             class Meta:
    
  45.                 abstract = True
    
  46. 
    
  47.         class ParentB(models.Model):
    
  48.             name = models.IntegerField()
    
  49. 
    
  50.             class Meta:
    
  51.                 abstract = True
    
  52. 
    
  53.         class Child(ParentA, ParentB):
    
  54.             pass
    
  55. 
    
  56.         self.assertEqual(Child.check(), [])
    
  57.         inherited_field = Child._meta.get_field("name")
    
  58.         self.assertIsInstance(inherited_field, models.CharField)
    
  59.         self.assertEqual(inherited_field.max_length, 255)
    
  60. 
    
  61.     def test_diamond_shaped_multiple_inheritance_is_depth_first(self):
    
  62.         """
    
  63.         In contrast to standard Python MRO, resolution of inherited fields is
    
  64.         strictly depth-first, rather than breadth-first in diamond-shaped cases.
    
  65. 
    
  66.         This is because a copy of the parent field descriptor is placed onto
    
  67.         the model class in ModelBase.__new__(), rather than the attribute
    
  68.         lookup going via bases. (It only **looks** like inheritance.)
    
  69. 
    
  70.         Here, Child inherits name from Root, rather than ParentB.
    
  71.         """
    
  72. 
    
  73.         class Root(models.Model):
    
  74.             name = models.CharField(max_length=255)
    
  75. 
    
  76.             class Meta:
    
  77.                 abstract = True
    
  78. 
    
  79.         class ParentA(Root):
    
  80.             class Meta:
    
  81.                 abstract = True
    
  82. 
    
  83.         class ParentB(Root):
    
  84.             name = models.IntegerField()
    
  85. 
    
  86.             class Meta:
    
  87.                 abstract = True
    
  88. 
    
  89.         class Child(ParentA, ParentB):
    
  90.             pass
    
  91. 
    
  92.         self.assertEqual(Child.check(), [])
    
  93.         inherited_field = Child._meta.get_field("name")
    
  94.         self.assertIsInstance(inherited_field, models.CharField)
    
  95.         self.assertEqual(inherited_field.max_length, 255)
    
  96. 
    
  97.     def test_target_field_may_be_pushed_down(self):
    
  98.         """
    
  99.         Where the Child model needs to inherit a field from a different base
    
  100.         than that given by depth-first resolution, the target field can be
    
  101.         **pushed down** by being re-declared.
    
  102.         """
    
  103. 
    
  104.         class Root(models.Model):
    
  105.             name = models.CharField(max_length=255)
    
  106. 
    
  107.             class Meta:
    
  108.                 abstract = True
    
  109. 
    
  110.         class ParentA(Root):
    
  111.             class Meta:
    
  112.                 abstract = True
    
  113. 
    
  114.         class ParentB(Root):
    
  115.             name = models.IntegerField()
    
  116. 
    
  117.             class Meta:
    
  118.                 abstract = True
    
  119. 
    
  120.         class Child(ParentA, ParentB):
    
  121.             name = models.IntegerField()
    
  122. 
    
  123.         self.assertEqual(Child.check(), [])
    
  124.         inherited_field = Child._meta.get_field("name")
    
  125.         self.assertIsInstance(inherited_field, models.IntegerField)
    
  126. 
    
  127.     def test_multiple_inheritance_cannot_shadow_concrete_inherited_field(self):
    
  128.         class ConcreteParent(models.Model):
    
  129.             name = models.CharField(max_length=255)
    
  130. 
    
  131.         class AbstractParent(models.Model):
    
  132.             name = models.IntegerField()
    
  133. 
    
  134.             class Meta:
    
  135.                 abstract = True
    
  136. 
    
  137.         class FirstChild(ConcreteParent, AbstractParent):
    
  138.             pass
    
  139. 
    
  140.         class AnotherChild(AbstractParent, ConcreteParent):
    
  141.             pass
    
  142. 
    
  143.         self.assertIsInstance(FirstChild._meta.get_field("name"), models.CharField)
    
  144.         self.assertEqual(
    
  145.             AnotherChild.check(),
    
  146.             [
    
  147.                 Error(
    
  148.                     "The field 'name' clashes with the field 'name' "
    
  149.                     "from model 'model_inheritance.concreteparent'.",
    
  150.                     obj=AnotherChild._meta.get_field("name"),
    
  151.                     id="models.E006",
    
  152.                 )
    
  153.             ],
    
  154.         )
    
  155. 
    
  156.     def test_virtual_field(self):
    
  157.         class RelationModel(models.Model):
    
  158.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  159.             object_id = models.PositiveIntegerField()
    
  160.             content_object = GenericForeignKey("content_type", "object_id")
    
  161. 
    
  162.         class RelatedModelAbstract(models.Model):
    
  163.             field = GenericRelation(RelationModel)
    
  164. 
    
  165.             class Meta:
    
  166.                 abstract = True
    
  167. 
    
  168.         class ModelAbstract(models.Model):
    
  169.             field = models.CharField(max_length=100)
    
  170. 
    
  171.             class Meta:
    
  172.                 abstract = True
    
  173. 
    
  174.         class OverrideRelatedModelAbstract(RelatedModelAbstract):
    
  175.             field = models.CharField(max_length=100)
    
  176. 
    
  177.         class ExtendModelAbstract(ModelAbstract):
    
  178.             field = GenericRelation(RelationModel)
    
  179. 
    
  180.         self.assertIsInstance(
    
  181.             OverrideRelatedModelAbstract._meta.get_field("field"), models.CharField
    
  182.         )
    
  183.         self.assertIsInstance(
    
  184.             ExtendModelAbstract._meta.get_field("field"), GenericRelation
    
  185.         )
    
  186. 
    
  187.     def test_cannot_override_indirect_abstract_field(self):
    
  188.         class AbstractBase(models.Model):
    
  189.             name = models.CharField(max_length=30)
    
  190. 
    
  191.             class Meta:
    
  192.                 abstract = True
    
  193. 
    
  194.         class ConcreteDescendant(AbstractBase):
    
  195.             pass
    
  196. 
    
  197.         msg = (
    
  198.             "Local field 'name' in class 'Descendant' clashes with field of "
    
  199.             "the same name from base class 'ConcreteDescendant'."
    
  200.         )
    
  201.         with self.assertRaisesMessage(FieldError, msg):
    
  202. 
    
  203.             class Descendant(ConcreteDescendant):
    
  204.                 name = models.IntegerField()
    
  205. 
    
  206.     def test_override_field_with_attr(self):
    
  207.         class AbstractBase(models.Model):
    
  208.             first_name = models.CharField(max_length=50)
    
  209.             last_name = models.CharField(max_length=50)
    
  210.             middle_name = models.CharField(max_length=30)
    
  211.             full_name = models.CharField(max_length=150)
    
  212. 
    
  213.             class Meta:
    
  214.                 abstract = True
    
  215. 
    
  216.         class Descendant(AbstractBase):
    
  217.             middle_name = None
    
  218. 
    
  219.             def full_name(self):
    
  220.                 return self.first_name + self.last_name
    
  221. 
    
  222.         msg = "Descendant has no field named %r"
    
  223.         with self.assertRaisesMessage(FieldDoesNotExist, msg % "middle_name"):
    
  224.             Descendant._meta.get_field("middle_name")
    
  225. 
    
  226.         with self.assertRaisesMessage(FieldDoesNotExist, msg % "full_name"):
    
  227.             Descendant._meta.get_field("full_name")
    
  228. 
    
  229.     def test_overriding_field_removed_by_concrete_model(self):
    
  230.         class AbstractModel(models.Model):
    
  231.             foo = models.CharField(max_length=30)
    
  232. 
    
  233.             class Meta:
    
  234.                 abstract = True
    
  235. 
    
  236.         class RemovedAbstractModelField(AbstractModel):
    
  237.             foo = None
    
  238. 
    
  239.         class OverrideRemovedFieldByConcreteModel(RemovedAbstractModelField):
    
  240.             foo = models.CharField(max_length=50)
    
  241. 
    
  242.         self.assertEqual(
    
  243.             OverrideRemovedFieldByConcreteModel._meta.get_field("foo").max_length, 50
    
  244.         )
    
  245. 
    
  246.     def test_shadowed_fkey_id(self):
    
  247.         class Foo(models.Model):
    
  248.             pass
    
  249. 
    
  250.         class AbstractBase(models.Model):
    
  251.             foo = models.ForeignKey(Foo, models.CASCADE)
    
  252. 
    
  253.             class Meta:
    
  254.                 abstract = True
    
  255. 
    
  256.         class Descendant(AbstractBase):
    
  257.             foo_id = models.IntegerField()
    
  258. 
    
  259.         self.assertEqual(
    
  260.             Descendant.check(),
    
  261.             [
    
  262.                 Error(
    
  263.                     "The field 'foo_id' clashes with the field 'foo' "
    
  264.                     "from model 'model_inheritance.descendant'.",
    
  265.                     obj=Descendant._meta.get_field("foo_id"),
    
  266.                     id="models.E006",
    
  267.                 )
    
  268.             ],
    
  269.         )
    
  270. 
    
  271.     def test_shadow_related_name_when_set_to_none(self):
    
  272.         class AbstractBase(models.Model):
    
  273.             bar = models.IntegerField()
    
  274. 
    
  275.             class Meta:
    
  276.                 abstract = True
    
  277. 
    
  278.         class Foo(AbstractBase):
    
  279.             bar = None
    
  280.             foo = models.IntegerField()
    
  281. 
    
  282.         class Bar(models.Model):
    
  283.             bar = models.ForeignKey(Foo, models.CASCADE, related_name="bar")
    
  284. 
    
  285.         self.assertEqual(Bar.check(), [])
    
  286. 
    
  287.     def test_reverse_foreign_key(self):
    
  288.         class AbstractBase(models.Model):
    
  289.             foo = models.CharField(max_length=100)
    
  290. 
    
  291.             class Meta:
    
  292.                 abstract = True
    
  293. 
    
  294.         class Descendant(AbstractBase):
    
  295.             pass
    
  296. 
    
  297.         class Foo(models.Model):
    
  298.             foo = models.ForeignKey(Descendant, models.CASCADE, related_name="foo")
    
  299. 
    
  300.         self.assertEqual(
    
  301.             Foo._meta.get_field("foo").check(),
    
  302.             [
    
  303.                 Error(
    
  304.                     "Reverse accessor 'Descendant.foo' for "
    
  305.                     "'model_inheritance.Foo.foo' clashes with field name "
    
  306.                     "'model_inheritance.Descendant.foo'.",
    
  307.                     hint=(
    
  308.                         "Rename field 'model_inheritance.Descendant.foo', or "
    
  309.                         "add/change a related_name argument to the definition "
    
  310.                         "for field 'model_inheritance.Foo.foo'."
    
  311.                     ),
    
  312.                     obj=Foo._meta.get_field("foo"),
    
  313.                     id="fields.E302",
    
  314.                 ),
    
  315.                 Error(
    
  316.                     "Reverse query name for 'model_inheritance.Foo.foo' "
    
  317.                     "clashes with field name "
    
  318.                     "'model_inheritance.Descendant.foo'.",
    
  319.                     hint=(
    
  320.                         "Rename field 'model_inheritance.Descendant.foo', or "
    
  321.                         "add/change a related_name argument to the definition "
    
  322.                         "for field 'model_inheritance.Foo.foo'."
    
  323.                     ),
    
  324.                     obj=Foo._meta.get_field("foo"),
    
  325.                     id="fields.E303",
    
  326.                 ),
    
  327.             ],
    
  328.         )
    
  329. 
    
  330.     def test_multi_inheritance_field_clashes(self):
    
  331.         class AbstractBase(models.Model):
    
  332.             name = models.CharField(max_length=30)
    
  333. 
    
  334.             class Meta:
    
  335.                 abstract = True
    
  336. 
    
  337.         class ConcreteBase(AbstractBase):
    
  338.             pass
    
  339. 
    
  340.         class AbstractDescendant(ConcreteBase):
    
  341.             class Meta:
    
  342.                 abstract = True
    
  343. 
    
  344.         class ConcreteDescendant(AbstractDescendant):
    
  345.             name = models.CharField(max_length=100)
    
  346. 
    
  347.         self.assertEqual(
    
  348.             ConcreteDescendant.check(),
    
  349.             [
    
  350.                 Error(
    
  351.                     "The field 'name' clashes with the field 'name' from "
    
  352.                     "model 'model_inheritance.concretebase'.",
    
  353.                     obj=ConcreteDescendant._meta.get_field("name"),
    
  354.                     id="models.E006",
    
  355.                 )
    
  356.             ],
    
  357.         )
    
  358. 
    
  359.     def test_override_one2one_relation_auto_field_clashes(self):
    
  360.         class ConcreteParent(models.Model):
    
  361.             name = models.CharField(max_length=255)
    
  362. 
    
  363.         class AbstractParent(models.Model):
    
  364.             name = models.IntegerField()
    
  365. 
    
  366.             class Meta:
    
  367.                 abstract = True
    
  368. 
    
  369.         msg = (
    
  370.             "Auto-generated field 'concreteparent_ptr' in class 'Descendant' "
    
  371.             "for parent_link to base class 'ConcreteParent' clashes with "
    
  372.             "declared field of the same name."
    
  373.         )
    
  374.         with self.assertRaisesMessage(FieldError, msg):
    
  375. 
    
  376.             class Descendant(ConcreteParent, AbstractParent):
    
  377.                 concreteparent_ptr = models.CharField(max_length=30)
    
  378. 
    
  379.     def test_abstract_model_with_regular_python_mixin_mro(self):
    
  380.         class AbstractModel(models.Model):
    
  381.             name = models.CharField(max_length=255)
    
  382.             age = models.IntegerField()
    
  383. 
    
  384.             class Meta:
    
  385.                 abstract = True
    
  386. 
    
  387.         class Mixin:
    
  388.             age = None
    
  389. 
    
  390.         class Mixin2:
    
  391.             age = 2
    
  392. 
    
  393.         class DescendantMixin(Mixin):
    
  394.             pass
    
  395. 
    
  396.         class ConcreteModel(models.Model):
    
  397.             foo = models.IntegerField()
    
  398. 
    
  399.         class ConcreteModel2(ConcreteModel):
    
  400.             age = models.SmallIntegerField()
    
  401. 
    
  402.         def fields(model):
    
  403.             if not hasattr(model, "_meta"):
    
  404.                 return []
    
  405.             return [(f.name, f.__class__) for f in model._meta.get_fields()]
    
  406. 
    
  407.         model_dict = {"__module__": "model_inheritance"}
    
  408.         model1 = type("Model1", (AbstractModel, Mixin), model_dict.copy())
    
  409.         model2 = type("Model2", (Mixin2, AbstractModel), model_dict.copy())
    
  410.         model3 = type("Model3", (DescendantMixin, AbstractModel), model_dict.copy())
    
  411.         model4 = type("Model4", (Mixin2, Mixin, AbstractModel), model_dict.copy())
    
  412.         model5 = type(
    
  413.             "Model5", (Mixin2, ConcreteModel2, Mixin, AbstractModel), model_dict.copy()
    
  414.         )
    
  415. 
    
  416.         self.assertEqual(
    
  417.             fields(model1),
    
  418.             [
    
  419.                 ("id", models.AutoField),
    
  420.                 ("name", models.CharField),
    
  421.                 ("age", models.IntegerField),
    
  422.             ],
    
  423.         )
    
  424. 
    
  425.         self.assertEqual(
    
  426.             fields(model2), [("id", models.AutoField), ("name", models.CharField)]
    
  427.         )
    
  428.         self.assertEqual(getattr(model2, "age"), 2)
    
  429. 
    
  430.         self.assertEqual(
    
  431.             fields(model3), [("id", models.AutoField), ("name", models.CharField)]
    
  432.         )
    
  433. 
    
  434.         self.assertEqual(
    
  435.             fields(model4), [("id", models.AutoField), ("name", models.CharField)]
    
  436.         )
    
  437.         self.assertEqual(getattr(model4, "age"), 2)
    
  438. 
    
  439.         self.assertEqual(
    
  440.             fields(model5),
    
  441.             [
    
  442.                 ("id", models.AutoField),
    
  443.                 ("foo", models.IntegerField),
    
  444.                 ("concretemodel_ptr", models.OneToOneField),
    
  445.                 ("age", models.SmallIntegerField),
    
  446.                 ("concretemodel2_ptr", models.OneToOneField),
    
  447.                 ("name", models.CharField),
    
  448.             ],
    
  449.         )