第4章:Django模型(Models)
4.1 Django 模型基础
ORM概念
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,用于在面向对象编程语言中实现不兼容类型系统之间的数据转换。Django ORM允许开发者使用Python代码来操作数据库,而不需要编写SQL语句。
ORM的优势
- 数据库无关性:同一套代码可以在不同数据库间切换
- 安全性:自动防止SQL注入攻击
- 便利性:使用Python语法操作数据,学习成本低
- 维护性:模型变更时自动生成迁移文件
Django ORM架构
Python代码 (Model) ←→ Django ORM ←→ 数据库定义模型类
Django模型是继承自django.db.models.Model的Python类,每个模型对应数据库中的一张表。
基本模型示例
python
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.urls import reverse
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, verbose_name="创建时间")
class Meta:
verbose_name = "分类"
verbose_name_plural = "分类"
ordering = ['name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('blog:category_detail', kwargs={'slug': self.slug})
class Article(models.Model):
"""文章模型"""
title = models.CharField(max_length=200, verbose_name="标题")
content = models.TextField(verbose_name="内容")
author = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="作者")
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, verbose_name="分类")
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
verbose_name = "文章"
verbose_name_plural = "文章"
ordering = ['-created_at']
def __str__(self):
return self.title模型类的组成部分
- 字段定义:定义数据库表的列
- Meta类:定义模型的元数据
- 方法:定义模型的行为和业务逻辑
字段类型详解
Django提供了丰富的字段类型来映射不同的数据类型。
文本字段
python
class TextFieldExample(models.Model):
# 短文本字段,必须指定max_length
title = models.CharField(max_length=200, verbose_name="标题")
# 长文本字段,用于大量文本
content = models.TextField(verbose_name="内容")
# 邮箱字段,自动验证邮箱格式
email = models.EmailField(verbose_name="邮箱地址")
# URL字段,自动验证URL格式
website = models.URLField(blank=True, verbose_name="网站")
# Slug字段,用于URL友好的字符串
slug = models.SlugField(unique=True, verbose_name="URL别名")
# JSON字段(Django 3.1+)
metadata = models.JSONField(default=dict, verbose_name="元数据")数字字段
python
class NumberFieldExample(models.Model):
# 整数字段
view_count = models.IntegerField(default=0, verbose_name="浏览次数")
# 正整数字段
like_count = models.PositiveIntegerField(default=0, verbose_name="点赞数")
# 小整数字段(-32768 到 32767)
rating = models.SmallIntegerField(default=0, verbose_name="评分")
# 大整数字段
big_number = models.BigIntegerField(default=0, verbose_name="大数字")
# 浮点数字段
price = models.FloatField(default=0.0, verbose_name="价格")
# 精确小数字段(推荐用于货币)
precise_price = models.DecimalField(
max_digits=10,
decimal_places=2,
default=0,
verbose_name="精确价格"
)日期时间字段
python
class DateTimeFieldExample(models.Model):
# 日期时间字段
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
# 仅日期字段
publish_date = models.DateField(default=timezone.now, verbose_name="发布日期")
# 仅时间字段
publish_time = models.TimeField(verbose_name="发布时间")
# 时长字段
duration = models.DurationField(verbose_name="时长")布尔和选择字段
python
class BooleanAndChoiceExample(models.Model):
# 布尔字段
is_published = models.BooleanField(default=False, verbose_name="是否发布")
# 可为空的布尔字段
is_featured = models.BooleanField(null=True, blank=True, verbose_name="是否推荐")
# 选择字段
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
('archived', '已归档'),
]
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft',
verbose_name="状态"
)
PRIORITY_CHOICES = [
(1, '低'),
(2, '中'),
(3, '高'),
]
priority = models.IntegerField(
choices=PRIORITY_CHOICES,
default=1,
verbose_name="优先级"
)文件字段
python
class FileFieldExample(models.Model):
# 文件字段
document = models.FileField(
upload_to='documents/%Y/%m/',
verbose_name="文档"
)
# 图片字段
image = models.ImageField(
upload_to='images/%Y/%m/',
blank=True,
verbose_name="图片"
)
# 自定义上传路径
def user_directory_path(instance, filename):
# 文件将上传到 MEDIA_ROOT/user_<id>/<filename>
return f'user_{instance.user.id}/{filename}'
avatar = models.ImageField(
upload_to=user_directory_path,
blank=True,
verbose_name="头像"
)UUID字段
python
import uuid
from django.db import models
class UUIDFieldExample(models.Model):
# UUID主键字段
id = models.UUIDField(
primary_key=True,
default=uuid.uuid4,
editable=False
)
# 普通UUID字段
tracking_id = models.UUIDField(
default=uuid.uuid4,
unique=True,
verbose_name="跟踪ID"
)字段选项
每个字段都可以接受一系列选项来控制其行为。
通用字段选项
python
class FieldOptionsExample(models.Model):
# null:数据库中是否允许为NULL
description = models.TextField(null=True, verbose_name="描述")
# blank:表单验证时是否允许为空
optional_field = models.CharField(max_length=100, blank=True, verbose_name="可选字段")
# default:默认值
status = models.CharField(max_length=20, default='active', verbose_name="状态")
# unique:是否唯一
email = models.EmailField(unique=True, verbose_name="邮箱")
# db_index:是否创建数据库索引
slug = models.SlugField(db_index=True, verbose_name="别名")
# verbose_name:字段的人类可读名称
title = models.CharField(max_length=200, verbose_name="标题")
# help_text:帮助文本
password = models.CharField(
max_length=128,
help_text="密码长度至少8位",
verbose_name="密码"
)
# editable:是否在表单中显示
created_at = models.DateTimeField(
auto_now_add=True,
editable=False,
verbose_name="创建时间"
)特殊字段选项
python
class SpecialFieldOptions(models.Model):
# auto_now_add:创建时自动设置为当前时间
created_at = models.DateTimeField(auto_now_add=True)
# auto_now:每次保存时自动更新为当前时间
updated_at = models.DateTimeField(auto_now=True)
# upload_to:文件上传路径
document = models.FileField(upload_to='documents/%Y/%m/%d/')
# max_digits和decimal_places:小数字段选项
price = models.DecimalField(max_digits=10, decimal_places=2)
# choices:选择项
CATEGORY_CHOICES = [
('tech', '技术'),
('life', '生活'),
('travel', '旅行'),
]
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES)实际应用示例
让我们创建一个完整的博客模型系统:
python
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
from django.urls import reverse
import uuid
class TimestampedModel(models.Model):
"""抽象基类,提供时间戳字段"""
created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
updated_at = models.DateTimeField(auto_now=True, verbose_name="更新时间")
class Meta:
abstract = True
class Category(TimestampedModel):
"""文章分类模型"""
name = models.CharField(max_length=100, unique=True, verbose_name="分类名称")
slug = models.SlugField(unique=True, verbose_name="URL别名")
description = models.TextField(blank=True, verbose_name="分类描述")
color = models.CharField(
max_length=7,
default='#007bff',
help_text="十六进制颜色代码",
verbose_name="颜色"
)
icon = models.CharField(max_length=50, blank=True, verbose_name="图标类名")
is_active = models.BooleanField(default=True, verbose_name="是否启用")
sort_order = models.PositiveIntegerField(default=0, verbose_name="排序")
class Meta:
verbose_name = "分类"
verbose_name_plural = "分类"
ordering = ['sort_order', 'name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('blog:category_detail', kwargs={'slug': self.slug})
@property
def article_count(self):
"""获取分类下的文章数量"""
return self.articles.filter(status='published').count()
class Tag(models.Model):
"""标签模型"""
name = models.CharField(max_length=50, unique=True, verbose_name="标签名称")
slug = models.SlugField(unique=True, verbose_name="URL别名")
description = models.TextField(blank=True, verbose_name="标签描述")
class Meta:
verbose_name = "标签"
verbose_name_plural = "标签"
ordering = ['name']
def __str__(self):
return self.name
class Article(TimestampedModel):
"""文章模型"""
STATUS_CHOICES = [
('draft', '草稿'),
('published', '已发布'),
('archived', '已归档'),
]
COMMENT_STATUS_CHOICES = [
('open', '开放评论'),
('closed', '关闭评论'),
('moderated', '审核后显示'),
]
# 基本字段
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200, verbose_name="标题")
slug = models.SlugField(unique_for_date='publish_date', verbose_name="URL别名")
content = models.TextField(verbose_name="内容")
# 摘要字段
excerpt = models.TextField(
max_length=500,
blank=True,
help_text="如果为空,将自动从内容中提取",
verbose_name="摘要"
)
# 关联字段
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="标签"
)
# 状态字段
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft',
verbose_name="状态"
)
comment_status = models.CharField(
max_length=20,
choices=COMMENT_STATUS_CHOICES,
default='open',
verbose_name="评论状态"
)
# 时间字段
publish_date = models.DateTimeField(
default=timezone.now,
verbose_name="发布时间"
)
# 统计字段
view_count = models.PositiveIntegerField(default=0, verbose_name="浏览次数")
like_count = models.PositiveIntegerField(default=0, verbose_name="点赞数")
# SEO字段
meta_title = models.CharField(
max_length=60,
blank=True,
help_text="SEO标题,留空则使用文章标题",
verbose_name="SEO标题"
)
meta_description = models.CharField(
max_length=160,
blank=True,
help_text="SEO描述",
verbose_name="SEO描述"
)
meta_keywords = models.CharField(
max_length=255,
blank=True,
help_text="SEO关键词,用逗号分隔",
verbose_name="SEO关键词"
)
# 特色图片
featured_image = models.ImageField(
upload_to='articles/%Y/%m/',
blank=True,
verbose_name="特色图片"
)
# 其他选项
is_featured = models.BooleanField(default=False, verbose_name="是否推荐")
allow_comments = models.BooleanField(default=True, verbose_name="允许评论")
is_top = models.BooleanField(default=False, verbose_name="是否置顶")
class Meta:
verbose_name = "文章"
verbose_name_plural = "文章"
ordering = ['-is_top', '-publish_date']
indexes = [
models.Index(fields=['status', 'publish_date']),
models.Index(fields=['author', 'status']),
models.Index(fields=['category', 'status']),
]
def __str__(self):
return self.title
def save(self, *args, **kwargs):
"""重写保存方法,自动生成摘要"""
if not self.excerpt and self.content:
# 从内容中提取前200个字符作为摘要
import re
# 移除HTML标签
clean_content = re.sub(r'<[^>]+>', '', self.content)
self.excerpt = clean_content[:200] + '...' if len(clean_content) > 200 else clean_content
# 自动生成SEO标题
if not self.meta_title:
self.meta_title = self.title[:60]
super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('blog:article_detail', kwargs={
'year': self.publish_date.year,
'month': self.publish_date.month,
'day': self.publish_date.day,
'slug': self.slug
})
@property
def is_published(self):
"""检查文章是否已发布"""
return (
self.status == 'published' and
self.publish_date <= timezone.now()
)
@property
def reading_time(self):
"""估算阅读时间(基于250字/分钟)"""
word_count = len(self.content.split())
return max(1, round(word_count / 250))
def get_related_articles(self, limit=5):
"""获取相关文章"""
related = Article.objects.filter(
status='published',
category=self.category
).exclude(id=self.id)
if self.tags.exists():
# 如果有标签,按标签相关性排序
related = related.filter(tags__in=self.tags.all()).distinct()
return related[:limit]
class Comment(TimestampedModel):
"""评论模型"""
article = models.ForeignKey(
Article,
on_delete=models.CASCADE,
related_name='comments',
verbose_name="文章"
)
author = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='comments',
verbose_name="评论者"
)
content = models.TextField(verbose_name="评论内容")
parent = models.ForeignKey(
'self',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='replies',
verbose_name="父评论"
)
is_approved = models.BooleanField(default=True, verbose_name="是否通过审核")
class Meta:
verbose_name = "评论"
verbose_name_plural = "评论"
ordering = ['created_at']
def __str__(self):
return f'{self.author.username} 对 "{self.article.title}" 的评论'
@property
def is_reply(self):
"""是否为回复评论"""
return self.parent is not None使用模型
python
# 在视图或shell中使用模型
from blog.models import Category, Article, Tag
from django.contrib.auth.models import User
# 创建分类
tech_category = Category.objects.create(
name="技术",
slug="tech",
description="技术相关文章",
color="#28a745"
)
# 创建标签
django_tag = Tag.objects.create(
name="Django",
slug="django"
)
# 创建文章
user = User.objects.get(username='admin')
article = Article.objects.create(
title="Django入门教程",
slug="django-tutorial",
content="这是一篇Django入门教程...",
author=user,
category=tech_category,
status='published'
)
# 添加标签
article.tags.add(django_tag)
# 查询示例
published_articles = Article.objects.filter(status='published')
tech_articles = Article.objects.filter(category__slug='tech')
recent_articles = Article.objects.filter(
status='published',
publish_date__gte=timezone.now() - timezone.timedelta(days=7)
)小结
Django模型是应用的数据基础:
- ✅ ORM优势:数据库无关性、安全性、便利性
- ✅ 模型定义:继承Model类,定义字段和方法
- ✅ 字段类型:文本、数字、日期、布尔、文件等丰富类型
- ✅ 字段选项:控制字段行为的各种选项
- ✅ 最佳实践:抽象基类、自定义方法、合理的字段设计
关键要点:
- 合理选择字段类型和选项
- 使用抽象基类减少重复代码
- 添加自定义方法增强模型功能
- 考虑SEO和性能优化
- 遵循Django命名约定
下一篇
我们将学习Django模型的关系字段,包括一对一、一对多和多对多关系。