Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #16905 -- Added extensible checks (nee validation) framework

This is the result of Christopher Medrela's 2013 Summer of Code project.

Thanks also to Preston Holmes, Tim Graham, Anssi Kääriäinen, Florian
Apolloner, and Alex Gaynor for review notes along the way.

Also: Fixes #8579, fixes #3055, fixes #19844.
  • Loading branch information...
commit d818e0c9b2b88276cc499974f9eee893170bf0a8 1 parent 6e7bd0b
@freakboy3742 freakboy3742 authored
Showing with 7,059 additions and 1,959 deletions.
  1. +11 −1 django/conf/__init__.py
  2. +10 −0 django/conf/global_settings.py
  3. +5 −1 django/contrib/admin/__init__.py
  4. +932 −0 django/contrib/admin/checks.py
  5. +52 −13 django/contrib/admin/options.py
  6. +1 −1  django/contrib/admin/sites.py
  7. +6 −0 django/contrib/auth/__init__.py
  8. +69 −0 django/contrib/auth/checks.py
  9. +1 −1  django/contrib/auth/management/commands/changepassword.py
  10. +70 −13 django/contrib/auth/tests/test_management.py
  11. +8 −0 django/contrib/contenttypes/__init__.py
  12. +18 −0 django/contrib/contenttypes/checks.py
  13. +165 −2 django/contrib/contenttypes/generic.py
  14. +1 −1  django/contrib/gis/management/commands/ogrinspect.py
  15. +50 −26 django/contrib/sites/managers.py
  16. +1 −1  django/contrib/staticfiles/management/commands/collectstatic.py
  17. +18 −0 django/core/checks/__init__.py
  18. +0 −39 django/core/checks/compatibility/base.py
  19. +94 −43 django/core/checks/compatibility/django_1_6_0.py
  20. +84 −0 django/core/checks/messages.py
  21. +49 −0 django/core/checks/model_checks.py
  22. +63 −0 django/core/checks/registry.py
  23. +109 −17 django/core/management/base.py
  24. +27 −9 django/core/management/commands/check.py
  25. +1 −1  django/core/management/commands/compilemessages.py
  26. +1 −1  django/core/management/commands/createcachetable.py
  27. +1 −1  django/core/management/commands/dbshell.py
  28. +1 −1  django/core/management/commands/diffsettings.py
  29. +1 −1  django/core/management/commands/inspectdb.py
  30. +1 −1  django/core/management/commands/makemessages.py
  31. +2 −2 django/core/management/commands/runserver.py
  32. +1 −1  django/core/management/commands/shell.py
  33. +1 −1  django/core/management/commands/test.py
  34. +1 −1  django/core/management/commands/testserver.py
  35. +10 −5 django/core/management/commands/validate.py
  36. +1 −1  django/core/management/templates.py
  37. +0 −411 django/core/management/validation.py
  38. +27 −1 django/db/backends/__init__.py
  39. +25 −8 django/db/backends/mysql/validation.py
  40. +303 −0 django/db/models/base.py
  41. +284 −14 django/db/models/fields/__init__.py
  42. +71 −3 django/db/models/fields/files.py
  43. +364 −3 django/db/models/fields/related.py
  44. +19 −0 django/db/models/manager.py
  45. +12 −7 django/db/models/options.py
  46. +12 −4 django/forms/models.py
  47. +2 −1  django/test/__init__.py
  48. +3 −3 django/test/testcases.py
  49. +20 −0 django/test/utils.py
  50. +3 −0  django/utils/termcolors.py
  51. +29 −3 docs/howto/custom-management-commands.txt
  52. +1 −0  docs/index.txt
  53. +10 −0 docs/internals/deprecation.txt
  54. +3 −3 docs/intro/tutorial01.txt
  55. +208 −0 docs/ref/checks.txt
  56. +31 −10 docs/ref/django-admin.txt
  57. +1 −0  docs/ref/index.txt
  58. +17 −0 docs/ref/settings.txt
  59. +41 −1 docs/releases/1.7.txt
  60. +2 −2 docs/topics/db/models.txt
  61. 0  tests/{invalid_models_tests/invalid_models → admin_checks}/__init__.py
  62. +57 −0 tests/admin_checks/models.py
  63. +436 −0 tests/admin_checks/tests.py
  64. 0  tests/{check → admin_scripts/app_raising_messages}/__init__.py
  65. +27 −0 tests/admin_scripts/app_raising_messages/models.py
  66. 0  tests/admin_scripts/app_raising_warning/__init__.py
  67. +16 −0 tests/admin_scripts/app_raising_warning/models.py
  68. +1 −1  tests/admin_scripts/management/commands/app_command.py
  69. +1 −1  tests/admin_scripts/management/commands/base_command.py
  70. +1 −1  tests/admin_scripts/management/commands/label_command.py
  71. +1 −1  tests/admin_scripts/management/commands/noargs_command.py
  72. +11 −0 tests/admin_scripts/management/commands/validation_command.py
  73. +144 −53 tests/admin_scripts/tests.py
  74. +2 −2 tests/admin_validation/tests.py
  75. +21 −10 tests/admin_views/tests.py
  76. +0 −128 tests/check/tests.py
  77. 0  tests/check_framework/__init__.py
  78. +8 −0 tests/{check → check_framework}/models.py
  79. +219 −0 tests/check_framework/tests.py
  80. +301 −2 tests/contenttypes_tests/tests.py
  81. +3 −0  tests/fixtures_model_package/tests.py
  82. +5 −4 tests/inline_formsets/tests.py
  83. +18 −0 tests/invalid_models_tests/base.py
  84. +0 −535 tests/invalid_models_tests/invalid_models/models.py
  85. +68 −0 tests/invalid_models_tests/test_backend_specific.py
  86. +334 −0 tests/invalid_models_tests/test_models.py
  87. +417 −0 tests/invalid_models_tests/test_ordinary_fields.py
  88. +1,037 −0 tests/invalid_models_tests/test_relative_fields.py
  89. +0 −46 tests/invalid_models_tests/tests.py
  90. +1 −1  tests/logging_tests/tests.py
  91. +5 −1 tests/migrate_signals/tests.py
  92. +16 −1 tests/migrations/test_commands.py
  93. +5 −0 tests/model_fields/tests.py
  94. +20 −16 tests/model_validation/tests.py
  95. +476 −474 tests/modeladmin/tests.py
  96. +4 −0 tests/proxy_model_inheritance/tests.py
  97. +0 −11 tests/sites_framework/models.py
  98. +45 −5 tests/sites_framework/tests.py
  99. +4 −1 tests/test_runner/tests.py
  100. +1 −1  tests/user_commands/management/commands/dance.py
  101. +0 −5 tests/validation/test_error_messages.py
