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

Django问题:如何避免保存大小写不同的重复唯一值

解决Django模型中名称字段大小写不敏感的唯一约束问题

当前你的Service模型中name字段的unique=True约束是大小写敏感的,导致IT、it这类内容可以重复存储。以下是几种可行的解决方案:

方案1:数据库层面实现大小写不敏感的唯一约束

这种方案最可靠,能直接在数据库层面阻止重复数据,即使绕过Django直接操作数据库也能生效。

PostgreSQL环境

使用Django提供的CITextField(需要先安装django-citext扩展):

pip install django-citext

修改模型字段:

from django.db import models
from django.contrib.postgres.fields import CITextField
import uuid
from django.db.models import SET_NULL

class Service(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = CITextField(max_length=127, unique=True, null=False, blank=False)
    is_active = models.BooleanField(default=True)
    is_deleted = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(
        "accounts.User",
        on_delete=SET_NULL,
        blank=False,
        null=True,
        related_name="service_created_by",
    )

    def __str__(self):
        return f"{self.name}"

MySQL环境

修改字段的db_collation为不区分大小写的排序规则:

class Service(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(
        max_length=127,
        unique=True,
        null=False,
        blank=False,
        db_collation='utf8mb4_unicode_ci'
    )
    # 其他字段保持不变...

方案2:Django层面添加验证逻辑

通过重写模型的clean方法,在保存前检查是否存在大小写不同的同名记录:

from django.db import models
import uuid
from django.db.models import SET_NULL
from django.core.exceptions import ValidationError

class Service(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=127, unique=True, null=False, blank=False)
    is_active = models.BooleanField(default=True)
    is_deleted = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(
        "accounts.User",
        on_delete=SET_NULL,
        blank=False,
        null=True,
        related_name="service_created_by",
    )

    def clean(self):
        super().clean()
        # 排除当前实例(更新场景),检查是否存在同名(不区分大小写)的未删除记录
        query = Service.objects.filter(
            name__iexact=self.name,
            is_deleted=False
        )
        if self.id:
            query = query.exclude(id=self.id)
        if query.exists():
            raise ValidationError(f"名称 '{self.name}' 已存在(不区分大小写)")

    def save(self, *args, **kwargs):
        self.full_clean()  # 强制执行验证逻辑
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.name}"

方案3:统一转换大小写后存储

保存时将名称统一转为小写(或大写),确保数据库中存储的格式一致,从根源避免重复:

from django.db import models
import uuid
from django.db.models import SET_NULL

class Service(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=127, unique=True, null=False, blank=False)
    is_active = models.BooleanField(default=True)
    is_deleted = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(
        "accounts.User",
        on_delete=SET_NULL,
        blank=False,
        null=True,
        related_name="service_created_by",
    )

    def save(self, *args, **kwargs):
        # 去除首尾空格并转为小写
        self.name = self.name.strip().lower()
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.name}"

进阶:保留原名称显示的同时实现唯一约束

如果需要保留用户输入的原大小写格式,可新增一个字段存储统一格式的名称,用该字段做唯一约束:

from django.db import models
import uuid
from django.db.models import SET_NULL

class Service(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name = models.CharField(max_length=127, null=False, blank=False)
    # 新增字段存储小写名称,用于唯一约束
    normalized_name = models.CharField(max_length=127, unique=True, null=False, blank=False)
    is_active = models.BooleanField(default=True)
    is_deleted = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True)
    created_by = models.ForeignKey(
        "accounts.User",
        on_delete=SET_NULL,
        blank=False,
        null=True,
        related_name="service_created_by",
    )

    def save(self, *args, **kwargs):
        self.normalized_name = self.name.strip().lower()
        super().save(*args, **kwargs)

    def __str__(self):
        return f"{self.name}"

内容的提问来源于stack exchange,提问作者Taha Omar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 12:10:58