test_abstract_inheritance.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. from django.contrib.contenttypes.fields import (
  2. GenericForeignKey, GenericRelation,
  3. )
  4. from django.contrib.contenttypes.models import ContentType
  5. from django.core.checks import Error
  6. from django.core.exceptions import FieldDoesNotExist, FieldError
  7. from django.db import models
  8. from django.test import SimpleTestCase
  9. from django.test.utils import isolate_apps
  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. class Meta:
  16. abstract = True
  17. class AbstractDescendant(AbstractBase):
  18. name = models.CharField(max_length=50)
  19. class Meta:
  20. abstract = True
  21. class DerivedChild(AbstractBase):
  22. name = models.CharField(max_length=50)
  23. class DerivedGrandChild(AbstractDescendant):
  24. pass
  25. self.assertEqual(AbstractDescendant._meta.get_field('name').max_length, 50)
  26. self.assertEqual(DerivedChild._meta.get_field('name').max_length, 50)
  27. self.assertEqual(DerivedGrandChild._meta.get_field('name').max_length, 50)
  28. def test_multiple_inheritance_allows_inherited_field(self):
  29. """
  30. Single layer multiple inheritance is as expected, deriving the
  31. inherited field from the first base.
  32. """
  33. class ParentA(models.Model):
  34. name = models.CharField(max_length=255)
  35. class Meta:
  36. abstract = True
  37. class ParentB(models.Model):
  38. name = models.IntegerField()
  39. class Meta:
  40. abstract = True
  41. class Child(ParentA, ParentB):
  42. pass
  43. self.assertEqual(Child.check(), [])
  44. inherited_field = Child._meta.get_field('name')
  45. self.assertTrue(isinstance(inherited_field, models.CharField))
  46. self.assertEqual(inherited_field.max_length, 255)
  47. def test_diamond_shaped_multiple_inheritance_is_depth_first(self):
  48. """
  49. In contrast to standard Python MRO, resolution of inherited fields is
  50. strictly depth-first, rather than breadth-first in diamond-shaped cases.
  51. This is because a copy of the parent field descriptor is placed onto
  52. the model class in ModelBase.__new__(), rather than the attribute
  53. lookup going via bases. (It only **looks** like inheritance.)
  54. Here, Child inherits name from Root, rather than ParentB.
  55. """
  56. class Root(models.Model):
  57. name = models.CharField(max_length=255)
  58. class Meta:
  59. abstract = True
  60. class ParentA(Root):
  61. class Meta:
  62. abstract = True
  63. class ParentB(Root):
  64. name = models.IntegerField()
  65. class Meta:
  66. abstract = True
  67. class Child(ParentA, ParentB):
  68. pass
  69. self.assertEqual(Child.check(), [])
  70. inherited_field = Child._meta.get_field('name')
  71. self.assertTrue(isinstance(inherited_field, models.CharField))
  72. self.assertEqual(inherited_field.max_length, 255)
  73. def test_target_field_may_be_pushed_down(self):
  74. """
  75. Where the Child model needs to inherit a field from a different base
  76. than that given by depth-first resolution, the target field can be
  77. **pushed down** by being re-declared.
  78. """
  79. class Root(models.Model):
  80. name = models.CharField(max_length=255)
  81. class Meta:
  82. abstract = True
  83. class ParentA(Root):
  84. class Meta:
  85. abstract = True
  86. class ParentB(Root):
  87. name = models.IntegerField()
  88. class Meta:
  89. abstract = True
  90. class Child(ParentA, ParentB):
  91. name = models.IntegerField()
  92. self.assertEqual(Child.check(), [])
  93. inherited_field = Child._meta.get_field('name')
  94. self.assertTrue(isinstance(inherited_field, models.IntegerField))
  95. def test_multiple_inheritance_cannot_shadow_concrete_inherited_field(self):
  96. class ConcreteParent(models.Model):
  97. name = models.CharField(max_length=255)
  98. class AbstractParent(models.Model):
  99. name = models.IntegerField()
  100. class Meta:
  101. abstract = True
  102. class FirstChild(ConcreteParent, AbstractParent):
  103. pass
  104. class AnotherChild(AbstractParent, ConcreteParent):
  105. pass
  106. self.assertIsInstance(FirstChild._meta.get_field('name'), models.CharField)
  107. self.assertEqual(
  108. AnotherChild.check(),
  109. [Error(
  110. "The field 'name' clashes with the field 'name' "
  111. "from model 'model_inheritance.concreteparent'.",
  112. obj=AnotherChild._meta.get_field('name'),
  113. id="models.E006",
  114. )]
  115. )
  116. def test_virtual_field(self):
  117. class RelationModel(models.Model):
  118. content_type = models.ForeignKey(ContentType, models.CASCADE)
  119. object_id = models.PositiveIntegerField()
  120. content_object = GenericForeignKey('content_type', 'object_id')
  121. class RelatedModelAbstract(models.Model):
  122. field = GenericRelation(RelationModel)
  123. class Meta:
  124. abstract = True
  125. class ModelAbstract(models.Model):
  126. field = models.CharField(max_length=100)
  127. class Meta:
  128. abstract = True
  129. class OverrideRelatedModelAbstract(RelatedModelAbstract):
  130. field = models.CharField(max_length=100)
  131. class ExtendModelAbstract(ModelAbstract):
  132. field = GenericRelation(RelationModel)
  133. self.assertIsInstance(OverrideRelatedModelAbstract._meta.get_field('field'), models.CharField)
  134. self.assertIsInstance(ExtendModelAbstract._meta.get_field('field'), GenericRelation)
  135. def test_cannot_override_indirect_abstract_field(self):
  136. class AbstractBase(models.Model):
  137. name = models.CharField(max_length=30)
  138. class Meta:
  139. abstract = True
  140. class ConcreteDescendant(AbstractBase):
  141. pass
  142. msg = (
  143. "Local field 'name' in class 'Descendant' clashes with field of "
  144. "the same name from base class 'ConcreteDescendant'."
  145. )
  146. with self.assertRaisesMessage(FieldError, msg):
  147. class Descendant(ConcreteDescendant):
  148. name = models.IntegerField()
  149. def test_override_field_with_attr(self):
  150. class AbstractBase(models.Model):
  151. first_name = models.CharField(max_length=50)
  152. last_name = models.CharField(max_length=50)
  153. middle_name = models.CharField(max_length=30)
  154. full_name = models.CharField(max_length=150)
  155. class Meta:
  156. abstract = True
  157. class Descendant(AbstractBase):
  158. middle_name = None
  159. def full_name(self):
  160. return self.first_name + self.last_name
  161. msg = "Descendant has no field named %r"
  162. with self.assertRaisesMessage(FieldDoesNotExist, msg % 'middle_name'):
  163. Descendant._meta.get_field('middle_name')
  164. with self.assertRaisesMessage(FieldDoesNotExist, msg % 'full_name'):
  165. Descendant._meta.get_field('full_name')
  166. def test_overriding_field_removed_by_concrete_model(self):
  167. class AbstractModel(models.Model):
  168. foo = models.CharField(max_length=30)
  169. class Meta:
  170. abstract = True
  171. class RemovedAbstractModelField(AbstractModel):
  172. foo = None
  173. class OverrideRemovedFieldByConcreteModel(RemovedAbstractModelField):
  174. foo = models.CharField(max_length=50)
  175. self.assertEqual(OverrideRemovedFieldByConcreteModel._meta.get_field('foo').max_length, 50)
  176. def test_shadowed_fkey_id(self):
  177. class Foo(models.Model):
  178. pass
  179. class AbstractBase(models.Model):
  180. foo = models.ForeignKey(Foo, models.CASCADE)
  181. class Meta:
  182. abstract = True
  183. class Descendant(AbstractBase):
  184. foo_id = models.IntegerField()
  185. self.assertEqual(
  186. Descendant.check(),
  187. [Error(
  188. "The field 'foo_id' clashes with the field 'foo' "
  189. "from model 'model_inheritance.descendant'.",
  190. obj=Descendant._meta.get_field('foo_id'),
  191. id='models.E006',
  192. )]
  193. )
  194. def test_shadow_related_name_when_set_to_none(self):
  195. class AbstractBase(models.Model):
  196. bar = models.IntegerField()
  197. class Meta:
  198. abstract = True
  199. class Foo(AbstractBase):
  200. bar = None
  201. foo = models.IntegerField()
  202. class Bar(models.Model):
  203. bar = models.ForeignKey(Foo, models.CASCADE, related_name='bar')
  204. self.assertEqual(Bar.check(), [])
  205. def test_reverse_foreign_key(self):
  206. class AbstractBase(models.Model):
  207. foo = models.CharField(max_length=100)
  208. class Meta:
  209. abstract = True
  210. class Descendant(AbstractBase):
  211. pass
  212. class Foo(models.Model):
  213. foo = models.ForeignKey(Descendant, models.CASCADE, related_name='foo')
  214. self.assertEqual(
  215. Foo._meta.get_field('foo').check(),
  216. [
  217. Error(
  218. "Reverse accessor for 'model_inheritance.Foo.foo' clashes "
  219. "with field name 'model_inheritance.Descendant.foo'.",
  220. hint=(
  221. "Rename field 'model_inheritance.Descendant.foo', or "
  222. "add/change a related_name argument to the definition "
  223. "for field 'model_inheritance.Foo.foo'."
  224. ),
  225. obj=Foo._meta.get_field('foo'),
  226. id='fields.E302',
  227. ),
  228. Error(
  229. "Reverse query name for 'model_inheritance.Foo.foo' "
  230. "clashes with field name "
  231. "'model_inheritance.Descendant.foo'.",
  232. hint=(
  233. "Rename field 'model_inheritance.Descendant.foo', or "
  234. "add/change a related_name argument to the definition "
  235. "for field 'model_inheritance.Foo.foo'."
  236. ),
  237. obj=Foo._meta.get_field('foo'),
  238. id='fields.E303',
  239. ),
  240. ]
  241. )
  242. def test_multi_inheritance_field_clashes(self):
  243. class AbstractBase(models.Model):
  244. name = models.CharField(max_length=30)
  245. class Meta:
  246. abstract = True
  247. class ConcreteBase(AbstractBase):
  248. pass
  249. class AbstractDescendant(ConcreteBase):
  250. class Meta:
  251. abstract = True
  252. class ConcreteDescendant(AbstractDescendant):
  253. name = models.CharField(max_length=100)
  254. self.assertEqual(
  255. ConcreteDescendant.check(),
  256. [Error(
  257. "The field 'name' clashes with the field 'name' from "
  258. "model 'model_inheritance.concretebase'.",
  259. obj=ConcreteDescendant._meta.get_field('name'),
  260. id="models.E006",
  261. )]
  262. )
  263. def test_override_one2one_relation_auto_field_clashes(self):
  264. class ConcreteParent(models.Model):
  265. name = models.CharField(max_length=255)
  266. class AbstractParent(models.Model):
  267. name = models.IntegerField()
  268. class Meta:
  269. abstract = True
  270. msg = (
  271. "Auto-generated field 'concreteparent_ptr' in class 'Descendant' "
  272. "for parent_link to base class 'ConcreteParent' clashes with "
  273. "declared field of the same name."
  274. )
  275. with self.assertRaisesMessage(FieldError, msg):
  276. class Descendant(ConcreteParent, AbstractParent):
  277. concreteparent_ptr = models.CharField(max_length=30)
  278. def test_abstract_model_with_regular_python_mixin_mro(self):
  279. class AbstractModel(models.Model):
  280. name = models.CharField(max_length=255)
  281. age = models.IntegerField()
  282. class Meta:
  283. abstract = True
  284. class Mixin:
  285. age = None
  286. class Mixin2:
  287. age = 2
  288. class DescendantMixin(Mixin):
  289. pass
  290. class ConcreteModel(models.Model):
  291. foo = models.IntegerField()
  292. class ConcreteModel2(ConcreteModel):
  293. age = models.SmallIntegerField()
  294. def fields(model):
  295. if not hasattr(model, '_meta'):
  296. return []
  297. return [(f.name, f.__class__) for f in model._meta.get_fields()]
  298. model_dict = {'__module__': 'model_inheritance'}
  299. model1 = type('Model1', (AbstractModel, Mixin), model_dict.copy())
  300. model2 = type('Model2', (Mixin2, AbstractModel), model_dict.copy())
  301. model3 = type('Model3', (DescendantMixin, AbstractModel), model_dict.copy())
  302. model4 = type('Model4', (Mixin2, Mixin, AbstractModel), model_dict.copy())
  303. model5 = type('Model5', (Mixin2, ConcreteModel2, Mixin, AbstractModel), model_dict.copy())
  304. self.assertEqual(
  305. fields(model1),
  306. [('id', models.AutoField), ('name', models.CharField), ('age', models.IntegerField)]
  307. )
  308. self.assertEqual(fields(model2), [('id', models.AutoField), ('name', models.CharField)])
  309. self.assertEqual(getattr(model2, 'age'), 2)
  310. self.assertEqual(fields(model3), [('id', models.AutoField), ('name', models.CharField)])
  311. self.assertEqual(fields(model4), [('id', models.AutoField), ('name', models.CharField)])
  312. self.assertEqual(getattr(model4, 'age'), 2)
  313. self.assertEqual(
  314. fields(model5),
  315. [
  316. ('id', models.AutoField), ('foo', models.IntegerField),
  317. ('concretemodel_ptr', models.OneToOneField),
  318. ('age', models.SmallIntegerField), ('concretemodel2_ptr', models.OneToOneField),
  319. ('name', models.CharField),
  320. ]
  321. )