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:
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¶
With Label and Help Text¶
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_ifandhide_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:
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¶
Unnamed Fieldset¶
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:
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 distributionflex-[2]- 2x proportional widthw-32- Fixed width (32 = 8rem)pr-2- Padding right (spacing between fields)pl-2- Padding left
Rendering¶
Renders as flexbox container:
Collection¶
A collection of related objects for inline editing.
⚠️ Requires djadmin-formset plugin!
Location: djadmin.layout.Collection
Import:
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¶
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
fieldsorlayout, not both - Must specify at least one of
fieldsorlayout modelmust be a Django model class- Field names in
fieldsmust exist on the model
Layout¶
Top-level layout definition for a form.
Location: djadmin.layout.Layout
Import:
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¶
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,
),
),
)