1. from unittest import mock
    
  2. 
    
  3. from django.contrib.contenttypes.checks import check_model_name_lengths
    
  4. from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
    
  5. from django.contrib.contenttypes.models import ContentType
    
  6. from django.core import checks
    
  7. from django.db import models
    
  8. from django.test import SimpleTestCase, override_settings
    
  9. from django.test.utils import isolate_apps
    
  10. 
    
  11. 
    
  12. @isolate_apps("contenttypes_tests", attr_name="apps")
    
  13. class GenericForeignKeyTests(SimpleTestCase):
    
  14.     databases = "__all__"
    
  15. 
    
  16.     def test_missing_content_type_field(self):
    
  17.         class TaggedItem(models.Model):
    
  18.             # no content_type field
    
  19.             object_id = models.PositiveIntegerField()
    
  20.             content_object = GenericForeignKey()
    
  21. 
    
  22.         expected = [
    
  23.             checks.Error(
    
  24.                 "The GenericForeignKey content type references the nonexistent "
    
  25.                 "field 'TaggedItem.content_type'.",
    
  26.                 obj=TaggedItem.content_object,
    
  27.                 id="contenttypes.E002",
    
  28.             )
    
  29.         ]
    
  30.         self.assertEqual(TaggedItem.content_object.check(), expected)
    
  31. 
    
  32.     def test_invalid_content_type_field(self):
    
  33.         class Model(models.Model):
    
  34.             content_type = models.IntegerField()  # should be ForeignKey
    
  35.             object_id = models.PositiveIntegerField()
    
  36.             content_object = GenericForeignKey("content_type", "object_id")
    
  37. 
    
  38.         self.assertEqual(
    
  39.             Model.content_object.check(),
    
  40.             [
    
  41.                 checks.Error(
    
  42.                     "'Model.content_type' is not a ForeignKey.",
    
  43.                     hint=(
    
  44.                         "GenericForeignKeys must use a ForeignKey to "
    
  45.                         "'contenttypes.ContentType' as the 'content_type' field."
    
  46.                     ),
    
  47.                     obj=Model.content_object,
    
  48.                     id="contenttypes.E003",
    
  49.                 )
    
  50.             ],
    
  51.         )
    
  52. 
    
  53.     def test_content_type_field_pointing_to_wrong_model(self):
    
  54.         class Model(models.Model):
    
  55.             content_type = models.ForeignKey(
    
  56.                 "self", models.CASCADE
    
  57.             )  # should point to ContentType
    
  58.             object_id = models.PositiveIntegerField()
    
  59.             content_object = GenericForeignKey("content_type", "object_id")
    
  60. 
    
  61.         self.assertEqual(
    
  62.             Model.content_object.check(),
    
  63.             [
    
  64.                 checks.Error(
    
  65.                     "'Model.content_type' is not a ForeignKey to "
    
  66.                     "'contenttypes.ContentType'.",
    
  67.                     hint=(
    
  68.                         "GenericForeignKeys must use a ForeignKey to "
    
  69.                         "'contenttypes.ContentType' as the 'content_type' field."
    
  70.                     ),
    
  71.                     obj=Model.content_object,
    
  72.                     id="contenttypes.E004",
    
  73.                 )
    
  74.             ],
    
  75.         )
    
  76. 
    
  77.     def test_missing_object_id_field(self):
    
  78.         class TaggedItem(models.Model):
    
  79.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  80.             # missing object_id field
    
  81.             content_object = GenericForeignKey()
    
  82. 
    
  83.         self.assertEqual(
    
  84.             TaggedItem.content_object.check(),
    
  85.             [
    
  86.                 checks.Error(
    
  87.                     "The GenericForeignKey object ID references the nonexistent "
    
  88.                     "field 'object_id'.",
    
  89.                     obj=TaggedItem.content_object,
    
  90.                     id="contenttypes.E001",
    
  91.                 )
    
  92.             ],
    
  93.         )
    
  94. 
    
  95.     def test_field_name_ending_with_underscore(self):
    
  96.         class Model(models.Model):
    
  97.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  98.             object_id = models.PositiveIntegerField()
    
  99.             content_object_ = GenericForeignKey("content_type", "object_id")
    
  100. 
    
  101.         self.assertEqual(
    
  102.             Model.content_object_.check(),
    
  103.             [
    
  104.                 checks.Error(
    
  105.                     "Field names must not end with an underscore.",
    
  106.                     obj=Model.content_object_,
    
  107.                     id="fields.E001",
    
  108.                 )
    
  109.             ],
    
  110.         )
    
  111. 
    
  112.     @override_settings(
    
  113.         INSTALLED_APPS=[
    
  114.             "django.contrib.auth",
    
  115.             "django.contrib.contenttypes",
    
  116.             "contenttypes_tests",
    
  117.         ]
    
  118.     )
    
  119.     def test_generic_foreign_key_checks_are_performed(self):
    
  120.         class Model(models.Model):
    
  121.             content_object = GenericForeignKey()
    
  122. 
    
  123.         with mock.patch.object(GenericForeignKey, "check") as check:
    
  124.             checks.run_checks(app_configs=self.apps.get_app_configs())
    
  125.         check.assert_called_once_with()
    
  126. 
    
  127. 
    
  128. @isolate_apps("contenttypes_tests")
    
  129. class GenericRelationTests(SimpleTestCase):
    
  130.     def test_valid_generic_relationship(self):
    
  131.         class TaggedItem(models.Model):
    
  132.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  133.             object_id = models.PositiveIntegerField()
    
  134.             content_object = GenericForeignKey()
    
  135. 
    
  136.         class Bookmark(models.Model):
    
  137.             tags = GenericRelation("TaggedItem")
    
  138. 
    
  139.         self.assertEqual(Bookmark.tags.field.check(), [])
    
  140. 
    
  141.     def test_valid_generic_relationship_with_explicit_fields(self):
    
  142.         class TaggedItem(models.Model):
    
  143.             custom_content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  144.             custom_object_id = models.PositiveIntegerField()
    
  145.             content_object = GenericForeignKey(
    
  146.                 "custom_content_type", "custom_object_id"
    
  147.             )
    
  148. 
    
  149.         class Bookmark(models.Model):
    
  150.             tags = GenericRelation(
    
  151.                 "TaggedItem",
    
  152.                 content_type_field="custom_content_type",
    
  153.                 object_id_field="custom_object_id",
    
  154.             )
    
  155. 
    
  156.         self.assertEqual(Bookmark.tags.field.check(), [])
    
  157. 
    
  158.     def test_pointing_to_missing_model(self):
    
  159.         class Model(models.Model):
    
  160.             rel = GenericRelation("MissingModel")
    
  161. 
    
  162.         self.assertEqual(
    
  163.             Model.rel.field.check(),
    
  164.             [
    
  165.                 checks.Error(
    
  166.                     "Field defines a relation with model 'MissingModel', "
    
  167.                     "which is either not installed, or is abstract.",
    
  168.                     obj=Model.rel.field,
    
  169.                     id="fields.E300",
    
  170.                 )
    
  171.             ],
    
  172.         )
    
  173. 
    
  174.     def test_valid_self_referential_generic_relationship(self):
    
  175.         class Model(models.Model):
    
  176.             rel = GenericRelation("Model")
    
  177.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  178.             object_id = models.PositiveIntegerField()
    
  179.             content_object = GenericForeignKey("content_type", "object_id")
    
  180. 
    
  181.         self.assertEqual(Model.rel.field.check(), [])
    
  182. 
    
  183.     def test_missing_generic_foreign_key(self):
    
  184.         class TaggedItem(models.Model):
    
  185.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  186.             object_id = models.PositiveIntegerField()
    
  187. 
    
  188.         class Bookmark(models.Model):
    
  189.             tags = GenericRelation("TaggedItem")
    
  190. 
    
  191.         self.assertEqual(
    
  192.             Bookmark.tags.field.check(),
    
  193.             [
    
  194.                 checks.Error(
    
  195.                     "The GenericRelation defines a relation with the model "
    
  196.                     "'contenttypes_tests.TaggedItem', but that model does not have a "
    
  197.                     "GenericForeignKey.",
    
  198.                     obj=Bookmark.tags.field,
    
  199.                     id="contenttypes.E004",
    
  200.                 )
    
  201.             ],
    
  202.         )
    
  203. 
    
  204.     @override_settings(TEST_SWAPPED_MODEL="contenttypes_tests.Replacement")
    
  205.     def test_pointing_to_swapped_model(self):
    
  206.         class Replacement(models.Model):
    
  207.             pass
    
  208. 
    
  209.         class SwappedModel(models.Model):
    
  210.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  211.             object_id = models.PositiveIntegerField()
    
  212.             content_object = GenericForeignKey()
    
  213. 
    
  214.             class Meta:
    
  215.                 swappable = "TEST_SWAPPED_MODEL"
    
  216. 
    
  217.         class Model(models.Model):
    
  218.             rel = GenericRelation("SwappedModel")
    
  219. 
    
  220.         self.assertEqual(
    
  221.             Model.rel.field.check(),
    
  222.             [
    
  223.                 checks.Error(
    
  224.                     "Field defines a relation with the model "
    
  225.                     "'contenttypes_tests.SwappedModel', "
    
  226.                     "which has been swapped out.",
    
  227.                     hint=(
    
  228.                         "Update the relation to point at 'settings.TEST_SWAPPED_MODEL'."
    
  229.                     ),
    
  230.                     obj=Model.rel.field,
    
  231.                     id="fields.E301",
    
  232.                 )
    
  233.             ],
    
  234.         )
    
  235. 
    
  236.     def test_field_name_ending_with_underscore(self):
    
  237.         class TaggedItem(models.Model):
    
  238.             content_type = models.ForeignKey(ContentType, models.CASCADE)
    
  239.             object_id = models.PositiveIntegerField()
    
  240.             content_object = GenericForeignKey()
    
  241. 
    
  242.         class InvalidBookmark(models.Model):
    
  243.             tags_ = GenericRelation("TaggedItem")
    
  244. 
    
  245.         self.assertEqual(
    
  246.             InvalidBookmark.tags_.field.check(),
    
  247.             [
    
  248.                 checks.Error(
    
  249.                     "Field names must not end with an underscore.",
    
  250.                     obj=InvalidBookmark.tags_.field,
    
  251.                     id="fields.E001",
    
  252.                 )
    
  253.             ],
    
  254.         )
    
  255. 
    
  256. 
    
  257. @isolate_apps("contenttypes_tests", attr_name="apps")
    
  258. class ModelCheckTests(SimpleTestCase):
    
  259.     def test_model_name_too_long(self):
    
  260.         model = type("A" * 101, (models.Model,), {"__module__": self.__module__})
    
  261.         self.assertEqual(
    
  262.             check_model_name_lengths(self.apps.get_app_configs()),
    
  263.             [
    
  264.                 checks.Error(
    
  265.                     "Model names must be at most 100 characters (got 101).",
    
  266.                     obj=model,
    
  267.                     id="contenttypes.E005",
    
  268.                 )
    
  269.             ],
    
  270.         )
    
  271. 
    
  272.     def test_model_name_max_length(self):
    
  273.         type("A" * 100, (models.Model,), {"__module__": self.__module__})
    
  274.         self.assertEqual(check_model_name_lengths(self.apps.get_app_configs()), [])