Skip to content

Layout API Examples

This document provides real-world examples of using the Layout API.

Table of Contents


Simple Forms

Example 1: Basic Contact Form

Works without plugin

from djadmin import ModelAdmin, register, Layout, Field, Fieldset

@register(Contact)
class ContactAdmin(ModelAdmin):
    layout = Layout(
        Field('name', required=True),
        Field('email', widget='email', required=True),
        Field('phone'),
        Field('message', widget='textarea', attrs={'rows': 6}),
    )

Renders as: - Simple vertical stack - Email field with proper input type - Textarea for message with 6 rows


Example 2: Two-Column Name Fields

Works without plugin

@register(Person)
class PersonAdmin(ModelAdmin):
    layout = Layout(
        Row(
            Field('first_name', css_classes=['flex-1', 'pr-2']),
            Field('last_name', css_classes=['flex-1', 'pl-2']),
        ),
        Field('email', widget='email'),
        Field('birth_date', widget='date'),
    )

Renders as: - First/last name side-by-side (50/50 split) - Email and birth date below (full width)


Example 3: Address Form

Works without plugin

@register(Address)
class AddressAdmin(ModelAdmin):
    layout = Layout(
        Field('street_address'),
        Field('street_address_2', required=False, label='Apt/Suite'),
        Row(
            Field('city', css_classes=['flex-1', 'pr-2']),
            Field('state', css_classes=['flex-1', 'px-2']),
            Field('zip_code', css_classes=['w-32', 'pl-2']),
        ),
        Field('country', widget='select'),
    )

Renders as: - Street addresses full width - City/State/Zip in row (city and state flex, zip fixed width) - Country dropdown below


Complex Layouts

Example 4: Product Form with Sections

Works without plugin

@register(Product)
class ProductAdmin(ModelAdmin):
    layout = Layout(
        Fieldset('Basic Information',
            Field('name', required=True),
            Field('sku', label='SKU', required=True),
            Row(
                Field('price', widget='number', css_classes=['flex-1', 'pr-2']),
                Field('cost', widget='number', css_classes=['flex-1', 'pl-2']),
            ),
        ),
        Fieldset('Description',
            Field('short_description', widget='textarea', attrs={'rows': 3}),
            Field('full_description', widget='textarea', attrs={'rows': 10}),
        ),
        Fieldset('Inventory',
            Row(
                Field('stock', widget='number', css_classes=['flex-1', 'pr-2']),
                Field('reorder_level', widget='number', css_classes=['flex-1', 'pl-2']),
            ),
            Field('supplier', widget='select'),
        ),
    )

Features: - Three distinct sections with legends - Price/cost and stock/reorder in rows - Text areas with different sizes - Mix of field types


Example 5: Article Form

Works without plugin

@register(Article)
class ArticleAdmin(ModelAdmin):
    layout = Layout(
        Fieldset('Article Details',
            Field('title', required=True),
            Field('slug', help_text='Auto-generated if left blank'),
            Row(
                Field('author', css_classes=['flex-1', 'pr-2']),
                Field('category', css_classes=['flex-1', 'pl-2']),
            ),
        ),
        Fieldset('Content',
            Field('excerpt', widget='textarea', attrs={'rows': 4}),
            Field('body', widget='textarea', attrs={'rows': 20}),
        ),
        Fieldset('Publishing',
            Row(
                Field('status', widget='select', css_classes=['flex-1', 'pr-2']),
                Field('published_at', widget='datetime', css_classes=['flex-1', 'pl-2']),
            ),
            Field('featured', widget='checkbox'),
        ),
    )

Features: - Logical grouping by function - Help text for slug field - Different textarea sizes - Status and publish date in row - Checkbox for featured flag


Collections (Inlines)

⚠️ All examples in this section require the djadmin-formset plugin

pip install django-admin-deux[djadmin-formset]

Example 6: Simple Collection

@register(Author)
class AuthorAdmin(ModelAdmin):
    layout = Layout(
        Fieldset('Author Information',
            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,
                fields=['title', 'isbn', 'published_date'],
                extra_siblings=1,
            ),
        ),
    )

