web/lib/django/contrib/admin/validation.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
--- a/web/lib/django/contrib/admin/validation.py	Wed May 19 17:43:59 2010 +0200
+++ b/web/lib/django/contrib/admin/validation.py	Tue May 25 02:43:45 2010 +0200
@@ -1,13 +1,11 @@
-try:
-    set
-except NameError:
-    from sets import Set as set   # Python 2.3 fallback
-
 from django.core.exceptions import ImproperlyConfigured
 from django.db import models
-from django.forms.models import BaseModelForm, BaseModelFormSet, fields_for_model, _get_foreign_key
+from django.forms.models import (BaseModelForm, BaseModelFormSet, fields_for_model,
+    _get_foreign_key)
 from django.contrib.admin.options import flatten_fieldsets, BaseModelAdmin
 from django.contrib.admin.options import HORIZONTAL, VERTICAL
+from django.contrib.admin.util import lookup_field
+
 
 __all__ = ['validate']
 
@@ -123,6 +121,18 @@
                 continue
             get_field(cls, model, opts, 'ordering[%d]' % idx, 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:
+                            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))
+
     # list_select_related = False
     # save_as = False
     # save_on_top = False
@@ -149,18 +159,26 @@
             validate_inline(inline, cls, model)
 
 def validate_inline(cls, parent, parent_model):
+
     # model is already verified to exist and be a Model
     if cls.fk_name: # default value is None
         f = get_field(cls, cls.model, cls.model._meta, 'fk_name', cls.fk_name)
         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)
+
     # extra = 3
-    # max_num = 0
-    for attr in ('extra', 'max_num'):
-        if not isinstance(getattr(cls, attr), int):
-            raise ImproperlyConfigured("'%s.%s' should be a integer."
-                    % (cls.__name__, attr))
+    if not isinstance(getattr(cls, 'extra'), int):
+        raise ImproperlyConfigured("'%s.extra' should be a integer."
+                % cls.__name__)
+
+    # max_num = None
+    max_num = getattr(cls, 'max_num', None)
+    if max_num is not None and not isinstance(max_num, int):
+        raise ImproperlyConfigured("'%s.max_num' should be an integer or None (default)."
+                % cls.__name__)
 
     # formset
     if hasattr(cls, 'formset') and not issubclass(cls.formset, BaseModelFormSet):
@@ -169,7 +187,6 @@
 
     # exclude
     if hasattr(cls, 'exclude') and cls.exclude:
-        fk = _get_foreign_key(parent_model, cls.model, can_fail=True)
         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 "
@@ -192,7 +209,22 @@
     if cls.fields: # default value is None
         check_isseq(cls, 'fields', cls.fields)
         for field in cls.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
             check_formfield(cls, model, opts, 'fields', field)
+            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.
+                continue
+            if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
+                raise ImproperlyConfigured("'%s.fields' can't include the ManyToManyField "
+                    "field '%s' because '%s' manually specifies "
+                    "a 'through' model." % (cls.__name__, field, field))
         if cls.fieldsets:
             raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
         if len(cls.fields) > len(set(cls.fields)):
@@ -211,11 +243,47 @@
                 raise ImproperlyConfigured("'fields' key is required in "
                         "%s.fieldsets[%d][1] field options dict."
                         % (cls.__name__, idx))
+            for fields in fieldset[1]['fields']:
+                # 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
+                    check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
+                    try:
+                        f = opts.get_field(field)
+                        if isinstance(f, models.ManyToManyField) and not f.rel.through._meta.auto_created:
+                            raise ImproperlyConfigured("'%s.fieldsets[%d][1]['fields']' "
+                                "can't include the ManyToManyField field '%s' because "
+                                "'%s' manually specifies a 'through' model." % (
+                                    cls.__name__, idx, 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.
+                        pass
         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__)
-        for field in flattened_fieldsets:
-            check_formfield(cls, model, opts, "fieldsets[%d][1]['fields']" % idx, field)
+
+    # exclude
+    if cls.exclude: # default value is None
+        check_isseq(cls, 'exclude', cls.exclude)
+        for field in cls.exclude:
+            check_formfield(cls, model, opts, 'exclude', field)
+            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.
+                continue
+        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):