1. 为什么需要掌握Django Admin与ORM进阶
第一次接触Django的开发者往往会被其自带的Admin后台所震撼——不需要编写前端代码,仅仅通过简单的配置就能生成功能完善的数据管理界面。这就像给你的项目配备了一个现成的"管理驾驶舱",但真正的高手都知道,Admin后台的威力远不止表面看到的这些。
我在2016年接手一个电商后台系统时,曾用三周时间重写了整个管理后台。后来才发现,其实80%的功能用Django Admin配合定制化配置就能实现。这个教训让我深刻认识到:理解Admin的深度定制能力,比急着开发自定义后台更重要。
ORM方面更是如此。很多开发者停留在基础的all()、filter()操作,遇到复杂查询就写原生SQL。实际上,Django ORM的查询能力强大到超乎想象,我在处理百万级数据的分析报表时,通过优化ORM查询将性能提升了40倍。
2. Admin后台深度配置指南
2.1 从零搭建基础Admin界面
先来看一个商品管理的典型场景。假设我们有如下模型:
python复制class Product(models.Model):
name = models.CharField(max_length=100)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField()
is_featured = models.BooleanField(default=False)
def __str__(self):
return f"{self.name} (${self.price})"
最基本的Admin注册是这样的:
python复制from django.contrib import admin
admin.site.register(Product)
但这样生成的界面非常基础。我们需要通过ModelAdmin类进行定制:
python复制@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = ('name', 'category', 'price', 'stock_status')
list_filter = ('category', 'is_featured')
search_fields = ('name',)
def stock_status(self, obj):
return "充足" if obj.stock > 10 else "紧缺"
stock_status.short_description = '库存状态'
几个关键配置项:
list_display:控制列表页显示的字段list_filter:添加右侧过滤侧边栏search_fields:启用搜索框
经验:在ModelAdmin中定义的方法(如stock_status)可以像模型字段一样显示,这是扩展显示内容的常用技巧。
2.2 高级定制技巧实战
2.2.1 批量操作与Action
假设我们需要批量设置精选商品:
python复制def make_featured(modeladmin, request, queryset):
queryset.update(is_featured=True)
make_featured.short_description = "设为精选商品"
class ProductAdmin(admin.ModelAdmin):
actions = [make_featured]
现在管理员可以勾选多个商品,然后从"动作"下拉菜单执行批量操作。
2.2.2 内联编辑关联模型
对于关联模型,可以使用内联编辑:
python复制class ProductImageInline(admin.TabularInline):
model = ProductImage
extra = 3 # 默认显示3个空表单
class ProductAdmin(admin.ModelAdmin):
inlines = [ProductImageInline]
这样在编辑商品时可以直接上传关联图片。
2.2.3 自定义表单与验证
我们可以重写get_form方法添加自定义验证:
python复制class ProductAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
form.base_fields['price'].min_value = 0
return form
这确保了价格不能为负数。
3. ORM查询进阶实战
3.1 单表查询优化
3.1.1 选择特定字段
避免使用Product.objects.all()获取全部字段:
python复制# 不好
products = Product.objects.all()
# 好:只获取需要的字段
products = Product.objects.only('name', 'price')
对于外键,使用select_related:
python复制# 引发N+1查询问题
for p in Product.objects.all():
print(p.category.name)
# 优化:一次性获取关联数据
for p in Product.objects.select_related('category'):
print(p.category.name)
3.1.2 条件查询的多种写法
python复制# 获取价格大于100的商品
products = Product.objects.filter(price__gt=100)
# 获取库存小于5的非精选商品
products = Product.objects.filter(
stock__lt=5,
is_featured=False
)
# 使用Q对象实现复杂逻辑
from django.db.models import Q
products = Product.objects.filter(
Q(price__gt=100) | Q(is_featured=True)
)
3.2 多表关联查询
假设我们有以下关联模型:
python复制class Category(models.Model):
name = models.CharField(max_length=50)
class Product(models.Model):
category = models.ForeignKey(Category, related_name='products')
# 其他字段...
class OrderItem(models.Model):
product = models.ForeignKey(Product)
quantity = models.PositiveIntegerField()
3.2.1 跨表查询
python复制# 查找分类为"电子产品"的所有商品
products = Product.objects.filter(category__name='电子产品')
# 反向查询:查找包含某商品的所有订单项
order_items = OrderItem.objects.filter(product__name='iPhone')
3.2.2 prefetch_related优化
对于多对多或反向关联:
python复制# 获取所有分类及各自的产品
categories = Category.objects.prefetch_related('products')
for category in categories:
print(category.products.all()) # 不会产生额外查询
3.3 聚合与注解
3.3.1 基础聚合
python复制from django.db.models import Count, Sum, Avg
# 统计商品总数
Product.objects.count()
# 按分类统计商品数量
Category.objects.annotate(
product_count=Count('products')
)
# 计算平均价格
Product.objects.aggregate(
avg_price=Avg('price')
)
3.3.2 复杂聚合案例
计算每个分类的商品总价值:
python复制from django.db.models import F, Sum
result = Category.objects.annotate(
total_value=Sum(F('products__price') * F('products__stock'))
).values('name', 'total_value')
这会生成类似以下的SQL:
sql复制SELECT
category.name,
SUM(product.price * product.stock) AS total_value
FROM category
JOIN product ON product.category_id = category.id
GROUP BY category.name
4. 性能优化与常见陷阱
4.1 N+1查询问题
这是ORM最常见的性能陷阱:
python复制# 错误示例:会产生N+1次查询
for product in Product.objects.all():
print(product.category.name) # 每次循环都查询一次category
解决方案:
python复制# 使用select_related
for product in Product.objects.select_related('category'):
print(product.category.name) # 只查询一次
4.2 查询集惰性评估
Django的QuerySet是惰性的:
python复制# 此时并未真正执行查询
queryset = Product.objects.filter(price__gt=100)
# 这些操作会触发查询执行
print(queryset.count()) # 执行COUNT查询
list(queryset) # 执行SELECT查询
重要:多次使用同一个QuerySet可能会导致多次查询,可以使用list()缓存结果。
4.3 索引优化建议
为常用查询字段添加索引:
python复制class Product(models.Model):
name = models.CharField(max_length=100, db_index=True)
category = models.ForeignKey('Category', on_delete=models.CASCADE)
price = models.DecimalField(max_digits=10, decimal_places=2, db_index=True)
对于复杂查询,可以考虑联合索引:
python复制class Meta:
indexes = [
models.Index(fields=['category', 'is_featured']),
]
5. 真实项目经验分享
5.1 Admin后台权限控制
在实际项目中,我们通常需要根据不同角色限制Admin权限:
python复制class ProductAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super().get_queryset(request)
if not request.user.is_superuser:
return qs.filter(is_approved=True)
return qs
5.2 复杂报表查询案例
曾经有一个需求:统计每个分类下销量前3的商品。ORM解决方案:
python复制from django.db.models import Window, F
from django.db.models.functions import Rank
products = Product.objects.annotate(
sales_rank=Window(
expression=Rank(),
partition_by=[F('category')],
order_by=F('total_sales').desc()
)
).filter(sales_rank__lte=3)
5.3 自定义ORM管理器
对于常用查询,可以封装到自定义管理器中:
python复制class ProductManager(models.Manager):
def featured(self):
return self.filter(is_featured=True)
def in_stock(self):
return self.filter(stock__gt=0)
class Product(models.Model):
objects = ProductManager()
使用方式:
python复制Product.objects.featured()
Product.objects.in_stock()
6. 调试技巧与工具
6.1 查看生成的SQL
python复制# 方式1:打印QuerySet的query属性
print(Product.objects.filter(price__gt=100).query)
# 方式2:使用connection.queries
from django.db import connection
products = list(Product.objects.filter(price__gt=100))
print(connection.queries)
6.2 Django Debug Toolbar
安装配置:
bash复制pip install django-debug-toolbar
settings.py配置:
python复制INSTALLED_APPS = [
'debug_toolbar',
]
MIDDLEWARE = [
'debug_toolbar.middleware.DebugToolbarMiddleware',
]
INTERNAL_IPS = ['127.0.0.1']
这个工具可以显示页面加载的所有SQL查询及其耗时。
6.3 性能测试建议
使用django.test.TestCase进行查询性能测试:
python复制from django.test import TestCase
class ProductTests(TestCase):
def setUp(self):
# 创建测试数据
self.category = Category.objects.create(name="测试分类")
for i in range(100):
Product.objects.create(
name=f"产品{i}",
category=self.category,
price=i*10,
stock=i
)
def test_query_performance(self):
with self.assertNumQueries(1): # 确保只执行1次查询
list(Product.objects.select_related('category'))