Skip to content

章节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数据库允许 NULLnull=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.title

on_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">&laquo; 首页</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 }}">末页 &raquo;</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_related1(JOIN)正向 ForeignKey/OneToOne
prefetch_related2+(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 问题

五、练习

基础练习

  1. 模型设计:设计一个简单的图书管理系统模型:

    • Author(作者):姓名、出生日期、简介
    • Book(图书):书名、ISBN、出版日期、价格、作者(外键)
    • Publisher(出版社):名称、地址、联系电话
    • 完成迁移
  2. 数据操作:在 Django Shell 中完成以下操作:

    python
    # 创建 3 个作者和 5 本书
    # 查询价格大于 50 的图书
    # 按出版日期降序排列
    # 更新某本书的价格
    # 删除某本书

进阶练习

  1. 分页功能:为一个包含 50 条数据的查询集实现分页,每页 8 条,包含上一页/下一页导航和页码列表
  2. 关联查询优化:查询所有图书及其作者信息,使用 select_related 优化,对比优化前后的 SQL 查询次数

挑战练习

  1. 博客文章统计系统
    • 使用 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})

Python 学习资料