跳到主要内容

模型层

模型是 Django 应用的核心,它定义了数据的结构和行为。Django 的 ORM(对象关系映射)系统让你可以使用 Python 代码来操作数据库,而不需要编写 SQL 语句。

模型基础

定义模型

每个模型都是一个 Python 类,继承自 django.db.models.Model

from django.db import models


class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
age = models.IntegerField()

def __str__(self):
return f"{self.first_name} {self.last_name}"

这个模型会在数据库中创建如下表结构:

CREATE TABLE myapp_person (
"id" bigint NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL,
"age" integer NOT NULL
);

模型字段类型

Django 提供了丰富的字段类型来满足各种数据存储需求:

数值类型

class Product(models.Model):
# 整数类型
quantity = models.IntegerField() # 任意大小整数
small_num = models.SmallIntegerField() # 小整数 (-32768 到 32767)
big_num = models.BigIntegerField() # 大整数

# 自增字段
counter = models.AutoField(primary_key=True) # 自增主键

# 浮点类型
price = models.FloatField() # 浮点数
precise_price = models.DecimalField( # 高精度小数
max_digits=10, # 总位数
decimal_places=2 # 小数位数
)

# 正整数
positive_num = models.PositiveIntegerField() # 正整数
positive_small = models.PositiveSmallIntegerField() # 正小整数

字符串类型

class Article(models.Model):
# 字符串
title = models.CharField(max_length=200) # 定长字符串
content = models.TextField() # 长文本
slug = models.SlugField(max_length=100) # URL 友好字符串

# 文件路径
file_path = models.FilePathField(path="/uploads") # 文件路径

# 邮箱和 URL
email = models.EmailField(max_length=254) # 邮箱
website = models.URLField(max_length=200) # URL

布尔类型

class Settings(models.Model):
is_active = models.BooleanField(default=True) # 布尔值
is_verified = models.NullBooleanField() # 可为空的布尔值(已弃用,使用 BooleanField(null=True))

日期时间类型

class Event(models.Model):
# 日期和时间
date = models.DateField() # 日期
time = models.TimeField() # 时间
datetime = models.DateTimeField() # 日期时间
duration = models.DurationField() # 时间段

# 自动设置时间
created = models.DateTimeField(auto_now_add=True) # 创建时自动设置
updated = models.DateTimeField(auto_now=True) # 保存时自动更新

文件类型

class Document(models.Model):
# 文件上传
file = models.FileField(upload_to='documents/') # 文件
image = models.ImageField( # 图片
upload_to='images/',
height_field='height',
width_field='width'
)
height = models.IntegerField()
width = models.IntegerField()

其他类型

class Advanced(models.Model):
# JSON 数据
metadata = models.JSONField() # JSON 数据

# UUID
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)

# IP 地址
ipv4 = models.GenericIPAddressField(protocol='IPv4')
ipv6 = models.GenericIPAddressField(protocol='IPv6')

# 二进制数据
data = models.BinaryField(max_length=100)

字段通用参数

所有字段类型都支持以下通用参数:

class Example(models.Model):
# null 和 blank
field1 = models.CharField(
null=True, # 数据库中允许 NULL
blank=True, # 表单验证时允许为空
max_length=100
)

# 默认值
field2 = models.CharField(
default='default_value', # 默认值
max_length=100
)

# 选择项
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
]
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES, # 选项
default='draft'
)

# 唯一性
email = models.EmailField(
unique=True, # 唯一约束
max_length=100
)

# 索引
name = models.CharField(
max_length=100,
db_index=True # 创建索引
)

# 帮助文本
description = models.TextField(
help_text='请输入详细描述' # 表单中显示的帮助文本
)

# 数据库列名
custom_field = models.CharField(
db_column='custom_column_name', # 数据库列名
max_length=100
)

# 只读字段
created = models.DateTimeField(
auto_now_add=True,
editable=False # 不可编辑
)

