def _check_relation(self, obj, parent_model): # There's no FK, but we do need to confirm that the ct_field and ct_fk_field are valid, # and that they are part of a GenericForeignKey. gfks = [ f for f in obj.model._meta.private_fields if isinstance(f, GenericForeignKey) ] if not gfks: return [ checks.Error( "'%s.%s' has no GenericForeignKey." % (obj.model._meta.app_label, obj.model._meta.object_name), obj=obj.__class__, id='admin.E301') ] else: # Check that the ct_field and ct_fk_fields exist try: obj.model._meta.get_field(obj.ct_field) except FieldDoesNotExist: return [ checks.Error( "'ct_field' references '%s', which is not a field on '%s.%s'." % (obj.ct_field, obj.model._meta.app_label, obj.model._meta.object_name), obj=obj.__class__, id='admin.E302') ] try: obj.model._meta.get_field(obj.ct_fk_field) except FieldDoesNotExist: return [ checks.Error( "'ct_fk_field' references '%s', which is not a field on '%s.%s'." % (obj.ct_fk_field, obj.model._meta.app_label, obj.model._meta.object_name), obj=obj.__class__, id='admin.E303') ] # There's one or more GenericForeignKeys; make sure that one of them # uses the right ct_field and ct_fk_field. for gfk in gfks: if gfk.ct_field == obj.ct_field and gfk.fk_field == obj.ct_fk_field: return [] return [ checks.Error( "'%s.%s' has no GenericForeignKey using content type field '%s' and object ID field '%s'." % (obj.model._meta.app_label, obj.model._meta.object_name, obj.ct_field, obj.ct_fk_field), obj=obj.__class__, id='admin.E304') ]
def check_field_type(self, field, field_type): """ MySQL has the following field length restriction: No character (varchar) fields can have a length exceeding 255 characters if they have a unique index on them. MySQL doesn't support a database index on some data types. """ errors = [] if (field_type.startswith('varchar') and field.unique and (field.max_length is None or int(field.max_length) > 255)): errors.append( checks.Error( 'MySQL does not allow unique CharFields to have a max_length > 255.', obj=field, id='mysql.E001', )) if field.db_index and field_type.lower( ) in self.connection._limited_data_types: errors.append( checks.Warning( 'MySQL does not support a database index on %s columns.' % field_type, hint=("An index won't be created. Silence this warning if " "you don't care about it."), obj=field, id='fields.W162', )) return errors
def test_pointing_to_swapped_model(self): class Replacement(models.Model): pass class SwappedModel(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey() class Meta: swappable = 'TEST_SWAPPED_MODEL' class Model(models.Model): rel = GenericRelation('SwappedModel') self.assertEqual(Model.rel.field.check(), [ checks.Error( "Field defines a relation with the model " "'contenttypes_tests.SwappedModel', " "which has been swapped out.", hint="Update the relation to point at 'settings.TEST_SWAPPED_MODEL'.", obj=Model.rel.field, id='fields.E301', ) ])
def _check_content_type_field(self): """ Check if field named `field_name` in model `model` exists and is a valid content_type field (is a ForeignKey to ContentType). """ try: field = self.model._meta.get_field(self.ct_field) except FieldDoesNotExist: return [ checks.Error( "The GenericForeignKey content type references the " "nonexistent field '%s.%s'." % (self.model._meta.object_name, self.ct_field), obj=self, id='contenttypes.E002', ) ] else: if not isinstance(field, models.ForeignKey): return [ checks.Error( "'%s.%s' is not a ForeignKey." % (self.model._meta.object_name, self.ct_field), hint= ("GenericForeignKeys must use a ForeignKey to " "'contenttypes.ContentType' as the 'content_type' field." ), obj=self, id='contenttypes.E003', ) ] elif field.remote_field.model != ContentType: return [ checks.Error( "'%s.%s' is not a ForeignKey to 'contenttypes.ContentType'." % (self.model._meta.object_name, self.ct_field), hint= ("GenericForeignKeys must use a ForeignKey to " "'contenttypes.ContentType' as the 'content_type' field." ), obj=self, id='contenttypes.E004', ) ] else: return []
def test_model_name_too_long(self): model = type('A' * 101, (models.Model,), {'__module__': self.__module__}) self.assertEqual(check_model_name_lengths(self.apps.get_app_configs()), [ checks.Error( 'Model names must be at most 100 characters (got 101).', obj=model, id='contenttypes.E005', ) ])
def check(self, **kwargs): errors = super().check(**kwargs) if self.base_field.remote_field: errors.append( checks.Error('Base field for array cannot be a related field.', obj=self, id='postgres.E002')) else: # Remove the field name checks as they are not needed here. base_errors = self.base_field.check() if base_errors: messages = '\n '.join('%s (%s)' % (error.msg, error.id) for error in base_errors) errors.append( checks.Error('Base field for array has errors:\n %s' % messages, obj=self, id='postgres.E001')) return errors
def _check_primary_key(self): if self._primary_key_set_explicitly: return [ checks.Error( "'primary_key' is not a valid argument for a %s." % self.__class__.__name__, obj=self, id='fields.E201', ) ] else: return []
def _check_field_name(self): if self.name.endswith("_"): return [ checks.Error( 'Field names must not end with an underscore.', obj=self, id='fields.E001', ) ] else: return []
def test_pointing_to_missing_model(self): class Model(models.Model): rel = GenericRelation('MissingModel') self.assertEqual(Model.rel.field.check(), [ checks.Error( "Field defines a relation with model 'MissingModel', " "which is either not installed, or is abstract.", obj=Model.rel.field, id='fields.E300', ) ])
def _check_upload_to(self): if isinstance(self.upload_to, str) and self.upload_to.startswith('/'): return [ checks.Error( "%s's 'upload_to' argument must be a relative path, not an " "absolute path." % self.__class__.__name__, obj=self, id='fields.E202', hint='Remove the leading slash.', ) ] else: return []
def test_field_name_ending_with_underscore(self): class Model(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) object_id = models.PositiveIntegerField() content_object_ = GenericForeignKey('content_type', 'object_id') self.assertEqual(Model.content_object_.check(), [ checks.Error( 'Field names must not end with an underscore.', obj=Model.content_object_, id='fields.E001', ) ])
def _check_object_id_field(self): try: self.model._meta.get_field(self.fk_field) except FieldDoesNotExist: return [ checks.Error( "The GenericForeignKey object ID references the " "nonexistent field '%s'." % self.fk_field, obj=self, id='contenttypes.E001', ) ] else: return []
def test_invalid_name(self): class InvalidArticle(models.Model): on_site = CurrentSiteManager("places_this_article_should_appear") errors = InvalidArticle.check() expected = [ checks.Error( "CurrentSiteManager could not find a field named " "'places_this_article_should_appear'.", obj=InvalidArticle.on_site, id='sites.E001', ) ] self.assertEqual(errors, expected)
def test_missing_object_id_field(self): class TaggedItem(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) # missing object_id field content_object = GenericForeignKey() self.assertEqual(TaggedItem.content_object.check(), [ checks.Error( "The GenericForeignKey object ID references the nonexistent " "field 'object_id'.", obj=TaggedItem.content_object, id='contenttypes.E001', ) ])
def test_CommaSeparatedIntegerField_deprecated(self): class CommaSeparatedIntegerModel(models.Model): csi = models.CommaSeparatedIntegerField(max_length=64) model = CommaSeparatedIntegerModel() self.assertEqual( model.check(), [checks.Error( 'CommaSeparatedIntegerField is removed except for support in ' 'historical migrations.', hint='Use CharField(validators=[validate_comma_separated_integer_list]) instead.', obj=CommaSeparatedIntegerModel._meta.get_field('csi'), id='fields.E901', )], )
def test_IPAddressField_deprecated(self): class IPAddressModel(models.Model): ip = models.IPAddressField() model = IPAddressModel() self.assertEqual( model.check(), [checks.Error( 'IPAddressField has been removed except for support in ' 'historical migrations.', hint='Use GenericIPAddressField instead.', obj=IPAddressModel._meta.get_field('ip'), id='fields.E900', )], )
def _check_image_library_installed(self): try: from PIL import Image # NOQA except ImportError: return [ checks.Error( 'Cannot use ImageField because Pillow is not installed.', hint=('Get Pillow at https://pypi.org/project/Pillow/ ' 'or run command "pip install Pillow".'), obj=self, id='fields.E210', ) ] else: return []
def test_new_fields(self): class NoNewFields(Person): newfield = models.BooleanField() class Meta: proxy = True errors = NoNewFields.check() expected = [ checks.Error( "Proxy model 'NoNewFields' contains model fields.", id='models.E017', ) ] self.assertEqual(errors, expected)
def test_missing_content_type_field(self): class TaggedItem(models.Model): # no content_type field object_id = models.PositiveIntegerField() content_object = GenericForeignKey() expected = [ checks.Error( "The GenericForeignKey content type references the nonexistent " "field 'TaggedItem.content_type'.", obj=TaggedItem.content_object, id='contenttypes.E002', ) ] self.assertEqual(TaggedItem.content_object.check(), expected)
def test_default_details(self): class MyField(models.Field): system_check_removed_details = {} class Model(models.Model): name = MyField() model = Model() self.assertEqual(model.check(), [ checks.Error( msg= 'MyField has been removed except for support in historical migrations.', obj=Model._meta.get_field('name'), id='fields.EXXX', ) ])
def test_field_name_ending_with_underscore(self): class TaggedItem(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) object_id = models.PositiveIntegerField() content_object = GenericForeignKey() class InvalidBookmark(models.Model): tags_ = GenericRelation('TaggedItem') self.assertEqual(InvalidBookmark.tags_.field.check(), [ checks.Error( 'Field names must not end with an underscore.', obj=InvalidBookmark.tags_.field, id='fields.E001', ) ])
def test_invalid_field_type(self): class ConfusedArticle(models.Model): site = models.IntegerField() on_site = CurrentSiteManager() errors = ConfusedArticle.check() expected = [ checks.Error( "CurrentSiteManager cannot use 'ConfusedArticle.site' as it is " "not a foreign key or a many-to-many field.", obj=ConfusedArticle.on_site, id='sites.E002', ) ] self.assertEqual(errors, expected)
def test_content_type_field_pointing_to_wrong_model(self): class Model(models.Model): content_type = models.ForeignKey('self', models.CASCADE) # should point to ContentType object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') self.assertEqual(Model.content_object.check(), [ checks.Error( "'Model.content_type' is not a ForeignKey to 'contenttypes.ContentType'.", hint=( "GenericForeignKeys must use a ForeignKey to " "'contenttypes.ContentType' as the 'content_type' field." ), obj=Model.content_object, id='contenttypes.E004', ) ])
def test_invalid_content_type_field(self): class Model(models.Model): content_type = models.IntegerField() # should be ForeignKey object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') self.assertEqual(Model.content_object.check(), [ checks.Error( "'Model.content_type' is not a ForeignKey.", hint=( "GenericForeignKeys must use a ForeignKey to " "'contenttypes.ContentType' as the 'content_type' field." ), obj=Model.content_object, id='contenttypes.E003', ) ])
def test_missing_generic_foreign_key(self): class TaggedItem(models.Model): content_type = models.ForeignKey(ContentType, models.CASCADE) object_id = models.PositiveIntegerField() class Bookmark(models.Model): tags = GenericRelation('TaggedItem') self.assertEqual(Bookmark.tags.field.check(), [ checks.Error( "The GenericRelation defines a relation with the model " "'contenttypes_tests.TaggedItem', but that model does not have a " "GenericForeignKey.", obj=Bookmark.tags.field, id='contenttypes.E004', ) ])
def test_user_specified_details(self): class MyField(models.Field): system_check_removed_details = { 'msg': 'Support for this field is gone.', 'hint': 'Use something else.', 'id': 'fields.E999', } class Model(models.Model): name = MyField() model = Model() self.assertEqual(model.check(), [ checks.Error( msg='Support for this field is gone.', hint='Use something else.', obj=Model._meta.get_field('name'), id='fields.E999', ) ])
def _check_generic_foreign_key_existence(self): target = self.remote_field.model if isinstance(target, ModelBase): fields = target._meta.private_fields if any( self._is_matching_generic_foreign_key(field) for field in fields): return [] else: return [ checks.Error( "The GenericRelation defines a relation with the model " "'%s.%s', but that model does not have a GenericForeignKey." % (target._meta.app_label, target._meta.object_name), obj=self, id='contenttypes.E004', ) ] else: return []