test_abstract_inheritance.py 13 KB

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