test_abstract_inheritance.py 12 KB

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