test_checks.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. from django.core import checks
  2. from django.db import connection, models
  3. from django.db.models import F
  4. from django.test import TestCase
  5. from django.test.utils import isolate_apps
  6. @isolate_apps("composite_pk")
  7. class CompositePKChecksTests(TestCase):
  8. maxDiff = None
  9. def test_composite_pk_must_be_unique_strings(self):
  10. test_cases = (
  11. (),
  12. (0,),
  13. (1,),
  14. ("id", False),
  15. ("id", "id"),
  16. (("id",),),
  17. )
  18. for i, args in enumerate(test_cases):
  19. with (
  20. self.subTest(args=args),
  21. self.assertRaisesMessage(
  22. ValueError, "CompositePrimaryKey args must be unique strings."
  23. ),
  24. ):
  25. models.CompositePrimaryKey(*args)
  26. def test_composite_pk_must_include_at_least_2_fields(self):
  27. expected_message = "CompositePrimaryKey must include at least two fields."
  28. with self.assertRaisesMessage(ValueError, expected_message):
  29. models.CompositePrimaryKey("id")
  30. def test_composite_pk_cannot_have_a_default(self):
  31. expected_message = "CompositePrimaryKey cannot have a default."
  32. with self.assertRaisesMessage(ValueError, expected_message):
  33. models.CompositePrimaryKey("tenant_id", "id", default=(1, 1))
  34. def test_composite_pk_cannot_have_a_database_default(self):
  35. expected_message = "CompositePrimaryKey cannot have a database default."
  36. with self.assertRaisesMessage(ValueError, expected_message):
  37. models.CompositePrimaryKey("tenant_id", "id", db_default=models.F("id"))
  38. def test_composite_pk_cannot_be_editable(self):
  39. expected_message = "CompositePrimaryKey cannot be editable."
  40. with self.assertRaisesMessage(ValueError, expected_message):
  41. models.CompositePrimaryKey("tenant_id", "id", editable=True)
  42. def test_composite_pk_must_be_a_primary_key(self):
  43. expected_message = "CompositePrimaryKey must be a primary key."
  44. with self.assertRaisesMessage(ValueError, expected_message):
  45. models.CompositePrimaryKey("tenant_id", "id", primary_key=False)
  46. def test_composite_pk_must_be_blank(self):
  47. expected_message = "CompositePrimaryKey must be blank."
  48. with self.assertRaisesMessage(ValueError, expected_message):
  49. models.CompositePrimaryKey("tenant_id", "id", blank=False)
  50. def test_composite_pk_must_not_have_other_pk_field(self):
  51. class Foo(models.Model):
  52. pk = models.CompositePrimaryKey("foo_id", "id")
  53. foo_id = models.IntegerField()
  54. id = models.IntegerField(primary_key=True)
  55. self.assertEqual(
  56. Foo.check(databases=self.databases),
  57. [
  58. checks.Error(
  59. "The model cannot have more than one field with "
  60. "'primary_key=True'.",
  61. obj=Foo,
  62. id="models.E026",
  63. ),
  64. ],
  65. )
  66. def test_composite_pk_cannot_include_nullable_field(self):
  67. class Foo(models.Model):
  68. pk = models.CompositePrimaryKey("foo_id", "id")
  69. foo_id = models.IntegerField()
  70. id = models.IntegerField(null=True)
  71. self.assertEqual(
  72. Foo.check(databases=self.databases),
  73. [
  74. checks.Error(
  75. "'id' cannot be included in the composite primary key.",
  76. hint="'id' field may not set 'null=True'.",
  77. obj=Foo,
  78. id="models.E042",
  79. ),
  80. ],
  81. )
  82. def test_composite_pk_can_include_fk_name(self):
  83. class Foo(models.Model):
  84. pass
  85. class Bar(models.Model):
  86. pk = models.CompositePrimaryKey("foo", "id")
  87. foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
  88. id = models.SmallIntegerField()
  89. self.assertEqual(Foo.check(databases=self.databases), [])
  90. self.assertEqual(Bar.check(databases=self.databases), [])
  91. def test_composite_pk_cannot_include_same_field(self):
  92. class Foo(models.Model):
  93. pass
  94. class Bar(models.Model):
  95. pk = models.CompositePrimaryKey("foo", "foo_id")
  96. foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
  97. id = models.SmallIntegerField()
  98. self.assertEqual(Foo.check(databases=self.databases), [])
  99. self.assertEqual(
  100. Bar.check(databases=self.databases),
  101. [
  102. checks.Error(
  103. "'foo_id' cannot be included in the composite primary key.",
  104. hint="'foo_id' and 'foo' are the same fields.",
  105. obj=Bar,
  106. id="models.E042",
  107. ),
  108. ],
  109. )
  110. def test_composite_pk_cannot_include_composite_pk_field(self):
  111. class Foo(models.Model):
  112. pk = models.CompositePrimaryKey("id", "pk")
  113. id = models.SmallIntegerField()
  114. self.assertEqual(
  115. Foo.check(databases=self.databases),
  116. [
  117. checks.Error(
  118. "'pk' cannot be included in the composite primary key.",
  119. hint="'pk' field has no column.",
  120. obj=Foo,
  121. id="models.E042",
  122. ),
  123. ],
  124. )
  125. def test_composite_pk_cannot_include_db_column(self):
  126. class Foo(models.Model):
  127. pk = models.CompositePrimaryKey("foo", "bar")
  128. foo = models.SmallIntegerField(db_column="foo_id")
  129. bar = models.SmallIntegerField(db_column="bar_id")
  130. class Bar(models.Model):
  131. pk = models.CompositePrimaryKey("foo_id", "bar_id")
  132. foo = models.SmallIntegerField(db_column="foo_id")
  133. bar = models.SmallIntegerField(db_column="bar_id")
  134. self.assertEqual(Foo.check(databases=self.databases), [])
  135. self.assertEqual(
  136. Bar.check(databases=self.databases),
  137. [
  138. checks.Error(
  139. "'foo_id' cannot be included in the composite primary key.",
  140. hint="'foo_id' is not a valid field.",
  141. obj=Bar,
  142. id="models.E042",
  143. ),
  144. checks.Error(
  145. "'bar_id' cannot be included in the composite primary key.",
  146. hint="'bar_id' is not a valid field.",
  147. obj=Bar,
  148. id="models.E042",
  149. ),
  150. ],
  151. )
  152. def test_foreign_object_can_refer_composite_pk(self):
  153. class Foo(models.Model):
  154. pass
  155. class Bar(models.Model):
  156. pk = models.CompositePrimaryKey("foo_id", "id")
  157. foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
  158. id = models.IntegerField()
  159. class Baz(models.Model):
  160. pk = models.CompositePrimaryKey("foo_id", "id")
  161. foo = models.ForeignKey(Foo, on_delete=models.CASCADE)
  162. id = models.IntegerField()
  163. bar_id = models.IntegerField()
  164. bar = models.ForeignObject(
  165. Bar,
  166. on_delete=models.CASCADE,
  167. from_fields=("foo_id", "bar_id"),
  168. to_fields=("foo_id", "id"),
  169. )
  170. self.assertEqual(Foo.check(databases=self.databases), [])
  171. self.assertEqual(Bar.check(databases=self.databases), [])
  172. self.assertEqual(Baz.check(databases=self.databases), [])
  173. def test_composite_pk_must_be_named_pk(self):
  174. class Foo(models.Model):
  175. primary_key = models.CompositePrimaryKey("foo_id", "id")
  176. foo_id = models.IntegerField()
  177. id = models.IntegerField()
  178. self.assertEqual(
  179. Foo.check(databases=self.databases),
  180. [
  181. checks.Error(
  182. "'CompositePrimaryKey' must be named 'pk'.",
  183. obj=Foo._meta.get_field("primary_key"),
  184. id="fields.E013",
  185. ),
  186. ],
  187. )
  188. def test_composite_pk_cannot_include_generated_field(self):
  189. is_oracle = connection.vendor == "oracle"
  190. class Foo(models.Model):
  191. pk = models.CompositePrimaryKey("id", "foo")
  192. id = models.IntegerField()
  193. foo = models.GeneratedField(
  194. expression=F("id"),
  195. output_field=models.IntegerField(),
  196. db_persist=not is_oracle,
  197. )
  198. self.assertEqual(
  199. Foo.check(databases=self.databases),
  200. [
  201. checks.Error(
  202. "'foo' cannot be included in the composite primary key.",
  203. hint="'foo' field is a generated field.",
  204. obj=Foo,
  205. id="models.E042",
  206. ),
  207. ],
  208. )