Features: - Inline editing of books - Simple field list - One empty form shown by default


Example 7: Collection with Custom Layout

@register(Author)
class AuthorAdmin(ModelAdmin):
    layout = Layout(
        Field('name'),
        Collection('books',
            model=Book,
            layout=Layout(
                Row(
                    Field('title', css_classes=['flex-2', 'pr-2']),
                    Field('isbn', css_classes=['flex-1', 'pl-2']),
                ),
                Row(
                    Field('published_date', css_classes=['flex-1', 'pr-2']),
                    Field('pages', widget='number', css_classes=['flex-1', 'pl-2']),
                ),
            ),
            is_sortable=True,
        ),
    )

Features: - Custom layout for each book - Title takes 2/3 width, ISBN 1/3 - Drag-and-drop reordering (is_sortable)


Example 8: Nested Collections

@register(Company)
class CompanyAdmin(ModelAdmin):
    layout = Layout(
        Fieldset('Company Details',
            Field('name'),
            Field('website', widget='email'),
        ),
        Fieldset('Departments',
            Collection('departments',
                model=Department,
                layout=Layout(
                    Field('name'),
                    Field('budget', widget='number'),
                    Collection('employees',  # Nested!
                        model=Employee,
                        fields=['name', 'title', 'email', 'phone'],
                        max_siblings=50,
                    ),
                ),
            ),
        ),
    )

Features: - Two-level nesting (Company → Department → Employee) - Each department can have up to 50 employees - Mix of simple and complex layouts


Example 9: Collection with Constraints

@register(Album)
class AlbumAdmin(ModelAdmin):
    layout = Layout(
        Field('title'),
        Field('artist'),
        Field('release_date'),
        Collection('tracks',
            model=Track,
            fields=['number', 'title', 'duration'],
            min_siblings=1,      # Must have at least 1 track
            max_siblings=30,     # Can't exceed 30 tracks
            extra_siblings=3,    # Show 3 empty forms
            is_sortable=True,    # Drag-and-drop track order
        ),
    )

Features: - Minimum/maximum constraints - Multiple empty forms - Sortable tracks


Conditional Fields

⚠️ Requires djadmin-formset plugin

Example 10: Product Type Conditionals

@register(Product)
class ProductAdmin(ModelAdmin):
    layout = Layout(
        Field('name'),
        Field('product_type', widget='select'),

        # Physical product fields
        Field('weight',
            show_if=".product_type === 'physical'",
            widget='number'
        ),
        Field('dimensions',
            show_if=".product_type === 'physical'"
        ),
        Field('shipping_class',
            show_if=".product_type === 'physical'",
            widget='select'
        ),

        # Digital product fields
        Field('file_size',
            show_if=".product_type === 'digital'",
            widget='number'
        ),
        Field('download_limit',
            show_if=".product_type === 'digital'",
            widget='number'
        ),
    )

Behavior: - Shows weight/dimensions/shipping for physical products - Shows file_size/download_limit for digital products - Fields appear/disappear instantly (no page refresh)


Example 11: Subscription Form

@register(Subscription)
class SubscriptionAdmin(ModelAdmin):
    layout = Layout(
        Field('email', widget='email'),
        Field('plan', widget='select'),

        # Show billing info only for paid plans
        Fieldset('Billing Information',
            Field('billing_name',
                show_if=".plan !== 'free'"
            ),
            Field('billing_email',
                show_if=".plan !== 'free'",
                widget='email'
            ),
            Row(
                Field('card_number',
                    show_if=".plan !== 'free'",
                    css_classes=['flex-2']
                ),
                Field('cvv',
                    show_if=".plan !== 'free'",
                    css_classes=['flex-1']
                ),
            ),
        ),
    )

Behavior: - Entire billing section hidden for free plan - Shows immediately when selecting paid plan


Example 12: Shipping Options

