Skip to content

Layout API Component Reference

This document provides detailed API reference for all Layout API components.

Table of Contents


Field

A single form field declaration with customization options.

Location: djadmin.layout.Field

Import:

from djadmin import Field
# Or: from djadmin.layout import Field

Parameters

Parameter Type Default Description
name str required Field name (must match model field)
label str \| None None Custom field label
widget Widget \| Type[Widget] \| str \| None None Widget class, instance, or shortcut string
required bool \| None None Override field's required status
help_text str \| None None Custom help text
initial Any \| None None Initial value
show_if str \| None None ⚠️ Requires plugin - Conditional visibility expression
hide_if str \| None None ⚠️ Requires plugin - Inverse conditional expression
calculate str \| None None ⚠️ Requires plugin - Auto-calculation expression
css_classes List[str] [] CSS classes for field container
attrs dict {} Additional widget attributes
extra_kwargs dict {} Additional form field kwargs

Widget Shortcuts

Instead of importing widget classes, use string shortcuts:

Field('bio', widget='textarea')  # Instead of Textarea()
Field('email', widget='email')   # Instead of EmailInput()
Field('count', widget='number')  # Instead of NumberInput()

Supported shortcuts: - 'text' / 'textinput'TextInput - 'textarea'Textarea - 'email'EmailInput - 'number'NumberInput - 'password'PasswordInput - 'checkbox'CheckboxInput - 'select'Select - 'radio'RadioSelect - 'date'DateInput - 'datetime'DateTimeInput - 'file'FileInput - 'hidden'HiddenInput

Examples

Basic Field

Field('name')

With Label and Help Text

Field('price',
    label='Unit Price ($)',
    help_text='Price per unit in USD',
    required=True
)

With Widget

# Using string shortcut
Field('bio', widget='textarea', attrs={'rows': 8})

# Using widget class
from django.forms import Textarea
Field('bio', widget=Textarea(attrs={'rows': 8}))

# Using widget instance
Field('bio', widget=Textarea, attrs={'rows': 8})

With CSS Classes (Flexbox)

# Flex-1 makes field take proportional space
Field('first_name', css_classes=['flex-1', 'pr-2'])
Field('last_name', css_classes=['flex-1', 'pl-2'])

# Fixed width
Field('zip', css_classes=['w-32'])

Conditional Visibility ⚠️ Requires Plugin

Field('ebook_size', show_if=".format === 'ebook'")
Field('weight', show_if=".type === 'physical'")
Field('tracking', hide_if=".shipping === 'pickup'")

Computed Fields ⚠️ Requires Plugin

Field('total',
    calculate='.price * .quantity',
    widget='number',
    attrs={'readonly': True}
)

Field('discount_price',
    calculate='.price * (1 - .discount / 100)'
)

Methods

has_advanced_features() -> bool

Check if field uses plugin-only features.

field = Field('name', show_if=".active === true")
field.has_advanced_features()  # True

field = Field('name')
field.has_advanced_features()  # False

Validation

  • Cannot have both show_if and hide_if
  • Widget shortcuts must be valid (see list above)
  • Field name must exist on model (validated at form creation)

Fieldset

Groups fields with an optional legend (heading).

Location: djadmin.layout.Fieldset

Import:

from djadmin import Fieldset
# Or: from djadmin.layout import Fieldset

Parameters

Parameter Type Default Description
legend str \| None required Fieldset heading (use None for unnamed)
*fields Field \| Collection \| Row required Nested fields/components
description str \| None None Description text below legend
css_classes List[str] [] CSS classes for fieldset element

Examples

Named Fieldset

Fieldset('Personal Information',
    Field('first_name'),
    Field('last_name'),
    Field('birth_date'),
)

Unnamed Fieldset

Fieldset(None,
    Field('name'),
    Field('email'),
)

Renders as <fieldset> without <legend>, useful for grouping without visual heading.

With Description

Fieldset('Advanced Options',
    Field('custom_field'),
    Field('expert_mode'),
    description='These fields are for advanced users only',
    css_classes=['border-yellow-500'],
)

With Nested Rows

Fieldset('Contact Information',
    Row(
        Field('email', css_classes=['flex-1']),
        Field('phone', css_classes=['flex-1']),
    ),
    Field('address'),
    description='How we can reach you',
)

Rendering

Renders as HTML <fieldset> element:

