Django从零开始(二):模型与ORM数据库操作
一、ORM与模型简介
Django的ORM(Object-Relational Mapping,对象关系映射)是框架最强大的特性之一。它允许你用Python类来定义数据库表结构,用Python对象来操作数据库记录,而无需手写任何SQL语句。
ORM的优势
- 数据库无关:同一套代码可以运行在SQLite、MySQL、PostgreSQL、Oracle上
- 安全性:自动处理SQL注入防护
- 开发效率:用Python语法代替SQL,代码更简洁易维护
- 迁移管理:自动生成数据库变更脚本,版本化管理表结构
二、定义数据模型
在 blog/models.py 中定义模型类,每个模型类对应数据库中的一张表:
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
class Category(models.Model):
"""文章分类模型"""
name = models.CharField('分类名称', max_length=100, unique=True)
slug = models.SlugField('URL别名', max_length=100, unique=True)
description = models.TextField('分类描述', blank=True, default='')
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '分类'
verbose_name_plural = '分类'
ordering = ['name']
def __str__(self):
return self.name
class Tag(models.Model):
"""文章标签模型"""
name = models.CharField('标签名称', max_length=50, unique=True)
slug = models.SlugField('URL别名', max_length=50, unique=True)
color = models.CharField('标签颜色', max_length=7, default='#333333')
class Meta:
verbose_name = '标签'
verbose_name_plural = '标签'
ordering = ['name']
def __str__(self):
return self.name
class Article(models.Model):
"""文章模型"""
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
('archived', '已归档'),
]
title = models.CharField('文章标题', max_length=200)
slug = models.SlugField('URL别名', max_length=200, unique=True)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='articles',
verbose_name='作者'
)
category = models.ForeignKey(
Category,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='articles',
verbose_name='所属分类'
)
tags = models.ManyToManyField(Tag, blank=True, related_name='articles', verbose_name='标签')
content = models.TextField('文章内容')
summary = models.CharField('摘要', max_length=500, blank=True, default='')
status = models.CharField(
'发布状态',
max_length=20,
choices=STATUS_CHOICES,
default='draft'
)
views = models.PositiveIntegerField('浏览次数', default=0)
is_top = models.BooleanField('是否置顶', default=False)
published_at = models.DateTimeField('发布时间', null=True, blank=True)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
updated_at = models.DateTimeField('更新时间', auto_now=True)
class Meta:
verbose_name = '文章'
verbose_name_plural = '文章'
ordering = ['-is_top', '-published_at', '-created_at']
indexes = [
models.Index(fields=['-created_at']),
models.Index(fields=['status']),
models.Index(fields=['slug']),
]
def __str__(self):
return self.title
class Comment(models.Model):
"""评论模型"""
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name='comments',
verbose_name='所属文章'
)
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='replies',
verbose_name='父评论'
)
nickname = models.CharField('昵称', max_length=50)
email = models.EmailField('邮箱')
content = models.TextField('评论内容')
is_approved = models.BooleanField('是否审核通过', default=False)
created_at = models.DateTimeField('创建时间', auto_now_add=True)
class Meta:
verbose_name = '评论'
verbose_name_plural = '评论'
ordering = ['created_at']
def __str__(self):
return f'{self.nickname} 评论了 {self.article.title}'
三、字段类型详解
3.1 常用字段类型
| 字段类型 | 数据库类型 | 说明 |
|---|---|---|
| CharField | VARCHAR | 字符串,必须指定max_length |
| TextField | TEXT | 长文本,无长度限制 |
| IntegerField | INT | 整数 |
| BigIntegerField | BIGINT | 大整数 |
| FloatField | FLOAT | 浮点数 |
| DecimalField | DECIMAL | 精确小数,需指定max_digits和decimal_places |
| BooleanField | BOOL | 布尔值 |
| DateField | DATE | 日期 |
| DateTimeField | DATETIME | 日期时间 |
| EmailField | VARCHAR | 邮箱,自动验证格式 |
| URLField | VARCHAR | URL地址,自动验证 |
| SlugField | VARCHAR | URL友好字符串 |
| UUIDField | CHAR(32) | UUID标识符 |
| FileField | VARCHAR | 文件上传 |
| ImageField | VARCHAR | 图片上传(依赖Pillow) |
| JSONField | JSON/TEXT | JSON数据(Django 3.1+) |
3.2 关系字段类型
# ForeignKey - 一对多关系
# 一个分类可以有多篇文章,文章只属于一个分类
category = models.ForeignKey(
Category,
on_delete=models.CASCADE, # 级联删除:分类删除时,文章也删除
# on_delete=models.SET_NULL, # 设为NULL:分类删除时,文章的分类字段设为空
# on_delete=models.PROTECT, # 保护:有文章的分类不允许删除
# on_delete=models.DO_NOTHING,# 什么都不做(不推荐)
related_name='articles', # 反向查询名称:category.articles
null=True,
blank=True,
verbose_name='所属分类'
)
# ManyToManyField - 多对多关系
# 一篇文章可以有多个标签,一个标签可以属于多篇文章
tags = models.ManyToManyField(
Tag,
blank=True,
related_name='articles',
verbose_name='标签'
)
# OneToOneField - 一对一关系
# 例如:一个用户只有一个个人资料
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile')
avatar = models.ImageField(upload_to='avatars/', null=True, blank=True)
bio = models.TextField('个人简介', blank=True)
3.3 字段常用参数
# 通用参数
name = models.CharField(
max_length=100, # 最大长度
verbose_name='名称', # 人类可读名称(Admin显示)
help_text='请输入名称', # 帮助文本(Admin表单提示)
null=True, # 数据库允许NULL
blank=True, # 表单允许空值
default='', # 默认值
unique=True, # 唯一约束
db_index=True, # 创建数据库索引
editable=False, # Admin中不可编辑
)
# 特殊参数
created = models.DateTimeField(auto_now_add=True) # 创建时自动设置当前时间
updated = models.DateTimeField(auto_now=True) # 每次保存时自动更新时间
四、数据库迁移操作
模型定义完成后,需要通过迁移操作将模型同步到数据库:
# 第一步:生成迁移文件
python manage.py makemigrations
# 输出示例:
# Migrations for 'blog':
# blog/migrations/0001_initial.py
# - Create model Category
# - Create model Tag
# - Create model Article
# - Create model Comment
# - Add field tags to article
# 查看生成的SQL语句(不执行)
python manage.py sqlmigrate blog 0001
# 第二步:执行迁移
python manage.py migrate
# 输出示例:
# Running migrations:
# Applying blog.0001_initial... OK
# 查看迁移状态
python manage.py showmigrations blog
迁移回滚
# 回滚到指定迁移版本
python manage.py migrate blog 0002
# 回滚blog应用的所有迁移
python manage.py migrate blog zero
# 创建空的迁移文件(用于自定义数据迁移)
python manage.py makemigrations --empty blog
五、ORM增删改查操作(CRUD)
5.1 使用Django Shell
# 进入Django Shell(推荐使用ipython)
pip install ipython
python manage.py shell
# 导入模型
from blog.models import Category, Tag, Article, Comment
from django.contrib.auth.models import User
5.2 创建数据(Create)
# 方法1:直接创建并保存
category = Category.objects.create(
name='Python',
slug='python',
description='Python编程相关文章'
)
# 方法2:先实例化再保存
tag = Tag(name='Django', slug='django', color='#092e20')
tag.save()
# 方法3:get_or_create - 获取或创建(避免重复)
category, created = Category.objects.get_or_create(
slug='django',
defaults={'name': 'Django', 'description': 'Django框架相关'}
)
# created为True表示新创建,False表示已存在
# 方法4:update_or_create - 更新或创建
category, created = Category.objects.update_or_create(
slug='flask',
defaults={'name': 'Flask', 'description': 'Flask框架'}
)
# 创建关联对象
user = User.objects.first()
article = Article.objects.create(
title='Django ORM详解',
slug='django-orm-guide',
author=user,
category=category,
content='这是一篇关于Django ORM的文章...',
status='published'
)
# 添加多对多关系
article.tags.add(tag)
article.tags.add(tag1, tag2) # 批量添加
article.tags.set([tag1, tag2, tag3]) # 设置标签(覆盖之前的)
article.tags.remove(tag) # 移除某个标签
article.tags.clear() # 清空所有标签
5.3 查询数据(Read)
# 获取所有记录
all_categories = Category.objects.all()
# 获取单条记录
category = Category.objects.get(pk=1)
category = Category.objects.get(slug='python')
# get找不到会抛出 DoesNotExist 异常
try:
category = Category.objects.get(slug='not-exist')
except Category.DoesNotExist:
print('分类不存在')
# filter - 条件过滤(返回QuerySet)
published_articles = Article.objects.filter(status='published')
python_articles = Article.objects.filter(category__slug='python')
# exclude - 排除条件
draft_articles = Article.objects.exclude(status='published')
# 链式查询
results = Article.objects.filter(
status='published'
).filter(
category__name='Python'
).exclude(
views=0
).order_by('-created_at')[:10]
# 常用查询条件
Article.objects.filter(title__exact='Django ORM') # 精确匹配
Article.objects.filter(title__iexact='django orm') # 不区分大小写精确匹配
Article.objects.filter(title__contains='Django') # 包含
Article.objects.filter(title__icontains='django') # 不区分大小写包含
Article.objects.filter(title__startswith='Django') # 以...开头
Article.objects.filter(title__endswith='教程') # 以...结尾
Article.objects.filter(views__gt=100) # 大于
Article.objects.filter(views__gte=100) # 大于等于
Article.objects.filter(views__lt=1000) # 小于
Article.objects.filter(views__lte=1000) # 小于等于
Article.objects.filter(views__range=[100, 500]) # 范围
Article.objects.filter(title__in=['标题1', '标题2']) # 在列表中
Article.objects.filter(published_at__year=2026) # 按年筛选
Article.objects.filter(published_at__month=4) # 按月筛选
Article.objects.filter(published_at__date=date.today()) # 按日期筛选
Article.objects.filter(created_at__isnull=True) # 为NULL
Article.objects.filter(tags__name='Django') # 跨关系查询
# 排序
Article.objects.order_by('created_at') # 升序
Article.objects.order_by('-created_at') # 降序
Article.objects.order_by('-views', 'title') # 多字段排序
5.4 更新数据(Update)
# 方法1:修改属性后保存
article = Article.objects.get(pk=1)
article.title = '新标题'
article.save()
# 方法2:批量更新
Article.objects.filter(status='draft').update(status='archived')
# 方法3:update_or_create
article, created = Article.objects.update_or_create(
slug='django-orm-guide',
defaults={
'title': 'Django ORM完全指南',
'content': '更新后的内容...',
}
)
# 更新关联字段
article.category = new_category
article.save()
# 原子性更新(避免竞态条件)
from django.db.models import F
Article.objects.filter(pk=1).update(views=F('views') + 1)
5.5 删除数据(Delete)
# 删除单条记录
article = Article.objects.get(pk=1)
article.delete()
# 批量删除
Article.objects.filter(status='archived').delete()
# 注意:delete()会返回被删除的对象数量和每种类型的删除数量
result = Article.objects.filter(views=0).delete()
print(result)
# (15, {'blog.Article': 10, 'blog.Comment': 5})
六、高级查询技巧
6.1 聚合查询
from django.db.models import Count, Sum, Avg, Max, Min
# 统计文章总数
total = Article.objects.count()
# 每个分类的文章数
categories = Category.objects.annotate(
article_count=Count('articles')
)
for cat in categories:
print(f'{cat.name}: {cat.article_count}篇文章')
# 统计已发布文章的总浏览量
result = Article.objects.filter(
status='published'
).aggregate(
total_views=Sum('views'),
avg_views=Avg('views'),
max_views=Max('views'),
min_views=Min('views'),
)
print(result)
# {'total_views': 50000, 'avg_views': 500, 'max_views': 5000, 'min_views': 10}
6.2 Q对象(复杂查询条件)
from django.db.models import Q
# OR条件
articles = Article.objects.filter(
Q(title__contains='Django') | Q(title__contains='Flask')
)
# AND + NOT组合
articles = Article.objects.filter(
Q(status='published') & ~Q(views=0)
)
# 复杂组合查询
articles = Article.objects.filter(
(Q(title__contains='Django') & Q(status='published')) |
(Q(title__contains='Flask') & Q(views__gt=100))
)
6.3 F对象(引用字段值)
from django.db.models import F
# 浏览量+1(直接在数据库层面操作,避免竞态条件)
Article.objects.filter(pk=1).update(views=F('views') + 1)
# 比较两个字段
Article.objects.filter(views__gt=F('comment_count'))
# F对象与算术运算
Article.objects.filter(views__gt=F('likes') * 10)
6.4 分页查询
from django.core.paginator import Paginator
# 创建分页器
article_list = Article.objects.filter(status='published')
paginator = Paginator(article_list, 10) # 每页10条
# 获取指定页
page = paginator.page(1)
print(f'总共 {paginator.count} 篇文章')
print(f'总共 {paginator.num_pages} 页')
print(f'当前页有 {len(page)} 篇文章')
print(f'是否有下一页: {page.has_next()}')
print(f'下一页页码: {page.next_page_number()}')
# 遍历当前页数据
for article in page.object_list:
print(article.title)
七、模型Meta选项
class Meta:
# 排序规则
ordering = ['-created_at']
# 数据库表名(默认为 appname_modelname)
db_table = 'blog_articles'
# Admin显示名称
verbose_name = '文章'
verbose_name_plural = '文章列表'
# 唯一约束(联合唯一)
constraints = [
models.UniqueConstraint(
fields=['title', 'author'],
name='unique_title_per_author'
)
]
# 索引
indexes = [
models.Index(fields=['-created_at'], name='idx_created_at'),
models.Index(fields=['status', 'is_top'], name='idx_status_top'),
]
# 抽象基类(不会创建数据库表)
abstract = True
# 权限
permissions = [
('can_publish', '可以发布文章'),
('can_edit_all', '可以编辑所有文章'),
]
八、总结
本章我们深入学习了Django ORM的核心知识:
- 定义模型类和字段类型(CharField、ForeignKey、ManyToManyField等)
- 数据库迁移操作(makemigrations、migrate)
- CRUD增删改查的多种方式
- 高级查询:聚合、Q对象、F对象、分页
- 模型Meta选项的常用配置
下一章我们将学习 Django视图(View)与URL路由,深入了解如何处理HTTP请求和返回响应。
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