null 与 blank 的区别

这是初学者容易混淆的概念:

  • null=True:数据库层面,允许该字段在数据库中为 NULL
  • blank=True:表单验证层面,允许表单提交时该字段为空
class Example(models.Model):
# 允许数据库为 NULL,表单可以为空
field1 = models.CharField(null=True, blank=True, max_length=100)

# 数据库不能为 NULL,但表单可以为空(会使用默认值)
field2 = models.CharField(blank=True, default='', max_length=100)

# 日期字段:null=True 允许数据库为 NULL
# blank=True 允许表单为空
birth_date = models.DateField(null=True, blank=True)

模型 Meta 选项

通过内部类 Meta 可以设置模型的元数据:

class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()

class Meta:
# 数据库表名
db_table = 'articles'

# 排序方式
ordering = ['-created_at']

# 唯一约束
unique_together = [['title', 'author']]

# 索引
indexes = [
models.Index(fields=['title']),
models.Index(fields=['-created_at']),
]

# 后台显示名称
verbose_name = '文章'
verbose_name_plural = '文章列表'

# 是否抽象类
abstract = True

# 权限
permissions = [
('can_publish', '可以发布文章'),
]

模型关系

Django 支持三种数据库关系:多对一、多对多和一对一。

多对一关系(ForeignKey)

多对一是最常见的关系类型,例如多篇文章属于一个作者:

class Author(models.Model):
name = models.CharField(max_length=100)

def __str__(self):
return self.name


class Article(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE, # 删除作者时删除所有文章
related_name='articles', # 反向关系名称
verbose_name='作者'
)

def __str__(self):
return self.title

on_delete 选项

当被引用的对象被删除时,Django 会根据 on_delete 参数决定如何处理:

class Article(models.Model):
# CASCADE:级联删除,删除作者时删除所有文章
author = models.ForeignKey(Author, on_delete=models.CASCADE)

# PROTECT:保护模式,如果有关联数据则抛出 ProtectedError
category = models.ForeignKey(Category, on_delete=models.PROTECT)

# SET_NULL:设置为 NULL,字段必须设置 null=True
reviewer = models.ForeignKey(
User,
on_delete=models.SET_NULL,
null=True
)

# SET_DEFAULT:设置为默认值,字段必须设置 default
status = models.ForeignKey(
Status,
on_delete=models.SET_DEFAULT,
default=1
)

# SET():设置为指定值或调用函数
editor = models.ForeignKey(
User,
on_delete=models.SET(get_default_editor)
)

# DO_NOTHING:不采取任何行动(不推荐)
tag = models.ForeignKey(Tag, on_delete=models.DO_NOTHING)

反向关系

通过 related_name 可以自定义反向关系的名称:

# 正向查询:文章 -> 作者
article = Article.objects.get(pk=1)
author = article.author

# 反向查询:作者 -> 文章
author = Author.objects.get(pk=1)
articles = author.articles.all() # 使用 related_name

如果不设置 related_name,Django 会使用 模型名_set 作为默认名称:

# 默认的反向关系名称
articles = author.article_set.all()

多对多关系(ManyToManyField)

多对多关系用于表示两个模型之间的多对多关联,例如文章和标签:

class Tag(models.Model):
name = models.CharField(max_length=50)

def __str__(self):
return self.name


class Article(models.Model):
title = models.CharField(max_length=200)
tags = models.ManyToManyField(
Tag,
related_name='articles',
blank=True
)

多对多操作

# 创建文章和标签
article = Article.objects.create(title='Django 教程')
tag1 = Tag.objects.create(name='Python')
tag2 = Tag.objects.create(name='Django')

# 添加标签
article.tags.add(tag1, tag2)

# 移除标签
article.tags.remove(tag1)

# 清空所有标签
article.tags.clear()

# 设置标签(替换现有标签)
article.tags.set([tag1, tag2])

