Basic Usage¶
This guide covers the essential ModelAdmin configuration options for everyday use.
Overview¶
The ModelAdmin class is the heart of django-admin-deux. It encapsulates all configuration and behavior for administering a Django model through the interface.
list_display Configuration¶
The list_display attribute controls which columns appear in your ListView.
Simple Field Names¶
The most basic usage is a list of field names:
from djadmin import ModelAdmin, register
from .models import Product
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = ['name', 'sku', 'price', 'status']
This displays the name, sku, price, and status fields as columns in the list view.
Using the Column Dataclass¶
For more control, use the Column dataclass:
from djadmin import ModelAdmin, register, Column
from .models import Product
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = [
'name',
Column('sku', label='SKU Code'),
Column('price', label='Price (USD)', classes='text-right'),
Column('status', empty_value='N/A'),
]
Column Parameters:
- field: Field name or callable (required)
- label: Custom column header (optional)
- empty_value: Display value for None/empty (default: '-')
- classes: CSS classes for the column (optional)
Mixed Style¶
You can mix simple strings and Column objects:
Custom Columns with Callables¶
Add computed columns using methods:
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = ['name', 'price', 'stock_indicator']
def stock_indicator(self, obj):
if obj.stock_quantity == 0:
return "Out of Stock"
elif obj.stock_quantity < 10:
return f"Low Stock ({obj.stock_quantity})"
else:
return f"In Stock ({obj.stock_quantity})"
stock_indicator.short_description = "Stock Status"
The method receives the model instance as obj and returns the display value.
Note: Add a short_description attribute to customize the column header.
Relationship Fields¶
Access related model fields using Django's double-underscore notation:
@register(Order)
class OrderAdmin(ModelAdmin):
list_display = [
'order_number',
'customer__full_name', # Access related field
'customer__email',
'total',
'created_at',
]
Pagination¶
Control how many records appear per page:
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = ['name', 'sku', 'price']
paginate_by = 50 # Default: 100
Custom Pagination Class¶
For advanced pagination needs, provide a custom pagination class:
from django.core.paginator import Paginator
class CustomPaginator(Paginator):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Custom pagination logic
@register(Product)
class ProductAdmin(ModelAdmin):
paginate_by = 50
pagination_class = CustomPaginator
Form Configuration¶
Basic Fields Configuration¶
Control which fields appear in forms:
@register(Product)
class ProductAdmin(ModelAdmin):
# All forms show these fields by default
fields = ['name', 'sku', 'category', 'price', 'stock_quantity']
Special Values:
- '__all__' (default) - Include all model fields
- ['field1', 'field2'] - Include only specified fields
Different Fields for Create vs Update¶
Often you want different fields for creating vs editing records:
@register(Product)
class ProductAdmin(ModelAdmin):
# Default for both create and update
fields = '__all__'
# Simplified create form (only essential fields)
create_fields = ['name', 'sku', 'category', 'price']
# Update form uses 'fields' (shows all fields)
Resolution Order:
- CreateView: create_fields → fields → '__all__'
- UpdateView: update_fields → fields → '__all__'
Example from webshop¶
# examples/webshop/djadmin.py
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = ['name', 'sku', 'category', 'price', 'stock_quantity', 'status']
paginate_by = 10
# Simplified create form
create_fields = [
'name', 'slug', 'sku', 'category', 'tags',
'description', 'price', 'cost', 'stock_quantity'
]
# Update shows all fields
update_fields = '__all__'
This pattern keeps the create form focused while allowing full editing later.
Custom Form Classes¶
For more control over form behavior and validation:
from django import forms
from .models import Product
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
widgets = {
'description': forms.Textarea(attrs={'rows': 4}),
'tags': forms.CheckboxSelectMultiple(),
}
def clean_price(self):
price = self.cleaned_data['price']
if price <= 0:
raise forms.ValidationError("Price must be positive")
return price
@register(Product)
class ProductAdmin(ModelAdmin):
form_class = ProductForm
Different Form Classes for Create vs Update¶
class ProductCreateForm(forms.ModelForm):
class Meta:
model = Product
fields = ['name', 'sku', 'category', 'price']
class ProductUpdateForm(forms.ModelForm):
class Meta:
model = Product
fields = '__all__'
# Update form includes custom validation
def clean_stock_quantity(self):
quantity = self.cleaned_data['stock_quantity']
if quantity < 0:
raise forms.ValidationError("Cannot have negative stock")
return quantity
@register(Product)
class ProductAdmin(ModelAdmin):
create_form_class = ProductCreateForm
update_form_class = ProductUpdateForm
Resolution Order:
- CreateView: create_form_class → form_class → auto-generated
- UpdateView: update_form_class → form_class → auto-generated
Note: When using custom form classes, the fields and create_fields/update_fields attributes are ignored. Define fields in the form's Meta.fields.
Multiple ModelAdmin Registrations¶
You can register the same model multiple times for different views:
# Simple view for quick access
@register(Product)
class SimpleProductAdmin(ModelAdmin):
list_display = ['name', 'sku', 'price']
fields = ['name', 'sku', 'price']
# Detailed view with all fields
@register(Product)
class DetailedProductAdmin(ModelAdmin):
list_display = ['name', 'sku', 'category', 'price', 'cost', 'stock_quantity', 'status']
fields = '__all__'
# Read-only view
@register(Product)
class ReadOnlyProductAdmin(ModelAdmin):
list_display = ['name', 'sku', 'price', 'status']
list_actions = [] # No add button
record_actions = [] # No edit/delete
bulk_actions = [] # No bulk operations
Both will appear in the dashboard as separate entries: - Product (SimpleProductAdmin) - Product (DetailedProductAdmin) - Product (ReadOnlyProductAdmin)
Each gets its own URL:
- /djadmin/myapp/product/0/
- /djadmin/myapp/product/1/
- /djadmin/myapp/product/2/
Default Values¶
When you don't specify configuration, these defaults are used:
class ModelAdmin:
list_display = ['__str__'] # Show model's __str__ method
general_actions = [ListAction] # Provided by core plugin
bulk_actions = [DeleteBulkAction] # Provided by core plugin
record_actions = [EditAction, DeleteAction] # Provided by core plugin
paginate_by = 100
fields = '__all__'
Best Practices¶
1. Start Simple, Add Complexity¶
Begin with basic configuration and add features as needed:
# Start here
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = ['name', 'price']
# Add more as you need it
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = ['name', 'sku', 'price', 'stock_indicator']
paginate_by = 50
create_fields = ['name', 'sku', 'price']
def stock_indicator(self, obj):
return "In Stock" if obj.stock_quantity > 0 else "Out of Stock"
2. Use Column for Better Control¶
Prefer Column when you need custom labels or styling:
# Good
list_display = [
Column('sku', label='SKU Code'),
Column('price', label='Price (USD)', classes='text-right'),
]
# Basic (works, but less control)
list_display = ['sku', 'price']
3. Separate Create and Update Fields¶
Keep create forms simple, allow full editing on update:
create_fields = ['name', 'sku', 'category', 'price'] # Essential only
fields = '__all__' # Update shows everything
4. Custom Methods for Computed Values¶
Add computed columns instead of cluttering your model:
def total_value(self, obj):
return obj.price * obj.stock_quantity
total_value.short_description = "Total Value"
5. Multiple ModelAdmins for Different Use Cases¶
Create specialized views for different user needs:
@register(Product)
class QuickProductAdmin(ModelAdmin):
"""Quick view for daily operations"""
list_display = ['name', 'sku', 'stock_quantity']
create_fields = ['name', 'sku', 'price']
@register(Product)
class FullProductAdmin(ModelAdmin):
"""Complete view for product management"""
list_display = ['name', 'sku', 'category', 'price', 'cost', 'margin', 'stock_quantity']
fields = '__all__'
Real-World Examples¶
E-commerce Product Admin¶
from djadmin import ModelAdmin, register, Column
@register(Product)
class ProductAdmin(ModelAdmin):
list_display = [
Column('name', label='Product Name'),
Column('sku', label='SKU'),
'category',
Column('price', label='Price (USD)', classes='text-right'),
'stock_status',
'created_at',
]
paginate_by = 25
create_fields = [
'name', 'slug', 'sku', 'category', 'tags',
'description', 'price', 'cost', 'stock_quantity'
]
update_fields = '__all__'
def stock_status(self, obj):
if obj.stock_quantity == 0:
return "Out of Stock"
elif obj.stock_quantity < 10:
return f"Low ({obj.stock_quantity})"
return f"In Stock ({obj.stock_quantity})"
stock_status.short_description = "Stock"
Customer Management¶
@register(Customer)
class CustomerAdmin(ModelAdmin):
list_display = [
'full_name',
'email',
Column('city', classes='text-muted'),
Column('country', classes='text-muted'),
'order_count',
'is_active',
]
paginate_by = 50
create_fields = [
'full_name', 'email', 'phone',
'address', 'city', 'country', 'postal_code'
]
def order_count(self, obj):
return obj.orders.count()
order_count.short_description = "Orders"
Blog Post Admin¶
@register(Post)
class PostAdmin(ModelAdmin):
list_display = [
'title',
'author',
'published_status',
'view_count',
'published_date',
]
create_fields = ['title', 'slug', 'author', 'content']
update_fields = '__all__'
def published_status(self, obj):
if obj.published_date and obj.published_date <= timezone.now():
return "Published"
elif obj.published_date:
return f"Scheduled ({obj.published_date})"
return "Draft"
published_status.short_description = "Status"
See Also¶
- CRUD Operations - Learn how create, update, and delete work
- Actions Guide - Deep dive into the action system
- Customization - Advanced customization techniques
- Dashboards - Understanding dashboard display
- Getting Started - Quick start tutorial