test_abstract_inheritance.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. from __future__ import unicode_literals
  2. from django.contrib.contenttypes.fields import (
  3. GenericForeignKey, GenericRelation,
  4. )
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.core.checks import Error
  7. from django.core.exceptions import FieldDoesNotExist, FieldError
  8. from django.db import models
  9. from django.test import TestCase
  10. from django.test.utils import isolate_apps
  11. @isolate_apps('model_inheritance')
  12. class AbstractInheritanceTests(TestCase):
  13. def test_single_parent(self):
  14. class AbstractBase(models.Model):
  15. name = models.CharField(max_length=30)
  16. class Meta:
  17. abstract = True
  18. class AbstractDescendant(AbstractBase):
  19. name = models.CharField(max_length=50)
  20. class Meta:
  21. abstract = True
  22. class DerivedChild(AbstractBase):
  23. name = models.CharField(max_length=50)
  24. class DerivedGrandChild(AbstractDescendant):
  25. pass
  26. self.assertEqual(AbstractDescendant._meta.get_field('name').max_length, 50)
  27. self.assertEqual(DerivedChild._meta.get_field('name').max_length, 50)
  28. self.assertEqual(DerivedGrandChild._meta.get_field('name').max_length, 50)
  29. def test_multiple_parents_mro(self):
  30. class AbstractBaseOne(models.Model):
  31. class Meta:
  32. abstract = True
  33. class AbstractBaseTwo(models.Model):
  34. name = models.CharField(max_length=30)
  35. class Meta:
  36. abstract = True
  37. class DescendantOne(AbstractBaseOne, AbstractBaseTwo):
  38. class Meta:
  39. abstract = True
  40. class DescendantTwo(AbstractBaseOne, AbstractBaseTwo):
  41. name = models.CharField(max_length=50)
  42. class Meta:
  43. abstract = True
  44. class Derived(DescendantOne, DescendantTwo):
  45. pass
  46. self.assertEqual(DescendantOne._meta.get_field('name').max_length, 30)
  47. self.assertEqual(DescendantTwo._meta.get_field('name').max_length, 50)
  48. self.assertEqual(Derived._meta.get_field('name').max_length, 50)
  49. def test_multiple_inheritance_cannot_shadow_concrete_inherited_field(self):
  50. class ConcreteParent(models.Model):
  51. name = models.CharField(max_length=255)
  52. class AbstractParent(models.Model):
  53. name = models.IntegerField()
  54. class Meta:
  55. abstract = True
  56. class FirstChild(ConcreteParent, AbstractParent):
  57. pass
  58. class AnotherChild(AbstractParent, ConcreteParent):
  59. pass
  60. self.assertIsInstance(FirstChild._meta.get_field('name'), models.CharField)
  61. self.assertEqual(
  62. AnotherChild.check(),
  63. [Error(
  64. "The field 'name' clashes with the field 'name' "
  65. "from model 'model_inheritance.concreteparent'.",
  66. obj=AnotherChild._meta.get_field('name'),
  67. id="models.E006",
  68. )]
  69. )
  70. def test_virtual_field(self):
  71. class RelationModel(models.Model):
  72. content_type = models.ForeignKey(ContentType, models.CASCADE)
  73. object_id = models.PositiveIntegerField()
  74. content_object = GenericForeignKey('content_type', 'object_id')
  75. class RelatedModelAbstract(models.Model):
  76. field = GenericRelation(RelationModel)
  77. class Meta:
  78. abstract = True
  79. class ModelAbstract(models.Model):
  80. field = models.CharField(max_length=100)
  81. class Meta:
  82. abstract = True
  83. class OverrideRelatedModelAbstract(RelatedModelAbstract):
  84. field = models.CharField(max_length=100)
  85. class ExtendModelAbstract(ModelAbstract):
  86. field = GenericRelation(RelationModel)
  87. self.assertIsInstance(OverrideRelatedModelAbstract._meta.get_field('field'), models.CharField)
  88. self.assertIsInstance(ExtendModelAbstract._meta.get_field('field'), GenericRelation)
  89. def test_cannot_override_indirect_abstract_field(self):
  90. class AbstractBase(models.Model):
  91. name = models.CharField(max_length=30)
  92. class Meta:
  93. abstract = True
  94. class ConcreteDescendant(AbstractBase):
  95. pass
  96. msg = (
  97. "Local field 'name' in class 'Descendant' clashes with field of "
  98. "the same name from base class 'ConcreteDescendant'."
  99. )
  100. with self.assertRaisesMessage(FieldError, msg):
  101. class Descendant(ConcreteDescendant):
  102. name = models.IntegerField()
  103. def test_override_field_with_attr(self):
  104. class AbstractBase(models.Model):
  105. first_name = models.CharField(max_length=50)
  106. last_name = models.CharField(max_length=50)
  107. middle_name = models.CharField(max_length=30)
  108. full_name = models.CharField(max_length=150)
  109. class Meta:
  110. abstract = True
  111. class Descendant(AbstractBase):
  112. middle_name = None
  113. def full_name(self):
  114. return self.first_name + self.last_name
  115. with self.assertRaises(FieldDoesNotExist):
  116. Descendant._meta.get_field('middle_name')
  117. with self.assertRaises(FieldDoesNotExist):
  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(object):
  233. age = None
  234. class Mixin2(object):
  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 list()
  245. return list((f.name, f.__class__) for f in model._meta.get_fields())
  246. model_dict = {'__module__': 'model_inheritance'}
  247. model1 = type(str('Model1'), (AbstractModel, Mixin), model_dict.copy())
  248. model2 = type(str('Model2'), (Mixin2, AbstractModel), model_dict.copy())
  249. model3 = type(str('Model3'), (DescendantMixin, AbstractModel), model_dict.copy())
  250. model4 = type(str('Model4'), (Mixin2, Mixin, AbstractModel), model_dict.copy())
  251. model5 = type(str('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. )