Fixed #12674 -- provide a way to override admin validation

Moved admin validation code to classes and have those be class
attributes to the ModelAdmin classes.
This commit is contained in:
Honza Kral 2013-02-23 16:10:32 +01:00
parent 886f7cc751
commit 4ad1eb1c14
5 changed files with 525 additions and 540 deletions

View File

@ -10,6 +10,7 @@ from django.contrib.contenttypes.models import ContentType
from django.contrib.admin import widgets, helpers from django.contrib.admin import widgets, helpers
from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects, from django.contrib.admin.util import (unquote, flatten_fieldsets, get_deleted_objects,
model_format_dict, NestedObjects) model_format_dict, NestedObjects)
from django.contrib.admin import validation
from django.contrib.admin.templatetags.admin_static import static from django.contrib.admin.templatetags.admin_static import static
from django.contrib import messages from django.contrib import messages
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
@ -87,6 +88,14 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
readonly_fields = () readonly_fields = ()
ordering = None ordering = None
# validation
validator_class = validation.BaseValidator
@classmethod
def validate(cls, model):
validator = cls.validator_class()
validator.validate(cls, model)
def __init__(self): def __init__(self):
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy() overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
overrides.update(self.formfield_overrides) overrides.update(self.formfield_overrides)
@ -371,6 +380,9 @@ class ModelAdmin(BaseModelAdmin):
actions_on_bottom = False actions_on_bottom = False
actions_selection_counter = True actions_selection_counter = True
# validation
validator_class = validation.ModelAdminValidator
def __init__(self, model, admin_site): def __init__(self, model, admin_site):
self.model = model self.model = model
self.opts = model._meta self.opts = model._meta
@ -1447,6 +1459,9 @@ class InlineModelAdmin(BaseModelAdmin):
verbose_name_plural = None verbose_name_plural = None
can_delete = True can_delete = True
# validation
validator_class = validation.InlineValidator
def __init__(self, parent_model, admin_site): def __init__(self, parent_model, admin_site):
self.admin_site = admin_site self.admin_site = admin_site
self.parent_model = parent_model self.parent_model = parent_model

View File

@ -66,12 +66,6 @@ class AdminSite(object):
if not admin_class: if not admin_class:
admin_class = ModelAdmin admin_class = ModelAdmin
# Don't import the humongous validation code unless required
if admin_class and settings.DEBUG:
from django.contrib.admin.validation import validate
else:
validate = lambda model, adminclass: None
if isinstance(model_or_iterable, ModelBase): if isinstance(model_or_iterable, ModelBase):
model_or_iterable = [model_or_iterable] model_or_iterable = [model_or_iterable]
for model in model_or_iterable: for model in model_or_iterable:
@ -94,8 +88,8 @@ class AdminSite(object):
options['__module__'] = __name__ options['__module__'] = __name__
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options) admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
# Validate (which might be a no-op) if admin_class is not ModelAdmin and settings.DEBUG:
validate(admin_class, model) admin_class.validate(model)
# Instantiate the admin class to save in the registry # Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self) self._registry[model] = admin_class(model, self)

View File

