本文目录导读:
我将通过一个电商后台案例,展示Django Admin的强大功能和灵活性。
基础模型设计
# models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Category(models.Model):
name = models.CharField('分类名称', max_length=100)
parent = models.ForeignKey(
'self', on_delete=models.CASCADE,
null=True, blank=True, verbose_name='父分类'
)
sort_order = models.IntegerField('排序', default=0)
is_active = models.BooleanField('启用', default=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
verbose_name = '商品分类'
verbose_name_plural = '商品分类'
ordering = ['sort_order', 'name']
def __str__(self):
return self.name
class Product(models.Model):
STATUS_CHOICES = [
('draft', '草稿'),
('active', '上架'),
('inactive', '下架'),
('deleted', '删除'),
]
sku = models.CharField('SKU', max_length=50, unique=True)
name = models.CharField('商品名称', max_length=200)
category = models.ForeignKey(
Category, on_delete=models.PROTECT,
verbose_name='分类', related_name='products'
)
price = models.DecimalField('价格', max_digits=10, decimal_places=2)
cost_price = models.DecimalField('成本价', max_digits=10, decimal_places=2)
stock = models.IntegerField('库存', default=0)
sales = models.IntegerField('销量', default=0)
status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='draft')
description = models.TextField('描述', blank=True)
attributes = models.JSONField('属性', default=dict, blank=True)
created_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name='创建人')
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '商品'
verbose_name_plural = '商品'
ordering = ['-created_at']
def __str__(self):
return f"{self.sku} - {self.name}"
@property
def profit_margin(self):
"""利润率"""
if self.cost_price:
return ((self.price - self.cost_price) / self.price) * 100
return 0
@property
def is_low_stock(self):
"""是否低库存"""
return self.stock < 10
class Order(models.Model):
STATUS_CHOICES = [
('pending', '待支付'),
('paid', '已支付'),
('shipping', '配送中'),
('completed', '已完成'),
('cancelled', '已取消'),
('refunding', '退款中'),
('refunded', '已退款'),
]
order_no = models.CharField('订单号', max_length=50, unique=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='用户')
status = models.CharField('状态', max_length=20, choices=STATUS_CHOICES, default='pending')
total_amount = models.DecimalField('总金额', max_digits=10, decimal_places=2)
payment_amount = models.DecimalField('实付金额', max_digits=10, decimal_places=2)
discount_amount = models.DecimalField('优惠金额', max_digits=10, decimal_places=2, default=0)
shipping_address = models.TextField('收货地址')
shipping_company = models.CharField('快递公司', max_length=50, blank=True)
tracking_no = models.CharField('快递单号', max_length=50, blank=True)
remark = models.TextField('备注', blank=True)
paid_at = models.DateTimeField('支付时间', null=True, blank=True)
shipped_at = models.DateTimeField('发货时间', null=True, blank=True)
completed_at = models.DateTimeField('完成时间', null=True, blank=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
verbose_name = '订单'
verbose_name_plural = '订单'
ordering = ['-created_at']
def __str__(self):
return self.order_no
def save(self, *args, **kwargs):
if not self.order_no:
self.order_no = self.generate_order_no()
super().save(*args, **kwargs)
@staticmethod
def generate_order_no():
"""生成订单号"""
return f"ORD{timezone.now().strftime('%Y%m%d%H%M%S')}{uuid.uuid4().hex[:6].upper()}"
Admin配置展示强大的功能
# admin.py
from django.contrib import admin
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.utils.html import format_html
from django.contrib import messages
from django.db.models import Count, Sum, Avg, Q, F
from django.db.models.functions import TruncDate, TruncMonth
from django.template.response import TemplateResponse
from django import forms
import json
from datetime import timedelta, datetime
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
# 树形展示 - Django Admin自动处理自关联的层级显示
list_display = ['name', 'parent', 'sort_order', 'product_count', 'is_active']
list_editable = ['sort_order', 'is_active'] # 列表编辑
list_filter = ['is_active', 'parent'] # 过滤器
search_fields = ['name'] # 搜索
def product_count(self, obj):
"""统计商品数量"""
return obj.products.count()
product_count.short_description = '商品数量'
# 批量操作示例
actions = ['enable_categories', 'disable_categories', 'set_sort_order_to_10']
def enable_categories(self, request, queryset):
queryset.update(is_active=True)
self.message_user(request, f'已启用 {queryset.count()} 个分类')
enable_categories.short_description = '启用所选分类'
def disable_categories(self, request, queryset):
queryset.update(is_active=False)
self.message_user(request, f'已禁用 {queryset.count()} 个分类')
disable_categories.short_description = '禁用所选分类'
@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
# 自定义列表显示
list_display = [
'sku', 'name', 'category', 'price_display',
'stock_display', 'status_colored', 'profit_margin_display',
'sales', 'created_at', 'actions_column'
]
list_editable = ['price', 'stock'] # 列表可编辑
list_filter = ['status', 'category', 'created_at'] # 日期层级筛选
search_fields = ['sku', 'name', 'category__name'] # 跨关联搜索
date_hierarchy = 'created_at' # 日期快速导航
list_per_page = 20 # 分页
list_max_show_all = 200
# 自定义字段显示
def price_display(self, obj):
return f"¥{obj.price}"
price_display.short_description = '售价'
price_display.admin_order_field = 'price' # 可排序
def stock_display(self, obj):
if obj.is_low_stock:
return format_html('<span style="color: red; font-weight: bold;">{} (预警)</span>', obj.stock)
return obj.stock
stock_display.short_description = '库存'
stock_display.admin_order_field = 'stock'
def status_colored(self, obj):
colors = {
'draft': 'gray',
'active': 'green',
'inactive': 'orange',
'deleted': 'red',
}
return format_html(
'<span style="color: {}; font-weight: bold;">{}</span>',
colors.get(obj.status, 'black'),
obj.get_status_display()
)
status_colored.short_description = '状态'
status_colored.admin_order_field = 'status'
def profit_margin_display(self, obj):
margin = obj.profit_margin
color = 'green' if margin > 30 else 'orange' if margin > 10 else 'red'
return format_html('<span style="color: {};">{:.1f}%</span>', color, margin)
profit_margin_display.short_description = '利润率'
def actions_column(self, obj):
"""操作列"""
return format_html(
'<a class="button" href="{}">查看</a> '
'<a class="button" href="{}">编辑</a> '
'<a class="button" href="{}">复制</a>',
reverse('admin:app_product_change', args=[obj.pk]),
reverse('admin:app_product_change', args=[obj.pk]),
reverse('admin:app_product_copy', args=[obj.pk]),
)
actions_column.short_description = '操作'
# 自定义表单布局
fieldsets = [
('基本信息', {
'fields': ['sku', 'name', 'category', 'status']
}),
('价格与库存', {
'fields': [('price', 'cost_price'), ('stock', 'sales')],
'classes': ['collapse'], # 可折叠
}),
('详细描述', {
'fields': ['description', 'attributes'],
'classes': ['wide'],
}),
('审计信息', {
'fields': ['created_by'],
'classes': ['collapse'],
}),
]
# 自动填充
autocomplete_fields = ['category', 'created_by']
raw_id_fields = ['created_by'] # 大数据量优化
readonly_fields = ['created_at', 'updated_at', 'sales'] # 只读字段
# 批量操作
actions = [
'active_products', 'inactive_products',
'increase_price_10_percent', 'export_selected_as_json'
]
def active_products(self, request, queryset):
updated = queryset.update(status='active')
self.message_user(request, f'已上架 {updated} 个商品')
active_products.short_description = '批量上架商品'
def increase_price_10_percent(self, request, queryset):
for product in queryset:
product.price = product.price * 1.1
product.save()
self.message_user(request, f'已为 {queryset.count()} 个商品涨价10%')
increase_price_10_percent.short_description = '批量涨价10%'
# 自定义视图
def get_urls(self):
from django.urls import path
urls = super().get_urls()
custom_urls = [
path('analytics/', self.admin_site.admin_view(self.product_analytics_view), name='product-analytics'),
path('<int:pk>/copy/', self.admin_site.admin_view(self.copy_product_view), name='product-copy'),
]
return custom_urls + urls
def product_analytics_view(self, request):
"""商品分析面板"""
context = dict(
self.admin_site.each_context(request),
title='商品分析',
# 统计数据
total_products=Product.objects.count(),
active_products=Product.objects.filter(status='active').count(),
low_stock_products=Product.objects.filter(stock__lt=10).count(),
# 分类统计
category_stats=Category.objects.annotate(
product_count=Count('products')
).values('name', 'product_count'),
# 价格区间统计
price_stats=Product.objects.aggregate(
avg_price=Avg('price'),
max_price=Max('price'),
min_price=Min('price'),
total_stock=Sum('stock'),
),
)
return TemplateResponse(request, 'admin/product_analytics.html', context)
def copy_product_view(self, request, pk):
"""复制商品"""
original = Product.objects.get(pk=pk)
original.pk = None
original.sku = f"{original.sku}-COPY"
original.name = f"{original.name}(复制)"
original.status = 'draft'
original.sales = 0
original.save()
self.message_user(request, f'已复制商品: {original.name}')
return HttpResponseRedirect(reverse('admin:app_product_change', args=[original.pk]))
@admin.register(Order)
class OrderAdmin(admin.ModelAdmin):
# 高级列表显示
list_display = [
'order_no', 'user_link', 'status_badge', 'payment_display',
'shipping_info', 'order_date', 'order_age_display'
]
list_filter = [
'status', 'created_at',
('paid_at', admin.DateFieldListFilter), # 自定义日期过滤
]
search_fields = ['order_no', 'user__username', 'user__email', 'shipping_address']
date_hierarchy = 'created_at'
def user_link(self, obj):
return format_html(
'<a href="{}">{}</a>',
reverse('admin:auth_user_change', args=[obj.user_id]),
obj.user.username
)
user_link.short_description = '用户'
user_link.admin_order_field = 'user__username'
def status_badge(self, obj):
colors = {
'pending': 'orange',
'paid': 'blue',
'shipping': 'purple',
'completed': 'green',
'cancelled': 'red',
'refunding': 'brown',
'refunded': 'gray',
}
return format_html(
'<span class="order-status" style="background: {}; padding: 2px 8px; border-radius: 4px; color: white;">{}</span>',
colors.get(obj.status, 'black'),
obj.get_status_display()
)
status_badge.short_description = '状态'
status_badge.admin_order_field = 'status'
def payment_display(self, obj):
return format_html(
'原价: ¥{}<br/>实付: <strong>¥{}</strong><br/>优惠: ¥{}',
obj.total_amount, obj.payment_amount, obj.discount_amount
)
payment_display.short_description = '金额信息'
def shipping_info(self, obj):
if obj.shipping_company:
return format_html(
'{}<br/>{}{}',
obj.shipping_address[:20] + '...',
obj.shipping_company,
obj.tracking_no or ''
)
return obj.shipping_address[:20] + '...'
shipping_info.short_description = '物流信息'
def order_age_display(self, obj):
"""订单年龄"""
age = timezone.now() - obj.created_at
if age.days > 0:
return f"{age.days}天前"
elif age.seconds > 3600:
return f"{age.seconds // 3600}小时前"
else:
return f"{age.seconds // 60}分钟前"
order_age_display.short_description = '下单时间'
order_age_display.admin_order_field = 'created_at'
# 行级操作
def get_actions(self, request):
actions = super().get_actions(request)
if request.user.is_superuser:
actions['mark_as_refunded'] = self.mark_as_refunded
return actions
def mark_as_refunded(self, request, queryset):
queryset.update(status='refunded')
self.message_user(request, f'已标记 {queryset.count()} 个订单为已退款')
mark_as_refunded.short_description = '标记为已退款'
# 自定义表单验证
def save_model(self, request, obj, form, change):
if not change: # 新建订单
if not obj.order_no:
obj.order_no = Order.generate_order_no()
super().save_model(request, obj, form, change)
# 自定义详情页模板
change_form_template = 'admin/order_change_form.html'
# 数据导出
actions = ['export_orders_as_csv']
def export_orders_as_csv(self, request, queryset):
import csv
from django.http import HttpResponse
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="orders.csv"'
writer = csv.writer(response)
writer.writerow(['订单号', '用户', '状态', '总金额', '实付金额', '下单时间'])
for order in queryset:
writer.writerow([
order.order_no, order.user.username,
order.get_status_display(), order.total_amount,
order.payment_amount, order.created_at
])
return response
export_orders_as_csv.short_description = '导出为CSV'
# 性能优化
def get_queryset(self, request):
return super().get_queryset(request).select_related('user')
# 自定义Admin站点
class ECommerceAdminSite(admin.AdminSite):
site_header = '电商管理系统'
site_title = '电商后台'
index_title = '数据概览'
def get_app_list(self, request):
app_list = super().get_app_list(request)
# 自定义排序和分组
return app_list
def index(self, request, extra_context=None):
extra_context = extra_context or {}
extra_context['stats'] = {
'products': Product.objects.count(),
'orders': Order.objects.count(),
'users': User.objects.count(),
'revenue': Order.objects.filter(
status__in=['paid', 'shipping', 'completed']
).aggregate(total=Sum('payment_amount'))['total'] or 0,
}
return super().index(request, extra_context)
admin_site = ECommerceAdminSite(name='ecommerce_admin')
# 注册models
admin_site.register(Category, CategoryAdmin)
admin_site.register(Product, ProductAdmin)
admin_site.register(Order, OrderAdmin)
# 也可以使用默认admin
admin.site.register(Category, CategoryAdmin)
admin.site.register(Product, ProductAdmin)
admin.site.register(Order, OrderAdmin)
高级自定义模板
<!-- templates/admin/order_change_form.html -->
{% extends "admin/change_form.html" %}
{% block object-tools-items %}
{{ block.super }}
{% if original and original.status == 'paid' %}
<li>
<a href="{% url 'admin:ship-order' original.pk %}" class="button">
发货处理
</a>
</li>
{% endif %}
{% endblock %}
Django Admin的强大功能总结
CRUD自动化
- 自动生成增删改查界面
- 支持批量操作
- 表单验证自动处理
搜索与筛选
- 跨字段搜索
- 多条件组合筛选
- 日期层级导航
- 自定义过滤器
列表显示
- 自定义列
- 条件格式化
- 可编辑列表
- 排序支持
- 分页处理
表单定制
- Fieldset分组
- 可折叠栏目
- 自动填充
- 只读字段
- 内联表单
权限控制
- 基于用户和组的权限
- 对象级权限
- 操作权限细粒度控制
性能优化
- select_related/prefetch_related
- raw_id_fields处理外键
- 分页和搜索索引
扩展性
- 自定义视图
- 自定义模板
- 自定义操作
- 信号处理
易用性
- 友好错误提示
- 操作日志
- 批量操作确认
- 即时保存
这个电商案例展示了Django Admin如何通过配置快速构建一个功能完善、美观实用的后台管理系统,大大减少了重复开发工作。