Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added ability to describe grouping of form fields in the same row to …

…the `fields` ModelAdmin attribute.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16225 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 2b5730873bd29e5bb449df5800dea610c462786a 1 parent 5f60567
@ramiro ramiro authored
View
77 django/contrib/admin/validation.py
@@ -222,6 +222,40 @@ def validate_inline(cls, parent, parent_model):
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
+ check_formfield(cls, model, opts, label, 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.
+ pass
+ 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
@@ -238,23 +272,7 @@ def validate_base(cls, model):
# fields
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))
+ 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)):
@@ -273,30 +291,7 @@ def validate_base(cls, model):
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
+ 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__)
View
48 docs/ref/contrib/admin/index.txt
@@ -160,27 +160,45 @@ subclass::
.. attribute:: ModelAdmin.fields
- Use this option as an alternative to ``fieldsets`` if the layout does not
- matter and if you want to only show a subset of the available fields in the
- form. For example, you could define a simpler version of the admin form for
- the ``django.contrib.flatpages.FlatPage`` model as follows::
+ If you need to achieve simple changes in the layout of fields in the forms
+ of the "add" and "change" pages like only showing a subset of the available
+ fields, modifying their order or grouping them in rows you can use the
+ ``fields`` option (for more complex layout needs see the
+ :attr:`~ModelAdmin.fieldsets` option described in the next section). For
+ example, you could define a simpler version of the admin form for the
+ ``django.contrib.flatpages.FlatPage`` model as follows::
class FlatPageAdmin(admin.ModelAdmin):
fields = ('url', 'title', 'content')
- In the above example, only the fields 'url', 'title' and 'content' will be
- displayed, sequentially, in the form.
+ In the above example, only the fields ``url``, ``title`` and ``content``
+ will be displayed, sequentially, in the form.
.. versionadded:: 1.2
``fields`` can contain values defined in :attr:`ModelAdmin.readonly_fields`
to be displayed as read-only.
+ .. versionadded:: 1.4
+
+ To display multiple fields on the same line, wrap those fields in their own
+ tuple. In this example, the ``url`` and ``title`` fields will display on the
+ same line and the ``content`` field will be displayed below them in its
+ own line::
+
+ class FlatPageAdmin(admin.ModelAdmin):
+ fields = (('url', 'title'), 'content')
+
.. admonition:: Note
This ``fields`` option should not be confused with the ``fields``
- dictionary key that is within the ``fieldsets`` option, as described in
- the previous section.
+ dictionary key that is within the :attr:`~ModelAdmin.fieldsets` option,
+ as described in the next section.
+
+ If neither ``fields`` nor :attr:`~ModelAdmin.fieldsets` options are present,
+ Django will default to displaying each field that isn't an ``AutoField`` and
+ has ``editable=True``, in a single fieldset, in the same order as the fields
+ are defined in the model.
.. attribute:: ModelAdmin.fieldsets
@@ -213,9 +231,10 @@ subclass::
.. image:: _images/flatfiles_admin.png
- If ``fieldsets`` isn't given, Django will default to displaying each field
- that isn't an ``AutoField`` and has ``editable=True``, in a single
- fieldset, in the same order as the fields are defined in the model.
+ If neither ``fieldsets`` nor :attr:`~ModelAdmin.fields` options are present,
+ Django will default to displaying each field that isn't an ``AutoField`` and
+ has ``editable=True``, in a single fieldset, in the same order as the fields
+ are defined in the model.
The ``field_options`` dictionary can have the following keys:
@@ -229,9 +248,10 @@ subclass::
'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
}
- To display multiple fields on the same line, wrap those fields in
- their own tuple. In this example, the ``first_name`` and
- ``last_name`` fields will display on the same line::
+ Just like with the :attr:`~ModelAdmin.fields` option, to display
+ multiple fields on the same line, wrap those fields in their own
+ tuple. In this example, the ``first_name`` and ``last_name`` fields
+ will display on the same line::
{
'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
View
7 tests/regressiontests/admin_validation/tests.py
@@ -201,7 +201,7 @@ class BookAdmin(admin.ModelAdmin):
validate,
BookAdmin, Book)
- def test_cannon_include_through(self):
+ def test_cannot_include_through(self):
class FieldsetBookAdmin(admin.ModelAdmin):
fieldsets = (
('Header 1', {'fields': ('name',)}),
@@ -212,6 +212,11 @@ class FieldsetBookAdmin(admin.ModelAdmin):
validate,
FieldsetBookAdmin, Book)
+ def test_nested_fields(self):
+ class NestedFieldsAdmin(admin.ModelAdmin):
+ fields = ('price', ('name', 'subtitle'))
+ validate(NestedFieldsAdmin, Book)
+
def test_nested_fieldsets(self):
class NestedFieldsetAdmin(admin.ModelAdmin):
fieldsets = (
Please sign in to comment.
Something went wrong with that request. Please try again.