Django实现Category与Product模型Slug字段全局唯一的方案
跨Category和Product模型验证Slug唯一性的解决方案
问题背景
现有Django模型结构如下:
from django.db import models from django.core.validators import MinLengthValidator class Category(models.Model): name = models.CharField(max_length=255, validators=[MinLengthValidator(3)]) parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.CASCADE ) slug = models.SlugField(max_length=255, null=False, unique=True) class Product(models.Model): name = models.CharField(max_length=255, validators=[MinLengthValidator(3)]) to_category = models.ForeignKey(Category, on_delete=models.SET_NULL, blank=True, null=True, ) slug = models.SlugField(max_length=255, null=False, unique=True)
当前问题:已创建slug为test的Category后,创建同slug的Category会触发唯一性警告(符合预期),但创建slug为test的Product时无警告,无法满足跨模型slug唯一的需求。
方法一:重写模型clean方法(推荐)
在两个模型中分别重写clean()方法,手动校验另一个模型是否存在相同slug,存在则抛出验证错误。
修改后的完整模型代码:
from django.db import models from django.core.validators import MinLengthValidator from django.core.exceptions import ValidationError class Category(models.Model): name = models.CharField(max_length=255, validators=[MinLengthValidator(3)]) parent = models.ForeignKey('self', blank=True, null=True, related_name='children', on_delete=models.CASCADE ) slug = models.SlugField(max_length=255, null=False, unique=True) def clean(self): super().clean() # 检查Product表中是否有重复slug if Product.objects.filter(slug=self.slug).exists(): raise ValidationError(f"slug '{self.slug}' 已被产品占用") class Product(models.Model): name = models.CharField(max_length=255, validators=[MinLengthValidator(3)]) to_category = models.ForeignKey(Category, on_delete=models.SET_NULL, blank=True, null=True, ) slug = models.SlugField(max_length=255, null=False, unique=True) def clean(self): super().clean() # 检查Category表中是否有重复slug if Category.objects.filter(slug=self.slug).exists(): raise ValidationError(f"slug '{self.slug}' 已被分类占用")
注意:admin后台会自动触发
clean()验证;若通过代码创建/更新实例,需手动调用instance.full_clean()触发所有校验,直接调用save()不会触发该验证。
方法二:用信号实现全局校验
如果不想在每个模型中重复写校验逻辑,可以用Django的pre_save信号,在保存前统一检查跨模型的slug唯一性:
from django.db.models.signals import pre_save from django.dispatch import receiver from django.core.exceptions import ValidationError from .models import Category, Product @receiver(pre_save, sender=Category) @receiver(pre_save, sender=Product) def check_slug_across_models(sender, instance, **kwargs): # 根据当前模型,检查另一个模型的slug重复情况 if sender == Category: if Product.objects.filter(slug=instance.slug).exists(): raise ValidationError(f"slug '{instance.slug}' 已被产品占用") else: if Category.objects.filter(slug=instance.slug).exists(): raise ValidationError(f"slug '{instance.slug}' 已被分类占用")
注意:信号触发的异常会在
save()时抛出,但admin后台可能无法友好展示错误提示,因此更推荐使用clean方法。
补充说明
数据库层面无法直接实现跨表的唯一约束,必须在应用层做校验。若要避免并发场景下的重复创建问题(如两个请求同时创建相同slug的不同模型实例),可以新增一个单独的SlugUnique表,用一对一关联到Category或Product,再给该表的slug字段加唯一约束,但这种实现复杂度较高,普通场景下用clean方法即可满足需求。
内容的提问来源于stack exchange,提问作者Vitalii Lastname
相关产品推荐
相关产品推荐

