怎样通过一个电商后台案例展示Django Admin的强大之处

访客 全栈框架 1

本文目录导读:

  1. 基础模型设计
  2. Admin配置展示强大的功能
  3. 高级自定义模板
  4. Django Admin的强大功能总结

我将通过一个电商后台案例,展示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>&nbsp;'
            '<a class="button" href="{}">编辑</a>&nbsp;'
            '<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如何通过配置快速构建一个功能完善、美观实用的后台管理系统,大大减少了重复开发工作。

标签: Django Admin 的强大之处 电商后台管理

抱歉,评论功能暂时关闭!