| name | django-unfold |
| description | Modern Django admin theme - Unfold - customization, settings, components, actions, filters, integrations |
| metadata | {"author":"mte90","version":"1.0.0","based_on":"https://github.com/unfoldadmin/django-unfold","tags":["python","django","admin","unfold","theme","dashboard"]} |
Django Unfold
Modern Django admin theme with beautiful design and advanced features.
Overview
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.
Installation
pip install django-unfold
pip install django-unfold==0.9.0
INSTALLED_APPS = [
'unfold',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
]
UNFOLD = {
}
Important: unfold must be first in INSTALLED_APPS to override Django templates.
Quick Start
Basic Configuration
INSTALLED_APPS = [
'unfold',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
]
URL Configuration
Unfold doesn't require changes to your URL configuration:
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
]
Settings Reference
Site Branding
UNFOLD = {
'SITE_HEADER': 'My Company Admin',
'SITE_TITLE': 'My Admin',
'INDEX_TITLE': 'Welcome to Dashboard',
'SITE_HEADER': '<div class="flex items-center gap-2"><span>🚀</span> My Company</div>',
}
Colors and Theme
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',
},
},
}
Sidebar Navigation
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/'},
],
},
],
},
}
Dashboard Widgets
UNFOLD = {
'DASHBOARD_WIDGETS': [
'unfold.widgets.DashboardStatistics',
'unfold.widgets.DashboardActions',
],
}
Feature Flags
UNFOLD = {
'SHOW_HISTORY': True,
'SHOW_VIEW_ON_SITE': True,
'AUTH_PASSWORD_VALIDATION': True,
}
Custom Admin Site
Custom Site Class
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)
context['custom_data'] = 'value'
return context
admin_site = CustomAdminSite(name='myadmin')
UNFOLD_ADMIN_SITE_CLASS = 'myapp.admin.CustomAdminSite'
Custom ModelAdmin
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']
sidebar_fieldsets = (
(None, {'fields': ('title', 'slug')}),
('Content', {'fields': ('content', 'excerpt')}),
)
Components
Buttons
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):
pass
Cards
{% 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 %}
Charts
{% component "chart" type="line" data=chart_data %}
{% endcomponent %}
Tables
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')
Fields and Widgets
Autocomplete Fields
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'
)
JSON Fields
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 form
Filters
Custom Filters
import unfold.filters as filters
class ArticleAdmin(ModelAdmin):
list_filter = [
('status', filters.DropdownFilter),
('category', filters.DropdownFilter),
('created_at', filters.DateRangeFilter),
('author', filters.AutocompleteFilter),
]
Filter Types
('status', filters.DropdownFilter)
('created_at', filters.DateRangeFilter)
('author', filters.AutocompleteFilter)
('is_published', filters.CheckboxFilter)
('views', filters.NumericFilter)
('title', filters.TextFilter)
Actions
Custom Actions
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):
buffer = io.StringIO()
writer = csv.writer(buffer)
writer.writerow(['Title', 'Status', 'Created'])
for obj in queryset:
writer.writerow([obj.title, obj.status, obj.created])
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'
Row Actions
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 actions
Tabs
Using Tabs
class 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',
}),
)
Integrations
django-guardian (Object Permissions)
pip install django-guardian
INSTALLED_APPS = [
'guardian',
'unfold',
'django.contrib.admin',
]
from django.contrib import admin
from unfold.contrib.guardian.admin import GuardedModelAdmin
class ArticleAdmin(GuardedModelAdmin):
pass
django-import-export
pip install django-import-export
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]
django-celery-beat
pip install django-celery-beat
django-constance
pip install django-constance[database]
INSTALLED_APPS = [
'constance',
'unfold',
]
Authentication Customization
Custom Login Form
from django import forms
from unfold.forms import LoginForm
class CustomLoginForm(LoginForm):
def clean(self):
cleaned_data = super().clean()
return cleaned_data
UNFOLD = {
'LOGIN': {
'form': 'myapp.forms.CustomLoginForm',
},
}
Custom Views
UNFOLD = {
'PASSWORD_CHANGE_FORM': 'myapp.forms.CustomPasswordChangeForm',
'PASSWORD_RESET_FORM': 'myapp.forms.CustomPasswordResetForm',
}
Dark Mode
Automatic Dark Mode
Unfold automatically supports dark mode based on system preference.
Manual Control
UNFOLD = {
'THEME': 'dark',
}
Custom Colors for Dark Mode
UNFOLD = {
'DARK_MODE_COLORS': {
'primary': {
'50': '224 242 254',
'100': '214 238 253',
},
},
}
Custom Styling
Custom CSS
UNFOLD = {
'STYLES': [
'css/custom.css',
],
}
{% extends "admin/base.html" %}
{% load static %}
{% block extrastyle %}
<link rel="stylesheet" href="{% static 'css/custom.css' %}">
{% endblock %}
Custom JavaScript
UNFOLD = {
'SCRIPTS': [
'js/custom.js',
],
}
Best Practices
- 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
References