Appearance
章节2:ORM 模型与数据库操作
一、章节概述
本章深入讲解 Django ORM(对象关系映射)的核心知识,包括模型定义、字段类型、数据库迁移、表关系映射、单表与多表增删改查、条件查询、排序分页以及聚合与关联查询等。掌握这些知识后,学生将能熟练使用 Django ORM 完成几乎所有数据库操作,无需编写原生 SQL。
二、学习目标
| 目标分类 | 具体目标 |
|---|---|
| 知识目标 | 理解 ORM 的概念与优势;掌握 Django 模型字段类型及选项;理解数据库表关系(一对多、一对一、多对多) |
| 技能目标 | 能独立设计模型并完成数据库迁移;熟练使用 ORM 进行增删改查操作;掌握过滤、排序、分页与聚合查询 |
| 素养目标 | 培养数据库设计的规范化意识;理解 ORM 与 SQL 之间的映射关系 |
三、核心知识点
3.1 模型定义与字段类型
定义
模型(Model) 是 Django ORM 的核心,每个模型类对应数据库中的一张表,类的属性对应表的字段。通过 Python 类定义数据模型,Django 自动负责生成和管理数据库表结构。
语法
python
from django.db import models
class Article(models.Model):
# 字段定义
title = models.CharField(max_length=200, verbose_name='标题')
content = models.TextField(verbose_name='内容')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name='发布时间')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新时间')
views = models.IntegerField(default=0, verbose_name='阅读量')
is_published = models.BooleanField(default=False, verbose_name='是否发布')
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, verbose_name='价格')
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
ordering = ['-pub_date'] # 默认排序(按发布时间降序)
db_table = 'blog_article' # 自定义表名
def __str__(self):
return self.title # 管理后台显示常用字段类型
| 字段类型 | 说明 | 对应数据库类型 |
|---|---|---|
CharField(max_length) | 字符串字段,需指定最大长度 | VARCHAR |
TextField() | 大文本字段 | LONGTEXT |
IntegerField() | 整数字段 | INT |
FloatField() | 浮点数字段 | DOUBLE |
DecimalField(max_digits, decimal_places) | 高精度十进制数字 | DECIMAL |
BooleanField() | 布尔字段 | TINYINT(1) |
DateField() | 日期字段 | DATE |
DateTimeField() | 日期时间字段 | DATETIME |
TimeField() | 时间字段 | TIME |
EmailField() | 邮箱字段(内置验证) | VARCHAR |
URLField() | URL 字段 | VARCHAR |
SlugField() | Slug(短标签)字段 | VARCHAR |
FileField(upload_to='path') | 文件上传字段 | VARCHAR |
ImageField(upload_to='path') | 图片上传字段 | VARCHAR |
JSONField() | JSON 字段(Django 3.1+) | JSON |
UUIDField() | UUID 字段 | CHAR(32) |
常用字段选项
| 选项 | 说明 | 示例 |
|---|---|---|
null=True | 数据库允许 NULL | null=True |
blank=True | 表单验证允许为空 | blank=True |
default=value | 设置默认值 | default=0 |
unique=True | 唯一约束 | unique=True |
db_index=True | 创建数据库索引 | db_index=True |
verbose_name | 人类可读的名称 | verbose_name='标题' |
help_text | 表单帮助文本 | help_text='请输入文章标题' |
choices | 枚举选项 | choices=STATUS_CHOICES |
auto_now_add=True | 创建时自动设置为当前时间 | auto_now_add=True |
auto_now=True | 每次保存时更新为当前时间 | auto_now=True |
枚举选项示例:
python
class Article(models.Model):
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
('archived', '已归档'),
]
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft',
verbose_name='状态'
)3.2 数据库迁移与表关系
定义
迁移(Migration) 是 Django 将模型定义同步到数据库的机制。通过迁移命令,Django 自动生成 SQL 并执行,无需手动编写 CREATE TABLE 等语句。
表关系 指模型之间的关联方式,分为三种:一对多(ForeignKey)、一对一(OneToOneField)、多对多(ManyToManyField)。
语法
▶ 迁移命令
bash
# 1. 生成迁移文件(检测模型变化)
python manage.py makemigrations
# 2. 查看生成的 SQL
python manage.py sqlmigrate blog 0001
# 3. 执行迁移(同步到数据库)
python manage.py migrate
# 4. 查看迁移状态
python manage.py showmigrations
# 5. 清空数据库(危险操作!)
python manage.py flush▶ 模型关系定义
python
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
"""分类 —— 被外键引用的一方(父表)"""
name = models.CharField(max_length=100, verbose_name='分类名称')
slug = models.SlugField(unique=True, verbose_name='URL 标识')
description = models.TextField(blank=True, verbose_name='描述')
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = '分类'
verbose_name_plural = '分类'
def __str__(self):
return self.name
class Tag(models.Model):
"""标签 —— 多对多关系"""
name = models.CharField(max_length=50, verbose_name='标签名称')
class Meta:
verbose_name = '标签'
verbose_name_plural = '标签'
def __str__(self):
return self.name
class Profile(models.Model):
"""用户扩展信息 —— 一对一关系"""
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name='profile',
verbose_name='用户'
)
bio = models.TextField(blank=True, verbose_name='个人简介')
avatar = models.ImageField(upload_to='avatars/', blank=True, verbose_name='头像')
phone = models.CharField(max_length=20, blank=True, verbose_name='手机号')
birth_date = models.DateField(null=True, blank=True, verbose_name='出生日期')
class Meta:
verbose_name = '用户资料'
verbose_name_plural = '用户资料'
def __str__(self):
return f"{self.user.username} 的资料"
class Article(models.Model):
"""文章 —— 包含多种关系"""
# 外键关系:Category → Article(一对多)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE, # 分类删除时级联删除文章
related_name='articles', # 反向查询:category.articles.all()
verbose_name='分类'
)
# 多对多关系:Tag ↔ Article
tags = models.ManyToManyField(
Tag,
blank=True,
related_name='articles',
verbose_name='标签'
)
# 外键到用户
author = models.ForeignKey(
User,
on_delete=models.SET_NULL, # 用户删除时,文章作者置空
null=True,
related_name='articles',
verbose_name='作者'
)
title = models.CharField(max_length=200, verbose_name='标题')
content = models.TextField(verbose_name='内容')
pub_date = models.DateTimeField(auto_now_add=True, verbose_name='发布时间')
update_date = models.DateTimeField(auto_now=True, verbose_name='更新时间')
views = models.IntegerField(default=0, verbose_name='阅读量')
is_published = models.BooleanField(default=False, verbose_name='是否发布')
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
ordering = ['-pub_date']
def __str__(self):
return self.titleon_delete 选项详解
| 选项 | 行为 | 适用场景 |
|---|---|---|
CASCADE | 级联删除,关联对象也删除 | 分类删除→文章删除 |
SET_NULL | 外键置为 NULL(需 null=True) | 用户删除→作者置空 |
SET_DEFAULT | 设为默认值(需 default=值) | 部门删除→员工归入默认部门 |
PROTECT | 阻止删除,抛出 ProtectedError | 需要保护的关键数据 |
RESTRICT | 类似 PROTECT,但允许级联对象删除 | Django 3.1+ 新特性 |
SET(value) | 设为指定值或可调用对象的返回值 | 灵活的置空策略 |
DO_NOTHING | 不做任何处理(可能引发数据库错误) | 数据库层已处理 |
3.3 ORM 单表增删改查
定义
Django ORM 提供了一套丰富的查询 API,允许开发者使用 Python 方法而非 SQL 语句进行数据库操作。所有查询都基于 Manager(默认名称为 objects)。
语法
▶ 增加(Create)
python
from blog.models import Article, Category
from django.utils import timezone
# 方法一:使用 create() 方法(推荐)
category = Category.objects.get(id=1)
article = Article.objects.create(
title='Django ORM 入门',
content='本文介绍 Django ORM 的基本操作...',
category=category,
pub_date=timezone.now(),
is_published=True,
)
# 方法二:先创建实例,再 save()
article = Article(
title='Python 高级特性',
content='装饰器、生成器、上下文管理器...',
category=category,
)
article.save() # 写入数据库
# 方法三:get_or_create() —— 查找或创建
obj, created = Article.objects.get_or_create(
title='唯一标题',
defaults={'content': '默认内容', 'category': category}
)
# obj → 获取或创建的对象
# created → True 表示新建,False 表示已存在
# 方法四:bulk_create() —— 批量创建
articles = [
Article(title=f'文章{i}', content=f'内容{i}', category=category)
for i in range(10)
]
Article.objects.bulk_create(articles) # 一次 SQL 插入▶ 查询(Retrieve)
python
# --- 单条查询 ---
# get():获取唯一一条,不存在或存在多条报错
article = Article.objects.get(id=1)
article = Article.objects.get(title='Django ORM 入门')
# 安全获取:get_object_or_404()(推荐)
from django.shortcuts import get_object_or_404
article = get_object_or_404(Article, id=1)
# first() / last():第一个 / 最后一个
article = Article.objects.first() # 按默认排序的第一个
article = Article.objects.last() # 按默认排序的最后一个
# --- 多条查询 ---
# all():查询所有
all_articles = Article.objects.all()
# filter():条件过滤
published = Article.objects.filter(is_published=True)
# exclude():排除条件
drafts = Article.objects.exclude(is_published=True)
# values():返回字典列表(只查询指定字段)
title_list = Article.objects.filter(is_published=True).values('id', 'title')
# values_list():返回元组列表
title_list = Article.objects.values_list('title', flat=True) # flat=True 展平为单值列表
# order_by():排序
latest = Article.objects.order_by('-pub_date') # 降序
oldest = Article.objects.order_by('pub_date') # 升序
# distinct():去重
categories = Article.objects.values('category').distinct()▶ 更新(Update)
python
# 方法一:查询后修改属性,再 save()
article = Article.objects.get(id=1)
article.title = '新的标题'
article.content = '新的内容'
article.save() # 更新所有字段
# 方法二:update() 方法(只更新指定字段,推荐)
Article.objects.filter(id=1).update(
title='新的标题',
views=100,
)
# 方法三:批量更新
Article.objects.filter(category=old_category).update(category=new_category)
# 方法四:F() 表达式(在数据库层操作,避免并发问题)
from django.db.models import F
# 阅读量 +1(原子操作)
Article.objects.filter(id=1).update(views=F('views') + 1)
# 所有已发布文章阅读量加倍
Article.objects.filter(is_published=True).update(views=F('views') * 2)
# update_or_create():更新或创建
obj, created = Article.objects.update_or_create(
title='唯一标题',
defaults={'content': '更新后的内容'}
)▶ 删除(Delete)
python
# 方法一:查询后调用 delete()
article = Article.objects.get(id=1)
article.delete() # 返回 (删除条数, 详细字典)
# 方法二:批量删除
Article.objects.filter(is_published=False, pub_date__year=2020).delete()
# 慎用:删除所有
# Article.objects.all().delete() # 危险操作!完整示例
python
# blog/views.py —— 文章的 CRUD 视图
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from .models import Article, Category
def article_list(request):
"""文章列表:展示所有已发布的文章"""
articles = Article.objects.filter(is_published=True).order_by('-pub_date')
return render(request, 'blog/article_list.html', {'articles': articles})
def article_detail(request, pk):
"""文章详情:阅读量 +1"""
article = get_object_or_404(Article, pk=pk)
Article.objects.filter(pk=pk).update(views=F('views') + 1) # 阅读量自增
return render(request, 'blog/article_detail.html', {'article': article})
def article_create(request):
"""创建文章"""
if request.method == 'POST':
title = request.POST.get('title')
content = request.POST.get('content')
category_id = request.POST.get('category')
article = Article.objects.create(
title=title,
content=content,
category_id=category_id,
author=request.user,
)
messages.success(request, '文章创建成功!')
return redirect('blog:article_detail', pk=article.pk)
categories = Category.objects.all()
return render(request, 'blog/article_form.html', {'categories': categories})
def article_update(request, pk):
"""更新文章"""
article = get_object_or_404(Article, pk=pk)
if request.method == 'POST':
article.title = request.POST.get('title')
article.content = request.POST.get('content')
article.category_id = request.POST.get('category')
article.save()
messages.success(request, '文章更新成功!')
return redirect('blog:article_detail', pk=article.pk)
categories = Category.objects.all()
return render(request, 'blog/article_form.html', {
'article': article,
'categories': categories,
})
def article_delete(request, pk):
"""删除文章"""
article = get_object_or_404(Article, pk=pk)
if request.method == 'POST':
article.delete()
messages.success(request, '文章删除成功!')
return redirect('blog:article_list')
return render(request, 'blog/article_confirm_delete.html', {'article': article})3.4 条件查询、排序与分页
定义
Django ORM 提供了丰富的字段查询表达式(Field Lookups),用于在 filter()、exclude()、get() 等方法中构建复杂的查询条件。分页通过 Django 内置的 Paginator 类实现。
语法
▶ 字段查询表达式(Field Lookups)
python
# 精确匹配
Article.objects.filter(title__exact='Django 入门') # title = 'Django 入门'
Article.objects.filter(title='Django 入门') # 简写
# 模糊匹配
Article.objects.filter(title__contains='Django') # LIKE '%Django%'(区分大小写)
Article.objects.filter(title__icontains='django') # LIKE '%django%'(不区分大小写)
Article.objects.filter(title__startswith='Django') # LIKE 'Django%'
Article.objects.filter(title__endswith='入门') # LIKE '%入门'
# 范围查询
Article.objects.filter(views__gt=100) # views > 100
Article.objects.filter(views__gte=100) # views >= 100
Article.objects.filter(views__lt=50) # views < 50
Article.objects.filter(views__lte=50) # views <= 50
Article.objects.filter(pub_date__range=('2025-01-01', '2025-12-31')) # BETWEEN
# 集合查询
Article.objects.filter(id__in=[1, 3, 5]) # IN (1,3,5)
Article.objects.filter(views__in=[10, 20, 30])
# 空值查询
Article.objects.filter(category__isnull=True) # category IS NULL
Article.objects.filter(category__isnull=False) # category IS NOT NULL
# 日期查询
Article.objects.filter(pub_date__year=2025) # 年份
Article.objects.filter(pub_date__month=6) # 月份(1-12)
Article.objects.filter(pub_date__day=15) # 日期
Article.objects.filter(pub_date__week_day=1) # 星期天=1,星期一=2...
Article.objects.filter(pub_date__hour=14) # 小时
# 关联字段查询(跨表)
Article.objects.filter(category__name='Python') # 按分类名称过滤
Article.objects.filter(author__username='admin') # 按作者用户名过滤▶ 链式查询
python
# 查询条件可以链式组合
articles = Article.objects \
.filter(is_published=True) \
.filter(category__name='Python') \
.exclude(views__lt=10) \
.order_by('-pub_date') \
.select_related('author', 'category')[:10]▶ Q 对象:复杂逻辑组合
python
from django.db.models import Q
# OR 条件
articles = Article.objects.filter(
Q(title__icontains='Django') | Q(content__icontains='Django')
)
# AND 与 OR 混合
articles = Article.objects.filter(
Q(category__name='Python') &
(Q(views__gte=100) | Q(is_published=True))
)
# NOT 条件
articles = Article.objects.filter(~Q(status='draft'))
# 动态构建查询
query = Q()
if request.GET.get('keyword'):
query &= Q(title__icontains=request.GET['keyword'])
if request.GET.get('category'):
query &= Q(category_id=request.GET['category'])
if request.GET.get('date_from'):
query &= Q(pub_date__gte=request.GET['date_from'])
articles = Article.objects.filter(query)▶ 排序
python
# 单字段排序
Article.objects.order_by('pub_date') # 升序(从旧到新)
Article.objects.order_by('-pub_date') # 降序(从新到旧)
# 多字段排序
Article.objects.order_by('-is_published', 'pub_date') # 先按发布状态,再按时间
# 随机排序(性能较差)
Article.objects.order_by('?')
# 反向排序
articles = Article.objects.order_by('pub_date')
articles.reverse() # 反转顺序
# 在 Model.Meta 中设置默认排序
class Article(models.Model):
class Meta:
ordering = ['-pub_date', 'title'] # 默认按发布时间降序,同时间按标题升序
# 无排序查询(覆盖 Meta.ordering,提高性能)
Article.objects.all().order_by() # 清空排序▶ 分页(Paginator)
python
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
def article_list(request):
"""分页展示文章列表"""
articles = Article.objects.filter(is_published=True).order_by('-pub_date')
paginator = Paginator(articles, 10) # 每页 10 条
page_number = request.GET.get('page', 1)
try:
page_obj = paginator.get_page(page_number)
# get_page() 自动处理非法页码和超出范围
except (PageNotAnInteger, EmptyPage):
page_obj = paginator.get_page(1)
context = {
'page_obj': page_obj,
'paginator': paginator,
'is_paginated': page_obj.has_other_pages(),
}
return render(request, 'blog/article_list.html', context)分页模板示例:
html
{% if is_paginated %}
<nav class="pagination">
<ul>
{% if page_obj.has_previous %}
<li><a href="?page=1">« 首页</a></li>
<li><a href="?page={{ page_obj.previous_page_number }}">上一页</a></li>
{% endif %}
<li class="active">
<span>第 {{ page_obj.number }} / {{ paginator.num_pages }} 页</span>
</li>
{% if page_obj.has_next %}
<li><a href="?page={{ page_obj.next_page_number }}">下一页</a></li>
<li><a href="?page={{ paginator.num_pages }}">末页 »</a></li>
{% endif %}
</ul>
</nav>
{% endif %}▶ 限制查询集(Slicing)
python
# 前 5 条
Article.objects.all()[:5]
# 第 6-10 条
Article.objects.all()[5:10]
# 单条(等价于 LIMIT 1)
Article.objects.all()[:1].get()3.5 聚合、关联查询与原生 SQL
定义
聚合(Aggregation) 是对查询集进行统计计算,如求和、计数、平均值等,对应 SQL 的 GROUP BY 和聚合函数。关联查询 指利用模型关系进行跨表查询,Django 提供了 select_related(正向关联,SQL JOIN)和 prefetch_related(反向关联,额外查询)来优化性能。
语法
▶ 聚合函数(aggregate)
aggregate() 返回一个字典,包含聚合计算结果。
python
from django.db.models import Count, Sum, Avg, Max, Min
# 基本聚合
result = Article.objects.aggregate(
total_articles=Count('id'), # 文章总数
total_views=Sum('views'), # 总阅读量
avg_views=Avg('views'), # 平均阅读量
max_views=Max('views'), # 最大阅读量
min_views=Min('views'), # 最小阅读量
)
# 输出:{'total_articles': 100, 'total_views': 5000, 'avg_views': 50.0, ...}
# 条件聚合
from django.db.models import Q, Count
result = Article.objects.aggregate(
published_count=Count('id', filter=Q(is_published=True)),
draft_count=Count('id', filter=Q(is_published=False)),
)▶ 分组聚合(annotate)
annotate() 为查询集中的每个对象添加聚合计算字段(对应 SQL 的 GROUP BY)。
python
from django.db.models import Count, Sum, Avg
# 统计每个分类的文章数量
categories = Category.objects.annotate(
article_count=Count('articles'), # 每个分类的文章数
total_views=Sum('articles__views'), # 每个分类的总阅读量
)
for cat in categories:
print(f"{cat.name}: {cat.article_count} 篇文章")
# 按作者分组,统计文章数和平均阅读量
from django.contrib.auth.models import User
authors = User.objects.annotate(
article_count=Count('articles'),
avg_views=Avg('articles__views'),
).filter(article_count__gt=0) # 只保留有文章的作者
# 在文章上添加评论数
articles = Article.objects.annotate(
comment_count=Count('comment_set'), # 反向关系名
# 或使用 related_name
# comment_count=Count('comments'),
)
# 带过滤的聚合
from django.db.models import Q, Count
articles = Article.objects.annotate(
published_comments=Count('comment_set', filter=Q(comment_set__is_approved=True))
)▶ select_related(正向关联,SQL JOIN)
解决 N+1 查询问题:访问外键关联对象时,若不优化会执行多次查询。
python
# ❌ 不加 select_related(N+1 次查询)
articles = Article.objects.all()
for article in articles:
print(article.category.name) # 每次循环都执行一次 SQL 查询!
print(article.author.username) # 又执行一次 SQL 查询
# ✅ 使用 select_related(1 次 JOIN 查询)
articles = Article.objects.select_related('category', 'author').all()
for article in articles:
print(article.category.name) # 已缓存,无额外查询
print(article.author.username) # 已缓存,无额外查询▶ prefetch_related(反向关联,额外查询)
处理一对一、一对多、多对多的反向关联,以及多对多关联。
python
# ❌ 不加 prefetch_related(N+1 次查询)
categories = Category.objects.all()
for category in categories:
print(category.articles.all()) # 每次循环都查询一次
# ✅ 使用 prefetch_related(2 次查询)
categories = Category.objects.prefetch_related('articles').all()
for category in categories:
print(category.articles.all()) # 已预加载
# 多级 prefetch
from django.db.models import Prefetch
# 预加载文章及其评论
categories = Category.objects.prefetch_related(
Prefetch(
'articles',
queryset=Article.objects.filter(is_published=True).select_related('author'),
to_attr='published_articles' # 自定义属性名
)
)
for category in categories:
for article in category.published_articles:
print(article.title, article.author.username)性能优化对比:
| 方法 | SQL 查询次数 | 适用场景 |
|---|---|---|
| 无优化 | N+1 | 不使用关联数据 |
| select_related | 1(JOIN) | 正向 ForeignKey/OneToOne |
| prefetch_related | 2+(IN 查询) | 反向 ForeignKey/ManyToMany |
▶ 原生 SQL 查询
当 ORM 无法满足复杂查询时,可以执行原生 SQL。
python
from django.db import connection, models
# 方式一:Manager.raw()(返回模型实例)
articles = Article.objects.raw(
'SELECT * FROM blog_article WHERE views > %s', [100]
)
for article in articles:
print(article.title)
# 方式二:connection.execute()(返回原始数据)
with connection.cursor() as cursor:
cursor.execute("""
SELECT
c.name,
COUNT(a.id) as article_count,
AVG(a.views) as avg_views
FROM blog_category c
LEFT JOIN blog_article a ON c.id = a.category_id
GROUP BY c.id
HAVING COUNT(a.id) > 5
ORDER BY article_count DESC
""")
rows = cursor.fetchall()
for row in rows:
print(f"分类: {row[0]}, 文章数: {row[1]}, 平均阅读: {row[2]:.1f}")
# 方式三:执行原生 SQL 不返回数据
with connection.cursor() as cursor:
cursor.execute("UPDATE blog_article SET views = 0 WHERE views IS NULL")四、小结
| 知识点 | 核心要点 |
|---|---|
| 模型定义 | 每个模型类 = 一张数据库表;字段类型对应数据库列类型;字段选项控制约束和行为 |
| 数据库迁移 | makemigrations 生成迁移文件,migrate 执行迁移;迁移文件应加入版本控制 |
| 表关系 | ForeignKey = 一对多;OneToOneField = 一对一;ManyToManyField = 多对多 |
| 增删改查 | create() 创建;filter()/get() 查询;update() 更新;delete() 删除 |
| 条件查询 | 字段查询表达式 __contains/__gt/__in 等;Q 对象实现 OR/NOT 逻辑;链式调用 |
| 分页 | Paginator 类实现分页;get_page() 安全获取当前页 |
| 聚合与关联 | aggregate() 汇总统计;annotate() 分组聚合;select_related/prefetch_related 解决 N+1 问题 |
五、练习
基础练习
模型设计:设计一个简单的图书管理系统模型:
Author(作者):姓名、出生日期、简介Book(图书):书名、ISBN、出版日期、价格、作者(外键)Publisher(出版社):名称、地址、联系电话- 完成迁移
数据操作:在 Django Shell 中完成以下操作:
python# 创建 3 个作者和 5 本书 # 查询价格大于 50 的图书 # 按出版日期降序排列 # 更新某本书的价格 # 删除某本书
进阶练习
- 分页功能:为一个包含 50 条数据的查询集实现分页,每页 8 条,包含上一页/下一页导航和页码列表
- 关联查询优化:查询所有图书及其作者信息,使用
select_related优化,对比优化前后的 SQL 查询次数
挑战练习
- 博客文章统计系统:
- 使用
annotate统计每个分类的文章数量、总阅读量、平均阅读量 - 使用
aggregate计算所有文章的总阅读量、最多阅读量、最少阅读量 - 使用
prefetch_related预加载每篇文章的评论 - 使用原生 SQL 查询阅读量前 10 的文章标题和作者名
- 使用
参考代码(练习 3 分页)
python
# 在视图函数中
from django.core.paginator import Paginator
def book_list(request):
books = Book.objects.all().order_by('-pub_date')
paginator = Paginator(books, 8)
page = request.GET.get('page')
page_obj = paginator.get_page(page)
return render(request, 'book_list.html', {'page_obj': page_obj})