@register(Order)
class OrderAdmin(ModelAdmin):
    layout = Layout(
        Field('customer'),
        Field('shipping_method', widget='select'),

        # Pickup location
        Field('pickup_location',
            show_if=".shipping_method === 'pickup'",
            widget='select'
        ),

        # Delivery address
        Fieldset('Delivery Address',
            Field('street',
                show_if=".shipping_method === 'delivery'"
            ),
            Row(
                Field('city',
                    show_if=".shipping_method === 'delivery'",
                    css_classes=['flex-1']
                ),
                Field('zip',
                    show_if=".shipping_method === 'delivery'",
                    css_classes=['flex-1']
                ),
            ),
        ),

        # Express shipping
        Field('delivery_date',
            show_if=".shipping_method === 'express'",
            widget='date'
        ),
    )

Behavior: - Different fields for pickup vs delivery vs express - Address fields only show for delivery option


Computed Fields

⚠️ Requires djadmin-formset plugin

Example 13: Price Calculator

@register(OrderItem)
class OrderItemAdmin(ModelAdmin):
    layout = Layout(
        Field('product', widget='select'),
        Row(
            Field('quantity', widget='number', css_classes=['flex-1']),
            Field('unit_price', widget='number', css_classes=['flex-1']),
            Field('total',
                calculate='.quantity * .unit_price',
                widget='number',
                attrs={'readonly': True},
                css_classes=['flex-1']
            ),
        ),
    )

Behavior: - Total auto-updates when quantity or price changes - Readonly field (display only) - Updates instantly without page refresh


Example 14: Discount Calculation

@register(Product)
class ProductAdmin(ModelAdmin):
    layout = Layout(
        Field('name'),
        Row(
            Field('price', widget='number', css_classes=['flex-1']),
            Field('discount_percent', widget='number', css_classes=['flex-1']),
            Field('final_price',
                calculate='.price * (1 - .discount_percent / 100)',
                widget='number',
                attrs={'readonly': True},
                css_classes=['flex-1']
            ),
        ),
    )

Behavior: - Final price updates as you type - Percentage automatically applied


Example 15: Complex Calculation

@register(Invoice)
class InvoiceAdmin(ModelAdmin):
    layout = Layout(
        Field('subtotal', widget='number'),
        Field('tax_rate', widget='number', label='Tax Rate (%)'),
        Field('shipping', widget='number'),

        # Calculated fields
        Field('tax_amount',
            calculate='.subtotal * (.tax_rate / 100)',
            widget='number',
            attrs={'readonly': True}
        ),
        Field('total',
            calculate='.subtotal + (.subtotal * (.tax_rate / 100)) + .shipping',
            widget='number',
            attrs={'readonly': True},
            label='Grand Total'
        ),
    )

Behavior: - Tax calculated from subtotal and rate - Total includes subtotal, tax, and shipping - All calculations update live


Real-World Scenarios

Example 16: E-commerce Product

Complete product form with all features

@register(Product)
class ProductAdmin(ModelAdmin):
    layout = Layout(
        Fieldset('Product Information',
            Field('name', required=True),
            Field('slug'),
            Row(
                Field('sku', label='SKU', css_classes=['flex-1', 'pr-2']),
                Field('category', widget='select', css_classes=['flex-1', 'pl-2']),
            ),
        ),

        Fieldset('Pricing',
            Row(
                Field('price', widget='number', css_classes=['flex-1', 'pr-2']),
                Field('cost', widget='number', css_classes=['flex-1', 'px-2']),
                Field('margin',
                    calculate='(.price - .cost) / .price * 100',
                    widget='number',
                    attrs={'readonly': True},
                    label='Margin %',
                    css_classes=['flex-1', 'pl-2']
                ),
            ),
        ),

        Fieldset('Description',
            Field('short_description', widget='textarea', attrs={'rows': 3}),
            Field('full_description', widget='textarea', attrs={'rows': 15}),
        ),

        Fieldset('Inventory',
            Row(
                Field('stock', widget='number', css_classes=['flex-1', 'pr-2']),
                Field('reorder_level', widget='number', css_classes=['flex-1', 'pl-2']),
            ),
            Field('low_stock_alert', widget='checkbox'),
        ),

        Fieldset('Images',
            Collection('images',
                model=ProductImage,
                fields=['image', 'alt_text', 'is_primary'],
                min_siblings=1,
                max_siblings=10,
                is_sortable=True,
            ),
        ),

        Fieldset('Variants',
            Collection('variants',
                model=ProductVariant,
                layout=Layout(
                    Row(
                        Field('name', css_classes=['flex-2']),
                        Field('sku', css_classes=['flex-1']),
                    ),
                    Row(
                        Field('price', widget='number', css_classes=['flex-1']),
                        Field('stock', widget='number', css_classes=['flex-1']),
                    ),
                ),
            ),
        ),
    )

