mirror of
				https://github.com/django/django.git
				synced 2025-11-04 13:39:16 +00:00 
			
		
		
		
	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
This commit is contained in:
		
							parent
							
								
									5f605678f0
								
							
						
					
					
						commit
						2b5730873b
					
				
					 3 changed files with 76 additions and 56 deletions
				
			
		| 
						 | 
					@ -222,6 +222,40 @@ def validate_inline(cls, parent, parent_model):
 | 
				
			||||||
    if hasattr(cls, "readonly_fields"):
 | 
					    if hasattr(cls, "readonly_fields"):
 | 
				
			||||||
        check_readonly_fields(cls, cls.model, cls.model._meta)
 | 
					        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):
 | 
					def validate_base(cls, model):
 | 
				
			||||||
    opts = model._meta
 | 
					    opts = model._meta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -238,23 +272,7 @@ def validate_base(cls, model):
 | 
				
			||||||
    # fields
 | 
					    # fields
 | 
				
			||||||
    if cls.fields: # default value is None
 | 
					    if cls.fields: # default value is None
 | 
				
			||||||
        check_isseq(cls, 'fields', cls.fields)
 | 
					        check_isseq(cls, 'fields', cls.fields)
 | 
				
			||||||
        for field in cls.fields:
 | 
					        validate_fields_spec(cls, model, opts, cls.fields, '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:
 | 
					        if cls.fieldsets:
 | 
				
			||||||
            raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
 | 
					            raise ImproperlyConfigured('Both fieldsets and fields are specified in %s.' % cls.__name__)
 | 
				
			||||||
        if len(cls.fields) > len(set(cls.fields)):
 | 
					        if len(cls.fields) > len(set(cls.fields)):
 | 
				
			||||||
| 
						 | 
					@ -273,30 +291,7 @@ def validate_base(cls, model):
 | 
				
			||||||
                raise ImproperlyConfigured("'fields' key is required in "
 | 
					                raise ImproperlyConfigured("'fields' key is required in "
 | 
				
			||||||
                        "%s.fieldsets[%d][1] field options dict."
 | 
					                        "%s.fieldsets[%d][1] field options dict."
 | 
				
			||||||
                        % (cls.__name__, idx))
 | 
					                        % (cls.__name__, idx))
 | 
				
			||||||
            for fields in fieldset[1]['fields']:
 | 
					            validate_fields_spec(cls, model, opts, fieldset[1]['fields'], "fieldsets[%d][1]['fields']" % idx)
 | 
				
			||||||
                # 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)
 | 
					        flattened_fieldsets = flatten_fieldsets(cls.fieldsets)
 | 
				
			||||||
        if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
 | 
					        if len(flattened_fieldsets) > len(set(flattened_fieldsets)):
 | 
				
			||||||
            raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
 | 
					            raise ImproperlyConfigured('There are duplicate field(s) in %s.fieldsets' % cls.__name__)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -160,27 +160,45 @@ subclass::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. attribute:: ModelAdmin.fields
 | 
					.. attribute:: ModelAdmin.fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Use this option as an alternative to ``fieldsets`` if the layout does not
 | 
					    If you need to achieve simple changes in the layout of fields in the forms
 | 
				
			||||||
    matter and if you want to only show a subset of the available fields in the
 | 
					    of the "add" and "change" pages like only showing a subset of the available
 | 
				
			||||||
    form. For example, you could define a simpler version of the admin form for
 | 
					    fields, modifying their order or grouping them in rows you can use the
 | 
				
			||||||
    the ``django.contrib.flatpages.FlatPage`` model as follows::
 | 
					    ``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):
 | 
					        class FlatPageAdmin(admin.ModelAdmin):
 | 
				
			||||||
            fields = ('url', 'title', 'content')
 | 
					            fields = ('url', 'title', 'content')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    In the above example, only the fields 'url', 'title' and 'content' will be
 | 
					    In the above example, only the fields ``url``, ``title`` and ``content``
 | 
				
			||||||
    displayed, sequentially, in the form.
 | 
					    will be displayed, sequentially, in the form.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .. versionadded:: 1.2
 | 
					    .. versionadded:: 1.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ``fields`` can contain values defined in :attr:`ModelAdmin.readonly_fields`
 | 
					    ``fields`` can contain values defined in :attr:`ModelAdmin.readonly_fields`
 | 
				
			||||||
    to be displayed as read-only.
 | 
					    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
 | 
					    .. admonition:: Note
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        This ``fields`` option should not be confused with the ``fields``
 | 
					        This ``fields`` option should not be confused with the ``fields``
 | 
				
			||||||
        dictionary key that is within the ``fieldsets`` option, as described in
 | 
					        dictionary key that is within the :attr:`~ModelAdmin.fieldsets` option,
 | 
				
			||||||
        the previous section.
 | 
					        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
 | 
					.. attribute:: ModelAdmin.fieldsets
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -213,9 +231,10 @@ subclass::
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .. image:: _images/flatfiles_admin.png
 | 
					        .. image:: _images/flatfiles_admin.png
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    If ``fieldsets`` isn't given, Django will default to displaying each field
 | 
					    If neither ``fieldsets`` nor :attr:`~ModelAdmin.fields` options are present,
 | 
				
			||||||
    that isn't an ``AutoField`` and has ``editable=True``, in a single
 | 
					    Django will default to displaying each field that isn't an ``AutoField`` and
 | 
				
			||||||
    fieldset, in the same order as the fields are defined in the model.
 | 
					    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:
 | 
					    The ``field_options`` dictionary can have the following keys:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -229,9 +248,10 @@ subclass::
 | 
				
			||||||
                'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
 | 
					                'fields': ('first_name', 'last_name', 'address', 'city', 'state'),
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            To display multiple fields on the same line, wrap those fields in
 | 
					            Just like with the :attr:`~ModelAdmin.fields` option, to display
 | 
				
			||||||
            their own tuple. In this example, the ``first_name`` and
 | 
					            multiple fields on the same line, wrap those fields in their own
 | 
				
			||||||
            ``last_name`` fields will display on the same line::
 | 
					            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'),
 | 
					                'fields': (('first_name', 'last_name'), 'address', 'city', 'state'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -201,7 +201,7 @@ class ValidationTestCase(TestCase):
 | 
				
			||||||
            validate,
 | 
					            validate,
 | 
				
			||||||
            BookAdmin, Book)
 | 
					            BookAdmin, Book)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_cannon_include_through(self):
 | 
					    def test_cannot_include_through(self):
 | 
				
			||||||
        class FieldsetBookAdmin(admin.ModelAdmin):
 | 
					        class FieldsetBookAdmin(admin.ModelAdmin):
 | 
				
			||||||
            fieldsets = (
 | 
					            fieldsets = (
 | 
				
			||||||
                ('Header 1', {'fields': ('name',)}),
 | 
					                ('Header 1', {'fields': ('name',)}),
 | 
				
			||||||
| 
						 | 
					@ -212,6 +212,11 @@ class ValidationTestCase(TestCase):
 | 
				
			||||||
            validate,
 | 
					            validate,
 | 
				
			||||||
            FieldsetBookAdmin, Book)
 | 
					            FieldsetBookAdmin, Book)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_nested_fields(self):
 | 
				
			||||||
 | 
					        class NestedFieldsAdmin(admin.ModelAdmin):
 | 
				
			||||||
 | 
					           fields = ('price', ('name', 'subtitle'))
 | 
				
			||||||
 | 
					        validate(NestedFieldsAdmin, Book)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_nested_fieldsets(self):
 | 
					    def test_nested_fieldsets(self):
 | 
				
			||||||
        class NestedFieldsetAdmin(admin.ModelAdmin):
 | 
					        class NestedFieldsetAdmin(admin.ModelAdmin):
 | 
				
			||||||
           fieldsets = (
 | 
					           fieldsets = (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue