嘿,Django开发者们,有没有这样一种经历:你吭哧吭哧定义好了一个模型,python manage.py makemigrations + migrate 一气呵成,感觉自己是代码界最靓的仔。结果,项目跑着跑着,数据库里冒出一堆null的鬼魂,或者用户邮箱重复注册到天荒地老,又或者日期字段给你来个“千年虫”预演。
然后你开始疯狂写form验证,在view里写一堆if...else来判断,搞得代码又臭又长。停!快住手!问题的根源,很可能在于你当初设计模型时,忽略了那些看似不起眼,实则法力无边的字段参数!
今天,咱们就来一场深度“扒皮”,看看这些参数到底有多能打。
第一章:参数界的“基础生存包”——不写会出人命的那种
这些参数,属于那种你如果不设置,Django就会用“最宽容”(也最危险)的方式对待你的数据。但真正的狠人,从不把决定权交给默认值。
null:数据库的“空位”许可证
-
- 口语化解读:这个字段在数据库里,允许存
NULL不?True就是允许,False就是不允许。这玩意儿是跟数据库直接对话的。 - 潜规则:强烈建议,对于字符型字段(
CharField,TextField),优先设置null=False。 为啥?因为Django的惯例是,如果一个字符串字段没值,就用空字符串'',而不是NULL。这样能避免数据库里出现两种“空”(NULL和'')的哲学难题。如果你看到一个CharField设置了null=True,心里要咯噔一下,这哥们可能有点故事。 - 示例:
bio = models.TextField(null=True, blank=True)# 个人简介,可留空,数据库也允许为NULL。
- 口语化解读:这个字段在数据库里,允许存
blank:管理员后台的“免填”金牌
-
- 口语化解读:在Django自带的Admin后台或者你用
ModelForm生成的表单里,这个字段能不能不填?True就是可以不填,False就是必须填。 nullvsblank傻傻分不清楚?
- 口语化解读:在Django自带的Admin后台或者你用
-
-
null是数据库层面的约束。blank是表单验证层面的约束。- 所以它们俩经常成对出现:
null=True, blank=True表示“数据库允许为空,表单也允许不填”。而如果一个字段是null=False, blank=True,那就意味着表单可以不填,但一旦提交,数据库会默认给它塞个值(比如空字符串),绝不会让它成为NULL。
-
default:万能备胎,永不空军
-
- 口语化解读:如果创建记录时没给这个字段传值,数据库就会自动把这个“备胎”转正。它可以是静态值,也可以是一个可调用对象(比如函数)。
- 骚操作:用
datetime.now而不是datetime.now()。因为now()会在模型定义时就被执行,时间就固定死了。而now是个函数,会在每次创建新记录时被执行。 - 示例:
from django.utils import timezone
created_time = models.DateTimeField(default=timezone.now) # 注意是timezone.now,不是timezone.now()
is_active = models.BooleanField(default=True) # 新用户默认激活
choices:给你的字段一个“选择题库”
-
- 口语化解读:这个字段的值,不能随便瞎写,必须从你给的选项里选。这不仅是表单层面会变成下拉框,在数据库层面也是一种约束(虽然数据库实现方式不同,但Django会帮你校验)。
- 最佳实践:一定要用二维元组的二元组,并且选项值用全大写常量,显得专业!
- 示例:
class UserProfile(models.Model):
GENDER_CHOICES = (
('M', '猛男'),
('F', '萌妹'),
('S', '神秘大佬'),
)
gender = models.CharField(max_length=1, choices=GENDER_CHOICES, default='S')
-
- Bonus:获取显示文字超方便:
user.get_gender_display(),妈妈再也不用担心我写if...else了。
- Bonus:获取显示文字超方便:
第二章:约束天团——专治各种不服和捣乱数据
这组参数是数据完整性的守护神,它们确保进入你数据库的数据,都是“良民”。
unique:独一无二的贵族
-
- 口语化解读:全表上下,这个字段的值不允许有任何重复。是防止重复注册、重复条目的终极武器。
- 示例:
email = models.EmailField(unique=True)# 经典用法,每个邮箱只能注册一个账号。
db_index:给数据库装上“搜索引擎”
-
- 口语化解读:告诉数据库:“哥们,这个字段我经常要查,你给我建个索引,加速一下。” 对于经常用于查询、过滤、排序的字段,加上它,性能提升立竿见影。但别滥用,索引会降低写入速度并占用空间。
- 示例:
title = models.CharField(max_length=200, db_index=True)# 文章标题,经常要被搜索。
primary_key:大佬中的大佬
-
- 口语化解读:如果你不指定,Django会自动给你加一个叫
id的字段当主键。但如果你任性,想用邮箱当主键呢?设置primary_key=True即可。一旦你这么做了,id字段就不会自动创建了。 - 慎用:除非有非常充分的理由,否则还是让Django管理自增ID吧,省心。
- 口语化解读:如果你不指定,Django会自动给你加一个叫
第三章:关系字段的“专属VIP包厢”
当你的模型开始“搞关系”,这些参数就登场了。
on_delete:当“主子”被删时,“奴才”何去何从?
-
- 口语化解读:这是
ForeignKey和OneToOneField的必填参数!不写会报错。它定义了当被关联的对象被删除时,当前这个对象该怎么处理。 - 核心选项:
- 口语化解读:这是
-
-
models.CASCADE:连坐。主子没了,奴才也跟着殉葬。最常用,但也最危险。models.PROTECT:保护。谁敢删这个主子,就抛异常阻止他。用于非常重要的关联。models.SET_NULL:设为空。主子没了,奴才的这个字段设为NULL(前提是字段设置了null=True)。models.SET_DEFAULT:设为默认值。主子没了,给奴才找个备胎(需要设default)。models.DO_NOTHING:摆烂。我啥也不干,数据库可能会报错,不推荐。
-
第四章:完整示例——来看一个“丰满”的模型
光说不练假把式,让我们把所有参数组合起来,创建一个博客文章模型,让你看看一个“武装到牙齿”的模型长啥样。
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class Article(models.Model):
# 基础生存包
title = models.CharField(
max_length=200,
verbose_name='文章标题',
help_text='请输入一个吸引眼球的标题'
)
body = models.TextField(verbose_name='文章正文')
# 状态选择
STATUS_CHOICES = (
('draft', '草稿'),
('published', '已发布'),
('archived', '归档'),
)
status = models.CharField(
max_length=10,
choices=STATUS_CHOICES,
default='draft',
db_index=True # 经常需要按状态过滤,所以加索引
)
# 关系与约束
author = models.ForeignKey(
User,
on_delete=models.CASCADE, # 用户注销,他的文章也全部删除
related_name='articles' # 反向关系名,以后可以用 user.articles.all() 取文章
)
category = models.ForeignKey(
Category,
on_delete=models.PROTECT, # 这个分类下有文章,就不允许删除分类!
null=True,
blank=True # 文章可以先不分类
)
tags = models.ManyToManyField('Tag', blank=True) # 多对多,允许为空
# 元数据与默认值
created_time = models.DateTimeField(default=timezone.now, db_index=True)
updated_time = models.DateTimeField(auto_now=True) # 每次保存自动更新为当前时间
published_time = models.DateTimeField(null=True, blank=True)
is_pinned = models.BooleanField(default=False, verbose_name='是否置顶')
# 唯一性约束
slug = models.SlugField(max_length=200, unique=True) # 用于生成友好的URL
class Meta:
ordering = ['-is_pinned', '-published_time'] # 默认排序:置顶的在前,然后按发布时间倒序
verbose_name = '文章'
verbose_name_plural = '所有文章' # 在Admin后台显示的名称
def __str__(self):
return self.title
def save(self, *args, **kwargs):
# 重写save方法,实现一些自定义逻辑
if self.status == 'published' and not self.published_time:
self.published_time = timezone.now()
super().save(*args, **kwargs)
class Tag(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
总结
看到没?字段参数根本不是可有可无的“选答题”,而是构建稳健、高效、可维护Django应用的“必答题”。它们是你对数据行为的一种声明,一种“我早就告诉过你该怎么做”的智慧。
下次当你定义模型时,别再models.CharField(max_length=100)就完事儿了。多花一分钟,问问自己:
- 它能为空吗?(
null,blank) - 它有默认值吗?(
default) - 它的值有限制吗?(
choices) - 它需要是唯一的吗?(
unique) - 它需要被快速查找吗?(
db_index) - 如果它关联别人,别人没了它咋办?(
on_delete)
把这些都想明白了,你的Django水平就从“能用”进阶到了“会玩”。你的代码将不再是“跑起来就行”,而是“跑得稳如老狗”。
现在,就去检查一下你的模型吧,看看有多少“坑”是可以提前用参数填上的!

被折叠的 条评论
为什么被折叠?