@ -3,358 +3,399 @@ from django.db import models
from django.db.models.fields import FieldDoesNotExist from django.db.models.fields import FieldDoesNotExist
from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model, from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
_get_foreign_key) _get_foreign_key)
from django.contrib.admin import ListFilter, FieldListFilter
from django.contrib.admin.util import get_fields_from_path, NotRelationField from django.contrib.admin.util import get_fields_from_path, NotRelationField
from django.contrib.admin.options import (flatten_fieldsets, BaseModelAdmin,
ModelAdmin, HORIZONTAL, VERTICAL) """
Does basic ModelAdmin option validation. Calls custom validation
classmethod in the end if it is provided in cls. The signature of the
custom validation classmethod should be: def validate(cls, model).
"""
__all__ = ['BaseValidator', 'InlineValidator']
__all__ = ['validate'] class BaseValidator(object):
def __init__(self):
# Before we can introspect models, they need to be fully loaded so that
# inter-relations are set up correctly. We force that here.
models.get_apps()
def validate(cls, model): def validate(self, cls, model):
""" for m in dir(self):
Does basic ModelAdmin option validation. Calls custom validation if m.startswith('validate_'):
classmethod in the end if it is provided in cls. The signature of the getattr(self, m)(cls, model)
custom validation classmethod should be: def validate(cls, model).
"""
# Before we can introspect models, they need to be fully loaded so that
# inter-relations are set up correctly. We force that here.
models.get_apps()
opts = model._meta def check_field_spec(self, cls, model, flds, label):
validate_base(cls, model) """
Validate the fields specification in `flds` from a ModelAdmin subclass
`cls` for the `model` model. Use `label` for reporting problems to the user.
# list_display The fields specification can be a ``fields`` option or a ``fields``
if hasattr(cls, 'list_display'): sub-option from a ``fieldsets`` option component.
check_isseq(cls, 'list_display', cls.list_display) """
for idx, field in enumerate(cls.list_display): for fields in flds:
if not callable(field): # The entry in fields might be a tuple. If it is a standalone
if not hasattr(cls, field): # field, make it into a tuple to make processing easier.
if not hasattr(model, field): if type(fields) != tuple:
try: fields = (fields,)
opts.get_field(field) for field in fields:
except models.FieldDoesNotExist: if field in cls.readonly_fields:
raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r." # Stuff can be put in fields that isn't actually a
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name)) # model field if it's in readonly_fields,
else: # readonly_fields will handle the validation of such
# getattr(model, field) could be an X_RelatedObjectsDescriptor # things.
f = fetch_attr(cls, model, opts, "list_display[%d]" % idx, field) continue
if isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
% (cls.__name__, idx, field))
# list_display_links
if hasattr(cls, 'list_display_links'):
check_isseq(cls, 'list_display_links', cls.list_display_links)
for idx, field in enumerate(cls.list_display_links):
if field not in cls.list_display:
raise ImproperlyConfigured("'%s.list_display_links[%d]' "
"refers to '%s' which is not defined in 'list_display'."
% (cls.__name__, idx, field))
# list_filter
if hasattr(cls, 'list_filter'):
check_isseq(cls, 'list_filter', cls.list_filter)
for idx, item in enumerate(cls.list_filter):
# There are three options for specifying a filter:
# 1: 'field' - a basic field filter, possibly w/ relationships (eg, 'field__rel')
# 2: ('field', SomeFieldListFilter) - a field-based list filter class
# 3: SomeListFilter - a non-field list filter class
if callable(item) and not isinstance(item, models.Field):
# If item is option 3, it should be a ListFilter...
if not issubclass(item, ListFilter):
raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
" which is not a descendant of ListFilter."
% (cls.__name__, idx, item.__name__))
# ... but not a FieldListFilter.
if issubclass(item, FieldListFilter):
raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
" which is of type FieldListFilter but is not"
" associated with a field name."
% (cls.__name__, idx, item.__name__))
else:
if isinstance(item, (tuple, list)):
# item is option #2
field, list_filter_class = item
if not issubclass(list_filter_class, FieldListFilter):
raise ImproperlyConfigured("'%s.list_filter[%d][1]'"
" is '%s' which is not of type FieldListFilter."
% (cls.__name__, idx, list_filter_class.__name__))
else:
# item is option #1
field = item
# Validate the field string
try: try:
get_fields_from_path(model, field) f = model._meta.get_field(field)
except (NotRelationField, FieldDoesNotExist): except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'" # If we can't find a field on the model that matches, it could be an
" which does not refer to a Field." # extra field on the form; nothing to check so move on to the next field.
continue
if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
raise ImproperlyConfigured("'%s.%s' "
"can't include the ManyToManyField field '%s' because "
"'%s' manually specifies a 'through' model." % (
cls.__name__, label, field, field))
def validate_raw_id_fields(self, cls, model):
" Validate that raw_id_fields only contains field names that are listed on the model. "
if hasattr(cls, 'raw_id_fields'):
check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
for idx, field in enumerate(cls.raw_id_fields):
f = get_field(cls, model, 'raw_id_fields', field)
if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
"be either a ForeignKey or ManyToManyField."
% (cls.__name__, idx, field)) % (cls.__name__, idx, field))
# list_per_page = 100 def validate_fields(self, cls, model):
if hasattr(cls, 'list_per_page') and not isinstance(cls.list_per_page, int): " Validate that fields only refer to existing fields, doesn't contain duplicates. "
raise ImproperlyConfigured("'%s.list_per_page' should be a integer." # fields
% cls.__name__) if cls.fields: # default value is None
check_isseq(cls, 'fields', cls.fields)
self.check_field_spec(cls, model, cls.fields, 'fields')
if cls.fieldsets:
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
if len(cls.fields) > len(set(cls.fields)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
# list_max_show_all def validate_fieldsets(self, cls, model):
if hasattr(cls, 'list_max_show_all') and not isinstance(cls.list_max_show_all, int): " Validate that fieldsets is properly formatted and doesn't contain duplicates. "
raise ImproperlyConfigured("'%s.list_max_show_all' should be an integer." from django.contrib.admin.options import flatten_fieldsets
% cls.__name__) if cls.fieldsets: # default value is None
check_isseq(cls, 'fieldsets', cls.fieldsets)
for idx, fieldset in enumerate(cls.fieldsets):
check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
if len(fieldset) != 2:
raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
"have exactly two elements." % (cls.__name__, idx))
check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
if 'fields' not in fieldset[1]:
raise ImproperlyConfigured("'fields' key is required in "
"%s.fieldsets[%d][1] field options dict."
% (cls.__name__, idx))
self.check_field_spec(cls, model, fieldset[1]['fields'], "fieldsets[%d][1]['fields']" % idx)
flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
# list_editable def validate_exclude(self, cls, model):
if hasattr(cls, 'list_editable') and cls.list_editable: " Validate that exclude is a sequence without duplicates. "
check_isseq(cls, 'list_editable', cls.list_editable) if cls.exclude: # default value is None
for idx, field_name in enumerate(cls.list_editable): check_isseq(cls, 'exclude', cls.exclude)
try: if len(cls.exclude) > len(set(cls.exclude)):
field = opts.get_field_by_name(field_name)[0] raise ImproperlyConfigured('There are duplicate field(s) in %s.exclude' % cls.__name__)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', not defined on %s.%s."
% (cls.__name__, idx, field_name, model._meta.app_label, model.__name__))
if field_name not in cls.list_display:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
"'%s' which is not defined in 'list_display'."
% (cls.__name__, idx, field_name))
if field_name in cls.list_display_links:
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
" and '%s.list_display_links'"
% (field_name, cls.__name__, cls.__name__))
if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
" the first field in list_display, '%s', which can't be"
" used unless list_display_links is set."
% (cls.__name__, idx, cls.list_display[0]))
if not field.editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', which isn't editable through the admin."
% (cls.__name__, idx, field_name))
# search_fields = () def validate_form(self, cls, model):
if hasattr(cls, 'search_fields'): " Validate that form subclasses BaseModelForm. "
check_isseq(cls, 'search_fields', cls.search_fields) if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
raise ImproperlyConfigured("%s.form does not inherit from "
"BaseModelForm." % cls.__name__)
# date_hierarchy = None def validate_filter_vertical(self, cls, model):
if cls.date_hierarchy: " Validate that filter_vertical is a sequence of field names. "
f = get_field(cls, model, opts, 'date_hierarchy', cls.date_hierarchy) if hasattr(cls, 'filter_vertical'):
if not isinstance(f, (models.DateField, models.DateTimeField)): check_isseq(cls, 'filter_vertical', cls.filter_vertical)
raise ImproperlyConfigured("'%s.date_hierarchy is " for idx, field in enumerate(cls.filter_vertical):
"neither an instance of DateField nor DateTimeField." f = get_field(cls, model, 'filter_vertical', field)
% cls.__name__) if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
# ordering = None def validate_filter_horizontal(self, cls, model):
if cls.ordering: " Validate that filter_horizontal is a sequence of field names. "
check_isseq(cls, 'ordering', cls.ordering) if hasattr(cls, 'filter_horizontal'):
for idx, field in enumerate(cls.ordering): check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
if field == '?' and len(cls.ordering) != 1: for idx, field in enumerate(cls.filter_horizontal):
raise ImproperlyConfigured("'%s.ordering' has the random " f = get_field(cls, model, 'filter_horizontal', field)
"ordering marker '?', but contains other fields as " if not isinstance(f, models.ManyToManyField):
"well. Please either remove '?' or the other fields." raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
def validate_radio_fields(self, cls, model):
" Validate that radio_fields is a dictionary of choice or foreign key fields. "
from django.contrib.admin.options import HORIZONTAL, VERTICAL
if hasattr(cls, 'radio_fields'):
check_isdict(cls, 'radio_fields', cls.radio_fields)
for field, val in cls.radio_fields.items():
f = get_field(cls, model, 'radio_fields', field)
if not (isinstance(f, models.ForeignKey) or f.choices):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither an instance of ForeignKey nor does "
"have choices set." % (cls.__name__, field))
if not val in (HORIZONTAL, VERTICAL):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither admin.HORIZONTAL nor admin.VERTICAL."
% (cls.__name__, field))
def validate_prepopulated_fields(self, cls, model):
" Validate that prepopulated_fields if a dictionary containing allowed field types. "
# prepopulated_fields
if hasattr(cls, 'prepopulated_fields'):
check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
for field, val in cls.prepopulated_fields.items():
f = get_field(cls, model, 'prepopulated_fields', field)
if isinstance(f, (models.DateTimeField, models.ForeignKey,
models.ManyToManyField)):
raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
"is either a DateTimeField, ForeignKey or "
"ManyToManyField. This isn't allowed."
% (cls.__name__, field))
check_isseq(cls, "prepopulated_fields['%s']" % field, val)
for idx, f in enumerate(val):
get_field(cls, model, "prepopulated_fields['%s'][%d]" % (field, idx), f)
def validate_ordering(self, cls, model):
" Validate that ordering refers to existing fields or is random. "
# ordering = None
if cls.ordering:
check_isseq(cls, 'ordering', cls.ordering)
for idx, field in enumerate(cls.ordering):
if field == '?' and len(cls.ordering) != 1:
raise ImproperlyConfigured("'%s.ordering' has the random "
"ordering marker '?', but contains other fields as "
"well. Please either remove '?' or the other fields."
% cls.__name__)
if field == '?':
continue
if field.startswith('-'):
field = field[1:]
# Skip ordering in the format field1__field2 (FIXME: checking
# this format would be nice, but it's a little fiddly).
if '__' in field:
continue
get_field(cls, model, 'ordering[%d]' % idx, field)
def validate_readonly_fields(self, cls, model):
" Validate that readonly_fields refers to proper attribute or field. "
if hasattr(cls, "readonly_fields"):
check_isseq(cls, "readonly_fields", cls.readonly_fields)
for idx, field in enumerate(cls.readonly_fields):
if not callable(field):
if not hasattr(cls, field):
if not hasattr(model, field):
try:
model._meta.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r."
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
class ModelAdminValidator(BaseValidator):
def validate_save_as(self, cls, model):
" Validate save_as is a boolean. "
check_type(cls, 'save_as', bool)
def validate_save_on_top(self, cls, model):
" Validate save_on_top is a boolean. "
check_type(cls, 'save_on_top', bool)
def validate_inlines(self, cls, model):
" Validate inline model admin classes. "
from django.contrib.admin.options import BaseModelAdmin
if hasattr(cls, 'inlines'):
check_isseq(cls, 'inlines', cls.inlines)
for idx, inline in enumerate(cls.inlines):
if not issubclass(inline, BaseModelAdmin):
raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit "
"from BaseModelAdmin." % (cls.__name__, idx))
if not inline.model:
raise ImproperlyConfigured("'model' is a required attribute "
"of '%s.inlines[%d]'." % (cls.__name__, idx))
if not issubclass(inline.model, models.Model):
raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
"inherit from models.Model." % (cls.__name__, idx))
inline.validate(inline.model)
self.check_inline(inline, model)
def check_inline(self, cls, parent_model):
" Validate inline class's fk field is not excluded. "
fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True)
if hasattr(cls, 'exclude') and cls.exclude:
if fk and fk.name in cls.exclude:
raise ImproperlyConfigured("%s cannot exclude the field "
"'%s' - this is the foreign key to the parent model "
"%s.%s." % (cls.__name__, fk.name, parent_model._meta.app_label, parent_model.__name__))
def validate_list_display(self, cls, model):
" Validate that list_display only contains fields or usable attributes. "
if hasattr(cls, 'list_display'):
check_isseq(cls, 'list_display', cls.list_display)
for idx, field in enumerate(cls.list_display):
if not callable(field):
if not hasattr(cls, field):
if not hasattr(model, field):
try:
model._meta.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("%s.list_display[%d], %r is not a callable or an attribute of %r or found in the model %r."
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name))
else:
# getattr(model, field) could be an X_RelatedObjectsDescriptor
f = fetch_attr(cls, model, "list_display[%d]" % idx, field)
if isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.list_display[%d]', '%s' is a ManyToManyField which is not supported."
% (cls.__name__, idx, field))
def validate_list_display_links(self, cls, model):
" Validate that list_display_links is a unique subset of list_display. "
if hasattr(cls, 'list_display_links'):
check_isseq(cls, 'list_display_links', cls.list_display_links)
for idx, field in enumerate(cls.list_display_links):
if field not in cls.list_display:
raise ImproperlyConfigured("'%s.list_display_links[%d]' "
"refers to '%s' which is not defined in 'list_display'."
% (cls.__name__, idx, field))
def validate_list_filter(self, cls, model):
"""
Validate that list_filter is a sequence of one of three options:
1: 'field' - a basic field filter, possibly w/ relationships (eg, 'field__rel')
2: ('field', SomeFieldListFilter) - a field-based list filter class
3: SomeListFilter - a non-field list filter class
"""
from django.contrib.admin import ListFilter, FieldListFilter
if hasattr(cls, 'list_filter'):
check_isseq(cls, 'list_filter', cls.list_filter)
for idx, item in enumerate(cls.list_filter):
if callable(item) and not isinstance(item, models.Field):
# If item is option 3, it should be a ListFilter...
if not issubclass(item, ListFilter):
raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
" which is not a descendant of ListFilter."
% (cls.__name__, idx, item.__name__))
# ... but not a FieldListFilter.
if issubclass(item, FieldListFilter):
raise ImproperlyConfigured("'%s.list_filter[%d]' is '%s'"
" which is of type FieldListFilter but is not"
" associated with a field name."
% (cls.__name__, idx, item.__name__))
else:
if isinstance(item, (tuple, list)):
# item is option #2
field, list_filter_class = item
if not issubclass(list_filter_class, FieldListFilter):
raise ImproperlyConfigured("'%s.list_filter[%d][1]'"
" is '%s' which is not of type FieldListFilter."
% (cls.__name__, idx, list_filter_class.__name__))
else:
# item is option #1
field = item
# Validate the field string
try:
get_fields_from_path(model, field)
except (NotRelationField, FieldDoesNotExist):
raise ImproperlyConfigured("'%s.list_filter[%d]' refers to '%s'"
" which does not refer to a Field."
% (cls.__name__, idx, field))
def validate_list_select_related(self, cls, model):
" Validate that list_select_related is a boolean. "
check_type(cls, 'list_select_related', bool)
def validate_list_per_page(self, cls, model):
" Validate that list_per_page is an integer. "
check_type(cls, 'list_per_page', int)
def validate_list_max_show_all(self, cls, model):
" Validate that list_max_show_all is an integer. "
check_type(cls, 'list_max_show_all', int)
def validate_list_editable(self, cls, model):
"""
Validate that list_editable is a sequence of editable fields from
list_display without first element.
"""
if hasattr(cls, 'list_editable') and cls.list_editable:
check_isseq(cls, 'list_editable', cls.list_editable)
for idx, field_name in enumerate(cls.list_editable):
try:
field = model._meta.get_field_by_name(field_name)[0]
except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', not defined on %s.%s."
% (cls.__name__, idx, field_name, model._meta.app_label, model.__name__))
if field_name not in cls.list_display:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to "
"'%s' which is not defined in 'list_display'."
% (cls.__name__, idx, field_name))
if field_name in cls.list_display_links:
raise ImproperlyConfigured("'%s' cannot be in both '%s.list_editable'"
" and '%s.list_display_links'"
% (field_name, cls.__name__, cls.__name__))
if not cls.list_display_links and cls.list_display[0] in cls.list_editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to"
" the first field in list_display, '%s', which can't be"
" used unless list_display_links is set."
% (cls.__name__, idx, cls.list_display[0]))
if not field.editable:
raise ImproperlyConfigured("'%s.list_editable[%d]' refers to a "
"field, '%s', which isn't editable through the admin."
% (cls.__name__, idx, field_name))
def validate_search_fields(self, cls, model):
" Validate search_fields is a sequence. "
if hasattr(cls, 'search_fields'):
check_isseq(cls, 'search_fields', cls.search_fields)
def validate_date_hierarchy(self, cls, model):
" Validate that date_hierarchy refers to DateField or DateTimeField. "
if cls.date_hierarchy:
f = get_field(cls, model, 'date_hierarchy', cls.date_hierarchy)
if not isinstance(f, (models.DateField, models.DateTimeField)):
raise ImproperlyConfigured("'%s.date_hierarchy is "
"neither an instance of DateField nor DateTimeField."
% cls.__name__) % cls.__name__)
if field == '?':
continue
if field.startswith('-'):
field = field[1:]
# Skip ordering in the format field1__field2 (FIXME: checking
# this format would be nice, but it's a little fiddly).
if '__' in field:
continue
get_field(cls, model, opts, 'ordering[%d]' % idx, field)
if hasattr(cls, "readonly_fields"):
check_readonly_fields(cls, model, opts)
# list_select_related = False
# save_as = False
# save_on_top = False
for attr in ('list_select_related', 'save_as', 'save_on_top'):
if not isinstance(getattr(cls, attr), bool):
raise ImproperlyConfigured("'%s.%s' should be a boolean."
% (cls.__name__, attr))
# inlines = [] class InlineValidator(BaseValidator):
if hasattr(cls, 'inlines'): def validate_fk_name(self, cls, model):
check_isseq(cls, 'inlines', cls.inlines) " Validate that fk_name refers to a ForeignKey. "
for idx, inline in enumerate(cls.inlines): if cls.fk_name: # default value is None
if not issubclass(inline, BaseModelAdmin): f = get_field(cls, model, 'fk_name', cls.fk_name)
raise ImproperlyConfigured("'%s.inlines[%d]' does not inherit " if not isinstance(f, models.ForeignKey):
"from BaseModelAdmin." % (cls.__name__, idx)) raise ImproperlyConfigured("'%s.fk_name is not an instance of "
if not inline.model: "models.ForeignKey." % cls.__name__)
raise ImproperlyConfigured("'model' is a required attribute "
"of '%s.inlines[%d]'." % (cls.__name__, idx))
if not issubclass(inline.model, models.Model):
raise ImproperlyConfigured("'%s.inlines[%d].model' does not "
"inherit from models.Model." % (cls.__name__, idx))
validate_base(inline, inline.model)
validate_inline(inline, cls, model)
def validate_inline(cls, parent, parent_model): def validate_extra(self, cls, model):
" Validate that extra is an integer. "
check_type(cls, 'extra', int)
# model is already verified to exist and be a Model def validate_max_num(self, cls, model):
if cls.fk_name: # default value is None " Validate that max_num is an integer. "
f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name) check_type(cls, 'max_num', int)
if not isinstance(f, models.ForeignKey):
raise ImproperlyConfigured("'%s.fk_name is not an instance of "
"models.ForeignKey." % cls.__name__)
fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name, can_fail=True) def validate_formset(self, cls, model):
" Validate formset is a subclass of BaseModelFormSet. "
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
raise ImproperlyConfigured("'%s.formset' does not inherit from "
"BaseModelFormSet." % cls.__name__)
# extra = 3
if not isinstance(cls.extra, int):
raise ImproperlyConfigured("'%s.extra' should be a integer."
% cls.__name__)
# max_num = None def check_type(cls, attr, type_):
max_num = getattr(cls, 'max_num', None) if getattr(cls, attr, None) is not None and not isinstance(getattr(cls, attr), type_):
if max_num is not None and not isinstance(max_num, int): raise ImproperlyConfigured("'%s.%s' should be a %s."
raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)." % (cls.__name__, attr, type_.__name__ ))
% cls.__name__)
# formset
if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
raise ImproperlyConfigured("'%s.formset' does not inherit from "
"BaseModelFormSet." % cls.__name__)
# exclude
if hasattr(cls, 'exclude') and cls.exclude:
if fk and fk.name in cls.exclude:
raise ImproperlyConfigured("%s cannot exclude the field "
"'%s' - this is the foreign key to the parent model "
"%s.%s." % (cls.__name__, fk.name, parent_model._meta.app_label, parent_model.__name__))
if hasattr(cls, "readonly_fields"):
check_readonly_fields(cls, cls.model, cls.model._meta)
def validate_fields_spec(cls, model, opts, flds, label):
"""
Validate the fields specification in `flds` from a ModelAdmin subclass
`cls` for the `model` model. `opts` is `model`'s Meta inner class.
Use `label` for reporting problems to the user.
The fields specification can be a ``fields`` option or a ``fields``
sub-option from a ``fieldsets`` option component.
"""
for fields in flds:
# The entry in fields might be a tuple. If it is a standalone
# field, make it into a tuple to make processing easier.
if type(fields) != tuple:
fields = (fields,)
for field in fields:
if field in cls.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.
continue
try:
f = opts.get_field(field)
except models.FieldDoesNotExist:
# If we can't find a field on the model that matches, it could be an
# extra field on the form; nothing to check so move on to the next field.
continue
if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
raise ImproperlyConfigured("'%s.%s' "
"can't include the ManyToManyField field '%s' because "
"'%s' manually specifies a 'through' model." % (
cls.__name__, label, field, field))
def validate_base(cls, model):
opts = model._meta
# raw_id_fields
if hasattr(cls, 'raw_id_fields'):
check_isseq(cls, 'raw_id_fields', cls.raw_id_fields)
for idx, field in enumerate(cls.raw_id_fields):
f = get_field(cls, model, opts, 'raw_id_fields', field)
if not isinstance(f, (models.ForeignKey, models.ManyToManyField)):
raise ImproperlyConfigured("'%s.raw_id_fields[%d]', '%s' must "
"be either a ForeignKey or ManyToManyField."
% (cls.__name__, idx, field))
# fields
if cls.fields: # default value is None
check_isseq(cls, 'fields', cls.fields)
validate_fields_spec(cls, model, opts, cls.fields, 'fields')
if cls.fieldsets:
raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
if len(cls.fields) > len(set(cls.fields)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.fields' % cls.__name__)
# fieldsets
if cls.fieldsets: # default value is None
check_isseq(cls, 'fieldsets', cls.fieldsets)
for idx, fieldset in enumerate(cls.fieldsets):
check_isseq(cls, 'fieldsets[%d]' % idx, fieldset)
if len(fieldset) != 2:
raise ImproperlyConfigured("'%s.fieldsets[%d]' does not "
"have exactly two elements." % (cls.__name__, idx))
check_isdict(cls, 'fieldsets[%d][1]' % idx, fieldset[1])
if 'fields' not in fieldset[1]:
raise ImproperlyConfigured("'fields' key is required in "
"%s.fieldsets[%d][1] field options dict."
% (cls.__name__, idx))
validate_fields_spec(cls, model, opts, fieldset[1]['fields'], "fieldsets[%d][1]['fields']" % idx)
flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
# exclude
if cls.exclude: # default value is None
check_isseq(cls, 'exclude', cls.exclude)
if len(cls.exclude) > len(set(cls.exclude)):
raise ImproperlyConfigured('There are duplicate field(s) in %s.exclude' % cls.__name__)
# form
if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
raise ImproperlyConfigured("%s.form does not inherit from "
"BaseModelForm." % cls.__name__)
# filter_vertical
if hasattr(cls, 'filter_vertical'):
check_isseq(cls, 'filter_vertical', cls.filter_vertical)
for idx, field in enumerate(cls.filter_vertical):
f = get_field(cls, model, opts, 'filter_vertical', field)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.filter_vertical[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
# filter_horizontal
if hasattr(cls, 'filter_horizontal'):
check_isseq(cls, 'filter_horizontal', cls.filter_horizontal)
for idx, field in enumerate(cls.filter_horizontal):
f = get_field(cls, model, opts, 'filter_horizontal', field)
if not isinstance(f, models.ManyToManyField):
raise ImproperlyConfigured("'%s.filter_horizontal[%d]' must be "
"a ManyToManyField." % (cls.__name__, idx))
# radio_fields
if hasattr(cls, 'radio_fields'):
check_isdict(cls, 'radio_fields', cls.radio_fields)
for field, val in cls.radio_fields.items():
f = get_field(cls, model, opts, 'radio_fields', field)
if not (isinstance(f, models.ForeignKey) or f.choices):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither an instance of ForeignKey nor does "
"have choices set." % (cls.__name__, field))
if not val in (HORIZONTAL, VERTICAL):
raise ImproperlyConfigured("'%s.radio_fields['%s']' "
"is neither admin.HORIZONTAL nor admin.VERTICAL."
% (cls.__name__, field))
# prepopulated_fields
if hasattr(cls, 'prepopulated_fields'):
check_isdict(cls, 'prepopulated_fields', cls.prepopulated_fields)
for field, val in cls.prepopulated_fields.items():
f = get_field(cls, model, opts, 'prepopulated_fields', field)
if isinstance(f, (models.DateTimeField, models.ForeignKey,
models.ManyToManyField)):
raise ImproperlyConfigured("'%s.prepopulated_fields['%s']' "
"is either a DateTimeField, ForeignKey or "
"ManyToManyField. This isn't allowed."
% (cls.__name__, field))
check_isseq(cls, "prepopulated_fields['%s']" % field, val)
for idx, f in enumerate(val):
get_field(cls, model, opts, "prepopulated_fields['%s'][%d]" % (field, idx), f)
def check_isseq(cls, label, obj): def check_isseq(cls, label, obj):
if not isinstance(obj, (list, tuple)): if not isinstance(obj, (list, tuple)):
@ -364,16 +405,16 @@ def check_isdict(cls, label, obj):
if not isinstance(obj, dict): if not isinstance(obj, dict):
raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label)) raise ImproperlyConfigured("'%s.%s' must be a dictionary." % (cls.__name__, label))
def get_field(cls, model, opts, label, field): def get_field(cls, model, label, field):
try: try:
return opts.get_field(field) return model._meta.get_field(field)
except models.FieldDoesNotExist: except models.FieldDoesNotExist:
raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s.%s'." raise ImproperlyConfigured("'%s.%s' refers to field '%s' that is missing from model '%s.%s'."
% (cls.__name__, label, field, model._meta.app_label, model.__name__)) % (cls.__name__, label, field, model._meta.app_label, model.__name__))
def fetch_attr(cls, model, opts, label, field): def fetch_attr(cls, model, label, field):
try: try:
return opts.get_field(field) return model._meta.get_field(field)
except models.FieldDoesNotExist: except models.FieldDoesNotExist:
pass pass
try: try:
@ -381,15 +422,3 @@ def fetch_attr(cls, model, opts, label, field):
except AttributeError: except AttributeError:
raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s.%s'." raise ImproperlyConfigured("'%s.%s' refers to '%s' that is neither a field, method or property of model '%s.%s'."
% (cls.__name__, label, field, model._meta.app_label, model.__name__)) % (cls.__name__, label, field, model._meta.app_label, model.__name__))
def check_readonly_fields(cls, model, opts):
check_isseq(cls, "readonly_fields", cls.readonly_fields)
for idx, field in enumerate(cls.readonly_fields):
if not callable(field):
if not hasattr(cls, field):
if not hasattr(model, field):
try:
opts.get_field(field)
except models.FieldDoesNotExist:
raise ImproperlyConfigured("%s.readonly_fields[%d], %r is not a callable or an attribute of %r or found in the model %r."
% (cls.__name__, idx, field, cls.__name__, model._meta.object_name))

