Django中使用Slug(Form+Model)时未自动生成Slug的问题
问题分析与解决:Django中Recipe的slug字段未自动生成
问题现象
通过Django Admin或自定义表单添加Recipe时,slug字段始终为None,导致基于slug的路由访问报错。需求为slug无需用户手动输入,自动基于title字段生成,且已将slug字段设置为不可编辑、唯一。
模型代码
from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator from django.urls import reverse class Ingredient(models.Model): name = models.CharField(max_length=100) def __str__(self): return f'{self.name}' class Recipe(models.Model): slug = models.SlugField(null=True, unique=True, blank=True, editable=False) title = models.CharField(null=False, max_length=100) description = models.CharField(null=False, max_length=250) preparation = models.TextField(null=False) score = models.IntegerField(null=True, validators=[MinValueValidator(1), MaxValueValidator(5)]) last_update = models.DateField(auto_now=True) presentation_image = models.ImageField(upload_to='images', null=True, blank=True) ingredients = models.ManyToManyField(Ingredient) def __str__(self): return f'{self.slug}, {self.title}, {self.description}, {self.preparation}, {self.score}, {self.last_update}, {self.presentation_image}, {self.ingredients}'
表单代码
from django import forms from django.core.validators import MinValueValidator, MaxValueValidator from .models import Recipe from .models import Ingredient class RecipeForm(forms.ModelForm): class Meta: model = Recipe exclude = ['slug', 'last_update'] widgets = { 'preparation': forms.Textarea() } labels = { 'title': 'Title', 'description': 'Description', 'preparation': 'Preparation', 'score': 'Score', 'presentation_image': 'Presentation Image', 'ingredients': 'Ingredients' }
逻辑错误原因
Django不会自动为SlugField生成内容,哪怕你将其设为不可编辑。当前代码完全没有将title转换为slug并赋值的逻辑,因此每次保存Recipe时,slug字段会保持默认的null状态。
解决方法
方法1:重写Recipe模型的save方法
在模型的save方法中添加slug生成逻辑,同时处理重复slug的情况以保证唯一性:
from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator from django.utils.text import slugify class Recipe(models.Model): slug = models.SlugField(null=True, unique=True, blank=True, editable=False) title = models.CharField(null=False, max_length=100) # 其余字段定义不变 def save(self, *args, **kwargs): # 如果slug为空,基于title生成 if not self.slug: base_slug = slugify(self.title) # 检查重复slug,存在则添加数字后缀 slug_count = Recipe.objects.filter(slug__startswith=base_slug).count() if slug_count > 0: self.slug = f"{base_slug}-{slug_count + 1}" else: self.slug = base_slug super().save(*args, **kwargs) # 其余方法不变
方法2:使用Django的pre_save信号
通过信号在模型保存前自动生成slug,适合不想修改模型save方法的场景:
# 在models.py末尾添加 from django.db.models.signals import pre_save from django.dispatch import receiver from django.utils.text import slugify @receiver(pre_save, sender=Recipe) def generate_recipe_slug(sender, instance, **kwargs): if not instance.slug: base_slug = slugify(instance.title) slug_count = sender.objects.filter(slug__startswith=base_slug).count() if slug_count > 0: instance.slug = f"{base_slug}-{slug_count + 1}" else: instance.slug = base_slug
注意事项
- 使用
django.utils.text.slugify生成slug,它会自动将字符串转换为符合规则的格式(小写、空格转连字符、去除特殊字符)。 - 必须处理slug重复情况,避免因不同title生成相同slug导致唯一约束报错。
- 针对已存在的Recipe记录,需手动生成slug并更新数据库(可通过Django shell批量处理)。
内容的提问来源于stack exchange,提问作者Dragos
相关产品推荐
相关产品推荐

