SlugField 是 Django 中专用于 URL 安全的字段,仅允许字母、数字、下划线和连字符,自动拒绝空格、中文等非法字符;它不生成值,只校验输入合法性,必须配合 <slug:slug> 路由转换器使用以实现前端拦截。

SlugField 是什么?不是字符串,是带校验的 URL 安全字段
SlugField 是 Django 内置的一个模型字段类型,本质是 CharField 的子类,但自带两层硬约束:只允许字母、数字、下划线和连字符(-),且自动拒绝空格、中文、斜杠、问号等 URL 不安全字符。它不负责生成值,只负责“守门”——你塞进去非法内容,保存时直接报 ValidationError。
常见错误现象:slug=models.CharField(max_length=200) 看似能用,但用户手动填入 "hello world" 或 "标题/2025" 会悄无声息存进数据库,后续拼 URL 时直接 404 或被 Nginx 拦截。
- 必须用
SlugField,而不是靠前端或视图“提醒用户别乱输” - 它默认不设
unique=True,重复 slug 会引发数据库唯一约束错误,不是字段级报错 - 若需按日期去重(如同一天不能有相同 slug),用
unique_for_date='publish',比全局唯一更贴近博客场景
slugify() 和 uuslug() 到底该选哪个?看是否要自动去重
纯文本转 slug 用 slugify()(来自第三方库 python-slugify),它只做转换:把 "Django 4.2 新特性!" → "django-42-xin-te-xing";而 uuslug()(来自 django-uuslug)是在此基础上加了“查重+自增”逻辑:如果 "django-42-xin-te-xing" 已存在,就自动变成 "django-42-xin-te-xing-1"。
使用场景:
- 手动管理 slug(比如运营后台可编辑)→ 用
slugify()+SlugField校验,冲突由业务逻辑处理 - 完全自动化、不允许人工干预(如用户注册昵称生成个人页)→ 必须用
uuslug(),否则 save 时撞唯一约束就崩 -
uuslug()默认对 Unicode 友好,但若需越南语/阿拉伯语等特殊映射,得显式传locale='vi'或'ar'
save() 里生成 slug 的坑:instance 参数漏掉就丢唯一性
这是最常踩的坑:写 self.slug = uuslug(self.title) 看似没问题,但实际会忽略当前 model 实例本身。结果就是——哪怕你正在更新 id=5 的文章,uuslug() 还是去全表查重,把 id=5 自己也当“已存在”算进去,导致末尾无意义地加 -1。
正确写法必须带 instance=self:
def save(self, *args, **kwargs):
if not self.slug:
self.slug = uuslug(self.title, instance=self, max_length=200)
super().save(*args, **kwargs)
- 漏掉
instance=self→ 每次都多算一次自己,slug 越改越长(xxx-1-1-1) -
max_length必须和SlugField(max_length=...)一致,否则截断后可能破坏唯一性 - 不要在
default=...里调用uuslug(),函数会在模型定义时执行一次,所有新记录共享同一个 slug
URL 路由里用 <slug:slug> 而不是 <str:slug>
Django 的 slug 路由转换器(<slug:slug>)不是装饰,是真实过滤器:它底层正则为 [a-zA-Z0-9_-]+,只要 URL 中 slug 部分含空格、中文或斜杠,整个请求根本不会进视图,直接 404。而 <str:slug> 会放行一切非斜杠字符,等于把校验甩给视图层,失去第一道防护。
示例对比:
-
path('post/<slug:slug>/', views.post_detail)→ 访问/post/你好世界/直接 404 -
path('post/<str:slug>/', views.post_detail)→ 同样 URL 会进视图,但get_object_or_404(Post, slug=...)必然查不到,还是 404,只是延迟暴露问题 - 模板中反向解析必须匹配:用
{% url 'post_detail' slug=post.slug %},不能传 title 或 id
真正容易被忽略的是:路由里的 slug 名字(冒号前)和视图参数名(函数定义里的)必须一致,否则 Django 解包失败报 TypeError: post_detail() missing 1 required positional argument —— 这个错误信息完全不提示是路由命名问题。


