View File

@ -2,7 +2,6 @@ from __future__ import absolute_import
from django import forms from django import forms
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.validation import validate, validate_inline
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase from django.test import TestCase
@ -38,13 +37,13 @@ class ValidationTestCase(TestCase):
"fields": ["title", "original_release"], "fields": ["title", "original_release"],
}), }),
] ]
validate(SongAdmin, Song) SongAdmin.validate(Song)
def test_custom_modelforms_with_fields_fieldsets(self): def test_custom_modelforms_with_fields_fieldsets(self):
""" """
# Regression test for #8027: custom ModelForms with fields/fieldsets # Regression test for #8027: custom ModelForms with fields/fieldsets
""" """
validate(ValidFields, Song) ValidFields.validate(Song)
def test_custom_get_form_with_fieldsets(self): def test_custom_get_form_with_fieldsets(self):
""" """
@ -52,7 +51,7 @@ class ValidationTestCase(TestCase):
is overridden. is overridden.
Refs #19445. Refs #19445.
""" """
validate(ValidFormFieldsets, Song) ValidFormFieldsets.validate(Song)
def test_exclude_values(self): def test_exclude_values(self):
""" """
@ -62,16 +61,16 @@ class ValidationTestCase(TestCase):
exclude = ('foo') exclude = ('foo')
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"'ExcludedFields1.exclude' must be a list or tuple.", "'ExcludedFields1.exclude' must be a list or tuple.",
validate, ExcludedFields1.validate,
ExcludedFields1, Book) Book)
def test_exclude_duplicate_values(self): def test_exclude_duplicate_values(self):
class ExcludedFields2(admin.ModelAdmin): class ExcludedFields2(admin.ModelAdmin):
exclude = ('name', 'name') exclude = ('name', 'name')
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"There are duplicate field(s) in ExcludedFields2.exclude", "There are duplicate field(s) in ExcludedFields2.exclude",
validate, ExcludedFields2.validate,
ExcludedFields2, Book) Book)
def test_exclude_in_inline(self): def test_exclude_in_inline(self):
class ExcludedFieldsInline(admin.TabularInline): class ExcludedFieldsInline(admin.TabularInline):
@ -84,8 +83,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"'ExcludedFieldsInline.exclude' must be a list or tuple.", "'ExcludedFieldsInline.exclude' must be a list or tuple.",
validate, ExcludedFieldsAlbumAdmin.validate,
ExcludedFieldsAlbumAdmin, Album) Album)
def test_exclude_inline_model_admin(self): def test_exclude_inline_model_admin(self):
""" """
@ -102,8 +101,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"SongInline cannot exclude the field 'album' - this is the foreign key to the parent model admin_validation.Album.", "SongInline cannot exclude the field 'album' - this is the foreign key to the parent model admin_validation.Album.",
validate, AlbumAdmin.validate,
AlbumAdmin, Album) Album)
def test_app_label_in_admin_validation(self): def test_app_label_in_admin_validation(self):
""" """
@ -114,8 +113,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"'RawIdNonexistingAdmin.raw_id_fields' refers to field 'nonexisting' that is missing from model 'admin_validation.Album'.", "'RawIdNonexistingAdmin.raw_id_fields' refers to field 'nonexisting' that is missing from model 'admin_validation.Album'.",
validate, RawIdNonexistingAdmin.validate,
RawIdNonexistingAdmin, Album) Album)
def test_fk_exclusion(self): def test_fk_exclusion(self):
""" """
@ -127,28 +126,35 @@ class ValidationTestCase(TestCase):
model = TwoAlbumFKAndAnE model = TwoAlbumFKAndAnE
exclude = ("e",) exclude = ("e",)
fk_name = "album1" fk_name = "album1"
validate_inline(TwoAlbumFKAndAnEInline, None, Album) class MyAdmin(admin.ModelAdmin):
inlines = [TwoAlbumFKAndAnEInline]
MyAdmin.validate(Album)
def test_inline_self_validation(self): def test_inline_self_validation(self):
class TwoAlbumFKAndAnEInline(admin.TabularInline): class TwoAlbumFKAndAnEInline(admin.TabularInline):
model = TwoAlbumFKAndAnE model = TwoAlbumFKAndAnE
class MyAdmin(admin.ModelAdmin):
inlines = [TwoAlbumFKAndAnEInline]
self.assertRaisesMessage(Exception, self.assertRaisesMessage(Exception,
"<class 'admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'admin_validation.models.Album'>", "<class 'admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'admin_validation.models.Album'>",
validate_inline, MyAdmin.validate, Album)
TwoAlbumFKAndAnEInline, None, Album)
def test_inline_with_specified(self): def test_inline_with_specified(self):
class TwoAlbumFKAndAnEInline(admin.TabularInline): class TwoAlbumFKAndAnEInline(admin.TabularInline):
model = TwoAlbumFKAndAnE model = TwoAlbumFKAndAnE
fk_name = "album1" fk_name = "album1"
validate_inline(TwoAlbumFKAndAnEInline, None, Album)
class MyAdmin(admin.ModelAdmin):
inlines = [TwoAlbumFKAndAnEInline]
MyAdmin.validate(Album)
def test_readonly(self): def test_readonly(self):
class SongAdmin(admin.ModelAdmin): class SongAdmin(admin.ModelAdmin):
readonly_fields = ("title",) readonly_fields = ("title",)
validate(SongAdmin, Song) SongAdmin.validate(Song)
def test_readonly_on_method(self): def test_readonly_on_method(self):
def my_function(obj): def my_function(obj):
@ -157,7 +163,7 @@ class ValidationTestCase(TestCase):
class SongAdmin(admin.ModelAdmin): class SongAdmin(admin.ModelAdmin):
readonly_fields = (my_function,) readonly_fields = (my_function,)
validate(SongAdmin, Song) SongAdmin.validate(Song)
def test_readonly_on_modeladmin(self): def test_readonly_on_modeladmin(self):
class SongAdmin(admin.ModelAdmin): class SongAdmin(admin.ModelAdmin):
@ -166,13 +172,13 @@ class ValidationTestCase(TestCase):
def readonly_method_on_modeladmin(self, obj): def readonly_method_on_modeladmin(self, obj):
pass pass
validate(SongAdmin, Song) SongAdmin.validate(Song)
def test_readonly_method_on_model(self): def test_readonly_method_on_model(self):
class SongAdmin(admin.ModelAdmin): class SongAdmin(admin.ModelAdmin):
readonly_fields = ("readonly_method_on_model",) readonly_fields = ("readonly_method_on_model",)
validate(SongAdmin, Song) SongAdmin.validate(Song)
def test_nonexistant_field(self): def test_nonexistant_field(self):
class SongAdmin(admin.ModelAdmin): class SongAdmin(admin.ModelAdmin):
@ -180,8 +186,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'.", "SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'.",
validate, SongAdmin.validate,
SongAdmin, Song) Song)
def test_nonexistant_field_on_inline(self): def test_nonexistant_field_on_inline(self):
class CityInline(admin.TabularInline): class CityInline(admin.TabularInline):
@ -190,8 +196,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"CityInline.readonly_fields[0], 'i_dont_exist' is not a callable or an attribute of 'CityInline' or found in the model 'City'.", "CityInline.readonly_fields[0], 'i_dont_exist' is not a callable or an attribute of 'CityInline' or found in the model 'City'.",
validate_inline, CityInline.validate,
CityInline, None, State) City)
def test_extra(self): def test_extra(self):
class SongAdmin(admin.ModelAdmin): class SongAdmin(admin.ModelAdmin):
@ -199,13 +205,13 @@ class ValidationTestCase(TestCase):
if instance.title == "Born to Run": if instance.title == "Born to Run":
return "Best Ever!" return "Best Ever!"
return "Status unknown." return "Status unknown."
validate(SongAdmin, Song) SongAdmin.validate(Song)
def test_readonly_lambda(self): def test_readonly_lambda(self):
class SongAdmin(admin.ModelAdmin): class SongAdmin(admin.ModelAdmin):
readonly_fields = (lambda obj: "test",) readonly_fields = (lambda obj: "test",)
validate(SongAdmin, Song) SongAdmin.validate(Song)
def test_graceful_m2m_fail(self): def test_graceful_m2m_fail(self):
""" """
@ -219,8 +225,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.", "'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
validate, BookAdmin.validate,
BookAdmin, Book) Book)
def test_cannot_include_through(self): def test_cannot_include_through(self):
class FieldsetBookAdmin(admin.ModelAdmin): class FieldsetBookAdmin(admin.ModelAdmin):
@ -230,20 +236,20 @@ class ValidationTestCase(TestCase):
) )
self.assertRaisesMessage(ImproperlyConfigured, self.assertRaisesMessage(ImproperlyConfigured,
"'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.", "'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
validate, FieldsetBookAdmin.validate,
FieldsetBookAdmin, Book) Book)
def test_nested_fields(self): def test_nested_fields(self):
class NestedFieldsAdmin(admin.ModelAdmin): class NestedFieldsAdmin(admin.ModelAdmin):
fields = ('price', ('name', 'subtitle')) fields = ('price', ('name', 'subtitle'))
validate(NestedFieldsAdmin, Book) NestedFieldsAdmin.validate(Book)
def test_nested_fieldsets(self): def test_nested_fieldsets(self):
class NestedFieldsetAdmin(admin.ModelAdmin): class NestedFieldsetAdmin(admin.ModelAdmin):
fieldsets = ( fieldsets = (
('Main', {'fields': ('price', ('name', 'subtitle'))}), ('Main', {'fields': ('price', ('name', 'subtitle'))}),
) )
validate(NestedFieldsetAdmin, Book) NestedFieldsetAdmin.validate(Book)
def test_explicit_through_override(self): def test_explicit_through_override(self):
""" """
@ -260,7 +266,7 @@ class ValidationTestCase(TestCase):
# If the through model is still a string (and hasn't been resolved to a model) # If the through model is still a string (and hasn't been resolved to a model)
# the validation will fail. # the validation will fail.
validate(BookAdmin, Book) BookAdmin.validate(Book)
def test_non_model_fields(self): def test_non_model_fields(self):
""" """
@ -274,7 +280,7 @@ class ValidationTestCase(TestCase):
form = SongForm form = SongForm
fields = ['title', 'extra_data'] fields = ['title', 'extra_data']
validate(FieldsOnFormOnlyAdmin, Song) FieldsOnFormOnlyAdmin.validate(Song)
def test_non_model_first_field(self): def test_non_model_first_field(self):
""" """
@ -292,4 +298,4 @@ class ValidationTestCase(TestCase):
form = SongForm form = SongForm
fields = ['extra_data', 'title'] fields = ['extra_data', 'title']
validate(FieldsOnFormOnlyAdmin, Song) FieldsOnFormOnlyAdmin.validate(Song)

