Commit d818e0c9 authored by Russell Keith-Magee's avatar Russell Keith-Magee
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.
parent 6e7bd0b6
Loading
Loading
Loading
Loading
+11 −1
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ class Settings(BaseSettings):
            )

        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 @@ class Settings(BaseSettings):
                            "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 @@ class Settings(BaseSettings):
            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 @@ class UserSettingsHolder(BaseSettings):
    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()
+10 −0
Original line number Diff line number Diff line
@@ -618,3 +618,13 @@ STATICFILES_FINDERS = (

# 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 = []
+5 −1
Original line number Diff line number Diff line
# 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 @@ __all__ = [

def autodiscover():
    autodiscover_modules('admin', register_to=site)

checks.register('admin')(check_admin_app)
+932 −0

File added.

Preview size limit exceeded, changes collapsed.

+52 −13
Original line number Diff line number Diff line
@@ -8,14 +8,18 @@ from django import forms
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 import Http404, HttpResponseRedirect
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):
        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 @@ class BaseModelAdmin(six.with_metaclass(RenameBaseModelAdminMethods)):
        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 @@ class ModelAdmin(BaseModelAdmin):
        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
Loading