# 查询
article.tags.all() # 获取文章的所有标签
tag.articles.all() # 获取标签的所有文章

中间表

当多对多关系需要存储额外信息时,可以使用中间表:

class Student(models.Model):
name = models.CharField(max_length=100)


class Course(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(
Student,
through='Enrollment', # 指定中间表
related_name='courses'
)


class Enrollment(models.Model):
"""选课记录(中间表)"""
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
enrollment_date = models.DateField(auto_now_add=True)
grade = models.CharField(max_length=2, blank=True)

class Meta:
unique_together = ['student', 'course']

使用中间表时,需要通过中间表来创建关系:

student = Student.objects.create(name='张三')
course = Course.objects.create(name='Python 入门')

# 通过中间表创建关系
Enrollment.objects.create(
student=student,
course=course,
grade='A'
)

一对一关系(OneToOneField)

一对一关系用于两个模型之间的一一对应,例如用户和用户资料:

from django.contrib.auth.models import User


class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
related_name='profile'
)
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to='avatars/', blank=True)

def __str__(self):
return f"{self.user.username} 的资料"

使用方式:

# 正向查询
profile = Profile.objects.get(user=user)

# 反向查询
user = User.objects.get(username='admin')
profile = user.profile

QuerySet API

QuerySet 是 Django ORM 的核心,它代表数据库中的一组对象。

查询方法

# 获取所有对象
all_posts = Post.objects.all()

# 过滤
published_posts = Post.objects.filter(status='published')
draft_posts = Post.objects.exclude(status='published')

# 链式查询
posts = Post.objects.filter(
status='published'
).filter(
author__username='admin'
).exclude(
title__contains='测试'
)

# 获取单个对象
post = Post.objects.get(pk=1) # 不存在会抛出 DoesNotExist
post = Post.objects.get(title='标题') # 多个结果会抛出 MultipleObjectsReturned

# 获取单个对象(不存在返回 None)
post = Post.objects.filter(pk=1).first()
post = Post.objects.filter(pk=1).last()

# 获取或创建
obj, created = Post.objects.get_or_create(
title='默认标题',
defaults={'content': '默认内容'}
)

# 更新或创建
obj, created = Post.objects.update_or_create(
title='标题',
defaults={'content': '新内容'}
)

查询表达式

Django 使用双下划线 __ 来表示查询关系:

# 精确匹配
Post.objects.filter(title='Django 教程')

# 模糊匹配
Post.objects.filter(title__contains='Django') # 包含(区分大小写)
Post.objects.filter(title__icontains='django') # 包含(不区分大小写)
Post.objects.filter(title__startswith='Django') # 开头匹配
Post.objects.filter(title__endswith='教程') # 结尾匹配

# 正则匹配
Post.objects.filter(title__regex=r'^Django') # 正则匹配

# 比较查询
Post.objects.filter(id__gt=10) # 大于
Post.objects.filter(id__gte=10) # 大于等于
Post.objects.filter(id__lt=10) # 小于
Post.objects.filter(id__lte=10) # 小于等于

# 范围查询
Post.objects.filter(id__in=[1, 2, 3]) # 在列表中
Post.objects.filter(created__range=[start, end]) # 在范围内

# 日期查询
Post.objects.filter(created__year=2024) # 年份
Post.objects.filter(created__month=1) # 月份
Post.objects.filter(created__day=1) # 日期
Post.objects.filter(created__date=date.today()) # 日期
Post.objects.filter(created__week_day=1) # 星期几

# 空值查询
Post.objects.filter(category__isnull=True) # 为空
Post.objects.filter(category__isnull=False) # 不为空

# 关联查询
Post.objects.filter(author__name='张三') # 跨关系查询
Post.objects.filter(tags__name='Python') # 多对多查询

排序和限制

# 排序
Post.objects.order_by('created') # 升序
Post.objects.order_by('-created') # 降序
Post.objects.order_by('author__name', '-created') # 多字段排序

