在前两篇中,我们搭建了 Django 环境,创建了模型、视图和模板,构建了一个简单的博客系统。本篇将带你深入 Django 的两大核心利器:Admin 管理工具 和ORM(对象关系映射)。我们将从 Admin 的基础配置开始,然后深入 ORM 的单表操作、多表关系查询,最后掌握聚合查询和 F/Q 对象等高级技巧。学完本篇,你将能够高效管理后台数据,并编写复杂、高效的数据库查询。
第一部分:Django Admin 管理工具
Django Admin 是一个自动生成的管理界面,它读取模型元数据,提供一个模型中心的管理界面。官方文档指出:"Django 最强大的部分之一是自动管理界面。它从模型中读取元数据,提供一个快速、以模型为中心的界面,让受信任的用户可以管理站点上的内容。"
1.1 激活与配置 Admin
在默认的项目模板中,Admin 已经是启用的。确认 settings.py 中的 INSTALLED_APPS 包含以下应用:
python
INSTALLED_APPS = [
'django.contrib.admin', # 管理站点
'django.contrib.auth', # 认证系统
'django.contrib.contenttypes', # 内容类型框架
'django.contrib.sessions', # 会话框架
'django.contrib.messages', # 消息框架
'django.contrib.staticfiles', # 静态文件管理
'blog', # 我们的应用
]
同时确保 MIDDLEWARE 中包含必要的会话和认证中间件。
创建超级用户
要登录 Admin 后台,需要创建一个超级用户。在终端执行:
bash
python manage.py createsuperuser
按照提示输入用户名、邮箱和密码。
访问 Admin 后台
启动开发服务器,访问 http://127.0.0.1:8000/admin/,使用刚创建的超级用户登录,你将看到 Django 默认的管理界面。
1.2 注册模型到 Admin
要让我们的 Article 模型出现在 Admin 后台,需要在应用目录下的 admin.py 中注册它。
python
# blog/admin.py
from django.contrib import admin
from .models import Article
# 简单注册
admin.site.register(Article)
刷新 Admin 页面,你将看到 Articles 列表,可以进行增删改查操作。
1.3 使用 ModelAdmin 自定义后台
ModelAdmin 类是模型在 Admin 界面的表示。通过自定义 ModelAdmin,我们可以控制列表显示字段、过滤器、搜索框、表单布局等。
创建一个 ModelAdmin 子类,并通过 admin.site.register() 注册:
python
# blog/admin.py
from django.contrib import admin
from .models import Article
class ArticleAdmin(admin.ModelAdmin):
# 列表页显示的字段
list_display = ('title', 'author', 'pub_date', 'updated_date')
# 列表页右侧过滤器
list_filter = ('pub_date', 'author')
# 列表页搜索框
search_fields = ('title', 'content')
# 日期层级导航
date_hierarchy = 'pub_date'
# 每页显示条数
list_per_page = 20
# 编辑页字段分组
fieldsets = (
('基本信息', {
'fields': ('title', 'author')
}),
('内容与时间', {
'fields': ('content', 'pub_date', 'updated_date'),
'classes': ('collapse',) # 可折叠
}),
)
admin.site.register(Article, ArticleAdmin)
list_display:指定列表页显示的字段,可以是模型字段或自定义方法。list_filter:在右侧生成基于指定字段的过滤器。search_fields:指定哪些字段参与搜索。date_hierarchy:在列表页顶部生成日期层级导航。fieldsets:将表单字段分组,提升编辑页的可读性。
1.4 关联对象管理:内联(Inline)
当模型有关联关系时(如 Article 有多个 Comment),我们希望在编辑文章时能同时管理其评论。Django 提供了两种内联:TabularInline(表格形式)和 StackedInline(堆叠形式)。
首先,为文章添加评论模型:
python
# blog/models.py
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
name = models.CharField('姓名', max_length=50)
content = models.TextField('内容')
created_date = models.DateTimeField('创建时间', auto_now_add=True)
def __str__(self):
return f'{self.name}: {self.content[:20]}'
执行 makemigrations 和 migrate 后,在 admin.py 中配置内联:
python
# blog/admin.py
class CommentInline(admin.TabularInline): # 或 StackedInline
model = Comment
extra = 1 # 额外显示的空表单数量
class ArticleAdmin(admin.ModelAdmin):
inlines = [CommentInline]
# ... 其他配置
现在,在编辑文章页面下方,你可以直接添加、编辑与该文章关联的评论。
1.5 自定义管理站点
除了修改 ModelAdmin,你还可以自定义 Admin 站点的标题、头部内容等。
python
# urls.py(或在任何地方覆盖默认AdminSite)
from django.contrib import admin
admin.site.site_header = "我的博客管理系统"
admin.site.site_title = "博客管理"
admin.site.index_title = "欢迎使用博客后台"
第二部分:ORM - 单表实例
ORM(对象关系映射)让我们能用 Python 代码操作数据库,无需编写 SQL。Django ORM 提供了丰富的 API 进行增删改查。
2.1 单表增删改查
增加记录
有两种主要方式:
python
from blog.models import Article
from django.utils import timezone
# 方式一:实例化对象并调用 save()
article = Article(
title='ORM教程',
content='详细讲解Django ORM...',
pub_date=timezone.now()
)
article.save() # 插入数据库
# 方式二:使用 objects.create()(推荐)
article = Article.objects.create(
title='ORM进阶',
content='学习复杂查询...',
pub_date=timezone.now()
)
查询记录
Django ORM 的查询 API 非常丰富:
python
# 查询所有记录(返回QuerySet)
all_articles = Article.objects.all()
# 过滤查询(返回QuerySet)
recent_articles = Article.objects.filter(pub_date__year=2026)
author_articles = Article.objects.filter(author__username='admin')
# 获取单个对象(不存在或多于一个会抛出异常)
try:
article = Article.objects.get(id=1)
except Article.DoesNotExist:
article = None
# 使用 get_object_or_404 快捷方式(在视图中常用)
from django.shortcuts import get_object_or_404
article = get_object_or_404(Article, id=1)
# 排除某些记录
draft_articles = Article.objects.exclude(status='draft')
# 排序
articles_by_date = Article.objects.order_by('-pub_date') # 倒序
# 链式查询
articles = Article.objects.filter(author__username='admin').order_by('-pub_date')[:5] # 前5条
更新记录
同样有两种方式:
python
# 方式一:获取对象,修改属性,调用 save()
article = Article.objects.get(id=1)
article.title = '新标题'
article.save()
# 方式二:批量更新(返回受影响行数)
updated_count = Article.objects.filter(author__username='admin').update(status='published')
注意 :对象没有 update() 方法,只有 QuerySet 才有。
删除记录
python
# 删除单个对象
article = Article.objects.get(id=1)
article.delete()
# 批量删除
deleted_count = Article.objects.filter(status='draft').delete()
2.2 常用查询方法与字段查询
Django ORM 提供了一系列"字段查询"参数,使用双下划线语法:
python
# 大于、小于、大于等于、小于等于
articles = Article.objects.filter(id__gt=10) # id > 10
articles = Article.objects.filter(pub_date__lte=timezone.now()) # 发布时间 <= 当前时间
# 包含、开头、结尾
articles = Article.objects.filter(title__contains='Django')
articles = Article.objects.filter(title__startswith='Django')
articles = Article.objects.filter(title__endswith='教程')
# 范围查询
articles = Article.objects.filter(id__range=[1, 10]) # id 在1到10之间
# 列表查询
articles = Article.objects.filter(id__in=[1, 3, 5])
# 日期查询
articles = Article.objects.filter(pub_date__year=2026)
articles = Article.objects.filter(pub_date__month=12)
articles = Article.objects.filter(pub_date__day=25)
# 空值查询
articles = Article.objects.filter(updated_date__isnull=True)
2.3 QuerySet 特性
QuerySet 是 Django ORM 的核心,具有以下特性:
- 惰性执行 :
QuerySet在真正被求值(如迭代、切片、打印)时才执行数据库查询。 - 链式调用 :可以在
QuerySet上继续调用过滤器等方法。 - 缓存 :同一个
QuerySet被多次求值时,只会执行一次查询。
常用 QuerySet 方法:
python
# 计数
count = Article.objects.filter(author__username='admin').count()
# 判断是否存在
exists = Article.objects.filter(title__contains='Django').exists()
# 获取第一条/最后一条
first = Article.objects.filter(author__username='admin').first()
last = Article.objects.filter(author__username='admin').last()
# 去重
distinct_authors = Article.objects.values('author').distinct()
# 获取特定字段的值
titles = Article.objects.values_list('title', flat=True) # 返回列表
第三部分:ORM - 多表实例
实际项目中,数据表之间通常存在关系。Django ORM 支持三种关系:ForeignKey(多对一)、ManyToManyField(多对多)、OneToOneField(一对一)。
3.1 定义模型关系
我们扩展博客模型,添加分类(Category)和标签(Tag):
python
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
name = models.CharField('分类名称', max_length=50)
created_date = models.DateTimeField('创建时间', auto_now_add=True)
def __str__(self):
return self.name
class Tag(models.Model):
name = models.CharField('标签名称', max_length=30)
def __str__(self):
return self.name
class Article(models.Model):
title = models.CharField('标题', max_length=200)
content = models.TextField('内容')
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='作者')
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True, verbose_name='分类')
tags = models.ManyToManyField(Tag, blank=True, verbose_name='标签')
pub_date = models.DateTimeField('发布时间', default=timezone.now)
updated_date = models.DateTimeField('更新时间', auto_now=True)
def __str__(self):
return self.title
ForeignKey:多对一关系。on_delete=models.CASCADE表示当作者删除时,其文章也级联删除;on_delete=models.SET_NULL表示当分类删除时,文章的分类字段设为null(需设置null=True)。ManyToManyField:多对多关系。Django 会自动创建中间表。
3.2 跨表查询
Django ORM 使用双下划线 __ 进行跨表查询。
正向查询(从有外键的模型出发)
python
# 查询所有文章及其分类名称
articles = Article.objects.select_related('category').all()
for article in articles:
print(article.title, article.category.name) # 不会产生额外查询
# 过滤:查询分类为"Python"的文章
articles = Article.objects.filter(category__name='Python')
# 过滤:查询作者为"admin"且标签包含"Django"的文章
articles = Article.objects.filter(
author__username='admin',
tags__name__contains='Django'
).distinct() # 多对多关系可能产生重复,用 distinct 去重
反向查询(从被关联的模型出发)
python
# 查询某个分类下的所有文章
category = Category.objects.get(name='Python')
articles = category.article_set.all() # 默认关联名:模型名小写_set
# 如果设置了 related_name,则使用 related_name
# 在 ForeignKey 中添加 related_name='articles'
# 那么可以:category.articles.all()
# 过滤:查询文章数量大于5的分类
from django.db.models import Count
categories = Category.objects.annotate(article_count=Count('article')).filter(article_count__gt=5)
3.3 跨表查询的四个模板
华为云社区的一篇文章总结了跨表查询的四个固定模板,非常实用:
| 需求方向 | 模板写法 | 示例 |
|---|---|---|
| 从"一"到"多"(书 → 人) | book.peopleinfo_set.all() |
BookInfo.objects.get(id=1).peopleinfo_set.all() |
| 从"多"到"一"(人 → 书) | person.book |
PeopleInfo.objects.get(id=1).book |
| 跨表过滤(按人物找书) | BookInfo.objects.filter(peopleinfo__字段__运算符=值) |
BookInfo.objects.filter(peopleinfo__name__exact='郭靖') |
| 反向过滤(按书找人) | PeopleInfo.objects.filter(book__字段__运算符=值) |
PeopleInfo.objects.filter(book__read_count__gt=30) |
易错点提醒:
peopleinfo__里的peopleinfo是关联模型类名小写,不是表名。- 双下划线
__一定要写两个,否则解析失败。 - 返回类型永远是
QuerySet,可继续链式过滤。
3.4 查询优化:select_related 和 prefetch_related
当需要访问关联对象时,如果不优化,Django 会执行多次查询(N+1问题)。select_related 和 prefetch_related 可以解决这个问题。
- select_related:用于 ForeignKey 和 OneToOneField,通过 SQL JOIN 将关联数据一次性查出来。
- prefetch_related:用于 ManyToManyField 和反向外键,分别查询并在 Python 中组合。
python
# 未优化:查询文章时,每次访问 category 都会执行一次查询
articles = Article.objects.all()
for article in articles:
print(article.category.name) # 产生 N 次查询
# 使用 select_related 优化
articles = Article.objects.select_related('category').all()
for article in articles:
print(article.category.name) # 只执行一次查询(JOIN)
# 使用 prefetch_related 优化多对多
articles = Article.objects.prefetch_related('tags').all()
for article in articles:
print([tag.name for tag in article.tags.all()]) # 只执行两次查询
第四部分:ORM - 聚合查询
聚合查询用于对一组记录进行统计计算,如计数、求和、平均值等。
4.1 aggregate():聚合整个 QuerySet
aggregate() 返回一个字典,包含聚合计算结果。
python
from django.db.models import Count, Sum, Avg, Max, Min
# 统计文章总数
result = Article.objects.aggregate(total=Count('id'))
print(result) # {'total': 42}
# 同时计算多个聚合
result = Article.objects.aggregate(
total=Count('id'),
avg_comments=Avg('comments__id'), # 假设 Article 有 comments 反向关系
max_pub_date=Max('pub_date')
)
# 计算所有文章的平均评论数(使用 related_name)
result = Article.objects.aggregate(avg_comments=Avg('comments__id'))
4.2 annotate():为每个对象添加聚合字段
annotate() 为 QuerySet 中的每个对象添加聚合字段,常用于分组统计。
python
from django.db.models import Count
# 为每篇文章添加评论数
articles = Article.objects.annotate(comment_count=Count('comments'))
for article in articles:
print(article.title, article.comment_count) # 直接使用聚合字段
# 按作者分组,统计每位作者的文章数
from django.contrib.auth.models import User
authors = User.objects.annotate(article_count=Count('article'))
for author in authors:
print(author.username, author.article_count)
# 按分类分组,统计每类文章数,并过滤出文章数大于3的分类
from blog.models import Category
categories = Category.objects.annotate(
article_count=Count('article')
).filter(article_count__gt=3)
4.3 聚合查询实战示例
假设我们需要统计博客的各项指标:
python
# 1. 所有已发布文章的总数
published_count = Article.objects.filter(status='published').count()
# 2. 每个分类下的文章数
category_stats = Category.objects.annotate(
article_count=Count('article')
).values('name', 'article_count')
# 3. 每篇文章的评论数,按评论数降序排列
hot_articles = Article.objects.annotate(
comment_count=Count('comments')
).order_by('-comment_count')[:10]
# 4. 本月发表文章最多的作者
from django.utils import timezone
now = timezone.now()
active_authors = User.objects.filter(
article__pub_date__year=now.year,
article__pub_date__month=now.month
).annotate(
monthly_articles=Count('article')
).order_by('-monthly_articles')[:5]
第五部分:高级查询 - F对象与Q对象
5.1 F对象:引用字段值
F 对象允许在查询中引用模型字段的值,而不是 Python 变量。
python
from django.db.models import F
# 将所有文章的评论数加1(假设有 comments_count 字段)
Article.objects.update(comments_count=F('comments_count') + 1)
# 查询评论数大于点赞数的文章
hot_articles = Article.objects.filter(comments_count__gt=F('likes_count'))
# 在 annotate 中使用 F
articles = Article.objects.annotate(
total_interactions=F('comments_count') + F('likes_count')
)
5.2 Q对象:复杂条件查询
Q 对象允许使用 &(AND)、|(OR)、~(NOT)构建复杂的查询条件。
python
from django.db.models import Q
# 查询标题包含"Django" 或 内容包含"ORM"的文章
articles = Article.objects.filter(
Q(title__contains='Django') | Q(content__contains='ORM')
)
# 组合条件:(标题包含Django 且 作者是admin) 或 (发布时间在2026年)
articles = Article.objects.filter(
(Q(title__contains='Django') & Q(author__username='admin')) |
Q(pub_date__year=2026)
)
# 排除某些条件(NOT)
articles = Article.objects.filter(
Q(title__contains='Django') & ~Q(status='draft')
)
# 动态构建查询
q = Q()
if search_title:
q &= Q(title__icontains=search_title)
if search_author:
q &= Q(author__username=search_author)
articles = Article.objects.filter(q)
5.3 综合示例:高级文章搜索
实现一个支持多条件组合搜索的功能:
python
def search_articles(request):
query = Q()
title = request.GET.get('title')
if title:
query &= Q(title__icontains=title)
author = request.GET.get('author')
if author:
query &= Q(author__username=author)
category = request.GET.get('category')
if category:
query &= Q(category__name=category)
start_date = request.GET.get('start_date')
if start_date:
query &= Q(pub_date__gte=start_date)
end_date = request.GET.get('end_date')
if end_date:
query &= Q(pub_date__lte=end_date)
# 如果没有任何条件,返回空结果或所有结果
if not query:
articles = Article.objects.none() # 空QuerySet
else:
articles = Article.objects.filter(query).distinct()
return render(request, 'blog/search_results.html', {'articles': articles})
6. 小结与练习
本篇总结
我们深入学习了 Django 的三大主题:
- Admin 管理工具:模型注册、ModelAdmin 自定义、内联管理、站点定制。
- ORM 单表操作:增删改查、字段查询、QuerySet 特性。
- ORM 多表操作:关系定义、正向反向查询、跨表过滤、查询优化。
- 聚合查询:aggregate 和 annotate 的用法。
- 高级查询:F 对象和 Q 对象。
练习
- Admin 自定义 :为你的博客模型创建一个自定义的
ModelAdmin,添加list_display、list_filter、search_fields和fieldsets。 - 多表查询:实现一个视图,显示每个分类下的文章列表,并在模板中展示分类名称和文章数量。
- 聚合统计:在博客首页添加一个统计模块,显示总文章数、总评论数、最活跃作者(文章数最多)。
- 高级搜索:实现一个搜索页面,支持按标题、作者、分类、日期范围组合搜索。
下一篇,我们将学习 Django 的 Form 组件、Auth 认证系统、Cookie/Session 和中间件,构建更完整的用户交互功能。敬请期待!