Features: - Automatic margin calculation - Image collection with sorting - Variant collection with custom layout - Logical grouping by function


Example 17: Event Registration

Conditional fields based on event type

@register(Event)
class EventAdmin(ModelAdmin):
    layout = Layout(
        Fieldset('Event Details',
            Field('title', required=True),
            Field('event_type', widget='select'),
            Field('date', widget='date'),
            Row(
                Field('start_time', widget='time', css_classes=['flex-1']),
                Field('end_time', widget='time', css_classes=['flex-1']),
            ),
        ),

        Fieldset('Location',
            # In-person events
            Field('venue_name',
                show_if=".event_type === 'in-person'"
            ),
            Field('address',
                show_if=".event_type === 'in-person'"
            ),
            Field('capacity',
                show_if=".event_type === 'in-person'",
                widget='number'
            ),

            # Virtual events
            Field('meeting_url',
                show_if=".event_type === 'virtual'",
                widget='email'
            ),
            Field('meeting_password',
                show_if=".event_type === 'virtual'",
                widget='password'
            ),

            # Hybrid events
            Field('stream_url',
                show_if=".event_type === 'hybrid'",
                widget='email'
            ),
        ),

        Fieldset('Registration',
            Field('max_attendees', widget='number'),
            Field('registration_deadline', widget='date'),
            Field('price', widget='number'),
        ),

        Fieldset('Speakers',
            Collection('speakers',
                model=Speaker,
                fields=['name', 'title', 'bio', 'photo'],
                max_siblings=10,
            ),
        ),
    )

Features: - Different fields for in-person/virtual/hybrid events - Speaker collection - Time range validation - Price calculation ready


Example 18: Customer Profile

Complete customer management

@register(Customer)
class CustomerAdmin(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('email', widget='email', required=True),
            Field('phone'),
            Field('birth_date', widget='date'),
        ),

        Fieldset('Account',
            Field('username', required=True),
            Row(
                Field('account_type', widget='select', css_classes=['flex-1', 'pr-2']),
                Field('status', widget='select', css_classes=['flex-1', 'pl-2']),
            ),
            Field('notes', widget='textarea', attrs={'rows': 4}),
        ),

        Fieldset('Addresses',
            Collection('addresses',
                model=Address,
                layout=Layout(
                    Field('label', label='Address Label (Home, Work, etc.)'),
                    Field('street'),
                    Field('street_2', required=False),
                    Row(
                        Field('city', css_classes=['flex-1']),
                        Field('state', css_classes=['flex-1']),
                        Field('zip', css_classes=['w-32']),
                    ),
                    Field('is_default', widget='checkbox'),
                ),
                max_siblings=5,
            ),
        ),

        Fieldset('Payment Methods',
            Collection('payment_methods',
                model=PaymentMethod,
                fields=['type', 'last_four', 'expiry', 'is_default'],
                max_siblings=3,
            ),
        ),
    )

Features: - Complete customer profile - Multiple addresses with custom layout - Payment methods collection - Default flags for addresses and payment methods


Next Steps


Summary

The Layout API supports a wide range of use cases:

  • Simple forms - Basic layouts without plugin
  • Complex layouts - Fieldsets, rows, nested structures
  • Collections - Inline editing (requires plugin)
  • Conditional logic - Show/hide fields (requires plugin)
  • Computed fields - Auto-calculations (requires plugin)
  • Real-world scenarios - E-commerce, events, CRM, etc.

Start simple and progressively enhance with plugins as needed!