<fieldset class="form-fieldset mb-6 border border-gray-300 rounded p-4">
    <legend class="text-lg font-semibold px-2">Personal Information</legend>
    <p class="fieldset-description text-sm text-gray-600 mb-4">
        Enter your personal details
    </p>
    <div class="fieldset-content">
        <!-- Fields here -->
    </div>
</fieldset>

Row

Horizontal layout of fields using flexbox.

Location: djadmin.layout.Row

Import:

from djadmin import Row
# Or: from djadmin.layout import Row

Parameters

Parameter Type Default Description
*fields Field \| Collection required Fields to display horizontally
css_classes List[str] [] CSS classes for row container

Examples

Two Columns

Row(
    Field('first_name', css_classes=['flex-1', 'pr-2']),
    Field('last_name', css_classes=['flex-1', 'pl-2']),
)

Three Columns

Row(
    Field('city', css_classes=['flex-1']),
    Field('state', css_classes=['flex-1']),
    Field('zip', css_classes=['flex-1']),
)

Unequal Widths

Row(
    Field('street', css_classes=['flex-[2]']),  # 2/3 width
    Field('apt', css_classes=['flex-1']),       # 1/3 width
)

Mixed Field Types

Row(
    Field('amount', widget='number', css_classes=['flex-1']),
    Field('currency', widget='select', css_classes=['w-32']),
)

Flexbox Classes

Use Tailwind CSS flex utilities:

  • flex-1 - Equal distribution
  • flex-[2] - 2x proportional width
  • w-32 - Fixed width (32 = 8rem)
  • pr-2 - Padding right (spacing between fields)
  • pl-2 - Padding left

Rendering

Renders as flexbox container:

<div class="form-row flex gap-4 mb-4">
    <!-- Fields here, each in flex item -->
</div>

Collection

A collection of related objects for inline editing.

⚠️ Requires djadmin-formset plugin!

Location: djadmin.layout.Collection

Import:

from djadmin import Collection
# Or: from djadmin.layout import Collection

Parameters

Parameter Type Default Description
name str required Related field name (e.g., 'books' for Author.books)
model Type[Model] required Related model class
fields List[str \| Field] \| None None Simple field list (XOR with layout)
layout Layout \| None None Complex layout (XOR with fields)
min_siblings int 0 Minimum number of items
max_siblings int 1000 Maximum number of items
extra_siblings int 1 Number of empty forms to show
is_sortable bool False Enable drag-and-drop ordering
legend str \| None None Custom legend (defaults to model verbose_name_plural)
form_class Type[Form] \| None None Custom form class for items

Examples

Simple Collection

Collection('books',
    model=Book,
    fields=['title', 'isbn', 'published_date'],
)

With Layout

Collection('books',
    model=Book,
    layout=Layout(
        Row(
            Field('title', css_classes=['flex-2']),
            Field('isbn', css_classes=['flex-1']),
        ),
        Field('published_date'),
    ),
)

Sortable Collection

Collection('books',
    model=Book,
    fields=['title', 'isbn'],
    is_sortable=True,  # Enable drag-and-drop
)

Nested Collections

Collection('addresses',
    model=Address,
    layout=Layout(
        Field('street'),
        Row(
            Field('city', css_classes=['flex-1']),
            Field('state', css_classes=['flex-1']),
            Field('zip', css_classes=['flex-1']),
        ),
        Collection('contacts',  # Nested!
            model=Contact,
            fields=['phone', 'email'],
        ),
    ),
)

With Constraints

Collection('photos',
    model=Photo,
    fields=['image', 'caption'],
    min_siblings=1,      # Must have at least 1
    max_siblings=10,     # Can't exceed 10
    extra_siblings=3,    # Show 3 empty forms
)

Without Plugin

If plugin is not installed, collections show a warning:

<div class="alert alert-warning">
    <strong>Inline editing not available</strong>
    <p>
        The 'books' collection requires the djadmin-formset plugin.
        Install with: <code>pip install django-admin-deux[djadmin-formset]</code>
    </p>
</div>

Validation

  • Must specify either fields or layout, not both
  • Must specify at least one of fields or layout
  • model must be a Django model class
  • Field names in fields must exist on the model

Layout

Top-level layout definition for a form.

Location: djadmin.layout.Layout

Import:

from djadmin import Layout
# Or: from djadmin.layout import Layout

Parameters

Parameter Type Default Description
*items Field \| Collection \| Fieldset \| Row required Layout components
renderer Type \| None None Custom renderer class (plugin only)
css_classes List[str] [] CSS classes for layout container

