def _check_fields(self, obj): """ Check that `fields` only refer to existing fields, doesn't contain duplicates. Check if at most one of `fields` and `fieldsets` is defined. """ if obj.fields is None: return [] elif not isinstance(obj.fields, (list, tuple)): return must_be('a list or tuple', option='fields', obj=obj, id='admin.E004') elif obj.fieldsets: return [ checks.Error( "Both 'fieldsets' and 'fields' are specified.", obj=obj.__class__, id='admin.E005', ) ] fields = flatten(obj.fields) if len(fields) != len(set(fields)): return [ checks.Error( "The value of 'fields' contains duplicate field(s).", obj=obj.__class__, id='admin.E006', ) ] return list( chain.from_iterable( self._check_field_spec(obj, obj.model, field_name, 'fields') for field_name in obj.fields))
def _check_field_name(self): field_name = self._get_field_name() try: field = self.model._meta.get_field(field_name) except FieldDoesNotExist: return [ checks.Error( "CurrentSiteManager could not find a field named '%s'." % field_name, obj=self, id='sites.E001', ) ] if not field.many_to_many and not isinstance(field, (models.ForeignKey)): return [ checks.Error( "CurrentSiteManager cannot use '%s.%s' as it is not a foreign key or a many-to-many field." % (self.model._meta.object_name, field_name), obj=self, id='sites.E002', ) ] return []
def _check_inlines_item(self, obj, model, inline, label): """ Check one inline model admin. """ inline_label = inline.__module__ + '.' + inline.__name__ from server.contrib.admin.options import InlineModelAdmin if not issubclass(inline, InlineModelAdmin): return [ checks.Error( "'%s' must inherit from 'InlineModelAdmin'." % inline_label, obj=obj.__class__, id='admin.E104', ) ] elif not inline.model: return [ checks.Error( "'%s' must have a 'model' attribute." % inline_label, obj=obj.__class__, id='admin.E105', ) ] elif not issubclass(inline.model, models.Model): return must_be('a Model', option='%s.model' % inline_label, obj=obj, id='admin.E106') else: return inline(model, obj.admin_site).check()
def _check_list_filter_item(self, obj, model, item, label): """ Check one item of `list_filter`, i.e. check if it is one of three options: 1. 'field' -- a basic field filter, possibly w/ relationships (e.g. 'field__rel') 2. ('field', SomeFieldListFilter) - a field-based list filter class 3. SomeListFilter - a non-field list filter class """ from server.contrib.admin import ListFilter, FieldListFilter if callable(item) and not isinstance(item, models.Field): # If item is option 3, it should be a ListFilter... if not issubclass(item, ListFilter): return must_inherit_from(parent='ListFilter', option=label, obj=obj, id='admin.E113') # ... but not a FieldListFilter. elif issubclass(item, FieldListFilter): return [ checks.Error( "The value of '%s' must not inherit from 'FieldListFilter'." % label, obj=obj.__class__, id='admin.E114', ) ] else: return [] elif isinstance(item, (tuple, list)): # item is option #2 field, list_filter_class = item if not issubclass(list_filter_class, FieldListFilter): return must_inherit_from(parent='FieldListFilter', option='%s[1]' % label, obj=obj, id='admin.E115') else: return [] else: # item is option #1 field = item # Validate the field string try: get_fields_from_path(model, field) except (NotRelationField, FieldDoesNotExist): return [ checks.Error( "The value of '%s' refers to '%s', which does not refer to a Field." % (label, field), obj=obj.__class__, id='admin.E116', ) ] else: return []
def _check_list_editable_item(self, obj, model, field_name, label): try: field = model._meta.get_field(field_name) except FieldDoesNotExist: return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E121') else: if field_name not in obj.list_display: return [ checks.Error( "The value of '%s' refers to '%s', which is not " "contained in 'list_display'." % (label, field_name), obj=obj.__class__, id='admin.E122', ) ] elif obj.list_display_links and field_name in obj.list_display_links: return [ checks.Error( "The value of '%s' cannot be in both 'list_editable' and 'list_display_links'." % field_name, obj=obj.__class__, id='admin.E123', ) ] # If list_display[0] is in list_editable, check that # list_display_links is set. See #22792 and #26229 for use cases. elif (obj.list_display[0] == field_name and not obj.list_display_links and obj.list_display_links is not None): return [ checks.Error( "The value of '%s' refers to the first field in 'list_display' ('%s'), " "which cannot be used unless 'list_display_links' is set." % (label, obj.list_display[0]), obj=obj.__class__, id='admin.E124', ) ] elif not field.editable: return [ checks.Error( "The value of '%s' refers to '%s', which is not editable through the admin." % (label, field_name), obj=obj.__class__, id='admin.E125', ) ] else: return []
def _check_fieldsets_item(self, obj, model, fieldset, label, seen_fields): """ Check an item of `fieldsets`, i.e. check that this is a pair of a set name and a dictionary containing "fields" key. """ if not isinstance(fieldset, (list, tuple)): return must_be('a list or tuple', option=label, obj=obj, id='admin.E008') elif len(fieldset) != 2: return must_be('of length 2', option=label, obj=obj, id='admin.E009') elif not isinstance(fieldset[1], dict): return must_be('a dictionary', option='%s[1]' % label, obj=obj, id='admin.E010') elif 'fields' not in fieldset[1]: return [ checks.Error( "The value of '%s[1]' must contain the key 'fields'." % label, obj=obj.__class__, id='admin.E011', ) ] elif not isinstance(fieldset[1]['fields'], (list, tuple)): return must_be('a list or tuple', option="%s[1]['fields']" % label, obj=obj, id='admin.E008') seen_fields.extend(flatten(fieldset[1]['fields'])) if len(seen_fields) != len(set(seen_fields)): return [ checks.Error( "There are duplicate field(s) in '%s[1]'." % label, obj=obj.__class__, id='admin.E012', ) ] return list( chain.from_iterable( self._check_field_spec(obj, model, fieldset_fields, '%s[1]["fields"]' % label) for fieldset_fields in fieldset[1]['fields']))
def _check_autocomplete_fields_item(self, obj, model, field_name, label): """ Check that an item in `autocomplete_fields` is a ForeignKey or a ManyToManyField and that the item has a related ModelAdmin with search_fields defined. """ try: field = model._meta.get_field(field_name) except FieldDoesNotExist: return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E037') else: if not field.many_to_many and not isinstance( field, models.ForeignKey): return must_be('a foreign key or a many-to-many field', option=label, obj=obj, id='admin.E038') related_admin = obj.admin_site._registry.get( field.remote_field.model) if related_admin is None: return [ checks.Error( 'An admin for model "%s" has to be registered ' 'to be referenced by %s.autocomplete_fields.' % ( field.remote_field.model.__name__, type(obj).__name__, ), obj=obj.__class__, id='admin.E039', ) ] elif not related_admin.search_fields: return [ checks.Error( '%s must define "search_fields", because it\'s ' 'referenced by %s.autocomplete_fields.' % ( related_admin.__class__.__name__, type(obj).__name__, ), obj=obj.__class__, id='admin.E040', ) ] return []
def _check_action_permission_methods(self, obj): """ Actions with an allowed_permission attribute require the ModelAdmin to implement a has_<perm>_permission() method for each permission. """ actions = obj._get_base_actions() errors = [] for func, name, _ in actions: if not hasattr(func, 'allowed_permissions'): continue for permission in func.allowed_permissions: method_name = 'has_%s_permission' % permission if not hasattr(obj, method_name): errors.append( checks.Error( '%s must define a %s() method for the %s action.' % ( obj.__class__.__name__, method_name, func.__name__, ), obj=obj.__class__, id='admin.E129', )) return errors
def _check_exclude_of_parent_model(self, obj, parent_model): # Do not perform more specific checks if the base checks result in an # error. errors = super()._check_exclude(obj) if errors: return [] # Skip if `fk_name` is invalid. if self._check_relation(obj, parent_model): return [] if obj.exclude is None: return [] fk = _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name) if fk.name in obj.exclude: return [ checks.Error( "Cannot exclude the field '%s', because it is the foreign key " "to the parent model '%s.%s'." % (fk.name, parent_model._meta.app_label, parent_model._meta.object_name), obj=obj.__class__, id='admin.E201', ) ] else: return []
def _check_prepopulated_fields_key(self, obj, model, field_name, label): """ Check a key of `prepopulated_fields` dictionary, i.e. check that it is a name of existing field and the field is one of the allowed types. """ try: field = model._meta.get_field(field_name) except FieldDoesNotExist: return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E027') else: if isinstance(field, (models.DateTimeField, models.ForeignKey, models.ManyToManyField)): return [ checks.Error( "The value of '%s' refers to '%s', which must not be a DateTimeField, " "a ForeignKey, a OneToOneField, or a ManyToManyField." % (label, field_name), obj=obj.__class__, id='admin.E028', ) ] else: return []
def _check_date_hierarchy(self, obj): """ Check that date_hierarchy refers to DateField or DateTimeField. """ if obj.date_hierarchy is None: return [] else: try: field = get_fields_from_path(obj.model, obj.date_hierarchy)[-1] except (NotRelationField, FieldDoesNotExist): return [ checks.Error( "The value of 'date_hierarchy' refers to '%s', which " "does not refer to a Field." % obj.date_hierarchy, obj=obj.__class__, id='admin.E127', ) ] else: if not isinstance(field, (models.DateField, models.DateTimeField)): return must_be('a DateField or DateTimeField', option='date_hierarchy', obj=obj, id='admin.E128') else: return []
def _check_radio_fields_key(self, obj, model, field_name, label): """ Check that a key of `radio_fields` dictionary is name of existing field and that the field is a ForeignKey or has `choices` defined. """ try: field = model._meta.get_field(field_name) except FieldDoesNotExist: return refer_to_missing_field(field=field_name, option=label, model=model, obj=obj, id='admin.E022') else: if not (isinstance(field, models.ForeignKey) or field.choices): return [ checks.Error( "The value of '%s' refers to '%s', which is not an " "instance of ForeignKey, and does not have a 'choices' definition." % (label, field_name), obj=obj.__class__, id='admin.E023', ) ] else: return []
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 _check_field_spec_item(self, obj, model, field_name, label): if field_name in obj.readonly_fields: # Stuff can be put in fields that isn't actually a model field if # it's in readonly_fields, readonly_fields will handle the # validation of such things. return [] else: try: field = model._meta.get_field(field_name) except FieldDoesNotExist: # If we can't find a field on the model that matches, it could # be an extra field on the form. return [] else: if (isinstance(field, models.ManyToManyField) and not field.remote_field.through._meta.auto_created): return [ checks.Error( "The value of '%s' cannot include the ManyToManyField '%s', " "because that field manually specifies a relationship model." % (label, field_name), obj=obj.__class__, id='admin.E013', ) ] else: return []
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 must_inherit_from(parent, option, obj, id): return [ checks.Error( "The value of '%s' must inherit from '%s'." % (option, parent), obj=obj.__class__, id=id, ), ]
def must_be(type, option, obj, id): return [ checks.Error( "The value of '%s' must be %s." % (option, type), obj=obj.__class__, id=id, ), ]
def _check_relation(self, obj, parent_model): try: _get_foreign_key(parent_model, obj.model, fk_name=obj.fk_name) except ValueError as e: return [ checks.Error(e.args[0], obj=obj.__class__, id='admin.E202') ] else: return []
def refer_to_missing_field(field, option, model, obj, id): return [ checks.Error( "The value of '%s' refers to '%s', which is not an attribute of '%s.%s'." % (option, field, model._meta.app_label, model._meta.object_name), obj=obj.__class__, id=id, ), ]
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_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 _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_view_on_site_url(self, obj): if not callable(obj.view_on_site) and not isinstance( obj.view_on_site, bool): return [ checks.Error( "The value of 'view_on_site' must be a callable or a boolean value.", obj=obj.__class__, id='admin.E025', ) ] else: return []
def _check_list_display_links_item(self, obj, field_name, label): if field_name not in obj.list_display: return [ checks.Error( "The value of '%s' refers to '%s', which is not defined in 'list_display'." % (label, field_name), obj=obj.__class__, id='admin.E111', ) ] else: return []
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 _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 check_dependencies(**kwargs): """ Check that the admin's dependencies are correctly installed. """ errors = [] # contrib.contenttypes must be installed. if not apps.is_installed('server.contrib.contenttypes'): missing_app = checks.Error( "'server.contrib.contenttypes' must be in INSTALLED_APPS in order " "to use the admin application.", id="admin.E401", ) errors.append(missing_app) # The auth context processor must be installed if using the default # authentication backend. try: default_template_engine = Engine.get_default() except Exception: # Skip this non-critical check: # 1. if the user has a non-trivial TEMPLATES setting and Server # can't find a default template engine # 2. if anything goes wrong while loading template engines, in # order to avoid raising an exception from a confusing location # Catching ImproperlyConfigured suffices for 1. but 2. requires # catching all exceptions. pass else: if ('server.contrib.auth.context_processors.auth' not in default_template_engine.context_processors and 'server.contrib.auth.backends.ModelBackend' in settings.AUTHENTICATION_BACKENDS): missing_template = checks.Error( "'server.contrib.auth.context_processors.auth' must be in " "TEMPLATES in order to use the admin application.", id="admin.E402") errors.append(missing_template) return errors
def _check_list_display_item(self, obj, model, item, label): if callable(item): return [] elif hasattr(obj, item): return [] elif hasattr(model, item): try: field = model._meta.get_field(item) except FieldDoesNotExist: return [] else: if isinstance(field, models.ManyToManyField): return [ checks.Error( "The value of '%s' must not be a ManyToManyField." % label, obj=obj.__class__, id='admin.E109', ) ] return [] else: return [ checks.Error( "The value of '%s' refers to '%s', which is not a callable, " "an attribute of '%s', or an attribute or method on '%s.%s'." % ( label, item, obj.__class__.__name__, model._meta.app_label, model._meta.object_name, ), obj=obj.__class__, id='admin.E108', ) ]
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 _check_radio_fields_value(self, obj, val, label): """ Check type of a value of `radio_fields` dictionary. """ from server.contrib.admin.options import HORIZONTAL, VERTICAL if val not in (HORIZONTAL, VERTICAL): return [ checks.Error( "The value of '%s' must be either admin.HORIZONTAL or admin.VERTICAL." % label, obj=obj.__class__, id='admin.E024', ) ] else: return []