You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 22:35:18