View File

@ -7,7 +7,6 @@ from django.conf import settings
from django.contrib.admin.options import (ModelAdmin, TabularInline, from django.contrib.admin.options import (ModelAdmin, TabularInline,
HORIZONTAL, VERTICAL) HORIZONTAL, VERTICAL)
from django.contrib.admin.sites import AdminSite from django.contrib.admin.sites import AdminSite
from django.contrib.admin.validation import validate
from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
from django.contrib.admin import (SimpleListFilter, from django.contrib.admin import (SimpleListFilter,
BooleanFieldListFilter) BooleanFieldListFilter)
@ -523,8 +522,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.", "'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -534,8 +532,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -545,15 +542,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields\[0\]', 'name' must be either a ForeignKey or ManyToManyField.", "'ValidationTestModelAdmin.raw_id_fields\[0\]', 'name' must be either a ForeignKey or ManyToManyField.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
raw_id_fields = ('users',) raw_id_fields = ('users',)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_fieldsets_validation(self): def test_fieldsets_validation(self):
@ -563,8 +559,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets' must be a list or tuple.", "'ValidationTestModelAdmin.fieldsets' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -574,8 +569,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]' must be a list or tuple.", "'ValidationTestModelAdmin.fieldsets\[0\]' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -585,8 +579,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]' does not have exactly two elements.", "'ValidationTestModelAdmin.fieldsets\[0\]' does not have exactly two elements.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -596,8 +589,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]\[1\]' must be a dictionary.", "'ValidationTestModelAdmin.fieldsets\[0\]\[1\]' must be a dictionary.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -607,15 +599,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'fields' key is required in ValidationTestModelAdmin.fieldsets\[0\]\[1\] field options dict.", "'fields' key is required in ValidationTestModelAdmin.fieldsets\[0\]\[1\] field options dict.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
fieldsets = (("General", {"fields": ("name",)}),) fieldsets = (("General", {"fields": ("name",)}),)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
fieldsets = (("General", {"fields": ("name",)}),) fieldsets = (("General", {"fields": ("name",)}),)
@ -624,8 +615,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"Both fieldsets and fields are specified in ValidationTestModelAdmin.", "Both fieldsets and fields are specified in ValidationTestModelAdmin.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -635,8 +625,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"There are duplicate field\(s\) in ValidationTestModelAdmin.fieldsets", "There are duplicate field\(s\) in ValidationTestModelAdmin.fieldsets",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -646,8 +635,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"There are duplicate field\(s\) in ValidationTestModelAdmin.fields", "There are duplicate field\(s\) in ValidationTestModelAdmin.fields",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -662,8 +650,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"ValidationTestModelAdmin.form does not inherit from BaseModelForm.", "ValidationTestModelAdmin.form does not inherit from BaseModelForm.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -676,7 +663,7 @@ class ValidationTests(unittest.TestCase):
}), }),
) )
validate(BandAdmin, Band) BandAdmin.validate(Band)
class AdminBandForm(forms.ModelForm): class AdminBandForm(forms.ModelForm):
delete = forms.BooleanField() delete = forms.BooleanField()
@ -690,7 +677,7 @@ class ValidationTests(unittest.TestCase):
}), }),
) )
validate(BandAdmin, Band) BandAdmin.validate(Band)
def test_filter_vertical_validation(self): def test_filter_vertical_validation(self):
@ -700,8 +687,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.", "'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -711,8 +697,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -722,15 +707,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical\[0\]' must be a ManyToManyField.", "'ValidationTestModelAdmin.filter_vertical\[0\]' must be a ManyToManyField.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
filter_vertical = ("users",) filter_vertical = ("users",)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_filter_horizontal_validation(self): def test_filter_horizontal_validation(self):
@ -740,8 +724,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.", "'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -751,8 +734,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -762,15 +744,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal\[0\]' must be a ManyToManyField.", "'ValidationTestModelAdmin.filter_horizontal\[0\]' must be a ManyToManyField.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
filter_horizontal = ("users",) filter_horizontal = ("users",)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_radio_fields_validation(self): def test_radio_fields_validation(self):
@ -780,8 +761,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields' must be a dictionary.", "'ValidationTestModelAdmin.radio_fields' must be a dictionary.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -791,8 +771,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -802,8 +781,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields\['name'\]' is neither an instance of ForeignKey nor does have choices set.", "'ValidationTestModelAdmin.radio_fields\['name'\]' is neither an instance of ForeignKey nor does have choices set.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -813,15 +791,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields\['state'\]' is neither admin.HORIZONTAL nor admin.VERTICAL.", "'ValidationTestModelAdmin.radio_fields\['state'\]' is neither admin.HORIZONTAL nor admin.VERTICAL.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
radio_fields = {"state": VERTICAL} radio_fields = {"state": VERTICAL}
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_prepopulated_fields_validation(self): def test_prepopulated_fields_validation(self):
@ -831,8 +808,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.", "'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -842,8 +818,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -853,8 +828,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields\['slug'\]\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.prepopulated_fields\['slug'\]\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -864,15 +838,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields\['users'\]' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.", "'ValidationTestModelAdmin.prepopulated_fields\['users'\]' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
prepopulated_fields = {"slug": ("name",)} prepopulated_fields = {"slug": ("name",)}
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_display_validation(self): def test_list_display_validation(self):
@ -882,8 +855,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display' must be a list or tuple.", "'ValidationTestModelAdmin.list_display' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -893,8 +865,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
str_prefix("ValidationTestModelAdmin.list_display\[0\], %(_)s'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'."), str_prefix("ValidationTestModelAdmin.list_display\[0\], %(_)s'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'."),
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -904,8 +875,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.", "'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -917,7 +887,7 @@ class ValidationTests(unittest.TestCase):
pass pass
list_display = ('name', 'decade_published_in', 'a_method', a_callable) list_display = ('name', 'decade_published_in', 'a_method', a_callable)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_display_links_validation(self): def test_list_display_links_validation(self):
@ -927,8 +897,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links' must be a list or tuple.", "'ValidationTestModelAdmin.list_display_links' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -938,8 +907,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' which is not defined in 'list_display'.", "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' which is not defined in 'list_display'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -949,8 +917,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'name' which is not defined in 'list_display'.", "'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'name' which is not defined in 'list_display'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -963,7 +930,7 @@ class ValidationTests(unittest.TestCase):
list_display = ('name', 'decade_published_in', 'a_method', a_callable) list_display = ('name', 'decade_published_in', 'a_method', a_callable)
list_display_links = ('name', 'decade_published_in', 'a_method', a_callable) list_display_links = ('name', 'decade_published_in', 'a_method', a_callable)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_filter_validation(self): def test_list_filter_validation(self):
@ -973,8 +940,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter' must be a list or tuple.", "'ValidationTestModelAdmin.list_filter' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -984,8 +950,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' refers to 'non_existent_field' which does not refer to a Field.", "'ValidationTestModelAdmin.list_filter\[0\]' refers to 'non_existent_field' which does not refer to a Field.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -998,8 +963,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' is 'RandomClass' which is not a descendant of ListFilter.", "'ValidationTestModelAdmin.list_filter\[0\]' is 'RandomClass' which is not a descendant of ListFilter.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1009,8 +973,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'RandomClass' which is not of type FieldListFilter.", "'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'RandomClass' which is not of type FieldListFilter.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1028,8 +991,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'AwesomeFilter' which is not of type FieldListFilter.", "'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'AwesomeFilter' which is not of type FieldListFilter.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1039,8 +1001,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' is 'BooleanFieldListFilter' which is of type FieldListFilter but is not associated with a field name.", "'ValidationTestModelAdmin.list_filter\[0\]' is 'BooleanFieldListFilter' which is of type FieldListFilter but is not associated with a field name.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1049,7 +1010,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter), 'no') list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter), 'no')
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_per_page_validation(self): def test_list_per_page_validation(self):
@ -1058,16 +1019,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_per_page' should be a integer.", "'ValidationTestModelAdmin.list_per_page' should be a int.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
list_per_page = 100 list_per_page = 100
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_max_show_all_allowed_validation(self): def test_max_show_all_allowed_validation(self):
@ -1076,16 +1036,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_max_show_all' should be an integer.", "'ValidationTestModelAdmin.list_max_show_all' should be a int.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
list_max_show_all = 200 list_max_show_all = 200
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_search_fields_validation(self): def test_search_fields_validation(self):
@ -1095,8 +1054,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.search_fields' must be a list or tuple.", "'ValidationTestModelAdmin.search_fields' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1108,8 +1066,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1119,15 +1076,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.", "'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
date_hierarchy = 'pub_date' date_hierarchy = 'pub_date'
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_ordering_validation(self): def test_ordering_validation(self):
@ -1137,8 +1093,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering' must be a list or tuple.", "'ValidationTestModelAdmin.ordering' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1148,8 +1103,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.", "'ValidationTestModelAdmin.ordering\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1159,25 +1113,24 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering' has the random ordering marker '\?', but contains other fields as well. Please either remove '\?' or the other fields.", "'ValidationTestModelAdmin.ordering' has the random ordering marker '\?', but contains other fields as well. Please either remove '\?' or the other fields.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
ordering = ('?',) ordering = ('?',)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
ordering = ('band__name',) ordering = ('band__name',)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
ordering = ('name',) ordering = ('name',)
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_select_related_validation(self): def test_list_select_related_validation(self):
@ -1186,16 +1139,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.list_select_related' should be a boolean.", "'ValidationTestModelAdmin.list_select_related' should be a bool.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
list_select_related = False list_select_related = False
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_save_as_validation(self): def test_save_as_validation(self):
@ -1204,16 +1156,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.save_as' should be a boolean.", "'ValidationTestModelAdmin.save_as' should be a bool.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
save_as = True save_as = True
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_save_on_top_validation(self): def test_save_on_top_validation(self):
@ -1222,16 +1173,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.save_on_top' should be a boolean.", "'ValidationTestModelAdmin.save_on_top' should be a bool.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
save_on_top = True save_on_top = True
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_inlines_validation(self): def test_inlines_validation(self):
@ -1241,8 +1191,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines' must be a list or tuple.", "'ValidationTestModelAdmin.inlines' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1255,8 +1204,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines\[0\]' does not inherit from BaseModelAdmin.", "'ValidationTestModelAdmin.inlines\[0\]' does not inherit from BaseModelAdmin.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1269,8 +1217,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'model' is a required attribute of 'ValidationTestModelAdmin.inlines\[0\]'.", "'model' is a required attribute of 'ValidationTestModelAdmin.inlines\[0\]'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1286,8 +1233,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines\[0\].model' does not inherit from models.Model.", "'ValidationTestModelAdmin.inlines\[0\].model' does not inherit from models.Model.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1297,7 +1243,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline] inlines = [ValidationTestInline]
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_fields_validation(self): def test_fields_validation(self):
@ -1311,8 +1257,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestInline.fields' must be a list or tuple.", "'ValidationTestInline.fields' must be a list or tuple.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1328,8 +1273,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestInlineModel'.", "'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestInlineModel'.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1340,7 +1284,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline] inlines = [ValidationTestInline]
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_extra_validation(self): def test_extra_validation(self):
@ -1353,9 +1297,8 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestInline.extra' should be a integer.", "'ValidationTestInline.extra' should be a int.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1366,7 +1309,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline] inlines = [ValidationTestInline]
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_max_num_validation(self): def test_max_num_validation(self):
@ -1379,9 +1322,8 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestInline.max_num' should be an integer or None \(default\).", "'ValidationTestInline.max_num' should be a int.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1392,7 +1334,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline] inlines = [ValidationTestInline]
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_formset_validation(self): def test_formset_validation(self):
@ -1409,8 +1351,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self, six.assertRaisesRegex(self,
ImproperlyConfigured, ImproperlyConfigured,
"'ValidationTestInline.formset' does not inherit from BaseModelFormSet.", "'ValidationTestInline.formset' does not inherit from BaseModelFormSet.",
validate, ValidationTestModelAdmin.validate,
ValidationTestModelAdmin,
ValidationTestModel, ValidationTestModel,
) )
@ -1424,4 +1365,4 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin): class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline] inlines = [ValidationTestInline]
validate(ValidationTestModelAdmin, ValidationTestModel) ValidationTestModelAdmin.validate(ValidationTestModel)