Renderer Customization Guide - djadmin-formset¶
Plugin: djadmin-formset
Version: 0.1.0 (Alpha)
Last Updated: October 21, 2025
Overview¶
The djadmin-formset plugin provides DjAdminFormRenderer, a custom renderer for django-formset that integrates seamlessly with the djadmin Tailwind CSS theme. You can customize the renderer or create your own to match your design system.
Default Renderer: DjAdminFormRenderer¶
Features¶
The DjAdminFormRenderer class provides:
- Tailwind CSS Integration: Matches djadmin's Tailwind-based theme
- Customizable CSS Classes: Override classes for all form elements
- Fieldset Support: Handles both named and unnamed fieldsets
- Collection Rendering: Styled inline forms with add/remove buttons
- Responsive Design: Mobile-friendly form layouts
Default CSS Classes¶
# djadmin_formset/renderers.py
class DjAdminFormRenderer(FormRenderer):
"""
Custom renderer for django-formset with djadmin theme integration.
Provides Tailwind CSS classes matching the djadmin design system.
"""
# Field wrapper
field_css_classes = 'mb-4'
# Label element
label_css_classes = 'block text-sm font-medium text-gray-700 mb-1'
# Input fields
field_input_css_classes = (
'w-full px-3 py-2 border border-gray-300 rounded-md '
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500'
)
# Textarea
textarea_css_classes = (
'w-full px-3 py-2 border border-gray-300 rounded-md '
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500'
)
# Select dropdown
select_css_classes = (
'w-full px-3 py-2 border border-gray-300 rounded-md '
'focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500'
)
# Checkbox
checkbox_css_classes = 'h-4 w-4 text-blue-600 border-gray-300 rounded'
# Help text
help_text_css_classes = 'mt-1 text-sm text-gray-500'
# Error messages
error_css_classes = 'mt-1 text-sm text-red-600'
# Fieldset
fieldset_css_classes = 'mb-6 p-4 border border-gray-300 rounded-lg'
# Fieldset legend
legend_css_classes = 'text-lg font-semibold text-gray-900 px-2'
# Collection wrapper
collection_css_classes = 'space-y-4'
# Collection item
collection_item_css_classes = (
'p-4 border border-gray-200 rounded-lg bg-gray-50 relative'
)
# Add/remove buttons
button_css_classes = (
'px-4 py-2 bg-blue-600 text-white rounded-md '
'hover:bg-blue-700 focus:outline-none focus:ring-2 '
'focus:ring-blue-500'
)
Basic Usage¶
Use Default Renderer¶
The default renderer is applied automatically to all forms:
from djadmin import ModelAdmin, register, Layout, Field
@register(Author)
class AuthorAdmin(ModelAdmin):
layout = Layout(
Field('name'),
Field('email'),
)
# DjAdminFormRenderer is used automatically
Explicit Renderer¶
You can explicitly set the renderer on a layout:
from djadmin import ModelAdmin, register, Layout, Field
from djadmin_formset.renderers import DjAdminFormRenderer
@register(Author)
class AuthorAdmin(ModelAdmin):
layout = Layout(
Field('name'),
Field('email'),
renderer=DjAdminFormRenderer, # Explicit
)
Customizing CSS Classes¶
Option 1: Override at Instantiation¶
Pass custom CSS classes when creating a renderer instance:
from djadmin import ModelAdmin, register, Layout, Field
from djadmin_formset.renderers import DjAdminFormRenderer
@register(Author)
class AuthorAdmin(ModelAdmin):
layout = Layout(
Field('name'),
Field('email'),
renderer=DjAdminFormRenderer(
# Override specific classes
field_css_classes='mb-6', # More spacing
label_css_classes='block text-base font-bold text-gray-800 mb-2',
field_input_css_classes='w-full px-4 py-3 border-2 border-blue-500 rounded-lg',
),
)
Option 2: Create Custom Renderer Subclass¶
For project-wide customization, create a custom renderer:
# myapp/renderers.py
from djadmin_formset.renderers import DjAdminFormRenderer
class MyCustomRenderer(DjAdminFormRenderer):
"""Custom renderer with Bootstrap 5 classes."""
# Override CSS classes
field_input_css_classes = 'form-control'
textarea_css_classes = 'form-control'
select_css_classes = 'form-select'
checkbox_css_classes = 'form-check-input'
label_css_classes = 'form-label'
help_text_css_classes = 'form-text text-muted'
error_css_classes = 'invalid-feedback'
fieldset_css_classes = 'border p-3 rounded mb-4'
legend_css_classes = 'h5 mb-3'
Use it in your admin:
from djadmin import ModelAdmin, register, Layout, Field
from myapp.renderers import MyCustomRenderer
@register(Author)
class AuthorAdmin(ModelAdmin):
layout = Layout(
Field('name'),
Field('email'),
renderer=MyCustomRenderer,
)
Option 3: Settings-Based Configuration¶
Override the default renderer globally via Django settings:
Then use the helper to get the configured renderer:
from djadmin import ModelAdmin, register, Layout, Field
from djadmin_formset.renderers import get_default_renderer
@register(Author)
class AuthorAdmin(ModelAdmin):
layout = Layout(
Field('name'),
Field('email'),
renderer=get_default_renderer(), # Uses setting
)
Advanced Customization¶
Customizing Rendering Methods¶
Override rendering methods for complete control:
# myapp/renderers.py
from djadmin_formset.renderers import DjAdminFormRenderer
class MyAdvancedRenderer(DjAdminFormRenderer):
"""Advanced renderer with custom rendering logic."""
def _amend_fieldset(self, context):
"""Customize fieldset rendering."""
# Call parent implementation
context = super()._amend_fieldset(context)
# Add custom context
context['show_description'] = True
context['collapse_by_default'] = False
return context
def _amend_form(self, context):
"""Customize form rendering."""
context = super()._amend_form(context)
# Add custom attributes
context['form_attrs'] = {
'data-form-type': 'djadmin',
'data-theme': 'custom',
}
return context
def _amend_collection(self, context):
"""Customize collection rendering."""
context = super()._amend_collection(context)
# Custom collection settings
context['allow_empty'] = True
context['max_items'] = 20
return context
Template Customization¶
Override the default templates used by django-formset:
# settings.py
DJADMIN_FORMSET_TEMPLATES = {
'help_text': 'myapp/formset/help_text.html',
'field': 'myapp/formset/field.html',
'fieldset': 'myapp/formset/fieldset.html',
}
The renderer will use these templates:
from django.conf import settings
from formset.renderers import FormRenderer
class DjAdminFormRenderer(FormRenderer):
"""Uses custom templates from settings."""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Override templates from settings
custom_templates = getattr(settings, 'DJADMIN_FORMSET_TEMPLATES', {})
if 'help_text' in custom_templates:
self.templates['help_text'] = custom_templates['help_text']
# ... similar for other templates
Example: Material Design Renderer¶
# myapp/renderers.py
from djadmin_formset.renderers import DjAdminFormRenderer
class MaterialRenderer(DjAdminFormRenderer):
"""Renderer using Material Design Lite classes."""
field_css_classes = 'mdl-textfield mdl-js-textfield mdl-textfield--floating-label'
label_css_classes = 'mdl-textfield__label'
field_input_css_classes = 'mdl-textfield__input'
error_css_classes = 'mdl-textfield__error'
button_css_classes = 'mdl-button mdl-js-button mdl-button--raised mdl-button--colored'
fieldset_css_classes = 'mdl-card mdl-shadow--2dp'
legend_css_classes = 'mdl-card__title-text'
def _amend_form(self, context):
"""Add Material Design form wrapper."""
context = super()._amend_form(context)
context['form_wrapper_classes'] = 'mdl-grid'
return context
CSS Class Reference¶
Field Elements¶
| Element | Attribute | Default |
|---|---|---|
| Field wrapper | field_css_classes |
'mb-4' |
| Label | label_css_classes |
'block text-sm font-medium text-gray-700 mb-1' |
| Text input | field_input_css_classes |
'w-full px-3 py-2 border ...' |
| Textarea | textarea_css_classes |
'w-full px-3 py-2 border ...' |
| Select | select_css_classes |
'w-full px-3 py-2 border ...' |
| Checkbox | checkbox_css_classes |
'h-4 w-4 text-blue-600 ...' |
| Help text | help_text_css_classes |
'mt-1 text-sm text-gray-500' |
| Error message | error_css_classes |
'mt-1 text-sm text-red-600' |
Layout Elements¶
| Element | Attribute | Default |
|---|---|---|
| Fieldset | fieldset_css_classes |
'mb-6 p-4 border ...' |
| Legend | legend_css_classes |
'text-lg font-semibold ...' |
| Collection wrapper | collection_css_classes |
'space-y-4' |
| Collection item | collection_item_css_classes |
'p-4 border border-gray-200 ...' |
| Buttons | button_css_classes |
'px-4 py-2 bg-blue-600 ...' |
Renderer Method Reference¶
__init__(**kwargs)¶
Constructor accepting CSS class overrides:
renderer = DjAdminFormRenderer(
field_css_classes='custom-field',
label_css_classes='custom-label',
)
_amend_fieldset(context)¶
Called when rendering fieldsets. Override to customize fieldset behavior:
def _amend_fieldset(self, context):
context = super()._amend_fieldset(context)
# Handle legend=None for unnamed fieldsets
if context.get('legend') is None:
context['show_legend'] = False
return context
_amend_form(context)¶
Called when rendering forms. Override to add form-level customization:
def _amend_form(self, context):
context = super()._amend_form(context)
context['form_classes'] = 'my-custom-form'
return context
_amend_collection(context)¶
Called when rendering collections. Override for collection customization:
def _amend_collection(self, context):
context = super()._amend_collection(context)
context['allow_sorting'] = True
context['show_add_button'] = True
return context
Best Practices¶
1. Use Inheritance¶
Don't create renderers from scratch - inherit from DjAdminFormRenderer:
# Good
class MyRenderer(DjAdminFormRenderer):
field_css_classes = 'custom-field'
# Avoid (unless you really need to)
class MyRenderer(FormRenderer):
# Have to implement everything yourself
2. Keep CSS Classes Consistent¶
Use a consistent design system throughout your renderer:
class ConsistentRenderer(DjAdminFormRenderer):
"""All elements use the same spacing unit (rem)."""
field_css_classes = 'mb-4' # 1rem spacing
fieldset_css_classes = 'mb-6' # 1.5rem spacing
collection_css_classes = 'mb-8' # 2rem spacing
3. Test Your Renderer¶
Create a test admin with various field types:
# tests/test_custom_renderer.py
from myapp.renderers import MyCustomRenderer
class RendererTestAdmin(ModelAdmin):
layout = Layout(
Field('text_field'),
Field('textarea_field', widget='textarea'),
Field('select_field'),
Field('checkbox_field'),
Fieldset('Group', Field('nested_field')),
renderer=MyCustomRenderer,
)
4. Document Custom Classes¶
If creating a custom renderer, document the CSS classes:
class MyRenderer(DjAdminFormRenderer):
"""
Custom renderer using Bootstrap 5.
CSS Classes:
- field_input_css_classes: 'form-control' (Bootstrap form control)
- label_css_classes: 'form-label' (Bootstrap label)
- error_css_classes: 'invalid-feedback' (Bootstrap error)
"""
Troubleshooting¶
Styles Not Applied¶
Symptom: Custom CSS classes not appearing in HTML
Check:
1. Renderer correctly specified in layout?
2. Template caching enabled? Clear cache: python manage.py clear_cache
3. Static files collected? Run: python manage.py collectstatic
Conflicts with Core Styles¶
Symptom: djadmin core styles conflict with renderer styles
Solution: Use more specific selectors or override in your CSS:
/* myapp/static/css/custom.css */
/* Override djadmin styles for formset forms */
form[data-formset] .custom-field {
/* Your custom styles */
}
Renderer Not Found¶
Symptom: ImportError: cannot import name 'MyRenderer'
Solution: Check import path in layout:
# Wrong
from myapp.renderers import MyRenderer # File not in path
# Correct
from myapp.renderers import MyRenderer # Ensure myapp is in INSTALLED_APPS
Examples¶
Example 1: Dark Mode Renderer¶
class DarkModeRenderer(DjAdminFormRenderer):
"""Dark mode theme renderer."""
field_input_css_classes = (
'w-full px-3 py-2 bg-gray-800 text-white border border-gray-600 rounded-md '
'focus:outline-none focus:ring-2 focus:ring-blue-400'
)
label_css_classes = 'block text-sm font-medium text-gray-300 mb-1'
fieldset_css_classes = 'mb-6 p-4 border border-gray-600 rounded-lg bg-gray-900'
legend_css_classes = 'text-lg font-semibold text-gray-100 px-2'
Example 2: Minimal Renderer¶
class MinimalRenderer(DjAdminFormRenderer):
"""Minimal, clean renderer with less visual weight."""
field_css_classes = 'mb-3'
label_css_classes = 'text-xs font-normal text-gray-600 mb-1'
field_input_css_classes = (
'w-full px-2 py-1 border-b border-gray-300 '
'focus:outline-none focus:border-blue-500'
)
fieldset_css_classes = 'mb-4'
legend_css_classes = 'text-sm font-medium text-gray-700 mb-2'
Example 3: Accessibility-First Renderer¶
class AccessibleRenderer(DjAdminFormRenderer):
"""Enhanced accessibility with ARIA attributes."""
def _amend_form(self, context):
context = super()._amend_form(context)
context['form_attrs'] = {
'role': 'form',
'aria-labelledby': 'form-title',
}
return context
def _amend_fieldset(self, context):
context = super()._amend_fieldset(context)
if context.get('legend'):
context['fieldset_attrs'] = {
'role': 'group',
'aria-labelledby': f"legend-{context['legend'].lower().replace(' ', '-')}",
}
return context