| name | django-unfold | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| description | Modern Django admin theme - Unfold - customization, settings, components, actions, filters, integrations | ||||||||||||||
| metadata |
|
Modern Django admin theme with beautiful design and advanced features.
Unfold is a modern theme for Django admin that provides:
- Beautiful design - Modern UI with Tailwind CSS
- Dark mode - Built-in dark theme support
- Custom components - Charts, tables, cards, buttons
- Easy customization - Settings, branding, sidebar
- Integrations - Works with django-celery-beat, django-import-export, etc.
pip install django-unfold
# Or with specific version
pip install django-unfold==0.9.0# settings.py
INSTALLED_APPS = [
'unfold', # Must come BEFORE django.contrib.admin
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
# ... other apps
]
# Unfold settings
UNFOLD = {
# Configuration here
}Important:
unfoldmust be first inINSTALLED_APPSto override Django templates.
# settings.py
INSTALLED_APPS = [
'unfold',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
]
# Optional: Add your custom admin site class
# UNFOLD_ADMIN_SITE_CLASS = 'path.to.CustomAdminSite'Unfold doesn't require changes to your URL configuration:
# urls.py - No changes needed!
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
# ... other urls
]UNFOLD = {
'SITE_HEADER': 'My Company Admin',
'SITE_TITLE': 'My Admin',
'INDEX_TITLE': 'Welcome to Dashboard',
# Or with HTML support
'SITE_HEADER': '<div class="flex items-center gap-2"><span>🚀</span> My Company</div>',
}UNFOLD = {
'COLORS': {
'primary': {
'50': '239 246 255',
'100': '219 234 254',
'200': '191 219 254',
'300': '147 197 253',
'400': '96 165 250',
'500': '59 130 246',
'600': '37 99 235',
'700': '29 78 216',
'800': '30 64 175',
'900': '30 58 138',
'950': '30 58 138',
},
},
'DARK_MODE_COLORS': {
'primary': {
'50': '239 246 255',
# ... dark mode colors
},
},
}UNFOLD = {
'SIDEBAR': {
'show_search': True,
'navigation': [
{
'title': 'Main',
'items': [
{'title': 'Dashboard', 'icon': 'dashboard', 'link': '/admin/'},
{'title': 'Users', 'icon': 'people', 'link': '/admin/auth/user/'},
],
},
{
'title': 'Content',
'items': [
{'title': 'Articles', 'icon': 'article', 'link': '/admin/myapp/article/'},
],
},
],
},
}UNFOLD = {
'DASHBOARD_WIDGETS': [
'unfold.widgets.DashboardStatistics',
'unfold.widgets.DashboardActions',
# Custom widgets
],
}UNFOLD = {
# Show/hide features
'SHOW_HISTORY': True,
'SHOW_VIEW_ON_SITE': True,
# Disable specific features
'AUTH_PASSWORD_VALIDATION': True,
}# admin.py
from unfold.admin import ModelAdmin, UnfoldAdminSite
from django.contrib.admin import AdminSite
class CustomAdminSite(UnfoldAdminSite):
site_header = 'My Custom Admin'
site_title = 'My Admin Panel'
index_title = 'Welcome to Management'
def each_context(self, request):
context = super().each_context(request)
# Add custom context
context['custom_data'] = 'value'
return context
admin_site = CustomAdminSite(name='myadmin')# settings.py
UNFOLD_ADMIN_SITE_CLASS = 'myapp.admin.CustomAdminSite'# admin.py
from django.contrib import admin
from unfold.admin import ModelAdmin
from .models import Article
@admin.register(Article, site=custom_admin_site)
class ArticleAdmin(ModelAdmin):
list_display = ['title', 'status', 'created_at']
search_fields = ['title', 'content']
list_filter = ['status', 'created_at']
# Unfold-specific
sidebar_fieldsets = (
(None, {'fields': ('title', 'slug')}),
('Content', {'fields': ('content', 'excerpt')}),
)from unfold.decorators import action
from unfold.actions import Actions
class ArticleAdmin(ModelAdmin):
@action(description='Publish selected')
def make_published(self, request, queryset):
queryset.update(status='published')
@action(description='Export to CSV')
def export_csv(self, request, queryset):
# Export logic
pass# In change_view.html or custom template
{% load unfold %}
{% component "card" title="Statistics" %}
<div class="p-4">
<p class="text-2xl font-bold">1,234</p>
<p class="text-gray-500">Total Users</p>
</div>
{% endcomponent %}# Using Unfold chart component
{% component "chart" type="line" data=chart_data %}
{% endcomponent %}# In list_display
class UserAdmin(ModelAdmin):
list_display = ['username', 'email', 'status_badge']
@bind_to(admin_order_field='is_active')
def status_badge(self, obj):
from unfold.helpers import icon
if obj.is_active:
return icon('check_circle', classes='text-green-500')
return icon('x_circle', classes='text-red-500')from unfold import fields
from unfold.forms import ModelForm
class ArticleForm(ModelForm):
class Meta:
model = Article
fields = '__all__'
author = fields.AutocompleteField(
queryset=User.objects.all(),
search_fields=['username', 'email'],
label='Author'
)from unfold.fields import JSONField
class ConfigAdmin(ModelAdmin):
fieldsets = (
(None, {
'fields': ('config_json',)
}),
)
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
form.base_fields['config_json'] = fields.JSONField(
widget=forms.Textarea(attrs={
'class': 'font-mono text-sm',
'rows': 10
})
)
return formimport unfold.filters as filters
class ArticleAdmin(ModelAdmin):
list_filter = [
('status', filters.DropdownFilter),
('category', filters.DropdownFilter),
('created_at', filters.DateRangeFilter),
('author', filters.AutocompleteFilter),
]# Dropdown filter
('status', filters.DropdownFilter)
# Date range filter
('created_at', filters.DateRangeFilter)
# Autocomplete filter (for FK/M2M with many items)
('author', filters.AutocompleteFilter)
# Checkbox/radio filter
('is_published', filters.CheckboxFilter)
# Numeric range filter
('views', filters.NumericFilter)
# Text search filter
('title', filters.TextFilter)from django.http import HttpResponse
from django.shortcuts import render
import csv
import io
class ArticleAdmin(ModelAdmin):
@action(description='Export selected to CSV')
def export_csv(self, request, queryset):
# Create CSV
buffer = io.StringIO()
writer = csv.writer(buffer)
writer.writerow(['Title', 'Status', 'Created'])
for obj in queryset:
writer.writerow([obj.title, obj.status, obj.created])
# Return response
response = HttpResponse(buffer.getvalue(), content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="articles.csv"'
return response
@action(description='Send to publication')
def publish(self, request, queryset):
queryset.update(status='published', published_at=timezone.now())
publish.short_description = 'Publish selected articles'class ArticleAdmin(ModelAdmin):
def get_row_actions(self, obj):
actions = super().get_row_actions(obj)
actions.append(
actions.Link(
'preview',
icon='visibility',
link=f'/admin/myapp/article/{obj.pk}/preview/'
)
)
return actionsclass ArticleAdmin(ModelAdmin):
tabs = [
{'title': 'Content', 'id': 'content'},
{'title': 'SEO', 'id': 'seo'},
{'title': 'Metadata', 'id': 'metadata'},
]
fieldsets = (
(None, {
'fields': ('title', 'content'),
'tab_id': 'content',
}),
('SEO Settings', {
'fields': ('meta_title', 'meta_description'),
'tab_id': 'seo',
}),
('Metadata', {
'fields': ('created_at', 'updated_at'),
'tab_id': 'metadata',
}),
)# Install
pip install django-guardian
# Add to INSTALLED_APPS
INSTALLED_APPS = [
'guardian',
'unfold',
'django.contrib.admin',
# ...
]
# Add to URLs
from django.contrib import admin
from unfold.contrib.guardian.admin import GuardedModelAdmin
class ArticleAdmin(GuardedModelAdmin):
passpip install django-import-export
# Works automatically with Unfold
# Custom resource if needed
from import_export import resources
from unfold.admin import ImportExportMixin
class ArticleResource(resources.ModelResource):
class Meta:
model = Article
fields = ('id', 'title', 'status', 'created_at')
class ArticleAdmin(ImportExportMixin, ModelAdmin):
resource_classes = [ArticleResource]# Install
pip install django-celery-beat
# Configuration is automatic with Unfold
# Shows schedule in admin dashboardpip install django-constance[database]
INSTALLED_APPS = [
'constance',
'unfold',
# ...
]
# Configuration is automatic
# Shows in admin under "Constance" section# Custom form for Unfold login
from django import forms
from unfold.forms import LoginForm
class CustomLoginForm(LoginForm):
def clean(self):
# Add custom validation
cleaned_data = super().clean()
# Custom logic
return cleaned_data# settings.py
UNFOLD = {
'LOGIN': {
'form': 'myapp.forms.CustomLoginForm',
},
}# Custom password reset
UNFOLD = {
'PASSWORD_CHANGE_FORM': 'myapp.forms.CustomPasswordChangeForm',
'PASSWORD_RESET_FORM': 'myapp.forms.CustomPasswordResetForm',
}Unfold automatically supports dark mode based on system preference.
# Force dark mode
UNFOLD = {
'THEME': 'dark', # 'dark' or 'light' or None (auto)
}UNFOLD = {
'DARK_MODE_COLORS': {
'primary': {
'50': '224 242 254',
'100': '214 238 253',
# ... custom dark mode colors
},
},
}# settings.py
UNFOLD = {
'STYLES': [
'css/custom.css',
],
}
# templates/admin/base.html
{% extends "admin/base.html" %}
{% load static %}
{% block extrastyle %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
{% endblock %}UNFOLD = {
'SCRIPTS': [
'js/custom.js',
],
}- Keep unfold first in INSTALLED_APPS
- Use Unfold ModelAdmin for all models
- Leverage tabs for organized forms
- Use filters for better list navigation
- Customize actions for bulk operations
- Enable dark mode - users love it
- Use integrations - they work out of the box
- GitHub: https://github.com/unfoldadmin/django-unfold
- Documentation: https://unfoldadmin.com/docs
- Demo: https://unfoldadmin.com/