# 限制结果
Post.objects.all()[:5] # 前 5 条
Post.objects.all()[5:10] # 第 6-10 条

# 获取单个值
Post.objects.values_list('title', flat=True) # 只返回标题列表
Post.objects.values('title', 'author__name') # 返回字典列表

聚合和分组

from django.db.models import Count, Sum, Avg, Max, Min

# 聚合
Post.objects.aggregate(
total=Count('id'),
avg_views=Avg('views'),
max_views=Max('views')
)

# 分组统计
Post.objects.values('author').annotate(
post_count=Count('id'),
total_views=Sum('views')
)

# 分组后过滤
Post.objects.values('author').annotate(
post_count=Count('id')
).filter(post_count__gt=5)

更新和删除

# 更新单个对象
post = Post.objects.get(pk=1)
post.title = '新标题'
post.save()

# 批量更新
Post.objects.filter(status='draft').update(status='published')

# 删除单个对象
post = Post.objects.get(pk=1)
post.delete()

# 批量删除
Post.objects.filter(status='draft').delete()

模型方法

可以在模型中定义自定义方法:

class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
views = models.IntegerField(default=0)
created = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ['-created']

def __str__(self):
return self.title

def get_absolute_url(self):
"""返回文章的 URL"""
from django.urls import reverse
return reverse('blog:post_detail', kwargs={'pk': self.pk})

def increase_views(self):
"""增加阅读量"""
self.views += 1
self.save(update_fields=['views'])

@property
def summary(self):
"""返回文章摘要"""
return self.content[:100] + '...' if len(self.content) > 100 else self.content

def was_published_recently(self):
"""判断是否最近发布"""
from django.utils import timezone
return self.created >= timezone.now() - datetime.timedelta(days=7)

模型继承

Django 支持三种模型继承方式:

抽象基类

用于提取公共字段,不会创建数据库表:

class BaseModel(models.Model):
"""抽象基类"""
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)

class Meta:
abstract = True # 标记为抽象类


class Post(BaseModel):
title = models.CharField(max_length=200)
content = models.TextField()


class Comment(BaseModel):
post = models.ForeignKey(Post, on_delete=models.CASCADE)
content = models.TextField()

多表继承

每个模型都有独立的数据库表:

class Place(models.Model):
name = models.CharField(max_length=100)
address = models.CharField(max_length=200)


class Restaurant(Place):
serves_pizza = models.BooleanField(default=False)
serves_burger = models.BooleanField(default=False)

代理模型

只修改模型的行为,不创建新表:

class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)


class OrderedPerson(Person):
class Meta:
proxy = True
ordering = ['last_name']

数据库迁移

迁移是 Django 管理数据库结构变化的系统:

# 生成迁移文件
python manage.py makemigrations

# 指定应用生成迁移
python manage.py makemigrations blog

# 查看迁移 SQL
python manage.py sqlmigrate blog 0001

# 执行迁移
python manage.py migrate

# 回滚迁移
python manage.py migrate blog 0001

# 查看迁移状态
python manage.py showmigrations

最佳实践

  1. 合理使用索引:为经常查询的字段添加索引
  2. 避免 N+1 查询:使用 select_relatedprefetch_related
  3. 使用批量操作:使用 bulk_createbulk_update
  4. 合理设计关系:根据业务需求选择合适的关系类型
  5. 使用事务:确保数据一致性
# select_related:用于外键和一对一关系
Post.objects.select_related('author', 'category').all()

# prefetch_related:用于多对多和反向关系
Post.objects.prefetch_related('tags').all()

# 批量创建
Post.objects.bulk_create([
Post(title='文章1', content='内容1'),
Post(title='文章2', content='内容2'),
])

# 事务
from django.db import transaction

with transaction.atomic():
post = Post.objects.create(title='标题', content='内容')
post.tags.add(tag1, tag2)