View
12 django/conf/__init__.py
@@ -99,7 +99,7 @@ def __init__(self, settings_module):
)
tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
-
+ self._explicit_settings = set()
for setting in dir(mod):
if setting.isupper():
setting_value = getattr(mod, setting)
@@ -110,6 +110,7 @@ def __init__(self, settings_module):
"Please fix your settings." % setting)
setattr(self, setting, setting_value)
+ self._explicit_settings.add(setting)
if not self.SECRET_KEY:
raise ImproperlyConfigured("The SECRET_KEY setting must not be empty.")
@@ -126,6 +127,9 @@ def __init__(self, settings_module):
os.environ['TZ'] = self.TIME_ZONE
time.tzset()
+ def is_overridden(self, setting):
+ return setting in self._explicit_settings
+
class UserSettingsHolder(BaseSettings):
"""
@@ -159,4 +163,10 @@ def __delattr__(self, name):
def __dir__(self):
return list(self.__dict__) + dir(self.default_settings)
+ def is_overridden(self, setting):
+ if setting in self._deleted:
+ return False
+ else:
+ return self.default_settings.is_overridden(setting)
+
settings = LazySettings()
View
10 django/conf/global_settings.py
@@ -618,3 +618,13 @@
# Migration module overrides for apps, by app label.
MIGRATION_MODULES = {}
+
+#################
+# SYSTEM CHECKS #
+#################
+
+# List of all issues generated by system checks that should be silenced. Light
+# issues like warnings, infos or debugs will not generate a message. Silencing
+# serious issues like errors and criticals does not result in hiding the
+# message, but Django will not stop you from e.g. running server.
+SILENCED_SYSTEM_CHECKS = []
View
6 django/contrib/admin/__init__.py
@@ -1,13 +1,15 @@
# ACTION_CHECKBOX_NAME is unused, but should stay since its import from here
# has been referenced in documentation.
+from django.contrib.admin.checks import check_admin_app
from django.contrib.admin.decorators import register
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.options import ModelAdmin, HORIZONTAL, VERTICAL
from django.contrib.admin.options import StackedInline, TabularInline
-from django.contrib.admin.sites import AdminSite, site
from django.contrib.admin.filters import (ListFilter, SimpleListFilter,
FieldListFilter, BooleanFieldListFilter, RelatedFieldListFilter,
ChoicesFieldListFilter, DateFieldListFilter, AllValuesFieldListFilter)
+from django.contrib.admin.sites import AdminSite, site
+from django.core import checks
from django.utils.module_loading import autodiscover_modules
__all__ = [
@@ -21,3 +23,5 @@
def autodiscover():
autodiscover_modules('admin', register_to=site)
+
+checks.register('admin')(check_admin_app)
View
932 django/contrib/admin/checks.py
@@ -0,0 +1,932 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from itertools import chain
+
+from django.contrib.admin.util import get_fields_from_path, NotRelationField
+from django.core import checks
+from django.db import models
+from django.db.models.fields import FieldDoesNotExist
+from django.forms.models import BaseModelForm, _get_foreign_key, BaseModelFormSet
+
+
+def check_admin_app(**kwargs):
+ from django.contrib.admin.sites import site
+
+ return list(chain.from_iterable(
+ model_admin.check(model, **kwargs)
+ for model, model_admin in site._registry.items()
+ ))
+
+
+class BaseModelAdminChecks(object):
+
+ def check(self, cls, model, **kwargs):
+ errors = []
+ errors.extend(self._check_raw_id_fields(cls, model))
+ errors.extend(self._check_fields(cls, model))
+ errors.extend(self._check_fieldsets(cls, model))
+ errors.extend(self._check_exclude(cls, model))
+ errors.extend(self._check_form(cls, model))
+ errors.extend(self._check_filter_vertical(cls, model))
+ errors.extend(self._check_filter_horizontal(cls, model))
+ errors.extend(self._check_radio_fields(cls, model))
+ errors.extend(self._check_prepopulated_fields(cls, model))
+ errors.extend(self._check_view_on_site_url(cls, model))
+ errors.extend(self._check_ordering(cls, model))
+ errors.extend(self._check_readonly_fields(cls, model))
+ return errors
+
+ def _check_raw_id_fields(self, cls, model):
+ """ Check that `raw_id_fields` only contains field names that are listed
+ on the model. """
+
+ if not isinstance(cls.raw_id_fields, (list, tuple)):
+ return must_be('a list or tuple', option='raw_id_fields', obj=cls, id='admin.E001')
+ else:
+ return list(chain(*[
+ self._check_raw_id_fields_item(cls, model, field_name, 'raw_id_fields[%d]' % index)
+ for index, field_name in enumerate(cls.raw_id_fields)
+ ]))
+
+ def _check_raw_id_fields_item(self, cls, model, field_name, label):
+ """ Check an item of `raw_id_fields`, i.e. check that field named
+ `field_name` exists in model `model` and is a ForeignKey or a
+ ManyToManyField. """
+
+ try:
+ field = model._meta.get_field(field_name)
+ except models.FieldDoesNotExist:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E002')
+ else:
+ if not isinstance(field, (models.ForeignKey, models.ManyToManyField)):
+ return must_be('a ForeignKey or ManyToManyField',
+ option=label, obj=cls, id='admin.E003')
+ else:
+ return []
+
+ def _check_fields(self, cls, model):
+ """ Check that `fields` only refer to existing fields, doesn't contain
+ duplicates. Check if at most one of `fields` and `fieldsets` is defined.
+ """
+
+ if cls.fields is None:
+ return []
+ elif not isinstance(cls.fields, (list, tuple)):
+ return must_be('a list or tuple', option='fields', obj=cls, id='admin.E004')
+ elif cls.fieldsets:
+ return [
+ checks.Error(
+ 'Both "fieldsets" and "fields" are specified.',
+ hint=None,
+ obj=cls,
+ id='admin.E005',
+ )
+ ]
+ elif len(cls.fields) != len(set(cls.fields)):
+ return [
+ checks.Error(
+ 'There are duplicate field(s) in "fields".',
+ hint=None,
+ obj=cls,
+ id='admin.E006',
+ )
+ ]
+ else:
+ return list(chain(*[
+ self._check_field_spec(cls, model, field_name, 'fields')
+ for field_name in cls.fields
+ ]))
+
+ def _check_fieldsets(self, cls, model):
+ """ Check that fieldsets is properly formatted and doesn't contain
+ duplicates. """
+
+ if cls.fieldsets is None:
+ return []
+ elif not isinstance(cls.fieldsets, (list, tuple)):
+ return must_be('a list or tuple', option='fieldsets', obj=cls, id='admin.E007')
+ else:
+ return list(chain(*[
+ self._check_fieldsets_item(cls, model, fieldset, 'fieldsets[%d]' % index)
+ for index, fieldset in enumerate(cls.fieldsets)
+ ]))
+
+ def _check_fieldsets_item(self, cls, model, fieldset, label):
+ """ 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=cls, id='admin.E008')
+ elif len(fieldset) != 2:
+ return must_be('a pair', option=label, obj=cls, id='admin.E009')
+ elif not isinstance(fieldset[1], dict):
+ return must_be('a dictionary', option='%s[1]' % label, obj=cls, id='admin.E010')
+ elif 'fields' not in fieldset[1]:
+ return [
+ checks.Error(
+ '"%s[1]" must contain "fields" key.' % label,
+ hint=None,
+ obj=cls,
+ id='admin.E011',
+ )
+ ]
+ elif len(fieldset[1]['fields']) != len(set(fieldset[1]['fields'])):
+ return [
+ checks.Error(
+ 'There are duplicate field(s) in "%s[1]".' % label,
+ hint=None,
+ obj=cls,
+ id='admin.E012',
+ )
+ ]
+ else:
+ return list(chain(*[
+ self._check_field_spec(cls, model, fields, '%s[1][\'fields\']' % label)
+ for fields in fieldset[1]['fields']
+ ]))
+
+ def _check_field_spec(self, cls, model, fields, label):
+ """ `fields` should be an item of `fields` or an item of
+ fieldset[1]['fields'] for any `fieldset` in `fieldsets`. It should be a
+ field name or a tuple of field names. """
+
+ if isinstance(fields, tuple):
+ return list(chain(*[
+ self._check_field_spec_item(cls, model, field_name, "%s[%d]" % (label, index))
+ for index, field_name in enumerate(fields)
+ ]))
+ else:
+ return self._check_field_spec_item(cls, model, fields, label)
+
+ def _check_field_spec_item(self, cls, model, field_name, label):
+ if field_name 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.
+ return []
+ else:
+ try:
+ field = model._meta.get_field(field_name)
+ except models.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.rel.through._meta.auto_created):
+ return [
+ checks.Error(
+ '"%s" cannot include the ManyToManyField "%s", '
+ 'because "%s" manually specifies relationship model.'
+ % (label, field_name, field_name),
+ hint=None,
+ obj=cls,
+ id='admin.E013',
+ )
+ ]
+ else:
+ return []
+
+ def _check_exclude(self, cls, model):
+ """ Check that exclude is a sequence without duplicates. """
+
+ if cls.exclude is None: # default value is None
+ return []
+ elif not isinstance(cls.exclude, (list, tuple)):
+ return must_be('a list or tuple', option='exclude', obj=cls, id='admin.E014')
+ elif len(cls.exclude) > len(set(cls.exclude)):
+ return [
+ checks.Error(
+ '"exclude" contains duplicate field(s).',
+ hint=None,
+ obj=cls,
+ id='admin.E015',
+ )
+ ]
+ else:
+ return []
+
+ def _check_form(self, cls, model):
+ """ Check that form subclasses BaseModelForm. """
+
+ if hasattr(cls, 'form') and not issubclass(cls.form, BaseModelForm):
+ return must_inherit_from(parent='BaseModelForm', option='form',
+ obj=cls, id='admin.E016')
+ else:
+ return []
+
+ def _check_filter_vertical(self, cls, model):
+ """ Check that filter_vertical is a sequence of field names. """
+
+ if not hasattr(cls, 'filter_vertical'):
+ return []
+ elif not isinstance(cls.filter_vertical, (list, tuple)):
+ return must_be('a list or tuple', option='filter_vertical', obj=cls, id='admin.E017')
+ else:
+ return list(chain(*[
+ self._check_filter_item(cls, model, field_name, "filter_vertical[%d]" % index)
+ for index, field_name in enumerate(cls.filter_vertical)
+ ]))
+
+ def _check_filter_horizontal(self, cls, model):
+ """ Check that filter_horizontal is a sequence of field names. """
+
+ if not hasattr(cls, 'filter_horizontal'):
+ return []
+ elif not isinstance(cls.filter_horizontal, (list, tuple)):
+ return must_be('a list or tuple', option='filter_horizontal', obj=cls, id='admin.E018')
+ else:
+ return list(chain(*[
+ self._check_filter_item(cls, model, field_name, "filter_horizontal[%d]" % index)
+ for index, field_name in enumerate(cls.filter_horizontal)
+ ]))
+
+ def _check_filter_item(self, cls, model, field_name, label):
+ """ Check one item of `filter_vertical` or `filter_horizontal`, i.e.
+ check that given field exists and is a ManyToManyField. """
+
+ try:
+ field = model._meta.get_field(field_name)
+ except models.FieldDoesNotExist:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E019')
+ else:
+ if not isinstance(field, models.ManyToManyField):
+ return must_be('a ManyToManyField', option=label, obj=cls, id='admin.E020')
+ else:
+ return []
+
+ def _check_radio_fields(self, cls, model):
+ """ Check that `radio_fields` is a dictionary. """
+
+ if not hasattr(cls, 'radio_fields'):
+ return []
+ elif not isinstance(cls.radio_fields, dict):
+ return must_be('a dictionary', option='radio_fields', obj=cls, id='admin.E021')
+ else:
+ return list(chain(*[
+ self._check_radio_fields_key(cls, model, field_name, 'radio_fields') +
+ self._check_radio_fields_value(cls, model, val, 'radio_fields[\'%s\']' % field_name)
+ for field_name, val in cls.radio_fields.items()
+ ]))
+
+ def _check_radio_fields_key(self, cls, 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 models.FieldDoesNotExist:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E022')
+ else:
+ if not (isinstance(field, models.ForeignKey) or field.choices):
+ return [
+ checks.Error(
+ '"%s" refers to "%s", which is neither an instance of ForeignKey nor does have choices set.' % (
+ label, field_name
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E023',
+ )
+ ]
+ else:
+ return []
+
+ def _check_radio_fields_value(self, cls, model, val, label):
+ """ Check type of a value of `radio_fields` dictionary. """
+
+ from django.contrib.admin.options import HORIZONTAL, VERTICAL
+
+ if val not in (HORIZONTAL, VERTICAL):
+ return [
+ checks.Error(
+ '"%s" is neither admin.HORIZONTAL nor admin.VERTICAL.' % label,
+ hint=None,
+ obj=cls,
+ id='admin.E024',
+ )
+ ]
+ else:
+ return []
+
+ def _check_view_on_site_url(self, cls, model):
+ if hasattr(cls, 'view_on_site'):
+ if not callable(cls.view_on_site) and not isinstance(cls.view_on_site, bool):
+ return [
+ checks.Error(
+ '"view_on_site" is not a callable or a boolean value.',
+ hint=None,
+ obj=cls,
+ id='admin.E025',
+ )
+ ]
+ else:
+ return []
+ else:
+ return []
+
+ def _check_prepopulated_fields(self, cls, model):
+ """ Check that `prepopulated_fields` is a dictionary containing allowed
+ field types. """
+
+ if not hasattr(cls, 'prepopulated_fields'):
+ return []
+ elif not isinstance(cls.prepopulated_fields, dict):
+ return must_be('a dictionary', option='prepopulated_fields', obj=cls, id='admin.E026')
+ else:
+ return list(chain(*[
+ self._check_prepopulated_fields_key(cls, model, field_name, 'prepopulated_fields') +
+ self._check_prepopulated_fields_value(cls, model, val, 'prepopulated_fields[\'%s\']' % field_name)
+ for field_name, val in cls.prepopulated_fields.items()
+ ]))
+
+ def _check_prepopulated_fields_key(self, cls, 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.
+ """
+
+ forbidden_field_types = (
+ models.DateTimeField,
+ models.ForeignKey,
+ models.ManyToManyField
+ )
+
+ try:
+ field = model._meta.get_field(field_name)
+ except models.FieldDoesNotExist:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E027')
+ else:
+ if isinstance(field, forbidden_field_types):
+ return [
+ checks.Error(
+ '"%s" refers to "%s", which must not be a DateTimeField, '
+ 'ForeignKey or ManyToManyField.' % (
+ label, field_name
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E028',
+ )
+ ]
+ else:
+ return []
+
+ def _check_prepopulated_fields_value(self, cls, model, val, label):
+ """ Check a value of `prepopulated_fields` dictionary, i.e. it's an
+ iterable of existing fields. """
+
+ if not isinstance(val, (list, tuple)):
+ return must_be('a list or tuple', option=label, obj=cls, id='admin.E029')
+ else:
+ return list(chain(*[
+ self._check_prepopulated_fields_value_item(cls, model, subfield_name, "%s[%r]" % (label, index))
+ for index, subfield_name in enumerate(val)
+ ]))
+
+ def _check_prepopulated_fields_value_item(self, cls, model, field_name, label):
+ """ For `prepopulated_fields` equal to {"slug": ("title",)},
+ `field_name` is "title". """
+
+ try:
+ model._meta.get_field(field_name)
+ except models.FieldDoesNotExist:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E030')
+ else:
+ return []
+
+ def _check_ordering(self, cls, model):
+ """ Check that ordering refers to existing fields or is random. """
+
+ # ordering = None
+ if cls.ordering is None: # The default value is None
+ return []
+ elif not isinstance(cls.ordering, (list, tuple)):
+ return must_be('a list or tuple', option='ordering', obj=cls, id='admin.E031')
+ else:
+ return list(chain(*[
+ self._check_ordering_item(cls, model, field_name, 'ordering[%d]' % index)
+ for index, field_name in enumerate(cls.ordering)
+ ]))
+
+ def _check_ordering_item(self, cls, model, field_name, label):
+ """ Check that `ordering` refers to existing fields. """
+
+ if field_name == '?' and len(cls.ordering) != 1:
+ return [
+ checks.Error(
+ '"ordering" has the random ordering marker "?", '
+ 'but contains other fields as well.',
+ hint='Either remove the "?", or remove the other fields.',
+ obj=cls,
+ id='admin.E032',
+ )
+ ]
+ elif field_name == '?':
+ return []
+ elif '__' in field_name:
+ # Skip ordering in the format field1__field2 (FIXME: checking
+ # this format would be nice, but it's a little fiddly).
+ return []
+ else:
+ if field_name.startswith('-'):
+ field_name = field_name[1:]
+
+ try:
+ model._meta.get_field(field_name)
+ except models.FieldDoesNotExist:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E033')
+ else:
+ return []
+
+ def _check_readonly_fields(self, cls, model):
+ """ Check that readonly_fields refers to proper attribute or field. """
+
+ if cls.readonly_fields == ():
+ return []
+ elif not isinstance(cls.readonly_fields, (list, tuple)):
+ return must_be('a list or tuple', option='readonly_fields', obj=cls, id='admin.E034')
+ else:
+ return list(chain(*[
+ self._check_readonly_fields_item(cls, model, field_name, "readonly_fields[%d]" % index)
+ for index, field_name in enumerate(cls.readonly_fields)
+ ]))
+
+ def _check_readonly_fields_item(self, cls, model, field_name, label):
+ if callable(field_name):
+ return []
+ elif hasattr(cls, field_name):
+ return []
+ elif hasattr(model, field_name):
+ return []
+ else:
+ try:
+ model._meta.get_field(field_name)
+ except models.FieldDoesNotExist:
+ return [
+ checks.Error(
+ '"%s" is neither a callable nor an attribute of "%s" nor found in the model %s.%s.' % (
+ label, cls.__name__, model._meta.app_label, model._meta.object_name
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E035',
+ )
+ ]
+ else:
+ return []
+
+
+class ModelAdminChecks(BaseModelAdminChecks):
+
+ def check(self, cls, model, **kwargs):
+ errors = super(ModelAdminChecks, self).check(cls, model=model, **kwargs)
+ errors.extend(self._check_save_as(cls, model))
+ errors.extend(self._check_save_on_top(cls, model))
+ errors.extend(self._check_inlines(cls, model))
+ errors.extend(self._check_list_display(cls, model))
+ errors.extend(self._check_list_display_links(cls, model))
+ errors.extend(self._check_list_filter(cls, model))
+ errors.extend(self._check_list_select_related(cls, model))
+ errors.extend(self._check_list_per_page(cls, model))
+ errors.extend(self._check_list_max_show_all(cls, model))
+ errors.extend(self._check_list_editable(cls, model))
+ errors.extend(self._check_search_fields(cls, model))
+ errors.extend(self._check_date_hierarchy(cls, model))
+ return errors
+
+ def _check_save_as(self, cls, model):
+ """ Check save_as is a boolean. """
+
+ if not isinstance(cls.save_as, bool):
+ return must_be('a boolean', option='save_as',
+ obj=cls, id='admin.E101')
+ else:
+ return []
+
+ def _check_save_on_top(self, cls, model):
+ """ Check save_on_top is a boolean. """
+
+ if not isinstance(cls.save_on_top, bool):
+ return must_be('a boolean', option='save_on_top',
+ obj=cls, id='admin.E102')
+ else:
+ return []
+
+ def _check_inlines(self, cls, model):
+ """ Check all inline model admin classes. """
+
+ if not isinstance(cls.inlines, (list, tuple)):
+ return must_be('a list or tuple', option='inlines', obj=cls, id='admin.E103')
+ else:
+ return list(chain(*[
+ self._check_inlines_item(cls, model, item, "inlines[%d]" % index)
+ for index, item in enumerate(cls.inlines)
+ ]))
+
+ def _check_inlines_item(self, cls, model, inline, label):
+ """ Check one inline model admin. """
+
+ from django.contrib.admin.options import BaseModelAdmin
+
+ if not issubclass(inline, BaseModelAdmin):
+ return must_inherit_from(parent='BaseModelAdmin', option=label,
+ obj=cls, id='admin.E104')
+ elif not inline.model:
+ return [
+ checks.Error(
+ '"model" is a required attribute of "%s".' % label,
+ hint=None,
+ obj=cls,
+ id='admin.E105',
+ )
+ ]
+ elif not issubclass(inline.model, models.Model):
+ return must_be('a Model', option='%s.model' % label,
+ obj=cls, id='admin.E106')
+ else:
+ return inline.check(model)
+
+ def _check_list_display(self, cls, model):
+ """ Check that list_display only contains fields or usable attributes.
+ """
+
+ if not isinstance(cls.list_display, (list, tuple)):
+ return must_be('a list or tuple', option='list_display', obj=cls, id='admin.E107')
+ else:
+ return list(chain(*[
+ self._check_list_display_item(cls, model, item, "list_display[%d]" % index)
+ for index, item in enumerate(cls.list_display)
+ ]))
+
+ def _check_list_display_item(self, cls, model, item, label):
+ if callable(item):
+ return []
+ elif hasattr(cls, item):
+ return []
+ elif hasattr(model, item):
+ # getattr(model, item) could be an X_RelatedObjectsDescriptor
+ try:
+ field = model._meta.get_field(item)
+ except models.FieldDoesNotExist:
+ try:
+ field = getattr(model, item)
+ except AttributeError:
+ field = None
+
+ if field is None:
+ return [
+ checks.Error(
+ '"%s" refers to "%s" that is neither a field, method nor a property of model %s.%s.' % (
+ label, item, model._meta.app_label, model._meta.object_name
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E108',
+ )
+ ]
+ elif isinstance(field, models.ManyToManyField):
+ return [
+ checks.Error(
+ '"%s" must not be a ManyToManyField.' % label,
+ hint=None,
+ obj=cls,
+ id='admin.E109',
+ )
+ ]
+ else:
+ return []
+ else:
+ try:
+ model._meta.get_field(item)
+ except models.FieldDoesNotExist:
+ return [
+ checks.Error(
+ '"%s" is neither a callable nor an attribute of "%s" nor found in model %s.%s.' % (
+ label, cls.__name__, model._meta.app_label, model._meta.object_name
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E110',
+ )
+ ]
+ else:
+ return []
+
+ def _check_list_display_links(self, cls, model):
+ """ Check that list_display_links is a unique subset of list_display.
+ """
+
+ if cls.list_display_links is None:
+ return []
+ elif not isinstance(cls.list_display_links, (list, tuple)):
+ return must_be('a list or tuple or None', option='list_display_links', obj=cls, id='admin.E111')
+ else:
+ return list(chain(*[
+ self._check_list_display_links_item(cls, model, field_name, "list_display_links[%d]" % index)
+ for index, field_name in enumerate(cls.list_display_links)
+ ]))
+
+ def _check_list_display_links_item(self, cls, model, field_name, label):
+ if field_name not in cls.list_display:
+ return [
+ checks.Error(
+ '"%s" refers to "%s", which is not defined in "list_display".' % (
+ label, field_name
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E112',
+ )
+ ]
+ else:
+ return []
+
+ def _check_list_filter(self, cls, model):
+ if not isinstance(cls.list_filter, (list, tuple)):
+ return must_be('a list or tuple', option='list_filter', obj=cls, id='admin.E113')
+ else:
+ return list(chain(*[
+ self._check_list_filter_item(cls, model, item, "list_filter[%d]" % index)
+ for index, item in enumerate(cls.list_filter)
+ ]))
+
+ def _check_list_filter_item(self, cls, 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 django.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=cls, id='admin.E114')
+ # ... but not a FieldListFilter.
+ elif issubclass(item, FieldListFilter):
+ return [
+ checks.Error(
+ '"%s" must not inherit from FieldListFilter.' % label,
+ hint=None,
+ obj=cls,
+ id='admin.E115',
+ )
+ ]
+ 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=cls, id='admin.E116')
+ 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(
+ '"%s" refers to "%s", which does not refer to a Field.' % (label, field),
+ hint=None,
+ obj=cls,
+ id='admin.E117',
+ )
+ ]
+ else:
+ return []
+
+ def _check_list_select_related(self, cls, model):
+ """ Check that list_select_related is a boolean, a list or a tuple. """
+
+ if not isinstance(cls.list_select_related, (bool, list, tuple)):
+ return must_be('a boolean, tuple or list', option='list_select_related',
+ obj=cls, id='admin.E118')
+ else:
+ return []
+
+ def _check_list_per_page(self, cls, model):
+ """ Check that list_per_page is an integer. """
+
+ if not isinstance(cls.list_per_page, int):
+ return must_be('an integer', option='list_per_page', obj=cls, id='admin.E119')
+ else:
+ return []
+
+ def _check_list_max_show_all(self, cls, model):
+ """ Check that list_max_show_all is an integer. """
+
+ if not isinstance(cls.list_max_show_all, int):
+ return must_be('an integer', option='list_max_show_all', obj=cls, id='admin.E120')
+ else:
+ return []
+
+ def _check_list_editable(self, cls, model):
+ """ Check that list_editable is a sequence of editable fields from
+ list_display without first element. """
+
+ if not isinstance(cls.list_editable, (list, tuple)):
+ return must_be('a list or tuple', option='list_editable', obj=cls, id='admin.E121')
+ else:
+ return list(chain(*[
+ self._check_list_editable_item(cls, model, item, "list_editable[%d]" % index)
+ for index, item in enumerate(cls.list_editable)
+ ]))
+
+ def _check_list_editable_item(self, cls, model, field_name, label):
+ try:
+ field = model._meta.get_field_by_name(field_name)[0]
+ except models.FieldDoesNotExist:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E122')
+ else:
+ if field_name not in cls.list_display:
+ return refer_to_missing_field(field=field_name, option=label,
+ model=model, obj=cls, id='admin.E123')
+ elif field_name in cls.list_display_links:
+ return [
+ checks.Error(
+ '"%s" cannot be in both "list_editable" and "list_display_links".' % field_name,
+ hint=None,
+ obj=cls,
+ id='admin.E124',
+ )
+ ]
+ elif not cls.list_display_links and cls.list_display[0] in cls.list_editable:
+ return [
+ checks.Error(
+ '"%s" refers to the first field in list_display ("%s"), '
+ 'which cannot be used unless list_display_links is set.' % (
+ label, cls.list_display[0]
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E125',
+ )
+ ]
+ elif not field.editable:
+ return [
+ checks.Error(
+ '"%s" refers to field "%s", whih is not editable through the admin.' % (
+ label, field_name
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E126',
+ )
+ ]
+
+ def _check_search_fields(self, cls, model):
+ """ Check search_fields is a sequence. """
+
+ if not isinstance(cls.search_fields, (list, tuple)):
+ return must_be('a list or tuple', option='search_fields', obj=cls, id='admin.E127')
+ else:
+ return []
+
+ def _check_date_hierarchy(self, cls, model):
+ """ Check that date_hierarchy refers to DateField or DateTimeField. """
+
+ if cls.date_hierarchy is None:
+ return []
+ else:
+ try:
+ field = model._meta.get_field(cls.date_hierarchy)
+ except models.FieldDoesNotExist:
+ return refer_to_missing_field(option='date_hierarchy',
+ field=cls.date_hierarchy,
+ model=model, obj=cls, id='admin.E128')
+ else:
+ if not isinstance(field, (models.DateField, models.DateTimeField)):
+ return must_be('a DateField or DateTimeField', option='date_hierarchy',
+ obj=cls, id='admin.E129')
+ else:
+ return []
+
+
+class InlineModelAdminChecks(BaseModelAdminChecks):
+
+ def check(self, cls, parent_model, **kwargs):
+ errors = super(InlineModelAdminChecks, self).check(cls, model=cls.model, **kwargs)
+ errors.extend(self._check_fk_name(cls, parent_model))
+ errors.extend(self._check_exclude_of_parent_model(cls, parent_model))
+ errors.extend(self._check_extra(cls))
+ errors.extend(self._check_max_num(cls))
+ errors.extend(self._check_formset(cls))
+ return errors
+
+ def _check_exclude_of_parent_model(self, cls, parent_model):
+ # Do not perform more specific checks if the base checks result in an
+ # error.
+ errors = super(InlineModelAdminChecks, self)._check_exclude(cls, parent_model)
+ if errors:
+ return []
+
+ # Skip if `fk_name` is invalid.
+ if self._check_fk_name(cls, parent_model):
+ return []
+
+ if cls.exclude is None:
+ return []
+
+ fk = _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name)
+ if fk.name in cls.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
+ ),
+ hint=None,
+ obj=cls,
+ id='admin.E201',
+ )
+ ]
+ else:
+ return []
+
+ def _check_fk_name(self, cls, parent_model):
+ try:
+ _get_foreign_key(parent_model, cls.model, fk_name=cls.fk_name)
+ except ValueError as e:
+ return [checks.Error(e.args[0], hint=None, obj=cls, id='admin.E202')]
+ else:
+ return []
+
+ def _check_extra(self, cls):
+ """ Check that extra is an integer. """
+
+ if not isinstance(cls.extra, int):
+ return must_be('an integer', option='extra', obj=cls, id='admin.E203')
+ else:
+ return []
+
+ def _check_max_num(self, cls):
+ """ Check that max_num is an integer. """
+
+ if cls.max_num is None:
+ return []
+ elif not isinstance(cls.max_num, int):
+ return must_be('an integer', option='max_num', obj=cls, id='admin.E204')
+ else:
+ return []
+
+ def _check_formset(self, cls):
+ """ Check formset is a subclass of BaseModelFormSet. """
+
+ if not issubclass(cls.formset, BaseModelFormSet):
+ return must_inherit_from(parent='BaseModelFormSet', option='formset',
+ obj=cls, id='admin.E205')
+ else:
+ return []
+
+
+def must_be(type, option, obj, id):
+ return [
+ checks.Error(
+ '"%s" must be %s.' % (option, type),
+ hint=None,
+ obj=obj,
+ id=id,
+ ),
+ ]
+
+
+def must_inherit_from(parent, option, obj, id):
+ return [
+ checks.Error(
+ '"%s" must inherit from %s.' % (option, parent),
+ hint=None,
+ obj=obj,
+ id=id,
+ ),
+ ]
+
+
+def refer_to_missing_field(field, option, model, obj, id):
+ return [
+ checks.Error(
+ '"%s" refers to field "%s", which is missing from model %s.%s.' % (
+ option, field, model._meta.app_label, model._meta.object_name
+ ),
+ hint=None,
+ obj=obj,
+ id=id,
+ ),
+ ]
View
65 django/contrib/admin/options.py
@@ -8,14 +8,18 @@
from django.conf import settings
from django.contrib import messages
from django.contrib.admin import widgets, helpers
-from django.contrib.admin.utils import (unquote, flatten_fieldsets, get_deleted_objects,
- model_format_dict, NestedObjects, lookup_needs_distinct)
from django.contrib.admin import validation
+from django.contrib.admin.checks import (BaseModelAdminChecks, ModelAdminChecks,
+ InlineModelAdminChecks)
+from django.contrib.admin.utils import (unquote, flatten_fieldsets,
+ get_deleted_objects, model_format_dict, NestedObjects,
+ lookup_needs_distinct)
from django.contrib.admin.templatetags.admin_static import static
from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
from django.contrib.auth import get_permission_codename
from django.contrib.contenttypes.models import ContentType
-from django.core.exceptions import PermissionDenied, ValidationError, FieldError
+from django.core import checks
+from django.core.exceptions import PermissionDenied, ValidationError, FieldError, ImproperlyConfigured
from django.core.paginator import Paginator
from django.core.urlresolvers import reverse
from django.db import models, transaction, router
@@ -30,16 +34,17 @@
from django.http.response import HttpResponseBase
from django.shortcuts import get_object_or_404
from django.template.response import SimpleTemplateResponse, TemplateResponse
-from django.utils.decorators import method_decorator
-from django.utils.html import escape, escapejs
-from django.utils.safestring import mark_safe
from django.utils import six
+from django.utils.decorators import method_decorator
from django.utils.deprecation import RenameMethodsBase
+from django.utils.encoding import force_text
+from django.utils.encoding import python_2_unicode_compatible
+from django.utils.html import escape, escapejs
from django.utils.http import urlencode
from django.utils.text import capfirst, get_text_list
from django.utils.translation import ugettext as _
from django.utils.translation import ungettext
-from django.utils.encoding import force_text
+from django.utils.safestring import mark_safe
from django.views.decorators.csrf import csrf_protect
@@ -103,14 +108,42 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
ordering = None
view_on_site = True
- # validation
- validator_class = validation.BaseValidator
+ # Validation of ModelAdmin definitions
+ # Old, deprecated style:
+ validator_class = None
+ default_validator_class = validation.BaseValidator
+ # New style:
+ checks_class = BaseModelAdminChecks
@classmethod
def validate(cls, model):
- validator = cls.validator_class()
+ warnings.warn(
+ 'ModelAdmin.validate() is deprecated. Use "check()" instead.',
+ PendingDeprecationWarning)
+ if cls.validator_class:
+ validator = cls.validator_class()
+ else:
+ validator = cls.default_validator_class()
validator.validate(cls, model)
+ @classmethod
+ def check(cls, model, **kwargs):
+ if cls.validator_class:
+ warnings.warn(
+ 'ModelAdmin.validator_class is deprecated. '
+ 'ModeAdmin validators must be converted to use '
+ 'the system check framework.',
+ PendingDeprecationWarning)
+ validator = cls.validator_class()
+ try:
+ validator.validate(cls, model)
+ except ImproperlyConfigured as e:
+ return [checks.Error(e.args[0], hint=None, obj=cls)]
+ else:
+ return []
+ else:
+ return cls.checks_class().check(cls, model, **kwargs)
+
def __init__(self):
self._orig_formfield_overrides = self.formfield_overrides
overrides = FORMFIELD_FOR_DBFIELD_DEFAULTS.copy()
@@ -435,6 +468,7 @@ def has_delete_permission(self, request, obj=None):
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
+@python_2_unicode_compatible
class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model."
@@ -469,7 +503,10 @@ class ModelAdmin(BaseModelAdmin):
actions_selection_counter = True
# validation
- validator_class = validation.ModelAdminValidator
+ # Old, deprecated style:
+ default_validator_class = validation.ModelAdminValidator
+ # New style:
+ checks_class = ModelAdminChecks
def __init__(self, model, admin_site):
self.model = model
@@ -477,6 +514,9 @@ def __init__(self, model, admin_site):
self.admin_site = admin_site
super(ModelAdmin, self).__init__()
+ def __str__(self):
+ return "%s.%s" % (self.model._meta.app_label, self.__class__.__name__)
+
def get_inline_instances(self, request, obj=None):
inline_instances = []
for inline_class in self.inlines:
@@ -1685,8 +1725,7 @@ class InlineModelAdmin(BaseModelAdmin):
verbose_name_plural = None
can_delete = True
- # validation
- validator_class = validation.InlineValidator
+ checks_class = InlineModelAdminChecks
def __init__(self, parent_model, admin_site):
self.admin_site = admin_site
View
2  django/contrib/admin/sites.py
@@ -100,7 +100,7 @@ def register(self, model_or_iterable, admin_class=None, **options):
admin_class = type("%sAdmin" % model.__name__, (admin_class,), options)
if admin_class is not ModelAdmin and settings.DEBUG:
- admin_class.validate(model)
+ admin_class.check(model)
# Instantiate the admin class to save in the registry
self._registry[model] = admin_class(model, self)
View
6 django/contrib/auth/__init__.py
@@ -2,6 +2,8 @@
import re
from django.conf import settings
+from django.contrib.auth.checks import check_user_model
+from django.core import checks
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.utils.module_loading import import_by_path
from django.middleware.csrf import rotate_token
@@ -13,6 +15,10 @@
REDIRECT_FIELD_NAME = 'next'
+# Register the user model checks
+checks.register('models')(check_user_model)
+
+
def load_backend(path):
return import_by_path(path)()
View
69 django/contrib/auth/checks.py
@@ -0,0 +1,69 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.apps import apps
+from django.core import checks
+
+
+def check_user_model(**kwargs):
+ from django.conf import settings
+
+ errors = []
+ app_name, model_name = settings.AUTH_USER_MODEL.split('.')
+
+ cls = apps.get_model(app_name, model_name)
+
+ # Check that REQUIRED_FIELDS is a list
+ if not isinstance(cls.REQUIRED_FIELDS, (list, tuple)):
+ errors.append(
+ checks.Error(
+ 'The REQUIRED_FIELDS must be a list or tuple.',
+ hint=None,
+ obj=cls,
+ id='auth.E001',
+ )
+ )
+
+ # Check that the USERNAME FIELD isn't included in REQUIRED_FIELDS.
+ if cls.USERNAME_FIELD in cls.REQUIRED_FIELDS:
+ errors.append(
+ checks.Error(
+ ('The field named as the USERNAME_FIELD '
+ 'must not be included in REQUIRED_FIELDS '
+ 'on a custom user model.'),
+ hint=None,
+ obj=cls,
+ id='auth.E002',
+ )
+ )
+
+ # Check that the username field is unique
+ if not cls._meta.get_field(cls.USERNAME_FIELD).unique:
+ if (settings.AUTHENTICATION_BACKENDS ==
+ ('django.contrib.auth.backends.ModelBackend',)):
+ errors.append(
+ checks.Error(
+ ('The %s.%s field must be unique because it is '
+ 'pointed to by USERNAME_FIELD.') % (
+ cls._meta.object_name, cls.USERNAME_FIELD
+ ),
+ hint=None,
+ obj=cls,
+ id='auth.E003',
+ )
+ )
+ else:
+ errors.append(
+ checks.Warning(
+ ('The %s.%s field is pointed to by USERNAME_FIELD, '
+ 'but it is not unique.') % (
+ cls._meta.object_name, cls.USERNAME_FIELD
+ ),
+ hint=('Ensure that your authentication backend can handle '
+ 'non-unique usernames.'),
+ obj=cls,
+ id='auth.W004',
+ )
+ )
+
+ return errors
View
2  django/contrib/auth/management/commands/changepassword.py
@@ -15,7 +15,7 @@ class Command(BaseCommand):
)
help = "Change a user's password for django.contrib.auth."
- requires_model_validation = False
+ requires_system_checks = False
def _get_pass(self, prompt="Password: "):
p = getpass.getpass(prompt=prompt)
View
83 django/contrib/auth/tests/test_management.py
@@ -3,17 +3,18 @@
from django.apps import apps
from django.contrib.auth import models, management
+from django.contrib.auth.checks import check_user_model
from django.contrib.auth.management import create_permissions
from django.contrib.auth.management.commands import changepassword
from django.contrib.auth.models import User
from django.contrib.auth.tests.custom_user import CustomUser
from django.contrib.auth.tests.utils import skipIfCustomUser
from django.contrib.contenttypes.models import ContentType
+from django.core import checks
from django.core import exceptions
from django.core.management import call_command
from django.core.management.base import CommandError
-from django.core.management.validation import get_validation_errors
-from django.test import TestCase, override_settings
+from django.test import TestCase, override_settings, override_system_checks
from django.utils import six
from django.utils.six import StringIO
@@ -161,7 +162,7 @@ def test_swappable_user(self):
email="joe@somewhere.org",
date_of_birth="1976-04-01",
stdout=new_io,
- skip_validation=True
+ skip_checks=True
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, 'Superuser created successfully.')
@@ -185,7 +186,7 @@ def test_swappable_user_missing_required_field(self):
username="joe@somewhere.org",
stdout=new_io,
stderr=new_io,
- skip_validation=True
+ skip_checks=True
)
self.assertEqual(CustomUser._default_manager.count(), 0)
@@ -193,25 +194,81 @@ def test_swappable_user_missing_required_field(self):
class CustomUserModelValidationTestCase(TestCase):
@override_settings(AUTH_USER_MODEL='auth.CustomUserNonListRequiredFields')
+ @override_system_checks([check_user_model])
def test_required_fields_is_list(self):
"REQUIRED_FIELDS should be a list."
- new_io = StringIO()
- get_validation_errors(new_io, apps.get_app_config('auth'))
- self.assertIn("The REQUIRED_FIELDS must be a list or tuple.", new_io.getvalue())
+
+ from .custom_user import CustomUserNonListRequiredFields
+ errors = checks.run_checks()
+ expected = [
+ checks.Error(
+ 'The REQUIRED_FIELDS must be a list or tuple.',
+ hint=None,
+ obj=CustomUserNonListRequiredFields,
+ id='auth.E001',
+ ),
+ ]
+ self.assertEqual(errors, expected)
@override_settings(AUTH_USER_MODEL='auth.CustomUserBadRequiredFields')
+ @override_system_checks([check_user_model])
def test_username_not_in_required_fields(self):
"USERNAME_FIELD should not appear in REQUIRED_FIELDS."
- new_io = StringIO()
- get_validation_errors(new_io, apps.get_app_config('auth'))
- self.assertIn("The field named as the USERNAME_FIELD should not be included in REQUIRED_FIELDS on a swappable User model.", new_io.getvalue())
+
+ from .custom_user import CustomUserBadRequiredFields
+ errors = checks.run_checks()
+ expected = [
+ checks.Error(
+ ('The field named as the USERNAME_FIELD must not be included '
+ 'in REQUIRED_FIELDS on a custom user model.'),
+ hint=None,
+ obj=CustomUserBadRequiredFields,
+ id='auth.E002',
+ ),
+ ]
+ self.assertEqual(errors, expected)
@override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername')
+ @override_system_checks([check_user_model])
def test_username_non_unique(self):
"A non-unique USERNAME_FIELD should raise a model validation error."
- new_io = StringIO()
- get_validation_errors(new_io, apps.get_app_config('auth'))
- self.assertIn("The USERNAME_FIELD must be unique. Add unique=True to the field parameters.", new_io.getvalue())
+
+ from .custom_user import CustomUserNonUniqueUsername
+ errors = checks.run_checks()
+ expected = [
+ checks.Error(
+ ('The CustomUserNonUniqueUsername.username field must be '
+ 'unique because it is pointed to by USERNAME_FIELD.'),
+ hint=None,
+ obj=CustomUserNonUniqueUsername,
+ id='auth.E003',
+ ),
+ ]
+ self.assertEqual(errors, expected)
+
+ @override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername',
+ AUTHENTICATION_BACKENDS=[
+ 'my.custom.backend',
+ ])
+ @override_system_checks([check_user_model])
+ def test_username_non_unique_with_custom_backend(self):
+ """ A non-unique USERNAME_FIELD should raise an error only if we use the
+ default authentication backend. Otherwise, an warning should be raised.
+ """
+
+ from .custom_user import CustomUserNonUniqueUsername
+ errors = checks.run_checks()
+ expected = [
+ checks.Warning(
+ ('The CustomUserNonUniqueUsername.username field is pointed to '
+ 'by USERNAME_FIELD, but it is not unique.'),
+ hint=('Ensure that your authentication backend can handle '
+ 'non-unique usernames.'),
+ obj=CustomUserNonUniqueUsername,
+ id='auth.W004',
+ )
+ ]
+ self.assertEqual(errors, expected)
class PermissionTestCase(TestCase):
View
8 django/contrib/contenttypes/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.contrib.contenttypes.checks import check_generic_foreign_keys
+from django.core import checks
+
+
+checks.register('models')(check_generic_foreign_keys)
View
18 django/contrib/contenttypes/checks.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.utils import six
+
+
+def check_generic_foreign_keys(**kwargs):
+ from .generic import GenericForeignKey
+ from django.db import models
+
+ errors = []
+ fields = (obj
+ for cls in models.get_models()
+ for obj in six.itervalues(vars(cls))
+ if isinstance(obj, GenericForeignKey))
+ for field in fields:
+ errors.extend(field.check())
+ return errors
View
167 django/contrib/contenttypes/generic.py
@@ -6,10 +6,12 @@
from collections import defaultdict
from functools import partial
+from django.core import checks
from django.core.exceptions import ObjectDoesNotExist
from django.db import connection
from django.db import models, router, transaction, DEFAULT_DB_ALIAS
-from django.db.models import signals
+from django.db.models import signals, FieldDoesNotExist
+from django.db.models.base import ModelBase
from django.db.models.fields.related import ForeignObject, ForeignObjectRel
from django.db.models.related import PathInfo
from django.db.models.sql.datastructures import Col
@@ -20,7 +22,7 @@
from django.contrib.contenttypes.models import ContentType
from django.utils import six
from django.utils.deprecation import RenameMethodsBase
-from django.utils.encoding import smart_text
+from django.utils.encoding import smart_text, python_2_unicode_compatible
class RenameGenericForeignKeyMethods(RenameMethodsBase):
@@ -29,6 +31,7 @@ class RenameGenericForeignKeyMethods(RenameMethodsBase):
)
+@python_2_unicode_compatible
class GenericForeignKey(six.with_metaclass(RenameGenericForeignKeyMethods)):
"""
Provides a generic relation to any object through content-type/object-id
@@ -53,6 +56,52 @@ def contribute_to_class(self, cls, name):
setattr(cls, name, self)
+ def __str__(self):
+ model = self.model
+ app = model._meta.app_label
+ return '%s.%s.%s' % (app, model._meta.object_name, self.name)
+
+ def check(self, **kwargs):
+ errors = []
+ errors.extend(self._check_content_type_field())
+ errors.extend(self._check_object_id_field())
+ errors.extend(self._check_field_name())
+ return errors
+
+ def _check_content_type_field(self):
+ return _check_content_type_field(
+ model=self.model,
+ field_name=self.ct_field,
+ checked_object=self)
+
+ def _check_object_id_field(self):
+ try:
+ self.model._meta.get_field(self.fk_field)
+ except FieldDoesNotExist:
+ return [
+ checks.Error(
+ 'The field refers to "%s" field which is missing.' % self.fk_field,
+ hint=None,
+ obj=self,
+ id='contenttypes.E001',
+ )
+ ]
+ else:
+ return []
+
+ def _check_field_name(self):
+ if self.name.endswith("_"):
+ return [
+ checks.Error(
+ 'Field names must not end with underscores.',
+ hint=None,
+ obj=self,
+ id='contenttypes.E002',
+ )
+ ]
+ else:
+ return []
+
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
"""
Handles initializing an object with the generic FK instead of
@@ -185,6 +234,72 @@ def __init__(self, to, **kwargs):
to, to_fields=[],
from_fields=[self.object_id_field_name], **kwargs)
+ def check(self, **kwargs):
+ errors = super(GenericRelation, self).check(**kwargs)
+ errors.extend(self._check_content_type_field())
+ errors.extend(self._check_object_id_field())
+ errors.extend(self._check_generic_foreign_key_existence())
+ return errors
+
+ def _check_content_type_field(self):
+ target = self.rel.to
+ if isinstance(target, ModelBase):
+ return _check_content_type_field(
+ model=target,
+ field_name=self.content_type_field_name,
+ checked_object=self)
+ else:
+ return []
+
+ def _check_object_id_field(self):
+ target = self.rel.to
+ if isinstance(target, ModelBase):
+ opts = target._meta
+ try:
+ opts.get_field(self.object_id_field_name)
+ except FieldDoesNotExist:
+ return [
+ checks.Error(
+ 'The field refers to %s.%s field which is missing.' % (
+ opts.object_name, self.object_id_field_name
+ ),
+ hint=None,
+ obj=self,
+ id='contenttypes.E003',
+ )
+ ]
+ else:
+ return []
+ else:
+ return []
+
+ def _check_generic_foreign_key_existence(self):
+ target = self.rel.to
+ if isinstance(target, ModelBase):
+ # Using `vars` is very ugly approach, but there is no better one,
+ # because GenericForeignKeys are not considered as fields and,
+ # therefore, are not included in `target._meta.local_fields`.
+ fields = target._meta.virtual_fields
+ if any(isinstance(field, GenericForeignKey) and
+ field.ct_field == self.content_type_field_name and
+ field.fk_field == self.object_id_field_name
+ for field in fields):
+ return []
+ else:
+ return [
+ checks.Warning(
+ ('The field defines a generic relation with the model '
+ '%s.%s, but the model lacks GenericForeignKey.') % (
+ target._meta.app_label, target._meta.object_name
+ ),
+ hint=None,
+ obj=self,
+ id='contenttypes.E004',
+ )
+ ]
+ else:
+ return []
+
def resolve_related_fields(self):
self.to_fields = [self.model._meta.pk.name]
return [(self.rel.to._meta.get_field_by_name(self.object_id_field_name)[0],
@@ -252,6 +367,54 @@ def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
})
+def _check_content_type_field(model, field_name, checked_object):
+ """ Check if field named `field_name` in model `model` exists and is
+ valid content_type field (is a ForeignKey to ContentType). """
+
+ try:
+ field = model._meta.get_field(field_name)
+ except FieldDoesNotExist:
+ return [
+ checks.Error(
+ 'The field refers to %s.%s field which is missing.' % (
+ model._meta.object_name, field_name
+ ),
+ hint=None,
+ obj=checked_object,
+ id='contenttypes.E005',
+ )
+ ]
+ else:
+ if not isinstance(field, models.ForeignKey):
+ return [
+ checks.Error(
+ ('"%s" field is used by a %s '
+ 'as content type field and therefore it must be '
+ 'a ForeignKey.') % (
+ field_name, checked_object.__class__.__name__
+ ),
+ hint=None,
+ obj=checked_object,
+ id='contenttypes.E006',
+ )
+ ]
+ elif field.rel.to != ContentType:
+ return [
+ checks.Error(
+ ('"%s" field is used by a %s '
+ 'as content type field and therefore it must be '
+ 'a ForeignKey to ContentType.') % (
+ field_name, checked_object.__class__.__name__
+ ),
+ hint=None,
+ obj=checked_object,
+ id='contenttypes.E007',
+ )
+ ]
+ else:
+ return []
+
+
class ReverseGenericRelatedObjectsDescriptor(object):
"""
This class provides the functionality that makes the related-object
View
2  django/contrib/gis/management/commands/ogrinspect.py
@@ -74,7 +74,7 @@ class Command(LabelCommand):
help='Generate mapping dictionary for use with `LayerMapping`.')
)
- requires_model_validation = False
+ requires_system_checks = False
def handle(self, *args, **options):
try:
View
76 django/contrib/sites/managers.py
@@ -1,42 +1,66 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
from django.conf import settings
+from django.core import checks
from django.db import models
from django.db.models.fields import FieldDoesNotExist
class CurrentSiteManager(models.Manager):
"Use this to limit objects to those associated with the current site."
+
def __init__(self, field_name=None):
super(CurrentSiteManager, self).__init__()
self.__field_name = field_name
- self.__is_validated = False
-
- def _validate_field_name(self):
- field_names = self.model._meta.get_all_field_names()
-
- # If a custom name is provided, make sure the field exists on the model
- if self.__field_name is not None and self.__field_name not in field_names:
- raise ValueError("%s couldn't find a field named %s in %s." %
- (self.__class__.__name__, self.__field_name, self.model._meta.object_name))
- # Otherwise, see if there is a field called either 'site' or 'sites'
- else:
- for potential_name in ['site', 'sites']:
- if potential_name in field_names:
- self.__field_name = potential_name
- self.__is_validated = True
- break
+ def check(self, **kwargs):
+ errors = super(CurrentSiteManager, self).check(**kwargs)
+ errors.extend(self._check_field_name())
+ return errors
- # Now do a type check on the field (FK or M2M only)
+ def _check_field_name(self):
+ field_name = self._get_field_name()
try:
- field = self.model._meta.get_field(self.__field_name)
- if not isinstance(field, (models.ForeignKey, models.ManyToManyField)):
- raise TypeError("%s must be a ForeignKey or ManyToManyField." % self.__field_name)
+ field = self.model._meta.get_field(field_name)
except FieldDoesNotExist:
- raise ValueError("%s couldn't find a field named %s in %s." %
- (self.__class__.__name__, self.__field_name, self.model._meta.object_name))
- self.__is_validated = True
+ return [
+ checks.Error(
+ "CurrentSiteManager could not find a field named '%s'." % field_name,
+ hint=('Ensure that you did not misspell the field name. '
+ 'Does the field exist?'),
+ obj=self,
+ id='sites.E001',
+ )
+ ]
+
+ if not isinstance(field, (models.ForeignKey, models.ManyToManyField)):
+ return [
+ checks.Error(
+ "CurrentSiteManager requires that '%s.%s' must be a "
+ "ForeignKey or ManyToManyField." % (
+ self.model._meta.object_name, field_name
+ ),
+ hint=None,
+ obj=self,
+ id='sites.E002',
+ )
+ ]
+
+ return []
+
+ def _get_field_name(self):
+ """ Return self.__field_name or 'site' or 'sites'. """
+
+ if not self.__field_name:
+ try:
+ self.model._meta.get_field('site')
+ except FieldDoesNotExist:
+ self.__field_name = 'sites'
+ else:
+ self.__field_name = 'site'
+ return self.__field_name
def get_queryset(self):
- if not self.__is_validated:
- self._validate_field_name()
- return super(CurrentSiteManager, self).get_queryset().filter(**{self.__field_name + '__id': settings.SITE_ID})
+ return super(CurrentSiteManager, self).get_queryset().filter(
+ **{self._get_field_name() + '__id': settings.SITE_ID})
View
2  django/contrib/staticfiles/management/commands/collectstatic.py
@@ -45,7 +45,7 @@ class Command(NoArgsCommand):
"'.*' and '*~'."),
)
help = "Collect static files in a single location."
- requires_model_validation = False
+ requires_system_checks = False
def __init__(self, *args, **kwargs):
super(NoArgsCommand, self).__init__(*args, **kwargs)
View
18 django/core/checks/__init__.py
@@ -0,0 +1,18 @@
+# -*- coding: utf8 -*-
+from __future__ import unicode_literals
+
+from .messages import (CheckMessage,
+ Debug, Info, Warning, Error, Critical,
+ DEBUG, INFO, WARNING, ERROR, CRITICAL)
+from .registry import register, run_checks, tag_exists
+
+# Import these to force registration of checks
+import django.core.checks.compatibility.django_1_6_0 # NOQA
+import django.core.checks.model_checks # NOQA
+
+__all__ = [
+ 'CheckMessage',
+ 'Debug', 'Info', 'Warning', 'Error', 'Critical',
+ 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL',
+ 'register', 'run_checks', 'tag_exists',
+]
View
39 django/core/checks/compatibility/base.py
@@ -1,39 +0,0 @@
-from __future__ import unicode_literals
-import warnings
-
-from django.core.checks.compatibility import django_1_6_0
-
-
-COMPAT_CHECKS = [
- # Add new modules at the top, so we keep things in descending order.
- # After two-three minor releases, old versions should get dropped.
- django_1_6_0,
-]
-
-
-def check_compatibility():
- """
- Runs through compatibility checks to warn the user with an existing install
- about changes in an up-to-date Django.
-
- Modules should be located in ``django.core.compat_checks`` (typically one
- per release of Django) & must have a ``run_checks`` function that runs
- all the checks.
-
- Returns a list of informational messages about incompatibilities.
- """
- messages = []
-
- for check_module in COMPAT_CHECKS:
- check = getattr(check_module, 'run_checks', None)
-
- if check is None:
- warnings.warn(
- "The '%s' module lacks a " % check_module.__name__ +
- "'run_checks' method, which is needed to verify compatibility."
- )
- continue
-
- messages.extend(check())
-
- return messages
View
137 django/core/checks/compatibility/django_1_6_0.py
@@ -1,10 +1,20 @@
+# -*- encoding: utf-8 -*-
from __future__ import unicode_literals
from django.apps import apps
-from django.db import models
+from .. import Warning, register
-def check_test_runner():
+
+@register('compatibility')
+def check_1_6_compatibility(**kwargs):
+ errors = []
+ errors.extend(_check_test_runner(**kwargs))
+ errors.extend(_check_boolean_field_default_value(**kwargs))
+ return errors
+
+
+def _check_test_runner(app_configs=None, **kwargs):
"""
Checks if the user has *not* overridden the ``TEST_RUNNER`` setting &
warns them about the default behavior changes.
@@ -13,53 +23,94 @@ def check_test_runner():
doing & avoid generating a message.
"""
from django.conf import settings
- new_default = 'django.test.runner.DiscoverRunner'
- test_runner_setting = getattr(settings, 'TEST_RUNNER', new_default)
-
- if test_runner_setting == new_default:
- message = [
- "Django 1.6 introduced a new default test runner ('%s')" % new_default,
- "You should ensure your tests are all running & behaving as expected. See",
- "https://docs.djangoproject.com/en/dev/releases/1.6/#discovery-of-tests-in-any-test-module",
- "for more information.",
+
+ # We need to establish if this is a project defined on the 1.5 project template,
+ # because if the project was generated on the 1.6 template, it will have be been
+ # developed with the new TEST_RUNNER behavior in mind.
+
+ # There's no canonical way to do this; so we leverage off the fact that 1.6
+ # also introduced a new project template, removing a bunch of settings from the
+ # default that won't be in common usage.
+
+ # We make this determination on a balance of probabilities. Each of these factors
+ # contributes a weight; if enough of them trigger, we've got a likely 1.6 project.
+ weight = 0
+
+ # If TEST_RUNNER is explicitly set, it's all a moot point - if it's been explcitly set,
+ # the user has opted into a specific set of behaviors, which won't change as the
+ # default changes.
+ if not settings.is_overridden('TEST_RUNNER'):
+ # Strong markers:
+ # SITE_ID = 1 is in 1.5 template, not defined in 1.6 template
+ try:
+ settings.SITE_ID
+ weight += 2
+ except AttributeError:
+ pass
+
+ # BASE_DIR is not defined in 1.5 template, set in 1.6 template
+ try:
+ settings.BASE_DIR
+ except AttributeError:
+ weight += 2
+
+ # TEMPLATE_LOADERS defined in 1.5 template, not defined in 1.6 template
+ if settings.is_overridden('TEMPLATE_LOADERS'):
+ weight += 2
+
+ # MANAGERS defined in 1.5 template, not defined in 1.6 template
+ if settings.is_overridden('MANAGERS'):
+ weight += 2
+
+ # Weaker markers - These are more likely to have been added in common usage
+ # ADMINS defined in 1.5 template, not defined in 1.6 template
+ if settings.is_overridden('ADMINS'):
+ weight += 1
+
+ # Clickjacking enabled by default in 1.6
+ if 'django.middleware.clickjacking.XFrameOptionsMiddleware' not in set(settings.MIDDLEWARE_CLASSES):
+ weight += 1
+
+ if weight >= 6:
+ return [
+ Warning(
+ "Some project unittests may not execute as expected.",
+ hint=("Django 1.6 introduced a new default test runner. It looks like "
+ "this project was generated using Django 1.5 or earlier. You should "
+ "ensure your tests are all running & behaving as expected. See "
+ "https://docs.djangoproject.com/en/dev/releases/1.6/#discovery-of-tests-in-any-test-module "
+ "for more information."),
+ obj=None,
+ id='1_6.W001',
+ )
]
- return ' '.join(message)
+ else:
+ return []
-def check_boolean_field_default_value():
+def _check_boolean_field_default_value(app_configs=None, **kwargs):
"""
Checks if there are any BooleanFields without a default value, &
- warns the user that the default has changed from False to Null.
+ warns the user that the default has changed from False to None.
"""
- fields = []
- for cls in apps.get_models():
- opts = cls._meta
- for f in opts.local_fields:
- if isinstance(f, models.BooleanField) and not f.has_default():
- fields.append(
- '%s.%s: "%s"' % (opts.app_label, opts.object_name, f.name)
- )
- if fields:
- fieldnames = ", ".join(fields)
- message = [
- "You have not set a default value for one or more BooleanFields:",
- "%s." % fieldnames,
- "In Django 1.6 the default value of BooleanField was changed from",
- "False to Null when Field.default isn't defined. See",
- "https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield"
- "for more information."
- ]
- return ' '.join(message)
+ from django.db import models
+ problem_fields = [
+ field
+ for model in apps.get_models(**kwargs)
+ if app_configs is None or model._meta.app_config in app_configs
+ for field in model._meta.local_fields
+ if isinstance(field, models.BooleanField) and not field.has_default()
+ ]
-def run_checks():
- """
- Required by the ``check`` management command, this returns a list of
- messages from all the relevant check functions for this version of Django.
- """
- checks = [
- check_test_runner(),
- check_boolean_field_default_value(),
+ return [
+ Warning(
+ "BooleanField does not have a default value. ",
+ hint=("Django 1.6 changed the default value of BooleanField from False to None. "
+ "See https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield "
+ "for more information."),
+ obj=field,
+ id='1_6.W002',
+ )
+ for field in problem_fields
]
- # Filter out the ``None`` or empty strings.
- return [output for output in checks if output]
View
84 django/core/checks/messages.py
@@ -0,0 +1,84 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.utils.encoding import python_2_unicode_compatible, force_str
+
+
+# Levels
+DEBUG = 10
+INFO = 20
+WARNING = 30
+ERROR = 40
+CRITICAL = 50
+
+
+@python_2_unicode_compatible
+class CheckMessage(object):
+
+ def __init__(self, level, msg, hint, obj=None, id=None):
+ assert isinstance(level, int), "The first argument should be level."
+ self.level = level
+ self.msg = msg
+ self.hint = hint
+ self.obj = obj
+ self.id = id
+
+ def __eq__(self, other):
+ return all(getattr(self, attr) == getattr(other, attr)
+ for attr in ['level', 'msg', 'hint', 'obj', 'id'])
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __str__(self):
+ from django.db import models
+
+ if self.obj is None:
+ obj = "?"
+ elif isinstance(self.obj, models.base.ModelBase):
+ # We need to hardcode ModelBase and Field cases because its __str__
+ # method doesn't return "applabel.modellabel" and cannot be changed.
+ model = self.obj
+ app = model._meta.app_label
+ obj = '%s.%s' % (app, model._meta.object_name)
+ else:
+ obj = force_str(self.obj)
+ id = "(%s) " % self.id if self.id else ""
+ hint = "\n\tHINT: %s" % self.hint if self.hint else ''
+ return "%s: %s%s%s" % (obj, id, self.msg, hint)
+
+ def __repr__(self):
+ return "<%s: level=%r, msg=%r, hint=%r, obj=%r, id=%r>" % \
+ (self.__class__.__name__, self.level, self.msg, self.hint, self.obj, self.id)
+
+ def is_serious(self):
+ return self.level >= ERROR
+
+ def is_silenced(self):
+ from django.conf import settings
+ return self.id in settings.SILENCED_SYSTEM_CHECKS
+
+
+class Debug(CheckMessage):
+ def __init__(self, *args, **kwargs):
+ return super(Debug, self).__init__(DEBUG, *args, **kwargs)
+
+
+class Info(CheckMessage):
+ def __init__(self, *args, **kwargs):
+ return super(Info, self).__init__(INFO, *args, **kwargs)
+
+
+class Warning(CheckMessage):
+ def __init__(self, *args, **kwargs):
+ return super(Warning, self).__init__(WARNING, *args, **kwargs)
+
+
+class Error(CheckMessage):
+ def __init__(self, *args, **kwargs):
+ return super(Error, self).__init__(ERROR, *args, **kwargs)
+
+
+class Critical(CheckMessage):
+ def __init__(self, *args, **kwargs):
+ return super(Critical, self).__init__(CRITICAL, *args, **kwargs)
View
49