Django 基础入门教程(第三篇):Admin后台与ORM进阶(单表、多表、聚合查询)

在前两篇中,我们搭建了 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]}'

执行 makemigrationsmigrate 后,在 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,可继续链式过滤。

当需要访问关联对象时,如果不优化,Django 会执行多次查询(N+1问题)。select_relatedprefetch_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 的三大主题:

  1. Admin 管理工具:模型注册、ModelAdmin 自定义、内联管理、站点定制。
  2. ORM 单表操作:增删改查、字段查询、QuerySet 特性。
  3. ORM 多表操作:关系定义、正向反向查询、跨表过滤、查询优化。
  4. 聚合查询:aggregate 和 annotate 的用法。
  5. 高级查询:F 对象和 Q 对象。

练习

  1. Admin 自定义 :为你的博客模型创建一个自定义的 ModelAdmin,添加 list_displaylist_filtersearch_fieldsfieldsets
  2. 多表查询:实现一个视图,显示每个分类下的文章列表,并在模板中展示分类名称和文章数量。
  3. 聚合统计:在博客首页添加一个统计模块,显示总文章数、总评论数、最活跃作者(文章数最多)。
  4. 高级搜索:实现一个搜索页面,支持按标题、作者、分类、日期范围组合搜索。

下一篇,我们将学习 Django 的 Form 组件、Auth 认证系统、Cookie/Session 和中间件,构建更完整的用户交互功能。敬请期待!

相关推荐
好家伙VCC2 小时前
# Deno框架实战:从零搭建一个安全、高效的Node.js替代项目 在现代
java·python·安全·node.js
Zwj-c2 小时前
【测试报告】学评一体化平台测试报告(功能测试、自动化测试、Bug描述)
python·功能测试·selenium·测试用例·bug
chushiyunen2 小时前
django数据库配置
数据库·python·django
全栈凯哥2 小时前
01.Python 和 PyCharm 安装与运行完全指南
python·pycharm
java1234_小锋2 小时前
Python常见面试题:请解释或描述一下Django的架构?
python·架构·django
青瓷程序设计2 小时前
【蝴蝶识别系统】~Python+深度学习+人工智能+算法模型+图像识别+2026原创
人工智能·python·深度学习
所谓伊人,在水一方3332 小时前
【Python零基础到精通】第13讲 | TensorFlow深度学习:从神经网络原理到实战
python·深度学习·神经网络·信息可视化·tensorflow
Fuxiao___2 小时前
RoboTwin2.0中训练OpenVLA-oft 全流程(bell + alarmclock)
人工智能·python·深度学习
justtoomuchforyou2 小时前
deepseek- 上千种类别如何image classification
python