1. import unittest
    
  2. 
    
  3. from django.core.checks import Error, Warning
    
  4. from django.core.checks.model_checks import _check_lazy_references
    
  5. from django.db import connection, connections, models
    
  6. from django.db.models.functions import Abs, Lower, Round
    
  7. from django.db.models.signals import post_init
    
  8. from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
    
  9. from django.test.utils import isolate_apps, override_settings, register_lookup
    
  10. 
    
  11. 
    
  12. class EmptyRouter:
    
  13.     pass
    
  14. 
    
  15. 
    
  16. def get_max_column_name_length():
    
  17.     allowed_len = None
    
  18.     db_alias = None
    
  19. 
    
  20.     for db in ("default", "other"):
    
  21.         connection = connections[db]
    
  22.         max_name_length = connection.ops.max_name_length()
    
  23.         if max_name_length is not None and not connection.features.truncates_names:
    
  24.             if allowed_len is None or max_name_length < allowed_len:
    
  25.                 allowed_len = max_name_length
    
  26.                 db_alias = db
    
  27. 
    
  28.     return (allowed_len, db_alias)
    
  29. 
    
  30. 
    
  31. @isolate_apps("invalid_models_tests")
    
  32. class IndexTogetherTests(SimpleTestCase):
    
  33.     def test_non_iterable(self):
    
  34.         class Model(models.Model):
    
  35.             class Meta:
    
  36.                 index_together = 42
    
  37. 
    
  38.         self.assertEqual(
    
  39.             Model.check(),
    
  40.             [
    
  41.                 Error(
    
  42.                     "'index_together' must be a list or tuple.",
    
  43.                     obj=Model,
    
  44.                     id="models.E008",
    
  45.                 ),
    
  46.             ],
    
  47.         )
    
  48. 
    
  49.     def test_non_list(self):
    
  50.         class Model(models.Model):
    
  51.             class Meta:
    
  52.                 index_together = "not-a-list"
    
  53. 
    
  54.         self.assertEqual(
    
  55.             Model.check(),
    
  56.             [
    
  57.                 Error(
    
  58.                     "'index_together' must be a list or tuple.",
    
  59.                     obj=Model,
    
  60.                     id="models.E008",
    
  61.                 ),
    
  62.             ],
    
  63.         )
    
  64. 
    
  65.     def test_list_containing_non_iterable(self):
    
  66.         class Model(models.Model):
    
  67.             class Meta:
    
  68.                 index_together = [("a", "b"), 42]
    
  69. 
    
  70.         self.assertEqual(
    
  71.             Model.check(),
    
  72.             [
    
  73.                 Error(
    
  74.                     "All 'index_together' elements must be lists or tuples.",
    
  75.                     obj=Model,
    
  76.                     id="models.E009",
    
  77.                 ),
    
  78.             ],
    
  79.         )
    
  80. 
    
  81.     def test_pointing_to_missing_field(self):
    
  82.         class Model(models.Model):
    
  83.             class Meta:
    
  84.                 index_together = [["missing_field"]]
    
  85. 
    
  86.         self.assertEqual(
    
  87.             Model.check(),
    
  88.             [
    
  89.                 Error(
    
  90.                     "'index_together' refers to the nonexistent field 'missing_field'.",
    
  91.                     obj=Model,
    
  92.                     id="models.E012",
    
  93.                 ),
    
  94.             ],
    
  95.         )
    
  96. 
    
  97.     def test_pointing_to_non_local_field(self):
    
  98.         class Foo(models.Model):
    
  99.             field1 = models.IntegerField()
    
  100. 
    
  101.         class Bar(Foo):
    
  102.             field2 = models.IntegerField()
    
  103. 
    
  104.             class Meta:
    
  105.                 index_together = [["field2", "field1"]]
    
  106. 
    
  107.         self.assertEqual(
    
  108.             Bar.check(),
    
  109.             [
    
  110.                 Error(
    
  111.                     "'index_together' refers to field 'field1' which is not "
    
  112.                     "local to model 'Bar'.",
    
  113.                     hint="This issue may be caused by multi-table inheritance.",
    
  114.                     obj=Bar,
    
  115.                     id="models.E016",
    
  116.                 ),
    
  117.             ],
    
  118.         )
    
  119. 
    
  120.     def test_pointing_to_m2m_field(self):
    
  121.         class Model(models.Model):
    
  122.             m2m = models.ManyToManyField("self")
    
  123. 
    
  124.             class Meta:
    
  125.                 index_together = [["m2m"]]
    
  126. 
    
  127.         self.assertEqual(
    
  128.             Model.check(),
    
  129.             [
    
  130.                 Error(
    
  131.                     "'index_together' refers to a ManyToManyField 'm2m', but "
    
  132.                     "ManyToManyFields are not permitted in 'index_together'.",
    
  133.                     obj=Model,
    
  134.                     id="models.E013",
    
  135.                 ),
    
  136.             ],
    
  137.         )
    
  138. 
    
  139.     def test_pointing_to_fk(self):
    
  140.         class Foo(models.Model):
    
  141.             pass
    
  142. 
    
  143.         class Bar(models.Model):
    
  144.             foo_1 = models.ForeignKey(
    
  145.                 Foo, on_delete=models.CASCADE, related_name="bar_1"
    
  146.             )
    
  147.             foo_2 = models.ForeignKey(
    
  148.                 Foo, on_delete=models.CASCADE, related_name="bar_2"
    
  149.             )
    
  150. 
    
  151.             class Meta:
    
  152.                 index_together = [["foo_1_id", "foo_2"]]
    
  153. 
    
  154.         self.assertEqual(Bar.check(), [])
    
  155. 
    
  156. 
    
  157. # unique_together tests are very similar to index_together tests.
    
  158. @isolate_apps("invalid_models_tests")
    
  159. class UniqueTogetherTests(SimpleTestCase):
    
  160.     def test_non_iterable(self):
    
  161.         class Model(models.Model):
    
  162.             class Meta:
    
  163.                 unique_together = 42
    
  164. 
    
  165.         self.assertEqual(
    
  166.             Model.check(),
    
  167.             [
    
  168.                 Error(
    
  169.                     "'unique_together' must be a list or tuple.",
    
  170.                     obj=Model,
    
  171.                     id="models.E010",
    
  172.                 ),
    
  173.             ],
    
  174.         )
    
  175. 
    
  176.     def test_list_containing_non_iterable(self):
    
  177.         class Model(models.Model):
    
  178.             one = models.IntegerField()
    
  179.             two = models.IntegerField()
    
  180. 
    
  181.             class Meta:
    
  182.                 unique_together = [("a", "b"), 42]
    
  183. 
    
  184.         self.assertEqual(
    
  185.             Model.check(),
    
  186.             [
    
  187.                 Error(
    
  188.                     "All 'unique_together' elements must be lists or tuples.",
    
  189.                     obj=Model,
    
  190.                     id="models.E011",
    
  191.                 ),
    
  192.             ],
    
  193.         )
    
  194. 
    
  195.     def test_non_list(self):
    
  196.         class Model(models.Model):
    
  197.             class Meta:
    
  198.                 unique_together = "not-a-list"
    
  199. 
    
  200.         self.assertEqual(
    
  201.             Model.check(),
    
  202.             [
    
  203.                 Error(
    
  204.                     "'unique_together' must be a list or tuple.",
    
  205.                     obj=Model,
    
  206.                     id="models.E010",
    
  207.                 ),
    
  208.             ],
    
  209.         )
    
  210. 
    
  211.     def test_valid_model(self):
    
  212.         class Model(models.Model):
    
  213.             one = models.IntegerField()
    
  214.             two = models.IntegerField()
    
  215. 
    
  216.             class Meta:
    
  217.                 # unique_together can be a simple tuple
    
  218.                 unique_together = ("one", "two")
    
  219. 
    
  220.         self.assertEqual(Model.check(), [])
    
  221. 
    
  222.     def test_pointing_to_missing_field(self):
    
  223.         class Model(models.Model):
    
  224.             class Meta:
    
  225.                 unique_together = [["missing_field"]]
    
  226. 
    
  227.         self.assertEqual(
    
  228.             Model.check(),
    
  229.             [
    
  230.                 Error(
    
  231.                     "'unique_together' refers to the nonexistent field "
    
  232.                     "'missing_field'.",
    
  233.                     obj=Model,
    
  234.                     id="models.E012",
    
  235.                 ),
    
  236.             ],
    
  237.         )
    
  238. 
    
  239.     def test_pointing_to_m2m(self):
    
  240.         class Model(models.Model):
    
  241.             m2m = models.ManyToManyField("self")
    
  242. 
    
  243.             class Meta:
    
  244.                 unique_together = [["m2m"]]
    
  245. 
    
  246.         self.assertEqual(
    
  247.             Model.check(),
    
  248.             [
    
  249.                 Error(
    
  250.                     "'unique_together' refers to a ManyToManyField 'm2m', but "
    
  251.                     "ManyToManyFields are not permitted in 'unique_together'.",
    
  252.                     obj=Model,
    
  253.                     id="models.E013",
    
  254.                 ),
    
  255.             ],
    
  256.         )
    
  257. 
    
  258.     def test_pointing_to_fk(self):
    
  259.         class Foo(models.Model):
    
  260.             pass
    
  261. 
    
  262.         class Bar(models.Model):
    
  263.             foo_1 = models.ForeignKey(
    
  264.                 Foo, on_delete=models.CASCADE, related_name="bar_1"
    
  265.             )
    
  266.             foo_2 = models.ForeignKey(
    
  267.                 Foo, on_delete=models.CASCADE, related_name="bar_2"
    
  268.             )
    
  269. 
    
  270.             class Meta:
    
  271.                 unique_together = [["foo_1_id", "foo_2"]]
    
  272. 
    
  273.         self.assertEqual(Bar.check(), [])
    
  274. 
    
  275. 
    
  276. @isolate_apps("invalid_models_tests")
    
  277. class IndexesTests(TestCase):
    
  278.     def test_pointing_to_missing_field(self):
    
  279.         class Model(models.Model):
    
  280.             class Meta:
    
  281.                 indexes = [models.Index(fields=["missing_field"], name="name")]
    
  282. 
    
  283.         self.assertEqual(
    
  284.             Model.check(),
    
  285.             [
    
  286.                 Error(
    
  287.                     "'indexes' refers to the nonexistent field 'missing_field'.",
    
  288.                     obj=Model,
    
  289.                     id="models.E012",
    
  290.                 ),
    
  291.             ],
    
  292.         )
    
  293. 
    
  294.     def test_pointing_to_m2m_field(self):
    
  295.         class Model(models.Model):
    
  296.             m2m = models.ManyToManyField("self")
    
  297. 
    
  298.             class Meta:
    
  299.                 indexes = [models.Index(fields=["m2m"], name="name")]
    
  300. 
    
  301.         self.assertEqual(
    
  302.             Model.check(),
    
  303.             [
    
  304.                 Error(
    
  305.                     "'indexes' refers to a ManyToManyField 'm2m', but "
    
  306.                     "ManyToManyFields are not permitted in 'indexes'.",
    
  307.                     obj=Model,
    
  308.                     id="models.E013",
    
  309.                 ),
    
  310.             ],
    
  311.         )
    
  312. 
    
  313.     def test_pointing_to_non_local_field(self):
    
  314.         class Foo(models.Model):
    
  315.             field1 = models.IntegerField()
    
  316. 
    
  317.         class Bar(Foo):
    
  318.             field2 = models.IntegerField()
    
  319. 
    
  320.             class Meta:
    
  321.                 indexes = [models.Index(fields=["field2", "field1"], name="name")]
    
  322. 
    
  323.         self.assertEqual(
    
  324.             Bar.check(),
    
  325.             [
    
  326.                 Error(
    
  327.                     "'indexes' refers to field 'field1' which is not local to "
    
  328.                     "model 'Bar'.",
    
  329.                     hint="This issue may be caused by multi-table inheritance.",
    
  330.                     obj=Bar,
    
  331.                     id="models.E016",
    
  332.                 ),
    
  333.             ],
    
  334.         )
    
  335. 
    
  336.     def test_pointing_to_fk(self):
    
  337.         class Foo(models.Model):
    
  338.             pass
    
  339. 
    
  340.         class Bar(models.Model):
    
  341.             foo_1 = models.ForeignKey(
    
  342.                 Foo, on_delete=models.CASCADE, related_name="bar_1"
    
  343.             )
    
  344.             foo_2 = models.ForeignKey(
    
  345.                 Foo, on_delete=models.CASCADE, related_name="bar_2"
    
  346.             )
    
  347. 
    
  348.             class Meta:
    
  349.                 indexes = [
    
  350.                     models.Index(fields=["foo_1_id", "foo_2"], name="index_name")
    
  351.                 ]
    
  352. 
    
  353.         self.assertEqual(Bar.check(), [])
    
  354. 
    
  355.     def test_name_constraints(self):
    
  356.         class Model(models.Model):
    
  357.             class Meta:
    
  358.                 indexes = [
    
  359.                     models.Index(fields=["id"], name="_index_name"),
    
  360.                     models.Index(fields=["id"], name="5index_name"),
    
  361.                 ]
    
  362. 
    
  363.         self.assertEqual(
    
  364.             Model.check(),
    
  365.             [
    
  366.                 Error(
    
  367.                     "The index name '%sindex_name' cannot start with an "
    
  368.                     "underscore or a number." % prefix,
    
  369.                     obj=Model,
    
  370.                     id="models.E033",
    
  371.                 )
    
  372.                 for prefix in ("_", "5")
    
  373.             ],
    
  374.         )
    
  375. 
    
  376.     def test_max_name_length(self):
    
  377.         index_name = "x" * 31
    
  378. 
    
  379.         class Model(models.Model):
    
  380.             class Meta:
    
  381.                 indexes = [models.Index(fields=["id"], name=index_name)]
    
  382. 
    
  383.         self.assertEqual(
    
  384.             Model.check(),
    
  385.             [
    
  386.                 Error(
    
  387.                     "The index name '%s' cannot be longer than 30 characters."
    
  388.                     % index_name,
    
  389.                     obj=Model,
    
  390.                     id="models.E034",
    
  391.                 ),
    
  392.             ],
    
  393.         )
    
  394. 
    
  395.     def test_index_with_condition(self):
    
  396.         class Model(models.Model):
    
  397.             age = models.IntegerField()
    
  398. 
    
  399.             class Meta:
    
  400.                 indexes = [
    
  401.                     models.Index(
    
  402.                         fields=["age"],
    
  403.                         name="index_age_gte_10",
    
  404.                         condition=models.Q(age__gte=10),
    
  405.                     ),
    
  406.                 ]
    
  407. 
    
  408.         errors = Model.check(databases=self.databases)
    
  409.         expected = (
    
  410.             []
    
  411.             if connection.features.supports_partial_indexes
    
  412.             else [
    
  413.                 Warning(
    
  414.                     "%s does not support indexes with conditions."
    
  415.                     % connection.display_name,
    
  416.                     hint=(
    
  417.                         "Conditions will be ignored. Silence this warning if you "
    
  418.                         "don't care about it."
    
  419.                     ),
    
  420.                     obj=Model,
    
  421.                     id="models.W037",
    
  422.                 )
    
  423.             ]
    
  424.         )
    
  425.         self.assertEqual(errors, expected)
    
  426. 
    
  427.     def test_index_with_condition_required_db_features(self):
    
  428.         class Model(models.Model):
    
  429.             age = models.IntegerField()
    
  430. 
    
  431.             class Meta:
    
  432.                 required_db_features = {"supports_partial_indexes"}
    
  433.                 indexes = [
    
  434.                     models.Index(
    
  435.                         fields=["age"],
    
  436.                         name="index_age_gte_10",
    
  437.                         condition=models.Q(age__gte=10),
    
  438.                     ),
    
  439.                 ]
    
  440. 
    
  441.         self.assertEqual(Model.check(databases=self.databases), [])
    
  442. 
    
  443.     def test_index_with_include(self):
    
  444.         class Model(models.Model):
    
  445.             age = models.IntegerField()
    
  446. 
    
  447.             class Meta:
    
  448.                 indexes = [
    
  449.                     models.Index(
    
  450.                         fields=["age"],
    
  451.                         name="index_age_include_id",
    
  452.                         include=["id"],
    
  453.                     ),
    
  454.                 ]
    
  455. 
    
  456.         errors = Model.check(databases=self.databases)
    
  457.         expected = (
    
  458.             []
    
  459.             if connection.features.supports_covering_indexes
    
  460.             else [
    
  461.                 Warning(
    
  462.                     "%s does not support indexes with non-key columns."
    
  463.                     % connection.display_name,
    
  464.                     hint=(
    
  465.                         "Non-key columns will be ignored. Silence this warning if "
    
  466.                         "you don't care about it."
    
  467.                     ),
    
  468.                     obj=Model,
    
  469.                     id="models.W040",
    
  470.                 )
    
  471.             ]
    
  472.         )
    
  473.         self.assertEqual(errors, expected)
    
  474. 
    
  475.     def test_index_with_include_required_db_features(self):
    
  476.         class Model(models.Model):
    
  477.             age = models.IntegerField()
    
  478. 
    
  479.             class Meta:
    
  480.                 required_db_features = {"supports_covering_indexes"}
    
  481.                 indexes = [
    
  482.                     models.Index(
    
  483.                         fields=["age"],
    
  484.                         name="index_age_include_id",
    
  485.                         include=["id"],
    
  486.                     ),
    
  487.                 ]
    
  488. 
    
  489.         self.assertEqual(Model.check(databases=self.databases), [])
    
  490. 
    
  491.     @skipUnlessDBFeature("supports_covering_indexes")
    
  492.     def test_index_include_pointing_to_missing_field(self):
    
  493.         class Model(models.Model):
    
  494.             class Meta:
    
  495.                 indexes = [
    
  496.                     models.Index(fields=["id"], include=["missing_field"], name="name"),
    
  497.                 ]
    
  498. 
    
  499.         self.assertEqual(
    
  500.             Model.check(databases=self.databases),
    
  501.             [
    
  502.                 Error(
    
  503.                     "'indexes' refers to the nonexistent field 'missing_field'.",
    
  504.                     obj=Model,
    
  505.                     id="models.E012",
    
  506.                 ),
    
  507.             ],
    
  508.         )
    
  509. 
    
  510.     @skipUnlessDBFeature("supports_covering_indexes")
    
  511.     def test_index_include_pointing_to_m2m_field(self):
    
  512.         class Model(models.Model):
    
  513.             m2m = models.ManyToManyField("self")
    
  514. 
    
  515.             class Meta:
    
  516.                 indexes = [models.Index(fields=["id"], include=["m2m"], name="name")]
    
  517. 
    
  518.         self.assertEqual(
    
  519.             Model.check(databases=self.databases),
    
  520.             [
    
  521.                 Error(
    
  522.                     "'indexes' refers to a ManyToManyField 'm2m', but "
    
  523.                     "ManyToManyFields are not permitted in 'indexes'.",
    
  524.                     obj=Model,
    
  525.                     id="models.E013",
    
  526.                 ),
    
  527.             ],
    
  528.         )
    
  529. 
    
  530.     @skipUnlessDBFeature("supports_covering_indexes")
    
  531.     def test_index_include_pointing_to_non_local_field(self):
    
  532.         class Parent(models.Model):
    
  533.             field1 = models.IntegerField()
    
  534. 
    
  535.         class Child(Parent):
    
  536.             field2 = models.IntegerField()
    
  537. 
    
  538.             class Meta:
    
  539.                 indexes = [
    
  540.                     models.Index(fields=["field2"], include=["field1"], name="name"),
    
  541.                 ]
    
  542. 
    
  543.         self.assertEqual(
    
  544.             Child.check(databases=self.databases),
    
  545.             [
    
  546.                 Error(
    
  547.                     "'indexes' refers to field 'field1' which is not local to "
    
  548.                     "model 'Child'.",
    
  549.                     hint="This issue may be caused by multi-table inheritance.",
    
  550.                     obj=Child,
    
  551.                     id="models.E016",
    
  552.                 ),
    
  553.             ],
    
  554.         )
    
  555. 
    
  556.     @skipUnlessDBFeature("supports_covering_indexes")
    
  557.     def test_index_include_pointing_to_fk(self):
    
  558.         class Target(models.Model):
    
  559.             pass
    
  560. 
    
  561.         class Model(models.Model):
    
  562.             fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
    
  563.             fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
    
  564. 
    
  565.             class Meta:
    
  566.                 constraints = [
    
  567.                     models.Index(
    
  568.                         fields=["id"],
    
  569.                         include=["fk_1_id", "fk_2"],
    
  570.                         name="name",
    
  571.                     ),
    
  572.                 ]
    
  573. 
    
  574.         self.assertEqual(Model.check(databases=self.databases), [])
    
  575. 
    
  576.     def test_func_index(self):
    
  577.         class Model(models.Model):
    
  578.             name = models.CharField(max_length=10)
    
  579. 
    
  580.             class Meta:
    
  581.                 indexes = [models.Index(Lower("name"), name="index_lower_name")]
    
  582. 
    
  583.         warn = Warning(
    
  584.             "%s does not support indexes on expressions." % connection.display_name,
    
  585.             hint=(
    
  586.                 "An index won't be created. Silence this warning if you don't "
    
  587.                 "care about it."
    
  588.             ),
    
  589.             obj=Model,
    
  590.             id="models.W043",
    
  591.         )
    
  592.         expected = [] if connection.features.supports_expression_indexes else [warn]
    
  593.         self.assertEqual(Model.check(databases=self.databases), expected)
    
  594. 
    
  595.     def test_func_index_required_db_features(self):
    
  596.         class Model(models.Model):
    
  597.             name = models.CharField(max_length=10)
    
  598. 
    
  599.             class Meta:
    
  600.                 indexes = [models.Index(Lower("name"), name="index_lower_name")]
    
  601.                 required_db_features = {"supports_expression_indexes"}
    
  602. 
    
  603.         self.assertEqual(Model.check(databases=self.databases), [])
    
  604. 
    
  605.     def test_func_index_complex_expression_custom_lookup(self):
    
  606.         class Model(models.Model):
    
  607.             height = models.IntegerField()
    
  608.             weight = models.IntegerField()
    
  609. 
    
  610.             class Meta:
    
  611.                 indexes = [
    
  612.                     models.Index(
    
  613.                         models.F("height")
    
  614.                         / (models.F("weight__abs") + models.Value(5)),
    
  615.                         name="name",
    
  616.                     ),
    
  617.                 ]
    
  618. 
    
  619.         with register_lookup(models.IntegerField, Abs):
    
  620.             self.assertEqual(Model.check(), [])
    
  621. 
    
  622.     def test_func_index_pointing_to_missing_field(self):
    
  623.         class Model(models.Model):
    
  624.             class Meta:
    
  625.                 indexes = [models.Index(Lower("missing_field").desc(), name="name")]
    
  626. 
    
  627.         self.assertEqual(
    
  628.             Model.check(),
    
  629.             [
    
  630.                 Error(
    
  631.                     "'indexes' refers to the nonexistent field 'missing_field'.",
    
  632.                     obj=Model,
    
  633.                     id="models.E012",
    
  634.                 ),
    
  635.             ],
    
  636.         )
    
  637. 
    
  638.     def test_func_index_pointing_to_missing_field_nested(self):
    
  639.         class Model(models.Model):
    
  640.             class Meta:
    
  641.                 indexes = [
    
  642.                     models.Index(Abs(Round("missing_field")), name="name"),
    
  643.                 ]
    
  644. 
    
  645.         self.assertEqual(
    
  646.             Model.check(),
    
  647.             [
    
  648.                 Error(
    
  649.                     "'indexes' refers to the nonexistent field 'missing_field'.",
    
  650.                     obj=Model,
    
  651.                     id="models.E012",
    
  652.                 ),
    
  653.             ],
    
  654.         )
    
  655. 
    
  656.     def test_func_index_pointing_to_m2m_field(self):
    
  657.         class Model(models.Model):
    
  658.             m2m = models.ManyToManyField("self")
    
  659. 
    
  660.             class Meta:
    
  661.                 indexes = [models.Index(Lower("m2m"), name="name")]
    
  662. 
    
  663.         self.assertEqual(
    
  664.             Model.check(),
    
  665.             [
    
  666.                 Error(
    
  667.                     "'indexes' refers to a ManyToManyField 'm2m', but "
    
  668.                     "ManyToManyFields are not permitted in 'indexes'.",
    
  669.                     obj=Model,
    
  670.                     id="models.E013",
    
  671.                 ),
    
  672.             ],
    
  673.         )
    
  674. 
    
  675.     def test_func_index_pointing_to_non_local_field(self):
    
  676.         class Foo(models.Model):
    
  677.             field1 = models.CharField(max_length=15)
    
  678. 
    
  679.         class Bar(Foo):
    
  680.             class Meta:
    
  681.                 indexes = [models.Index(Lower("field1"), name="name")]
    
  682. 
    
  683.         self.assertEqual(
    
  684.             Bar.check(),
    
  685.             [
    
  686.                 Error(
    
  687.                     "'indexes' refers to field 'field1' which is not local to "
    
  688.                     "model 'Bar'.",
    
  689.                     hint="This issue may be caused by multi-table inheritance.",
    
  690.                     obj=Bar,
    
  691.                     id="models.E016",
    
  692.                 ),
    
  693.             ],
    
  694.         )
    
  695. 
    
  696.     def test_func_index_pointing_to_fk(self):
    
  697.         class Foo(models.Model):
    
  698.             pass
    
  699. 
    
  700.         class Bar(models.Model):
    
  701.             foo_1 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_1")
    
  702.             foo_2 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_2")
    
  703. 
    
  704.             class Meta:
    
  705.                 indexes = [
    
  706.                     models.Index(Lower("foo_1_id"), Lower("foo_2"), name="index_name"),
    
  707.                 ]
    
  708. 
    
  709.         self.assertEqual(Bar.check(), [])
    
  710. 
    
  711. 
    
  712. @isolate_apps("invalid_models_tests")
    
  713. class FieldNamesTests(TestCase):
    
  714.     databases = {"default", "other"}
    
  715. 
    
  716.     def test_ending_with_underscore(self):
    
  717.         class Model(models.Model):
    
  718.             field_ = models.CharField(max_length=10)
    
  719.             m2m_ = models.ManyToManyField("self")
    
  720. 
    
  721.         self.assertEqual(
    
  722.             Model.check(),
    
  723.             [
    
  724.                 Error(
    
  725.                     "Field names must not end with an underscore.",
    
  726.                     obj=Model._meta.get_field("field_"),
    
  727.                     id="fields.E001",
    
  728.                 ),
    
  729.                 Error(
    
  730.                     "Field names must not end with an underscore.",
    
  731.                     obj=Model._meta.get_field("m2m_"),
    
  732.                     id="fields.E001",
    
  733.                 ),
    
  734.             ],
    
  735.         )
    
  736. 
    
  737.     max_column_name_length, column_limit_db_alias = get_max_column_name_length()
    
  738. 
    
  739.     @unittest.skipIf(
    
  740.         max_column_name_length is None,
    
  741.         "The database doesn't have a column name length limit.",
    
  742.     )
    
  743.     def test_M2M_long_column_name(self):
    
  744.         """
    
  745.         #13711 -- Model check for long M2M column names when database has
    
  746.         column name length limits.
    
  747.         """
    
  748. 
    
  749.         # A model with very long name which will be used to set relations to.
    
  750.         class VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz(
    
  751.             models.Model
    
  752.         ):
    
  753.             title = models.CharField(max_length=11)
    
  754. 
    
  755.         # Main model for which checks will be performed.
    
  756.         class ModelWithLongField(models.Model):
    
  757.             m2m_field = models.ManyToManyField(
    
  758.                 VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
    
  759.                 related_name="rn1",
    
  760.             )
    
  761.             m2m_field2 = models.ManyToManyField(
    
  762.                 VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
    
  763.                 related_name="rn2",
    
  764.                 through="m2msimple",
    
  765.             )
    
  766.             m2m_field3 = models.ManyToManyField(
    
  767.                 VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
    
  768.                 related_name="rn3",
    
  769.                 through="m2mcomplex",
    
  770.             )
    
  771.             fk = models.ForeignKey(
    
  772.                 VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
    
  773.                 models.CASCADE,
    
  774.                 related_name="rn4",
    
  775.             )
    
  776. 
    
  777.         # Models used for setting `through` in M2M field.
    
  778.         class m2msimple(models.Model):
    
  779.             id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
    
  780. 
    
  781.         class m2mcomplex(models.Model):
    
  782.             id2 = models.ForeignKey(ModelWithLongField, models.CASCADE)
    
  783. 
    
  784.         long_field_name = "a" * (self.max_column_name_length + 1)
    
  785.         models.ForeignKey(
    
  786.             VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
    
  787.             models.CASCADE,
    
  788.         ).contribute_to_class(m2msimple, long_field_name)
    
  789. 
    
  790.         models.ForeignKey(
    
  791.             VeryLongModelNamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz,
    
  792.             models.CASCADE,
    
  793.             db_column=long_field_name,
    
  794.         ).contribute_to_class(m2mcomplex, long_field_name)
    
  795. 
    
  796.         errors = ModelWithLongField.check(databases=("default", "other"))
    
  797. 
    
  798.         # First error because of M2M field set on the model with long name.
    
  799.         m2m_long_name = (
    
  800.             "verylongmodelnamezzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz_id"
    
  801.         )
    
  802.         if self.max_column_name_length > len(m2m_long_name):
    
  803.             # Some databases support names longer than the test name.
    
  804.             expected = []
    
  805.         else:
    
  806.             expected = [
    
  807.                 Error(
    
  808.                     'Autogenerated column name too long for M2M field "%s". '
    
  809.                     'Maximum length is "%s" for database "%s".'
    
  810.                     % (
    
  811.                         m2m_long_name,
    
  812.                         self.max_column_name_length,
    
  813.                         self.column_limit_db_alias,
    
  814.                     ),
    
  815.                     hint="Use 'through' to create a separate model for "
    
  816.                     "M2M and then set column_name using 'db_column'.",
    
  817.                     obj=ModelWithLongField,
    
  818.                     id="models.E019",
    
  819.                 )
    
  820.             ]
    
  821. 
    
  822.         # Second error because the FK specified in the `through` model
    
  823.         # `m2msimple` has auto-generated name longer than allowed.
    
  824.         # There will be no check errors in the other M2M because it
    
  825.         # specifies db_column for the FK in `through` model even if the actual
    
  826.         # name is longer than the limits of the database.
    
  827.         expected.append(
    
  828.             Error(
    
  829.                 'Autogenerated column name too long for M2M field "%s_id". '
    
  830.                 'Maximum length is "%s" for database "%s".'
    
  831.                 % (
    
  832.                     long_field_name,
    
  833.                     self.max_column_name_length,
    
  834.                     self.column_limit_db_alias,
    
  835.                 ),
    
  836.                 hint="Use 'through' to create a separate model for "
    
  837.                 "M2M and then set column_name using 'db_column'.",
    
  838.                 obj=ModelWithLongField,
    
  839.                 id="models.E019",
    
  840.             )
    
  841.         )
    
  842. 
    
  843.         self.assertEqual(errors, expected)
    
  844.         # Check for long column names is called only for specified database
    
  845.         # aliases.
    
  846.         self.assertEqual(ModelWithLongField.check(databases=None), [])
    
  847. 
    
  848.     @unittest.skipIf(
    
  849.         max_column_name_length is None,
    
  850.         "The database doesn't have a column name length limit.",
    
  851.     )
    
  852.     def test_local_field_long_column_name(self):
    
  853.         """
    
  854.         #13711 -- Model check for long column names
    
  855.         when database does not support long names.
    
  856.         """
    
  857. 
    
  858.         class ModelWithLongField(models.Model):
    
  859.             title = models.CharField(max_length=11)
    
  860. 
    
  861.         long_field_name = "a" * (self.max_column_name_length + 1)
    
  862.         long_field_name2 = "b" * (self.max_column_name_length + 1)
    
  863.         models.CharField(max_length=11).contribute_to_class(
    
  864.             ModelWithLongField, long_field_name
    
  865.         )
    
  866.         models.CharField(max_length=11, db_column="vlmn").contribute_to_class(
    
  867.             ModelWithLongField, long_field_name2
    
  868.         )
    
  869.         self.assertEqual(
    
  870.             ModelWithLongField.check(databases=("default", "other")),
    
  871.             [
    
  872.                 Error(
    
  873.                     'Autogenerated column name too long for field "%s". '
    
  874.                     'Maximum length is "%s" for database "%s".'
    
  875.                     % (
    
  876.                         long_field_name,
    
  877.                         self.max_column_name_length,
    
  878.                         self.column_limit_db_alias,
    
  879.                     ),
    
  880.                     hint="Set the column name manually using 'db_column'.",
    
  881.                     obj=ModelWithLongField,
    
  882.                     id="models.E018",
    
  883.                 )
    
  884.             ],
    
  885.         )
    
  886.         # Check for long column names is called only for specified database
    
  887.         # aliases.
    
  888.         self.assertEqual(ModelWithLongField.check(databases=None), [])
    
  889. 
    
  890.     def test_including_separator(self):
    
  891.         class Model(models.Model):
    
  892.             some__field = models.IntegerField()
    
  893. 
    
  894.         self.assertEqual(
    
  895.             Model.check(),
    
  896.             [
    
  897.                 Error(
    
  898.                     'Field names must not contain "__".',
    
  899.                     obj=Model._meta.get_field("some__field"),
    
  900.                     id="fields.E002",
    
  901.                 )
    
  902.             ],
    
  903.         )
    
  904. 
    
  905.     def test_pk(self):
    
  906.         class Model(models.Model):
    
  907.             pk = models.IntegerField()
    
  908. 
    
  909.         self.assertEqual(
    
  910.             Model.check(),
    
  911.             [
    
  912.                 Error(
    
  913.                     "'pk' is a reserved word that cannot be used as a field name.",
    
  914.                     obj=Model._meta.get_field("pk"),
    
  915.                     id="fields.E003",
    
  916.                 )
    
  917.             ],
    
  918.         )
    
  919. 
    
  920.     def test_db_column_clash(self):
    
  921.         class Model(models.Model):
    
  922.             foo = models.IntegerField()
    
  923.             bar = models.IntegerField(db_column="foo")
    
  924. 
    
  925.         self.assertEqual(
    
  926.             Model.check(),
    
  927.             [
    
  928.                 Error(
    
  929.                     "Field 'bar' has column name 'foo' that is used by "
    
  930.                     "another field.",
    
  931.                     hint="Specify a 'db_column' for the field.",
    
  932.                     obj=Model,
    
  933.                     id="models.E007",
    
  934.                 )
    
  935.             ],
    
  936.         )
    
  937. 
    
  938. 
    
  939. @isolate_apps("invalid_models_tests")
    
  940. class ShadowingFieldsTests(SimpleTestCase):
    
  941.     def test_field_name_clash_with_child_accessor(self):
    
  942.         class Parent(models.Model):
    
  943.             pass
    
  944. 
    
  945.         class Child(Parent):
    
  946.             child = models.CharField(max_length=100)
    
  947. 
    
  948.         self.assertEqual(
    
  949.             Child.check(),
    
  950.             [
    
  951.                 Error(
    
  952.                     "The field 'child' clashes with the field "
    
  953.                     "'child' from model 'invalid_models_tests.parent'.",
    
  954.                     obj=Child._meta.get_field("child"),
    
  955.                     id="models.E006",
    
  956.                 )
    
  957.             ],
    
  958.         )
    
  959. 
    
  960.     def test_field_name_clash_with_m2m_through(self):
    
  961.         class Parent(models.Model):
    
  962.             clash_id = models.IntegerField()
    
  963. 
    
  964.         class Child(Parent):
    
  965.             clash = models.ForeignKey("Child", models.CASCADE)
    
  966. 
    
  967.         class Model(models.Model):
    
  968.             parents = models.ManyToManyField(
    
  969.                 to=Parent,
    
  970.                 through="Through",
    
  971.                 through_fields=["parent", "model"],
    
  972.             )
    
  973. 
    
  974.         class Through(models.Model):
    
  975.             parent = models.ForeignKey(Parent, models.CASCADE)
    
  976.             model = models.ForeignKey(Model, models.CASCADE)
    
  977. 
    
  978.         self.assertEqual(
    
  979.             Child.check(),
    
  980.             [
    
  981.                 Error(
    
  982.                     "The field 'clash' clashes with the field 'clash_id' from "
    
  983.                     "model 'invalid_models_tests.parent'.",
    
  984.                     obj=Child._meta.get_field("clash"),
    
  985.                     id="models.E006",
    
  986.                 )
    
  987.             ],
    
  988.         )
    
  989. 
    
  990.     def test_multiinheritance_clash(self):
    
  991.         class Mother(models.Model):
    
  992.             clash = models.IntegerField()
    
  993. 
    
  994.         class Father(models.Model):
    
  995.             clash = models.IntegerField()
    
  996. 
    
  997.         class Child(Mother, Father):
    
  998.             # Here we have two clashed: id (automatic field) and clash, because
    
  999.             # both parents define these fields.
    
  1000.             pass
    
  1001. 
    
  1002.         self.assertEqual(
    
  1003.             Child.check(),
    
  1004.             [
    
  1005.                 Error(
    
  1006.                     "The field 'id' from parent model "
    
  1007.                     "'invalid_models_tests.mother' clashes with the field 'id' "
    
  1008.                     "from parent model 'invalid_models_tests.father'.",
    
  1009.                     obj=Child,
    
  1010.                     id="models.E005",
    
  1011.                 ),
    
  1012.                 Error(
    
  1013.                     "The field 'clash' from parent model "
    
  1014.                     "'invalid_models_tests.mother' clashes with the field 'clash' "
    
  1015.                     "from parent model 'invalid_models_tests.father'.",
    
  1016.                     obj=Child,
    
  1017.                     id="models.E005",
    
  1018.                 ),
    
  1019.             ],
    
  1020.         )
    
  1021. 
    
  1022.     def test_inheritance_clash(self):
    
  1023.         class Parent(models.Model):
    
  1024.             f_id = models.IntegerField()
    
  1025. 
    
  1026.         class Target(models.Model):
    
  1027.             # This field doesn't result in a clash.
    
  1028.             f_id = models.IntegerField()
    
  1029. 
    
  1030.         class Child(Parent):
    
  1031.             # This field clashes with parent "f_id" field.
    
  1032.             f = models.ForeignKey(Target, models.CASCADE)
    
  1033. 
    
  1034.         self.assertEqual(
    
  1035.             Child.check(),
    
  1036.             [
    
  1037.                 Error(
    
  1038.                     "The field 'f' clashes with the field 'f_id' "
    
  1039.                     "from model 'invalid_models_tests.parent'.",
    
  1040.                     obj=Child._meta.get_field("f"),
    
  1041.                     id="models.E006",
    
  1042.                 )
    
  1043.             ],
    
  1044.         )
    
  1045. 
    
  1046.     def test_multigeneration_inheritance(self):
    
  1047.         class GrandParent(models.Model):
    
  1048.             clash = models.IntegerField()
    
  1049. 
    
  1050.         class Parent(GrandParent):
    
  1051.             pass
    
  1052. 
    
  1053.         class Child(Parent):
    
  1054.             pass
    
  1055. 
    
  1056.         class GrandChild(Child):
    
  1057.             clash = models.IntegerField()
    
  1058. 
    
  1059.         self.assertEqual(
    
  1060.             GrandChild.check(),
    
  1061.             [
    
  1062.                 Error(
    
  1063.                     "The field 'clash' clashes with the field 'clash' "
    
  1064.                     "from model 'invalid_models_tests.grandparent'.",
    
  1065.                     obj=GrandChild._meta.get_field("clash"),
    
  1066.                     id="models.E006",
    
  1067.                 )
    
  1068.             ],
    
  1069.         )
    
  1070. 
    
  1071.     def test_id_clash(self):
    
  1072.         class Target(models.Model):
    
  1073.             pass
    
  1074. 
    
  1075.         class Model(models.Model):
    
  1076.             fk = models.ForeignKey(Target, models.CASCADE)
    
  1077.             fk_id = models.IntegerField()
    
  1078. 
    
  1079.         self.assertEqual(
    
  1080.             Model.check(),
    
  1081.             [
    
  1082.                 Error(
    
  1083.                     "The field 'fk_id' clashes with the field 'fk' from model "
    
  1084.                     "'invalid_models_tests.model'.",
    
  1085.                     obj=Model._meta.get_field("fk_id"),
    
  1086.                     id="models.E006",
    
  1087.                 )
    
  1088.             ],
    
  1089.         )
    
  1090. 
    
  1091. 
    
  1092. @isolate_apps("invalid_models_tests")
    
  1093. class OtherModelTests(SimpleTestCase):
    
  1094.     def test_unique_primary_key(self):
    
  1095.         invalid_id = models.IntegerField(primary_key=False)
    
  1096. 
    
  1097.         class Model(models.Model):
    
  1098.             id = invalid_id
    
  1099. 
    
  1100.         self.assertEqual(
    
  1101.             Model.check(),
    
  1102.             [
    
  1103.                 Error(
    
  1104.                     "'id' can only be used as a field name if the field also sets "
    
  1105.                     "'primary_key=True'.",
    
  1106.                     obj=Model,
    
  1107.                     id="models.E004",
    
  1108.                 ),
    
  1109.             ],
    
  1110.         )
    
  1111. 
    
  1112.     def test_ordering_non_iterable(self):
    
  1113.         class Model(models.Model):
    
  1114.             class Meta:
    
  1115.                 ordering = "missing_field"
    
  1116. 
    
  1117.         self.assertEqual(
    
  1118.             Model.check(),
    
  1119.             [
    
  1120.                 Error(
    
  1121.                     "'ordering' must be a tuple or list "
    
  1122.                     "(even if you want to order by only one field).",
    
  1123.                     obj=Model,
    
  1124.                     id="models.E014",
    
  1125.                 ),
    
  1126.             ],
    
  1127.         )
    
  1128. 
    
  1129.     def test_just_ordering_no_errors(self):
    
  1130.         class Model(models.Model):
    
  1131.             order = models.PositiveIntegerField()
    
  1132. 
    
  1133.             class Meta:
    
  1134.                 ordering = ["order"]
    
  1135. 
    
  1136.         self.assertEqual(Model.check(), [])
    
  1137. 
    
  1138.     def test_just_order_with_respect_to_no_errors(self):
    
  1139.         class Question(models.Model):
    
  1140.             pass
    
  1141. 
    
  1142.         class Answer(models.Model):
    
  1143.             question = models.ForeignKey(Question, models.CASCADE)
    
  1144. 
    
  1145.             class Meta:
    
  1146.                 order_with_respect_to = "question"
    
  1147. 
    
  1148.         self.assertEqual(Answer.check(), [])
    
  1149. 
    
  1150.     def test_ordering_with_order_with_respect_to(self):
    
  1151.         class Question(models.Model):
    
  1152.             pass
    
  1153. 
    
  1154.         class Answer(models.Model):
    
  1155.             question = models.ForeignKey(Question, models.CASCADE)
    
  1156.             order = models.IntegerField()
    
  1157. 
    
  1158.             class Meta:
    
  1159.                 order_with_respect_to = "question"
    
  1160.                 ordering = ["order"]
    
  1161. 
    
  1162.         self.assertEqual(
    
  1163.             Answer.check(),
    
  1164.             [
    
  1165.                 Error(
    
  1166.                     "'ordering' and 'order_with_respect_to' cannot be used together.",
    
  1167.                     obj=Answer,
    
  1168.                     id="models.E021",
    
  1169.                 ),
    
  1170.             ],
    
  1171.         )
    
  1172. 
    
  1173.     def test_non_valid(self):
    
  1174.         class RelationModel(models.Model):
    
  1175.             pass
    
  1176. 
    
  1177.         class Model(models.Model):
    
  1178.             relation = models.ManyToManyField(RelationModel)
    
  1179. 
    
  1180.             class Meta:
    
  1181.                 ordering = ["relation"]
    
  1182. 
    
  1183.         self.assertEqual(
    
  1184.             Model.check(),
    
  1185.             [
    
  1186.                 Error(
    
  1187.                     "'ordering' refers to the nonexistent field, related field, "
    
  1188.                     "or lookup 'relation'.",
    
  1189.                     obj=Model,
    
  1190.                     id="models.E015",
    
  1191.                 ),
    
  1192.             ],
    
  1193.         )
    
  1194. 
    
  1195.     def test_ordering_pointing_to_missing_field(self):
    
  1196.         class Model(models.Model):
    
  1197.             class Meta:
    
  1198.                 ordering = ("missing_field",)
    
  1199. 
    
  1200.         self.assertEqual(
    
  1201.             Model.check(),
    
  1202.             [
    
  1203.                 Error(
    
  1204.                     "'ordering' refers to the nonexistent field, related field, "
    
  1205.                     "or lookup 'missing_field'.",
    
  1206.                     obj=Model,
    
  1207.                     id="models.E015",
    
  1208.                 )
    
  1209.             ],
    
  1210.         )
    
  1211. 
    
  1212.     def test_ordering_pointing_to_missing_foreignkey_field(self):
    
  1213.         class Model(models.Model):
    
  1214.             missing_fk_field = models.IntegerField()
    
  1215. 
    
  1216.             class Meta:
    
  1217.                 ordering = ("missing_fk_field_id",)
    
  1218. 
    
  1219.         self.assertEqual(
    
  1220.             Model.check(),
    
  1221.             [
    
  1222.                 Error(
    
  1223.                     "'ordering' refers to the nonexistent field, related field, "
    
  1224.                     "or lookup 'missing_fk_field_id'.",
    
  1225.                     obj=Model,
    
  1226.                     id="models.E015",
    
  1227.                 )
    
  1228.             ],
    
  1229.         )
    
  1230. 
    
  1231.     def test_ordering_pointing_to_missing_related_field(self):
    
  1232.         class Model(models.Model):
    
  1233.             test = models.IntegerField()
    
  1234. 
    
  1235.             class Meta:
    
  1236.                 ordering = ("missing_related__id",)
    
  1237. 
    
  1238.         self.assertEqual(
    
  1239.             Model.check(),
    
  1240.             [
    
  1241.                 Error(
    
  1242.                     "'ordering' refers to the nonexistent field, related field, "
    
  1243.                     "or lookup 'missing_related__id'.",
    
  1244.                     obj=Model,
    
  1245.                     id="models.E015",
    
  1246.                 )
    
  1247.             ],
    
  1248.         )
    
  1249. 
    
  1250.     def test_ordering_pointing_to_missing_related_model_field(self):
    
  1251.         class Parent(models.Model):
    
  1252.             pass
    
  1253. 
    
  1254.         class Child(models.Model):
    
  1255.             parent = models.ForeignKey(Parent, models.CASCADE)
    
  1256. 
    
  1257.             class Meta:
    
  1258.                 ordering = ("parent__missing_field",)
    
  1259. 
    
  1260.         self.assertEqual(
    
  1261.             Child.check(),
    
  1262.             [
    
  1263.                 Error(
    
  1264.                     "'ordering' refers to the nonexistent field, related field, "
    
  1265.                     "or lookup 'parent__missing_field'.",
    
  1266.                     obj=Child,
    
  1267.                     id="models.E015",
    
  1268.                 )
    
  1269.             ],
    
  1270.         )
    
  1271. 
    
  1272.     def test_ordering_pointing_to_non_related_field(self):
    
  1273.         class Child(models.Model):
    
  1274.             parent = models.IntegerField()
    
  1275. 
    
  1276.             class Meta:
    
  1277.                 ordering = ("parent__missing_field",)
    
  1278. 
    
  1279.         self.assertEqual(
    
  1280.             Child.check(),
    
  1281.             [
    
  1282.                 Error(
    
  1283.                     "'ordering' refers to the nonexistent field, related field, "
    
  1284.                     "or lookup 'parent__missing_field'.",
    
  1285.                     obj=Child,
    
  1286.                     id="models.E015",
    
  1287.                 )
    
  1288.             ],
    
  1289.         )
    
  1290. 
    
  1291.     def test_ordering_pointing_to_two_related_model_field(self):
    
  1292.         class Parent2(models.Model):
    
  1293.             pass
    
  1294. 
    
  1295.         class Parent1(models.Model):
    
  1296.             parent2 = models.ForeignKey(Parent2, models.CASCADE)
    
  1297. 
    
  1298.         class Child(models.Model):
    
  1299.             parent1 = models.ForeignKey(Parent1, models.CASCADE)
    
  1300. 
    
  1301.             class Meta:
    
  1302.                 ordering = ("parent1__parent2__missing_field",)
    
  1303. 
    
  1304.         self.assertEqual(
    
  1305.             Child.check(),
    
  1306.             [
    
  1307.                 Error(
    
  1308.                     "'ordering' refers to the nonexistent field, related field, "
    
  1309.                     "or lookup 'parent1__parent2__missing_field'.",
    
  1310.                     obj=Child,
    
  1311.                     id="models.E015",
    
  1312.                 )
    
  1313.             ],
    
  1314.         )
    
  1315. 
    
  1316.     def test_ordering_pointing_multiple_times_to_model_fields(self):
    
  1317.         class Parent(models.Model):
    
  1318.             field1 = models.CharField(max_length=100)
    
  1319.             field2 = models.CharField(max_length=100)
    
  1320. 
    
  1321.         class Child(models.Model):
    
  1322.             parent = models.ForeignKey(Parent, models.CASCADE)
    
  1323. 
    
  1324.             class Meta:
    
  1325.                 ordering = ("parent__field1__field2",)
    
  1326. 
    
  1327.         self.assertEqual(
    
  1328.             Child.check(),
    
  1329.             [
    
  1330.                 Error(
    
  1331.                     "'ordering' refers to the nonexistent field, related field, "
    
  1332.                     "or lookup 'parent__field1__field2'.",
    
  1333.                     obj=Child,
    
  1334.                     id="models.E015",
    
  1335.                 )
    
  1336.             ],
    
  1337.         )
    
  1338. 
    
  1339.     def test_ordering_allows_registered_lookups(self):
    
  1340.         class Model(models.Model):
    
  1341.             test = models.CharField(max_length=100)
    
  1342. 
    
  1343.             class Meta:
    
  1344.                 ordering = ("test__lower",)
    
  1345. 
    
  1346.         with register_lookup(models.CharField, Lower):
    
  1347.             self.assertEqual(Model.check(), [])
    
  1348. 
    
  1349.     def test_ordering_pointing_to_lookup_not_transform(self):
    
  1350.         class Model(models.Model):
    
  1351.             test = models.CharField(max_length=100)
    
  1352. 
    
  1353.             class Meta:
    
  1354.                 ordering = ("test__isnull",)
    
  1355. 
    
  1356.         self.assertEqual(Model.check(), [])
    
  1357. 
    
  1358.     def test_ordering_pointing_to_related_model_pk(self):
    
  1359.         class Parent(models.Model):
    
  1360.             pass
    
  1361. 
    
  1362.         class Child(models.Model):
    
  1363.             parent = models.ForeignKey(Parent, models.CASCADE)
    
  1364. 
    
  1365.             class Meta:
    
  1366.                 ordering = ("parent__pk",)
    
  1367. 
    
  1368.         self.assertEqual(Child.check(), [])
    
  1369. 
    
  1370.     def test_ordering_pointing_to_foreignkey_field(self):
    
  1371.         class Parent(models.Model):
    
  1372.             pass
    
  1373. 
    
  1374.         class Child(models.Model):
    
  1375.             parent = models.ForeignKey(Parent, models.CASCADE)
    
  1376. 
    
  1377.             class Meta:
    
  1378.                 ordering = ("parent_id",)
    
  1379. 
    
  1380.         self.assertFalse(Child.check())
    
  1381. 
    
  1382.     def test_name_beginning_with_underscore(self):
    
  1383.         class _Model(models.Model):
    
  1384.             pass
    
  1385. 
    
  1386.         self.assertEqual(
    
  1387.             _Model.check(),
    
  1388.             [
    
  1389.                 Error(
    
  1390.                     "The model name '_Model' cannot start or end with an underscore "
    
  1391.                     "as it collides with the query lookup syntax.",
    
  1392.                     obj=_Model,
    
  1393.                     id="models.E023",
    
  1394.                 )
    
  1395.             ],
    
  1396.         )
    
  1397. 
    
  1398.     def test_name_ending_with_underscore(self):
    
  1399.         class Model_(models.Model):
    
  1400.             pass
    
  1401. 
    
  1402.         self.assertEqual(
    
  1403.             Model_.check(),
    
  1404.             [
    
  1405.                 Error(
    
  1406.                     "The model name 'Model_' cannot start or end with an underscore "
    
  1407.                     "as it collides with the query lookup syntax.",
    
  1408.                     obj=Model_,
    
  1409.                     id="models.E023",
    
  1410.                 )
    
  1411.             ],
    
  1412.         )
    
  1413. 
    
  1414.     def test_name_contains_double_underscores(self):
    
  1415.         class Test__Model(models.Model):
    
  1416.             pass
    
  1417. 
    
  1418.         self.assertEqual(
    
  1419.             Test__Model.check(),
    
  1420.             [
    
  1421.                 Error(
    
  1422.                     "The model name 'Test__Model' cannot contain double underscores "
    
  1423.                     "as it collides with the query lookup syntax.",
    
  1424.                     obj=Test__Model,
    
  1425.                     id="models.E024",
    
  1426.                 )
    
  1427.             ],
    
  1428.         )
    
  1429. 
    
  1430.     def test_property_and_related_field_accessor_clash(self):
    
  1431.         class Model(models.Model):
    
  1432.             fk = models.ForeignKey("self", models.CASCADE)
    
  1433. 
    
  1434.         # Override related field accessor.
    
  1435.         Model.fk_id = property(lambda self: "ERROR")
    
  1436. 
    
  1437.         self.assertEqual(
    
  1438.             Model.check(),
    
  1439.             [
    
  1440.                 Error(
    
  1441.                     "The property 'fk_id' clashes with a related field accessor.",
    
  1442.                     obj=Model,
    
  1443.                     id="models.E025",
    
  1444.                 )
    
  1445.             ],
    
  1446.         )
    
  1447. 
    
  1448.     def test_single_primary_key(self):
    
  1449.         class Model(models.Model):
    
  1450.             foo = models.IntegerField(primary_key=True)
    
  1451.             bar = models.IntegerField(primary_key=True)
    
  1452. 
    
  1453.         self.assertEqual(
    
  1454.             Model.check(),
    
  1455.             [
    
  1456.                 Error(
    
  1457.                     "The model cannot have more than one field with "
    
  1458.                     "'primary_key=True'.",
    
  1459.                     obj=Model,
    
  1460.                     id="models.E026",
    
  1461.                 )
    
  1462.             ],
    
  1463.         )
    
  1464. 
    
  1465.     @override_settings(TEST_SWAPPED_MODEL_BAD_VALUE="not-a-model")
    
  1466.     def test_swappable_missing_app_name(self):
    
  1467.         class Model(models.Model):
    
  1468.             class Meta:
    
  1469.                 swappable = "TEST_SWAPPED_MODEL_BAD_VALUE"
    
  1470. 
    
  1471.         self.assertEqual(
    
  1472.             Model.check(),
    
  1473.             [
    
  1474.                 Error(
    
  1475.                     "'TEST_SWAPPED_MODEL_BAD_VALUE' is not of the form "
    
  1476.                     "'app_label.app_name'.",
    
  1477.                     id="models.E001",
    
  1478.                 ),
    
  1479.             ],
    
  1480.         )
    
  1481. 
    
  1482.     @override_settings(TEST_SWAPPED_MODEL_BAD_MODEL="not_an_app.Target")
    
  1483.     def test_swappable_missing_app(self):
    
  1484.         class Model(models.Model):
    
  1485.             class Meta:
    
  1486.                 swappable = "TEST_SWAPPED_MODEL_BAD_MODEL"
    
  1487. 
    
  1488.         self.assertEqual(
    
  1489.             Model.check(),
    
  1490.             [
    
  1491.                 Error(
    
  1492.                     "'TEST_SWAPPED_MODEL_BAD_MODEL' references 'not_an_app.Target', "
    
  1493.                     "which has not been installed, or is abstract.",
    
  1494.                     id="models.E002",
    
  1495.                 ),
    
  1496.             ],
    
  1497.         )
    
  1498. 
    
  1499.     def test_two_m2m_through_same_relationship(self):
    
  1500.         class Person(models.Model):
    
  1501.             pass
    
  1502. 
    
  1503.         class Group(models.Model):
    
  1504.             primary = models.ManyToManyField(
    
  1505.                 Person, through="Membership", related_name="primary"
    
  1506.             )
    
  1507.             secondary = models.ManyToManyField(
    
  1508.                 Person, through="Membership", related_name="secondary"
    
  1509.             )
    
  1510. 
    
  1511.         class Membership(models.Model):
    
  1512.             person = models.ForeignKey(Person, models.CASCADE)
    
  1513.             group = models.ForeignKey(Group, models.CASCADE)
    
  1514. 
    
  1515.         self.assertEqual(
    
  1516.             Group.check(),
    
  1517.             [
    
  1518.                 Error(
    
  1519.                     "The model has two identical many-to-many relations through "
    
  1520.                     "the intermediate model 'invalid_models_tests.Membership'.",
    
  1521.                     obj=Group,
    
  1522.                     id="models.E003",
    
  1523.                 )
    
  1524.             ],
    
  1525.         )
    
  1526. 
    
  1527.     def test_two_m2m_through_same_model_with_different_through_fields(self):
    
  1528.         class Country(models.Model):
    
  1529.             pass
    
  1530. 
    
  1531.         class ShippingMethod(models.Model):
    
  1532.             to_countries = models.ManyToManyField(
    
  1533.                 Country,
    
  1534.                 through="ShippingMethodPrice",
    
  1535.                 through_fields=("method", "to_country"),
    
  1536.             )
    
  1537.             from_countries = models.ManyToManyField(
    
  1538.                 Country,
    
  1539.                 through="ShippingMethodPrice",
    
  1540.                 through_fields=("method", "from_country"),
    
  1541.                 related_name="+",
    
  1542.             )
    
  1543. 
    
  1544.         class ShippingMethodPrice(models.Model):
    
  1545.             method = models.ForeignKey(ShippingMethod, models.CASCADE)
    
  1546.             to_country = models.ForeignKey(Country, models.CASCADE)
    
  1547.             from_country = models.ForeignKey(Country, models.CASCADE)
    
  1548. 
    
  1549.         self.assertEqual(ShippingMethod.check(), [])
    
  1550. 
    
  1551.     def test_onetoone_with_parent_model(self):
    
  1552.         class Place(models.Model):
    
  1553.             pass
    
  1554. 
    
  1555.         class ParkingLot(Place):
    
  1556.             other_place = models.OneToOneField(
    
  1557.                 Place, models.CASCADE, related_name="other_parking"
    
  1558.             )
    
  1559. 
    
  1560.         self.assertEqual(ParkingLot.check(), [])
    
  1561. 
    
  1562.     def test_onetoone_with_explicit_parent_link_parent_model(self):
    
  1563.         class Place(models.Model):
    
  1564.             pass
    
  1565. 
    
  1566.         class ParkingLot(Place):
    
  1567.             place = models.OneToOneField(
    
  1568.                 Place, models.CASCADE, parent_link=True, primary_key=True
    
  1569.             )
    
  1570.             other_place = models.OneToOneField(
    
  1571.                 Place, models.CASCADE, related_name="other_parking"
    
  1572.             )
    
  1573. 
    
  1574.         self.assertEqual(ParkingLot.check(), [])
    
  1575. 
    
  1576.     def test_m2m_table_name_clash(self):
    
  1577.         class Foo(models.Model):
    
  1578.             bar = models.ManyToManyField("Bar", db_table="myapp_bar")
    
  1579. 
    
  1580.             class Meta:
    
  1581.                 db_table = "myapp_foo"
    
  1582. 
    
  1583.         class Bar(models.Model):
    
  1584.             class Meta:
    
  1585.                 db_table = "myapp_bar"
    
  1586. 
    
  1587.         self.assertEqual(
    
  1588.             Foo.check(),
    
  1589.             [
    
  1590.                 Error(
    
  1591.                     "The field's intermediary table 'myapp_bar' clashes with the "
    
  1592.                     "table name of 'invalid_models_tests.Bar'.",
    
  1593.                     obj=Foo._meta.get_field("bar"),
    
  1594.                     id="fields.E340",
    
  1595.                 )
    
  1596.             ],
    
  1597.         )
    
  1598. 
    
  1599.     @override_settings(
    
  1600.         DATABASE_ROUTERS=["invalid_models_tests.test_models.EmptyRouter"]
    
  1601.     )
    
  1602.     def test_m2m_table_name_clash_database_routers_installed(self):
    
  1603.         class Foo(models.Model):
    
  1604.             bar = models.ManyToManyField("Bar", db_table="myapp_bar")
    
  1605. 
    
  1606.             class Meta:
    
  1607.                 db_table = "myapp_foo"
    
  1608. 
    
  1609.         class Bar(models.Model):
    
  1610.             class Meta:
    
  1611.                 db_table = "myapp_bar"
    
  1612. 
    
  1613.         self.assertEqual(
    
  1614.             Foo.check(),
    
  1615.             [
    
  1616.                 Warning(
    
  1617.                     "The field's intermediary table 'myapp_bar' clashes with the "
    
  1618.                     "table name of 'invalid_models_tests.Bar'.",
    
  1619.                     obj=Foo._meta.get_field("bar"),
    
  1620.                     hint=(
    
  1621.                         "You have configured settings.DATABASE_ROUTERS. Verify "
    
  1622.                         "that the table of 'invalid_models_tests.Bar' is "
    
  1623.                         "correctly routed to a separate database."
    
  1624.                     ),
    
  1625.                     id="fields.W344",
    
  1626.                 ),
    
  1627.             ],
    
  1628.         )
    
  1629. 
    
  1630.     def test_m2m_field_table_name_clash(self):
    
  1631.         class Foo(models.Model):
    
  1632.             pass
    
  1633. 
    
  1634.         class Bar(models.Model):
    
  1635.             foos = models.ManyToManyField(Foo, db_table="clash")
    
  1636. 
    
  1637.         class Baz(models.Model):
    
  1638.             foos = models.ManyToManyField(Foo, db_table="clash")
    
  1639. 
    
  1640.         self.assertEqual(
    
  1641.             Bar.check() + Baz.check(),
    
  1642.             [
    
  1643.                 Error(
    
  1644.                     "The field's intermediary table 'clash' clashes with the "
    
  1645.                     "table name of 'invalid_models_tests.Baz.foos'.",
    
  1646.                     obj=Bar._meta.get_field("foos"),
    
  1647.                     id="fields.E340",
    
  1648.                 ),
    
  1649.                 Error(
    
  1650.                     "The field's intermediary table 'clash' clashes with the "
    
  1651.                     "table name of 'invalid_models_tests.Bar.foos'.",
    
  1652.                     obj=Baz._meta.get_field("foos"),
    
  1653.                     id="fields.E340",
    
  1654.                 ),
    
  1655.             ],
    
  1656.         )
    
  1657. 
    
  1658.     @override_settings(
    
  1659.         DATABASE_ROUTERS=["invalid_models_tests.test_models.EmptyRouter"]
    
  1660.     )
    
  1661.     def test_m2m_field_table_name_clash_database_routers_installed(self):
    
  1662.         class Foo(models.Model):
    
  1663.             pass
    
  1664. 
    
  1665.         class Bar(models.Model):
    
  1666.             foos = models.ManyToManyField(Foo, db_table="clash")
    
  1667. 
    
  1668.         class Baz(models.Model):
    
  1669.             foos = models.ManyToManyField(Foo, db_table="clash")
    
  1670. 
    
  1671.         self.assertEqual(
    
  1672.             Bar.check() + Baz.check(),
    
  1673.             [
    
  1674.                 Warning(
    
  1675.                     "The field's intermediary table 'clash' clashes with the "
    
  1676.                     "table name of 'invalid_models_tests.%s.foos'." % clashing_model,
    
  1677.                     obj=model_cls._meta.get_field("foos"),
    
  1678.                     hint=(
    
  1679.                         "You have configured settings.DATABASE_ROUTERS. Verify "
    
  1680.                         "that the table of 'invalid_models_tests.%s.foos' is "
    
  1681.                         "correctly routed to a separate database." % clashing_model
    
  1682.                     ),
    
  1683.                     id="fields.W344",
    
  1684.                 )
    
  1685.                 for model_cls, clashing_model in [(Bar, "Baz"), (Baz, "Bar")]
    
  1686.             ],
    
  1687.         )
    
  1688. 
    
  1689.     def test_m2m_autogenerated_table_name_clash(self):
    
  1690.         class Foo(models.Model):
    
  1691.             class Meta:
    
  1692.                 db_table = "bar_foos"
    
  1693. 
    
  1694.         class Bar(models.Model):
    
  1695.             # The autogenerated `db_table` will be bar_foos.
    
  1696.             foos = models.ManyToManyField(Foo)
    
  1697. 
    
  1698.             class Meta:
    
  1699.                 db_table = "bar"
    
  1700. 
    
  1701.         self.assertEqual(
    
  1702.             Bar.check(),
    
  1703.             [
    
  1704.                 Error(
    
  1705.                     "The field's intermediary table 'bar_foos' clashes with the "
    
  1706.                     "table name of 'invalid_models_tests.Foo'.",
    
  1707.                     obj=Bar._meta.get_field("foos"),
    
  1708.                     id="fields.E340",
    
  1709.                 )
    
  1710.             ],
    
  1711.         )
    
  1712. 
    
  1713.     @override_settings(
    
  1714.         DATABASE_ROUTERS=["invalid_models_tests.test_models.EmptyRouter"]
    
  1715.     )
    
  1716.     def test_m2m_autogenerated_table_name_clash_database_routers_installed(self):
    
  1717.         class Foo(models.Model):
    
  1718.             class Meta:
    
  1719.                 db_table = "bar_foos"
    
  1720. 
    
  1721.         class Bar(models.Model):
    
  1722.             # The autogenerated db_table is bar_foos.
    
  1723.             foos = models.ManyToManyField(Foo)
    
  1724. 
    
  1725.             class Meta:
    
  1726.                 db_table = "bar"
    
  1727. 
    
  1728.         self.assertEqual(
    
  1729.             Bar.check(),
    
  1730.             [
    
  1731.                 Warning(
    
  1732.                     "The field's intermediary table 'bar_foos' clashes with the "
    
  1733.                     "table name of 'invalid_models_tests.Foo'.",
    
  1734.                     obj=Bar._meta.get_field("foos"),
    
  1735.                     hint=(
    
  1736.                         "You have configured settings.DATABASE_ROUTERS. Verify "
    
  1737.                         "that the table of 'invalid_models_tests.Foo' is "
    
  1738.                         "correctly routed to a separate database."
    
  1739.                     ),
    
  1740.                     id="fields.W344",
    
  1741.                 ),
    
  1742.             ],
    
  1743.         )
    
  1744. 
    
  1745.     def test_m2m_unmanaged_shadow_models_not_checked(self):
    
  1746.         class A1(models.Model):
    
  1747.             pass
    
  1748. 
    
  1749.         class C1(models.Model):
    
  1750.             mm_a = models.ManyToManyField(A1, db_table="d1")
    
  1751. 
    
  1752.         # Unmanaged models that shadow the above models. Reused table names
    
  1753.         # shouldn't be flagged by any checks.
    
  1754.         class A2(models.Model):
    
  1755.             class Meta:
    
  1756.                 managed = False
    
  1757. 
    
  1758.         class C2(models.Model):
    
  1759.             mm_a = models.ManyToManyField(A2, through="Intermediate")
    
  1760. 
    
  1761.             class Meta:
    
  1762.                 managed = False
    
  1763. 
    
  1764.         class Intermediate(models.Model):
    
  1765.             a2 = models.ForeignKey(A2, models.CASCADE, db_column="a1_id")
    
  1766.             c2 = models.ForeignKey(C2, models.CASCADE, db_column="c1_id")
    
  1767. 
    
  1768.             class Meta:
    
  1769.                 db_table = "d1"
    
  1770.                 managed = False
    
  1771. 
    
  1772.         self.assertEqual(C1.check(), [])
    
  1773.         self.assertEqual(C2.check(), [])
    
  1774. 
    
  1775.     def test_m2m_to_concrete_and_proxy_allowed(self):
    
  1776.         class A(models.Model):
    
  1777.             pass
    
  1778. 
    
  1779.         class Through(models.Model):
    
  1780.             a = models.ForeignKey("A", models.CASCADE)
    
  1781.             c = models.ForeignKey("C", models.CASCADE)
    
  1782. 
    
  1783.         class ThroughProxy(Through):
    
  1784.             class Meta:
    
  1785.                 proxy = True
    
  1786. 
    
  1787.         class C(models.Model):
    
  1788.             mm_a = models.ManyToManyField(A, through=Through)
    
  1789.             mm_aproxy = models.ManyToManyField(
    
  1790.                 A, through=ThroughProxy, related_name="proxied_m2m"
    
  1791.             )
    
  1792. 
    
  1793.         self.assertEqual(C.check(), [])
    
  1794. 
    
  1795.     @isolate_apps("django.contrib.auth", kwarg_name="apps")
    
  1796.     def test_lazy_reference_checks(self, apps):
    
  1797.         class DummyModel(models.Model):
    
  1798.             author = models.ForeignKey("Author", models.CASCADE)
    
  1799. 
    
  1800.             class Meta:
    
  1801.                 app_label = "invalid_models_tests"
    
  1802. 
    
  1803.         class DummyClass:
    
  1804.             def __call__(self, **kwargs):
    
  1805.                 pass
    
  1806. 
    
  1807.             def dummy_method(self):
    
  1808.                 pass
    
  1809. 
    
  1810.         def dummy_function(*args, **kwargs):
    
  1811.             pass
    
  1812. 
    
  1813.         apps.lazy_model_operation(dummy_function, ("auth", "imaginarymodel"))
    
  1814.         apps.lazy_model_operation(dummy_function, ("fanciful_app", "imaginarymodel"))
    
  1815. 
    
  1816.         post_init.connect(dummy_function, sender="missing-app.Model", apps=apps)
    
  1817.         post_init.connect(DummyClass(), sender="missing-app.Model", apps=apps)
    
  1818.         post_init.connect(
    
  1819.             DummyClass().dummy_method, sender="missing-app.Model", apps=apps
    
  1820.         )
    
  1821. 
    
  1822.         self.assertEqual(
    
  1823.             _check_lazy_references(apps),
    
  1824.             [
    
  1825.                 Error(
    
  1826.                     "%r contains a lazy reference to auth.imaginarymodel, "
    
  1827.                     "but app 'auth' doesn't provide model 'imaginarymodel'."
    
  1828.                     % dummy_function,
    
  1829.                     obj=dummy_function,
    
  1830.                     id="models.E022",
    
  1831.                 ),
    
  1832.                 Error(
    
  1833.                     "%r contains a lazy reference to fanciful_app.imaginarymodel, "
    
  1834.                     "but app 'fanciful_app' isn't installed." % dummy_function,
    
  1835.                     obj=dummy_function,
    
  1836.                     id="models.E022",
    
  1837.                 ),
    
  1838.                 Error(
    
  1839.                     "An instance of class 'DummyClass' was connected to "
    
  1840.                     "the 'post_init' signal with a lazy reference to the sender "
    
  1841.                     "'missing-app.model', but app 'missing-app' isn't installed.",
    
  1842.                     hint=None,
    
  1843.                     obj="invalid_models_tests.test_models",
    
  1844.                     id="signals.E001",
    
  1845.                 ),
    
  1846.                 Error(
    
  1847.                     "Bound method 'DummyClass.dummy_method' was connected to the "
    
  1848.                     "'post_init' signal with a lazy reference to the sender "
    
  1849.                     "'missing-app.model', but app 'missing-app' isn't installed.",
    
  1850.                     hint=None,
    
  1851.                     obj="invalid_models_tests.test_models",
    
  1852.                     id="signals.E001",
    
  1853.                 ),
    
  1854.                 Error(
    
  1855.                     "The field invalid_models_tests.DummyModel.author was declared "
    
  1856.                     "with a lazy reference to 'invalid_models_tests.author', but app "
    
  1857.                     "'invalid_models_tests' isn't installed.",
    
  1858.                     hint=None,
    
  1859.                     obj=DummyModel.author.field,
    
  1860.                     id="fields.E307",
    
  1861.                 ),
    
  1862.                 Error(
    
  1863.                     "The function 'dummy_function' was connected to the 'post_init' "
    
  1864.                     "signal with a lazy reference to the sender "
    
  1865.                     "'missing-app.model', but app 'missing-app' isn't installed.",
    
  1866.                     hint=None,
    
  1867.                     obj="invalid_models_tests.test_models",
    
  1868.                     id="signals.E001",
    
  1869.                 ),
    
  1870.             ],
    
  1871.         )
    
  1872. 
    
  1873. 
    
  1874. class MultipleAutoFieldsTests(TestCase):
    
  1875.     def test_multiple_autofields(self):
    
  1876.         msg = (
    
  1877.             "Model invalid_models_tests.MultipleAutoFields can't have more "
    
  1878.             "than one auto-generated field."
    
  1879.         )
    
  1880.         with self.assertRaisesMessage(ValueError, msg):
    
  1881. 
    
  1882.             class MultipleAutoFields(models.Model):
    
  1883.                 auto1 = models.AutoField(primary_key=True)
    
  1884.                 auto2 = models.AutoField(primary_key=True)
    
  1885. 
    
  1886. 
    
  1887. @isolate_apps("invalid_models_tests")
    
  1888. class JSONFieldTests(TestCase):
    
  1889.     @skipUnlessDBFeature("supports_json_field")
    
  1890.     def test_ordering_pointing_to_json_field_value(self):
    
  1891.         class Model(models.Model):
    
  1892.             field = models.JSONField()
    
  1893. 
    
  1894.             class Meta:
    
  1895.                 ordering = ["field__value"]
    
  1896. 
    
  1897.         self.assertEqual(Model.check(databases=self.databases), [])
    
  1898. 
    
  1899.     def test_check_jsonfield(self):
    
  1900.         class Model(models.Model):
    
  1901.             field = models.JSONField()
    
  1902. 
    
  1903.         error = Error(
    
  1904.             "%s does not support JSONFields." % connection.display_name,
    
  1905.             obj=Model,
    
  1906.             id="fields.E180",
    
  1907.         )
    
  1908.         expected = [] if connection.features.supports_json_field else [error]
    
  1909.         self.assertEqual(Model.check(databases=self.databases), expected)
    
  1910. 
    
  1911.     def test_check_jsonfield_required_db_features(self):
    
  1912.         class Model(models.Model):
    
  1913.             field = models.JSONField()
    
  1914. 
    
  1915.             class Meta:
    
  1916.                 required_db_features = {"supports_json_field"}
    
  1917. 
    
  1918.         self.assertEqual(Model.check(databases=self.databases), [])
    
  1919. 
    
  1920. 
    
  1921. @isolate_apps("invalid_models_tests")
    
  1922. class ConstraintsTests(TestCase):
    
  1923.     def test_check_constraints(self):
    
  1924.         class Model(models.Model):
    
  1925.             age = models.IntegerField()
    
  1926. 
    
  1927.             class Meta:
    
  1928.                 constraints = [
    
  1929.                     models.CheckConstraint(check=models.Q(age__gte=18), name="is_adult")
    
  1930.                 ]
    
  1931. 
    
  1932.         errors = Model.check(databases=self.databases)
    
  1933.         warn = Warning(
    
  1934.             "%s does not support check constraints." % connection.display_name,
    
  1935.             hint=(
    
  1936.                 "A constraint won't be created. Silence this warning if you "
    
  1937.                 "don't care about it."
    
  1938.             ),
    
  1939.             obj=Model,
    
  1940.             id="models.W027",
    
  1941.         )
    
  1942.         expected = (
    
  1943.             [] if connection.features.supports_table_check_constraints else [warn]
    
  1944.         )
    
  1945.         self.assertCountEqual(errors, expected)
    
  1946. 
    
  1947.     def test_check_constraints_required_db_features(self):
    
  1948.         class Model(models.Model):
    
  1949.             age = models.IntegerField()
    
  1950. 
    
  1951.             class Meta:
    
  1952.                 required_db_features = {"supports_table_check_constraints"}
    
  1953.                 constraints = [
    
  1954.                     models.CheckConstraint(check=models.Q(age__gte=18), name="is_adult")
    
  1955.                 ]
    
  1956. 
    
  1957.         self.assertEqual(Model.check(databases=self.databases), [])
    
  1958. 
    
  1959.     def test_check_constraint_pointing_to_missing_field(self):
    
  1960.         class Model(models.Model):
    
  1961.             class Meta:
    
  1962.                 required_db_features = {"supports_table_check_constraints"}
    
  1963.                 constraints = [
    
  1964.                     models.CheckConstraint(
    
  1965.                         name="name",
    
  1966.                         check=models.Q(missing_field=2),
    
  1967.                     ),
    
  1968.                 ]
    
  1969. 
    
  1970.         self.assertEqual(
    
  1971.             Model.check(databases=self.databases),
    
  1972.             [
    
  1973.                 Error(
    
  1974.                     "'constraints' refers to the nonexistent field 'missing_field'.",
    
  1975.                     obj=Model,
    
  1976.                     id="models.E012",
    
  1977.                 ),
    
  1978.             ]
    
  1979.             if connection.features.supports_table_check_constraints
    
  1980.             else [],
    
  1981.         )
    
  1982. 
    
  1983.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  1984.     def test_check_constraint_pointing_to_reverse_fk(self):
    
  1985.         class Model(models.Model):
    
  1986.             parent = models.ForeignKey("self", models.CASCADE, related_name="parents")
    
  1987. 
    
  1988.             class Meta:
    
  1989.                 constraints = [
    
  1990.                     models.CheckConstraint(name="name", check=models.Q(parents=3)),
    
  1991.                 ]
    
  1992. 
    
  1993.         self.assertEqual(
    
  1994.             Model.check(databases=self.databases),
    
  1995.             [
    
  1996.                 Error(
    
  1997.                     "'constraints' refers to the nonexistent field 'parents'.",
    
  1998.                     obj=Model,
    
  1999.                     id="models.E012",
    
  2000.                 ),
    
  2001.             ],
    
  2002.         )
    
  2003. 
    
  2004.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  2005.     def test_check_constraint_pointing_to_reverse_o2o(self):
    
  2006.         class Model(models.Model):
    
  2007.             parent = models.OneToOneField("self", models.CASCADE)
    
  2008. 
    
  2009.             class Meta:
    
  2010.                 constraints = [
    
  2011.                     models.CheckConstraint(
    
  2012.                         name="name",
    
  2013.                         check=models.Q(model__isnull=True),
    
  2014.                     ),
    
  2015.                 ]
    
  2016. 
    
  2017.         self.assertEqual(
    
  2018.             Model.check(databases=self.databases),
    
  2019.             [
    
  2020.                 Error(
    
  2021.                     "'constraints' refers to the nonexistent field 'model'.",
    
  2022.                     obj=Model,
    
  2023.                     id="models.E012",
    
  2024.                 ),
    
  2025.             ],
    
  2026.         )
    
  2027. 
    
  2028.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  2029.     def test_check_constraint_pointing_to_m2m_field(self):
    
  2030.         class Model(models.Model):
    
  2031.             m2m = models.ManyToManyField("self")
    
  2032. 
    
  2033.             class Meta:
    
  2034.                 constraints = [
    
  2035.                     models.CheckConstraint(name="name", check=models.Q(m2m=2)),
    
  2036.                 ]
    
  2037. 
    
  2038.         self.assertEqual(
    
  2039.             Model.check(databases=self.databases),
    
  2040.             [
    
  2041.                 Error(
    
  2042.                     "'constraints' refers to a ManyToManyField 'm2m', but "
    
  2043.                     "ManyToManyFields are not permitted in 'constraints'.",
    
  2044.                     obj=Model,
    
  2045.                     id="models.E013",
    
  2046.                 ),
    
  2047.             ],
    
  2048.         )
    
  2049. 
    
  2050.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  2051.     def test_check_constraint_pointing_to_fk(self):
    
  2052.         class Target(models.Model):
    
  2053.             pass
    
  2054. 
    
  2055.         class Model(models.Model):
    
  2056.             fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
    
  2057.             fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
    
  2058. 
    
  2059.             class Meta:
    
  2060.                 constraints = [
    
  2061.                     models.CheckConstraint(
    
  2062.                         name="name",
    
  2063.                         check=models.Q(fk_1_id=2) | models.Q(fk_2=2),
    
  2064.                     ),
    
  2065.                 ]
    
  2066. 
    
  2067.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2068. 
    
  2069.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  2070.     def test_check_constraint_pointing_to_pk(self):
    
  2071.         class Model(models.Model):
    
  2072.             age = models.SmallIntegerField()
    
  2073. 
    
  2074.             class Meta:
    
  2075.                 constraints = [
    
  2076.                     models.CheckConstraint(
    
  2077.                         name="name",
    
  2078.                         check=models.Q(pk__gt=5) & models.Q(age__gt=models.F("pk")),
    
  2079.                     ),
    
  2080.                 ]
    
  2081. 
    
  2082.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2083. 
    
  2084.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  2085.     def test_check_constraint_pointing_to_non_local_field(self):
    
  2086.         class Parent(models.Model):
    
  2087.             field1 = models.IntegerField()
    
  2088. 
    
  2089.         class Child(Parent):
    
  2090.             pass
    
  2091. 
    
  2092.             class Meta:
    
  2093.                 constraints = [
    
  2094.                     models.CheckConstraint(name="name", check=models.Q(field1=1)),
    
  2095.                 ]
    
  2096. 
    
  2097.         self.assertEqual(
    
  2098.             Child.check(databases=self.databases),
    
  2099.             [
    
  2100.                 Error(
    
  2101.                     "'constraints' refers to field 'field1' which is not local to "
    
  2102.                     "model 'Child'.",
    
  2103.                     hint="This issue may be caused by multi-table inheritance.",
    
  2104.                     obj=Child,
    
  2105.                     id="models.E016",
    
  2106.                 ),
    
  2107.             ],
    
  2108.         )
    
  2109. 
    
  2110.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  2111.     def test_check_constraint_pointing_to_joined_fields(self):
    
  2112.         class Model(models.Model):
    
  2113.             name = models.CharField(max_length=10)
    
  2114.             field1 = models.PositiveSmallIntegerField()
    
  2115.             field2 = models.PositiveSmallIntegerField()
    
  2116.             field3 = models.PositiveSmallIntegerField()
    
  2117.             parent = models.ForeignKey("self", models.CASCADE)
    
  2118.             previous = models.OneToOneField("self", models.CASCADE, related_name="next")
    
  2119. 
    
  2120.             class Meta:
    
  2121.                 constraints = [
    
  2122.                     models.CheckConstraint(
    
  2123.                         name="name1",
    
  2124.                         check=models.Q(
    
  2125.                             field1__lt=models.F("parent__field1")
    
  2126.                             + models.F("parent__field2")
    
  2127.                         ),
    
  2128.                     ),
    
  2129.                     models.CheckConstraint(
    
  2130.                         name="name2", check=models.Q(name=Lower("parent__name"))
    
  2131.                     ),
    
  2132.                     models.CheckConstraint(
    
  2133.                         name="name3", check=models.Q(parent__field3=models.F("field1"))
    
  2134.                     ),
    
  2135.                     models.CheckConstraint(
    
  2136.                         name="name4",
    
  2137.                         check=models.Q(name=Lower("previous__name")),
    
  2138.                     ),
    
  2139.                 ]
    
  2140. 
    
  2141.         joined_fields = [
    
  2142.             "parent__field1",
    
  2143.             "parent__field2",
    
  2144.             "parent__field3",
    
  2145.             "parent__name",
    
  2146.             "previous__name",
    
  2147.         ]
    
  2148.         errors = Model.check(databases=self.databases)
    
  2149.         expected_errors = [
    
  2150.             Error(
    
  2151.                 "'constraints' refers to the joined field '%s'." % field_name,
    
  2152.                 obj=Model,
    
  2153.                 id="models.E041",
    
  2154.             )
    
  2155.             for field_name in joined_fields
    
  2156.         ]
    
  2157.         self.assertCountEqual(errors, expected_errors)
    
  2158. 
    
  2159.     @skipUnlessDBFeature("supports_table_check_constraints")
    
  2160.     def test_check_constraint_pointing_to_joined_fields_complex_check(self):
    
  2161.         class Model(models.Model):
    
  2162.             name = models.PositiveSmallIntegerField()
    
  2163.             field1 = models.PositiveSmallIntegerField()
    
  2164.             field2 = models.PositiveSmallIntegerField()
    
  2165.             parent = models.ForeignKey("self", models.CASCADE)
    
  2166. 
    
  2167.             class Meta:
    
  2168.                 constraints = [
    
  2169.                     models.CheckConstraint(
    
  2170.                         name="name",
    
  2171.                         check=models.Q(
    
  2172.                             (
    
  2173.                                 models.Q(name="test")
    
  2174.                                 & models.Q(field1__lt=models.F("parent__field1"))
    
  2175.                             )
    
  2176.                             | (
    
  2177.                                 models.Q(name__startswith=Lower("parent__name"))
    
  2178.                                 & models.Q(
    
  2179.                                     field1__gte=(
    
  2180.                                         models.F("parent__field1")
    
  2181.                                         + models.F("parent__field2")
    
  2182.                                     )
    
  2183.                                 )
    
  2184.                             )
    
  2185.                         )
    
  2186.                         | (models.Q(name="test1")),
    
  2187.                     ),
    
  2188.                 ]
    
  2189. 
    
  2190.         joined_fields = ["parent__field1", "parent__field2", "parent__name"]
    
  2191.         errors = Model.check(databases=self.databases)
    
  2192.         expected_errors = [
    
  2193.             Error(
    
  2194.                 "'constraints' refers to the joined field '%s'." % field_name,
    
  2195.                 obj=Model,
    
  2196.                 id="models.E041",
    
  2197.             )
    
  2198.             for field_name in joined_fields
    
  2199.         ]
    
  2200.         self.assertCountEqual(errors, expected_errors)
    
  2201. 
    
  2202.     def test_check_constraint_raw_sql_check(self):
    
  2203.         class Model(models.Model):
    
  2204.             class Meta:
    
  2205.                 required_db_features = {"supports_table_check_constraints"}
    
  2206.                 constraints = [
    
  2207.                     models.CheckConstraint(check=models.Q(id__gt=0), name="q_check"),
    
  2208.                     models.CheckConstraint(
    
  2209.                         check=models.ExpressionWrapper(
    
  2210.                             models.Q(price__gt=20),
    
  2211.                             output_field=models.BooleanField(),
    
  2212.                         ),
    
  2213.                         name="expression_wrapper_check",
    
  2214.                     ),
    
  2215.                     models.CheckConstraint(
    
  2216.                         check=models.expressions.RawSQL(
    
  2217.                             "id = 0",
    
  2218.                             params=(),
    
  2219.                             output_field=models.BooleanField(),
    
  2220.                         ),
    
  2221.                         name="raw_sql_check",
    
  2222.                     ),
    
  2223.                     models.CheckConstraint(
    
  2224.                         check=models.Q(
    
  2225.                             models.ExpressionWrapper(
    
  2226.                                 models.Q(
    
  2227.                                     models.expressions.RawSQL(
    
  2228.                                         "id = 0",
    
  2229.                                         params=(),
    
  2230.                                         output_field=models.BooleanField(),
    
  2231.                                     )
    
  2232.                                 ),
    
  2233.                                 output_field=models.BooleanField(),
    
  2234.                             )
    
  2235.                         ),
    
  2236.                         name="nested_raw_sql_check",
    
  2237.                     ),
    
  2238.                 ]
    
  2239. 
    
  2240.         expected_warnings = (
    
  2241.             [
    
  2242.                 Warning(
    
  2243.                     "Check constraint 'raw_sql_check' contains RawSQL() expression and "
    
  2244.                     "won't be validated during the model full_clean().",
    
  2245.                     hint="Silence this warning if you don't care about it.",
    
  2246.                     obj=Model,
    
  2247.                     id="models.W045",
    
  2248.                 ),
    
  2249.                 Warning(
    
  2250.                     "Check constraint 'nested_raw_sql_check' contains RawSQL() "
    
  2251.                     "expression and won't be validated during the model full_clean().",
    
  2252.                     hint="Silence this warning if you don't care about it.",
    
  2253.                     obj=Model,
    
  2254.                     id="models.W045",
    
  2255.                 ),
    
  2256.             ]
    
  2257.             if connection.features.supports_table_check_constraints
    
  2258.             else []
    
  2259.         )
    
  2260.         self.assertEqual(Model.check(databases=self.databases), expected_warnings)
    
  2261. 
    
  2262.     def test_unique_constraint_with_condition(self):
    
  2263.         class Model(models.Model):
    
  2264.             age = models.IntegerField()
    
  2265. 
    
  2266.             class Meta:
    
  2267.                 constraints = [
    
  2268.                     models.UniqueConstraint(
    
  2269.                         fields=["age"],
    
  2270.                         name="unique_age_gte_100",
    
  2271.                         condition=models.Q(age__gte=100),
    
  2272.                     ),
    
  2273.                 ]
    
  2274. 
    
  2275.         errors = Model.check(databases=self.databases)
    
  2276.         expected = (
    
  2277.             []
    
  2278.             if connection.features.supports_partial_indexes
    
  2279.             else [
    
  2280.                 Warning(
    
  2281.                     "%s does not support unique constraints with conditions."
    
  2282.                     % connection.display_name,
    
  2283.                     hint=(
    
  2284.                         "A constraint won't be created. Silence this warning if "
    
  2285.                         "you don't care about it."
    
  2286.                     ),
    
  2287.                     obj=Model,
    
  2288.                     id="models.W036",
    
  2289.                 ),
    
  2290.             ]
    
  2291.         )
    
  2292.         self.assertEqual(errors, expected)
    
  2293. 
    
  2294.     def test_unique_constraint_with_condition_required_db_features(self):
    
  2295.         class Model(models.Model):
    
  2296.             age = models.IntegerField()
    
  2297. 
    
  2298.             class Meta:
    
  2299.                 required_db_features = {"supports_partial_indexes"}
    
  2300.                 constraints = [
    
  2301.                     models.UniqueConstraint(
    
  2302.                         fields=["age"],
    
  2303.                         name="unique_age_gte_100",
    
  2304.                         condition=models.Q(age__gte=100),
    
  2305.                     ),
    
  2306.                 ]
    
  2307. 
    
  2308.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2309. 
    
  2310.     def test_unique_constraint_condition_pointing_to_missing_field(self):
    
  2311.         class Model(models.Model):
    
  2312.             age = models.SmallIntegerField()
    
  2313. 
    
  2314.             class Meta:
    
  2315.                 required_db_features = {"supports_partial_indexes"}
    
  2316.                 constraints = [
    
  2317.                     models.UniqueConstraint(
    
  2318.                         name="name",
    
  2319.                         fields=["age"],
    
  2320.                         condition=models.Q(missing_field=2),
    
  2321.                     ),
    
  2322.                 ]
    
  2323. 
    
  2324.         self.assertEqual(
    
  2325.             Model.check(databases=self.databases),
    
  2326.             [
    
  2327.                 Error(
    
  2328.                     "'constraints' refers to the nonexistent field 'missing_field'.",
    
  2329.                     obj=Model,
    
  2330.                     id="models.E012",
    
  2331.                 ),
    
  2332.             ]
    
  2333.             if connection.features.supports_partial_indexes
    
  2334.             else [],
    
  2335.         )
    
  2336. 
    
  2337.     def test_unique_constraint_condition_pointing_to_joined_fields(self):
    
  2338.         class Model(models.Model):
    
  2339.             age = models.SmallIntegerField()
    
  2340.             parent = models.ForeignKey("self", models.CASCADE)
    
  2341. 
    
  2342.             class Meta:
    
  2343.                 required_db_features = {"supports_partial_indexes"}
    
  2344.                 constraints = [
    
  2345.                     models.UniqueConstraint(
    
  2346.                         name="name",
    
  2347.                         fields=["age"],
    
  2348.                         condition=models.Q(parent__age__lt=2),
    
  2349.                     ),
    
  2350.                 ]
    
  2351. 
    
  2352.         self.assertEqual(
    
  2353.             Model.check(databases=self.databases),
    
  2354.             [
    
  2355.                 Error(
    
  2356.                     "'constraints' refers to the joined field 'parent__age__lt'.",
    
  2357.                     obj=Model,
    
  2358.                     id="models.E041",
    
  2359.                 )
    
  2360.             ]
    
  2361.             if connection.features.supports_partial_indexes
    
  2362.             else [],
    
  2363.         )
    
  2364. 
    
  2365.     def test_unique_constraint_pointing_to_reverse_o2o(self):
    
  2366.         class Model(models.Model):
    
  2367.             parent = models.OneToOneField("self", models.CASCADE)
    
  2368. 
    
  2369.             class Meta:
    
  2370.                 required_db_features = {"supports_partial_indexes"}
    
  2371.                 constraints = [
    
  2372.                     models.UniqueConstraint(
    
  2373.                         fields=["parent"],
    
  2374.                         name="name",
    
  2375.                         condition=models.Q(model__isnull=True),
    
  2376.                     ),
    
  2377.                 ]
    
  2378. 
    
  2379.         self.assertEqual(
    
  2380.             Model.check(databases=self.databases),
    
  2381.             [
    
  2382.                 Error(
    
  2383.                     "'constraints' refers to the nonexistent field 'model'.",
    
  2384.                     obj=Model,
    
  2385.                     id="models.E012",
    
  2386.                 ),
    
  2387.             ]
    
  2388.             if connection.features.supports_partial_indexes
    
  2389.             else [],
    
  2390.         )
    
  2391. 
    
  2392.     def test_deferrable_unique_constraint(self):
    
  2393.         class Model(models.Model):
    
  2394.             age = models.IntegerField()
    
  2395. 
    
  2396.             class Meta:
    
  2397.                 constraints = [
    
  2398.                     models.UniqueConstraint(
    
  2399.                         fields=["age"],
    
  2400.                         name="unique_age_deferrable",
    
  2401.                         deferrable=models.Deferrable.DEFERRED,
    
  2402.                     ),
    
  2403.                 ]
    
  2404. 
    
  2405.         errors = Model.check(databases=self.databases)
    
  2406.         expected = (
    
  2407.             []
    
  2408.             if connection.features.supports_deferrable_unique_constraints
    
  2409.             else [
    
  2410.                 Warning(
    
  2411.                     "%s does not support deferrable unique constraints."
    
  2412.                     % connection.display_name,
    
  2413.                     hint=(
    
  2414.                         "A constraint won't be created. Silence this warning if "
    
  2415.                         "you don't care about it."
    
  2416.                     ),
    
  2417.                     obj=Model,
    
  2418.                     id="models.W038",
    
  2419.                 ),
    
  2420.             ]
    
  2421.         )
    
  2422.         self.assertEqual(errors, expected)
    
  2423. 
    
  2424.     def test_deferrable_unique_constraint_required_db_features(self):
    
  2425.         class Model(models.Model):
    
  2426.             age = models.IntegerField()
    
  2427. 
    
  2428.             class Meta:
    
  2429.                 required_db_features = {"supports_deferrable_unique_constraints"}
    
  2430.                 constraints = [
    
  2431.                     models.UniqueConstraint(
    
  2432.                         fields=["age"],
    
  2433.                         name="unique_age_deferrable",
    
  2434.                         deferrable=models.Deferrable.IMMEDIATE,
    
  2435.                     ),
    
  2436.                 ]
    
  2437. 
    
  2438.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2439. 
    
  2440.     def test_unique_constraint_pointing_to_missing_field(self):
    
  2441.         class Model(models.Model):
    
  2442.             class Meta:
    
  2443.                 constraints = [
    
  2444.                     models.UniqueConstraint(fields=["missing_field"], name="name")
    
  2445.                 ]
    
  2446. 
    
  2447.         self.assertEqual(
    
  2448.             Model.check(databases=self.databases),
    
  2449.             [
    
  2450.                 Error(
    
  2451.                     "'constraints' refers to the nonexistent field 'missing_field'.",
    
  2452.                     obj=Model,
    
  2453.                     id="models.E012",
    
  2454.                 ),
    
  2455.             ],
    
  2456.         )
    
  2457. 
    
  2458.     def test_unique_constraint_pointing_to_m2m_field(self):
    
  2459.         class Model(models.Model):
    
  2460.             m2m = models.ManyToManyField("self")
    
  2461. 
    
  2462.             class Meta:
    
  2463.                 constraints = [models.UniqueConstraint(fields=["m2m"], name="name")]
    
  2464. 
    
  2465.         self.assertEqual(
    
  2466.             Model.check(databases=self.databases),
    
  2467.             [
    
  2468.                 Error(
    
  2469.                     "'constraints' refers to a ManyToManyField 'm2m', but "
    
  2470.                     "ManyToManyFields are not permitted in 'constraints'.",
    
  2471.                     obj=Model,
    
  2472.                     id="models.E013",
    
  2473.                 ),
    
  2474.             ],
    
  2475.         )
    
  2476. 
    
  2477.     def test_unique_constraint_pointing_to_non_local_field(self):
    
  2478.         class Parent(models.Model):
    
  2479.             field1 = models.IntegerField()
    
  2480. 
    
  2481.         class Child(Parent):
    
  2482.             field2 = models.IntegerField()
    
  2483. 
    
  2484.             class Meta:
    
  2485.                 constraints = [
    
  2486.                     models.UniqueConstraint(fields=["field2", "field1"], name="name"),
    
  2487.                 ]
    
  2488. 
    
  2489.         self.assertEqual(
    
  2490.             Child.check(databases=self.databases),
    
  2491.             [
    
  2492.                 Error(
    
  2493.                     "'constraints' refers to field 'field1' which is not local to "
    
  2494.                     "model 'Child'.",
    
  2495.                     hint="This issue may be caused by multi-table inheritance.",
    
  2496.                     obj=Child,
    
  2497.                     id="models.E016",
    
  2498.                 ),
    
  2499.             ],
    
  2500.         )
    
  2501. 
    
  2502.     def test_unique_constraint_pointing_to_fk(self):
    
  2503.         class Target(models.Model):
    
  2504.             pass
    
  2505. 
    
  2506.         class Model(models.Model):
    
  2507.             fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
    
  2508.             fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
    
  2509. 
    
  2510.             class Meta:
    
  2511.                 constraints = [
    
  2512.                     models.UniqueConstraint(fields=["fk_1_id", "fk_2"], name="name"),
    
  2513.                 ]
    
  2514. 
    
  2515.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2516. 
    
  2517.     def test_unique_constraint_with_include(self):
    
  2518.         class Model(models.Model):
    
  2519.             age = models.IntegerField()
    
  2520. 
    
  2521.             class Meta:
    
  2522.                 constraints = [
    
  2523.                     models.UniqueConstraint(
    
  2524.                         fields=["age"],
    
  2525.                         name="unique_age_include_id",
    
  2526.                         include=["id"],
    
  2527.                     ),
    
  2528.                 ]
    
  2529. 
    
  2530.         errors = Model.check(databases=self.databases)
    
  2531.         expected = (
    
  2532.             []
    
  2533.             if connection.features.supports_covering_indexes
    
  2534.             else [
    
  2535.                 Warning(
    
  2536.                     "%s does not support unique constraints with non-key columns."
    
  2537.                     % connection.display_name,
    
  2538.                     hint=(
    
  2539.                         "A constraint won't be created. Silence this warning if "
    
  2540.                         "you don't care about it."
    
  2541.                     ),
    
  2542.                     obj=Model,
    
  2543.                     id="models.W039",
    
  2544.                 ),
    
  2545.             ]
    
  2546.         )
    
  2547.         self.assertEqual(errors, expected)
    
  2548. 
    
  2549.     def test_unique_constraint_with_include_required_db_features(self):
    
  2550.         class Model(models.Model):
    
  2551.             age = models.IntegerField()
    
  2552. 
    
  2553.             class Meta:
    
  2554.                 required_db_features = {"supports_covering_indexes"}
    
  2555.                 constraints = [
    
  2556.                     models.UniqueConstraint(
    
  2557.                         fields=["age"],
    
  2558.                         name="unique_age_include_id",
    
  2559.                         include=["id"],
    
  2560.                     ),
    
  2561.                 ]
    
  2562. 
    
  2563.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2564. 
    
  2565.     @skipUnlessDBFeature("supports_covering_indexes")
    
  2566.     def test_unique_constraint_include_pointing_to_missing_field(self):
    
  2567.         class Model(models.Model):
    
  2568.             class Meta:
    
  2569.                 constraints = [
    
  2570.                     models.UniqueConstraint(
    
  2571.                         fields=["id"],
    
  2572.                         include=["missing_field"],
    
  2573.                         name="name",
    
  2574.                     ),
    
  2575.                 ]
    
  2576. 
    
  2577.         self.assertEqual(
    
  2578.             Model.check(databases=self.databases),
    
  2579.             [
    
  2580.                 Error(
    
  2581.                     "'constraints' refers to the nonexistent field 'missing_field'.",
    
  2582.                     obj=Model,
    
  2583.                     id="models.E012",
    
  2584.                 ),
    
  2585.             ],
    
  2586.         )
    
  2587. 
    
  2588.     @skipUnlessDBFeature("supports_covering_indexes")
    
  2589.     def test_unique_constraint_include_pointing_to_m2m_field(self):
    
  2590.         class Model(models.Model):
    
  2591.             m2m = models.ManyToManyField("self")
    
  2592. 
    
  2593.             class Meta:
    
  2594.                 constraints = [
    
  2595.                     models.UniqueConstraint(
    
  2596.                         fields=["id"],
    
  2597.                         include=["m2m"],
    
  2598.                         name="name",
    
  2599.                     ),
    
  2600.                 ]
    
  2601. 
    
  2602.         self.assertEqual(
    
  2603.             Model.check(databases=self.databases),
    
  2604.             [
    
  2605.                 Error(
    
  2606.                     "'constraints' refers to a ManyToManyField 'm2m', but "
    
  2607.                     "ManyToManyFields are not permitted in 'constraints'.",
    
  2608.                     obj=Model,
    
  2609.                     id="models.E013",
    
  2610.                 ),
    
  2611.             ],
    
  2612.         )
    
  2613. 
    
  2614.     @skipUnlessDBFeature("supports_covering_indexes")
    
  2615.     def test_unique_constraint_include_pointing_to_non_local_field(self):
    
  2616.         class Parent(models.Model):
    
  2617.             field1 = models.IntegerField()
    
  2618. 
    
  2619.         class Child(Parent):
    
  2620.             field2 = models.IntegerField()
    
  2621. 
    
  2622.             class Meta:
    
  2623.                 constraints = [
    
  2624.                     models.UniqueConstraint(
    
  2625.                         fields=["field2"],
    
  2626.                         include=["field1"],
    
  2627.                         name="name",
    
  2628.                     ),
    
  2629.                 ]
    
  2630. 
    
  2631.         self.assertEqual(
    
  2632.             Child.check(databases=self.databases),
    
  2633.             [
    
  2634.                 Error(
    
  2635.                     "'constraints' refers to field 'field1' which is not local to "
    
  2636.                     "model 'Child'.",
    
  2637.                     hint="This issue may be caused by multi-table inheritance.",
    
  2638.                     obj=Child,
    
  2639.                     id="models.E016",
    
  2640.                 ),
    
  2641.             ],
    
  2642.         )
    
  2643. 
    
  2644.     @skipUnlessDBFeature("supports_covering_indexes")
    
  2645.     def test_unique_constraint_include_pointing_to_fk(self):
    
  2646.         class Target(models.Model):
    
  2647.             pass
    
  2648. 
    
  2649.         class Model(models.Model):
    
  2650.             fk_1 = models.ForeignKey(Target, models.CASCADE, related_name="target_1")
    
  2651.             fk_2 = models.ForeignKey(Target, models.CASCADE, related_name="target_2")
    
  2652. 
    
  2653.             class Meta:
    
  2654.                 constraints = [
    
  2655.                     models.UniqueConstraint(
    
  2656.                         fields=["id"],
    
  2657.                         include=["fk_1_id", "fk_2"],
    
  2658.                         name="name",
    
  2659.                     ),
    
  2660.                 ]
    
  2661. 
    
  2662.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2663. 
    
  2664.     def test_func_unique_constraint(self):
    
  2665.         class Model(models.Model):
    
  2666.             name = models.CharField(max_length=10)
    
  2667. 
    
  2668.             class Meta:
    
  2669.                 constraints = [
    
  2670.                     models.UniqueConstraint(Lower("name"), name="lower_name_uq"),
    
  2671.                 ]
    
  2672. 
    
  2673.         warn = Warning(
    
  2674.             "%s does not support unique constraints on expressions."
    
  2675.             % connection.display_name,
    
  2676.             hint=(
    
  2677.                 "A constraint won't be created. Silence this warning if you "
    
  2678.                 "don't care about it."
    
  2679.             ),
    
  2680.             obj=Model,
    
  2681.             id="models.W044",
    
  2682.         )
    
  2683.         expected = [] if connection.features.supports_expression_indexes else [warn]
    
  2684.         self.assertEqual(Model.check(databases=self.databases), expected)
    
  2685. 
    
  2686.     def test_func_unique_constraint_required_db_features(self):
    
  2687.         class Model(models.Model):
    
  2688.             name = models.CharField(max_length=10)
    
  2689. 
    
  2690.             class Meta:
    
  2691.                 constraints = [
    
  2692.                     models.UniqueConstraint(Lower("name"), name="lower_name_unq"),
    
  2693.                 ]
    
  2694.                 required_db_features = {"supports_expression_indexes"}
    
  2695. 
    
  2696.         self.assertEqual(Model.check(databases=self.databases), [])
    
  2697. 
    
  2698.     @skipUnlessDBFeature("supports_expression_indexes")
    
  2699.     def test_func_unique_constraint_expression_custom_lookup(self):
    
  2700.         class Model(models.Model):
    
  2701.             height = models.IntegerField()
    
  2702.             weight = models.IntegerField()
    
  2703. 
    
  2704.             class Meta:
    
  2705.                 constraints = [
    
  2706.                     models.UniqueConstraint(
    
  2707.                         models.F("height")
    
  2708.                         / (models.F("weight__abs") + models.Value(5)),
    
  2709.                         name="name",
    
  2710.                     ),
    
  2711.                 ]
    
  2712. 
    
  2713.         with register_lookup(models.IntegerField, Abs):
    
  2714.             self.assertEqual(Model.check(databases=self.databases), [])
    
  2715. 
    
  2716.     @skipUnlessDBFeature("supports_expression_indexes")
    
  2717.     def test_func_unique_constraint_pointing_to_missing_field(self):
    
  2718.         class Model(models.Model):
    
  2719.             class Meta:
    
  2720.                 constraints = [
    
  2721.                     models.UniqueConstraint(Lower("missing_field").desc(), name="name"),
    
  2722.                 ]
    
  2723. 
    
  2724.         self.assertEqual(
    
  2725.             Model.check(databases=self.databases),
    
  2726.             [
    
  2727.                 Error(
    
  2728.                     "'constraints' refers to the nonexistent field 'missing_field'.",
    
  2729.                     obj=Model,
    
  2730.                     id="models.E012",
    
  2731.                 ),
    
  2732.             ],
    
  2733.         )
    
  2734. 
    
  2735.     @skipUnlessDBFeature("supports_expression_indexes")
    
  2736.     def test_func_unique_constraint_pointing_to_missing_field_nested(self):
    
  2737.         class Model(models.Model):
    
  2738.             class Meta:
    
  2739.                 constraints = [
    
  2740.                     models.UniqueConstraint(Abs(Round("missing_field")), name="name"),
    
  2741.                 ]
    
  2742. 
    
  2743.         self.assertEqual(
    
  2744.             Model.check(databases=self.databases),
    
  2745.             [
    
  2746.                 Error(
    
  2747.                     "'constraints' refers to the nonexistent field 'missing_field'.",
    
  2748.                     obj=Model,
    
  2749.                     id="models.E012",
    
  2750.                 ),
    
  2751.             ],
    
  2752.         )
    
  2753. 
    
  2754.     @skipUnlessDBFeature("supports_expression_indexes")
    
  2755.     def test_func_unique_constraint_pointing_to_m2m_field(self):
    
  2756.         class Model(models.Model):
    
  2757.             m2m = models.ManyToManyField("self")
    
  2758. 
    
  2759.             class Meta:
    
  2760.                 constraints = [models.UniqueConstraint(Lower("m2m"), name="name")]
    
  2761. 
    
  2762.         self.assertEqual(
    
  2763.             Model.check(databases=self.databases),
    
  2764.             [
    
  2765.                 Error(
    
  2766.                     "'constraints' refers to a ManyToManyField 'm2m', but "
    
  2767.                     "ManyToManyFields are not permitted in 'constraints'.",
    
  2768.                     obj=Model,
    
  2769.                     id="models.E013",
    
  2770.                 ),
    
  2771.             ],
    
  2772.         )
    
  2773. 
    
  2774.     @skipUnlessDBFeature("supports_expression_indexes")
    
  2775.     def test_func_unique_constraint_pointing_to_non_local_field(self):
    
  2776.         class Foo(models.Model):
    
  2777.             field1 = models.CharField(max_length=15)
    
  2778. 
    
  2779.         class Bar(Foo):
    
  2780.             class Meta:
    
  2781.                 constraints = [models.UniqueConstraint(Lower("field1"), name="name")]
    
  2782. 
    
  2783.         self.assertEqual(
    
  2784.             Bar.check(databases=self.databases),
    
  2785.             [
    
  2786.                 Error(
    
  2787.                     "'constraints' refers to field 'field1' which is not local to "
    
  2788.                     "model 'Bar'.",
    
  2789.                     hint="This issue may be caused by multi-table inheritance.",
    
  2790.                     obj=Bar,
    
  2791.                     id="models.E016",
    
  2792.                 ),
    
  2793.             ],
    
  2794.         )
    
  2795. 
    
  2796.     @skipUnlessDBFeature("supports_expression_indexes")
    
  2797.     def test_func_unique_constraint_pointing_to_fk(self):
    
  2798.         class Foo(models.Model):
    
  2799.             id = models.CharField(primary_key=True, max_length=255)
    
  2800. 
    
  2801.         class Bar(models.Model):
    
  2802.             foo_1 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_1")
    
  2803.             foo_2 = models.ForeignKey(Foo, models.CASCADE, related_name="bar_2")
    
  2804. 
    
  2805.             class Meta:
    
  2806.                 constraints = [
    
  2807.                     models.UniqueConstraint(
    
  2808.                         Lower("foo_1_id"),
    
  2809.                         Lower("foo_2"),
    
  2810.                         name="name",
    
  2811.                     ),
    
  2812.                 ]
    
  2813. 
    
  2814.         self.assertEqual(Bar.check(databases=self.databases), [])