模型层
模型是 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
最佳实践
- 合理使用索引:为经常查询的字段添加索引
- 避免 N+1 查询:使用
select_related和prefetch_related - 使用批量操作:使用
bulk_create和bulk_update - 合理设计关系:根据业务需求选择合适的关系类型
- 使用事务:确保数据一致性
# 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)