Examples

Simple Layout

Layout(
    Field('name'),
    Field('email'),
    Field('bio', widget='textarea'),
)

With Fieldsets

Layout(
    Fieldset('Personal Information',
        Field('name'),
        Field('birth_date'),
    ),
    Fieldset('Contact',
        Field('email'),
        Field('phone'),
    ),
)

With Rows

Layout(
    Row(
        Field('first_name', css_classes=['flex-1']),
        Field('last_name', css_classes=['flex-1']),
    ),
    Field('email'),
)

Complex Layout

Layout(
    Fieldset('Author Details',
        Row(
            Field('first_name', css_classes=['flex-1', 'pr-2']),
            Field('last_name', css_classes=['flex-1', 'pl-2']),
        ),
        Field('bio', widget='textarea', attrs={'rows': 6}),
    ),
    Fieldset('Publications',
        Collection('books',
            model=Book,
            layout=Layout(
                Row(
                    Field('title', css_classes=['flex-2']),
                    Field('isbn', css_classes=['flex-1']),
                ),
                Field('published_date'),
            ),
            is_sortable=True,
        ),
    ),
)

Class Methods

from_fieldsets(fieldsets)

Convert Django admin fieldsets to Layout.

# Django admin format
fieldsets = (
    ('Personal', {
        'fields': ('name', ('first_name', 'last_name')),
        'description': 'Enter personal information',
    }),
)

# Automatic conversion
layout = Layout.from_fieldsets(fieldsets)

# Equivalent to:
Layout(
    Fieldset('Personal',
        Field('name'),
        Row(
            Field('first_name'),
            Field('last_name'),
        ),
        description='Enter personal information',
    ),
)

Conversion rules: - Tuple ('field1', 'field2')Row(Field('field1'), Field('field2')) - (None, {...})Fieldset(None, ...) - 'classes'css_classes - 'description'description

Instance Methods

get_field_names() -> list[str]

Extract all field names from the layout, recursively traversing all components.

Returns: List of field names (strings)

Use case: Automatic field extraction for Django's ModelFormMixin when no explicit fields attribute is provided.

layout = Layout(
    Field('name'),
    Fieldset('Contact',
        Field('email'),
        Row(
            Field('city'),
            Field('state'),
        ),
    ),
    Collection('books', model=Book, fields=['title', 'isbn']),
)

field_names = layout.get_field_names()
# ['name', 'email', 'city', 'state']
# Note: Collection fields are excluded (inline editing, not top-level fields)

Behavior: - Recursively extracts field names from Field components - Traverses Fieldset and Row components - Excludes Collection components (they represent related models, not top-level fields) - Returns flattened list with no duplicates

Example - Auto-field extraction:

class ProductAdmin(ModelAdmin):
    layout = Layout(
        Field('name'),
        Field('price'),
        Field('description', widget='textarea'),
    )
    # No need to specify: fields = ['name', 'price', 'description']
    # Automatically extracted from layout!

get_features() -> Set[str]

Get the set of features required by this layout.

layout = Layout(
    Field('name'),
    Collection('books', model=Book, fields=['title']),
    Field('weight', show_if=".type === 'physical'"),
)

features = layout.get_features()
# {'collections', 'inlines', 'conditional_fields'}

Returned features: - 'collections' / 'inlines' - Has Collection components - 'conditional_fields' - Has show_if or hide_if - 'computed_fields' - Has calculate

Used by feature validation system to check plugin requirements.

Validation

  • Must contain at least one item
  • All items must be valid components (Field, Collection, Fieldset, Row)

Usage in ModelAdmin

All components are used together in ModelAdmin:

from djadmin import ModelAdmin, register, Layout, Field, Fieldset, Row, Collection
from myapp.models import Author, Book

@register(Author)
class AuthorAdmin(ModelAdmin):
    layout = Layout(
        Fieldset('Personal Information',
            Row(
                Field('first_name', css_classes=['flex-1', 'pr-2']),
                Field('last_name', css_classes=['flex-1', 'pl-2']),
            ),
            Field('birth_date', label='Date of Birth'),
        ),
        Fieldset('Biography',
            Field('bio', widget='textarea', attrs={'rows': 8}),
        ),
        Fieldset('Publications',
            Collection('books',
                model=Book,
                fields=['title', 'isbn', 'published_date'],
                is_sortable=True,
            ),
        ),
    )

See Also