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

Django如何仅对新记录添加唯一约束?

这个场景我太熟悉了——给已有数据的模型加唯一约束,结果旧数据的重复直接炸了迁移,还想保留旧数据只管新记录?没问题,给你几个实用的方案,适配不同的Django版本和数据库:

方案1:分阶段迁移(通用所有数据库,最稳妥)

这个方法靠三次迁移实现“软约束到硬约束”的过渡,既保留旧数据,又能约束新记录:

  • 第一步:先加代码层面的唯一校验
    先修改模型添加unique=True,但让Django暂时不在数据库创建唯一约束:

    模型修改后:

    from django.db import models
    class Test(models.Model):
        name = models.CharField("Title", null=False, blank=False, max_length=300, unique=True)
    
    执行python manage.py makemigrations生成迁移文件,比如0002_test_name_unique.py。打开这个文件,找到AlterField操作,手动加上db_constraint=False参数:
    operations = [
        migrations.AlterField(
            model_name='test',
            name='name',
            field=models.CharField(blank=False, max_length=300, null=False, unique=True, verbose_name='Title', db_constraint=False),
        ),
    ]
    
    执行python manage.py migrate完成迁移。这时候Django的ORM会在代码层面校验新记录的唯一性,但数据库里还没加硬约束,旧数据的重复完全不受影响。
  • 第二步:(可选但建议)清理旧重复数据
    如果之后想开启数据库层面的硬约束,最好先处理旧重复数据——比如给重复的name加个后缀(name_1、name_2),或者合并重复记录。当然如果不想碰旧数据,这步可以跳过,保持代码层面的约束就行。
  • 第三步:开启数据库硬约束(可选)
    当你准备好后,再次执行python manage.py makemigrations,Django会自动生成一个添加数据库唯一约束的迁移文件(这次会去掉db_constraint=False),执行python manage.py migrate后,数据库层面也会约束新记录的唯一性。
方案2:PostgreSQL专属——部分索引约束

如果你用的是PostgreSQL,可以利用它的部分索引特性,只对新记录生效:

  • 先不要在模型里加unique=True,而是生成一个空迁移文件:
    python manage.py makemigrations --empty yourappname
    
    打开生成的空迁移文件,手动添加创建部分索引的SQL:
    from django.db import migrations, models
    
    class Migration(migrations.Migration):
        dependencies = [
            ('yourappname', '0001_initial'), # 替换成你的初始迁移文件名
        ]
    
        operations = [
            migrations.RunSQL(
                # 假设当前最大id是旧数据的分界点,只约束id更大的新记录
                "CREATE UNIQUE INDEX idx_test_name_unique_new ON test(name) WHERE id > (SELECT MAX(id) FROM test);",
                # 回滚时的SQL
                reverse_sql="DROP INDEX idx_test_name_unique_new;"
            ),
        ]
    
  • 然后在模型的clean()方法里加代码层面的校验,确保接口提交也能拦截:
    from django.core.exceptions import ValidationError
    
    class Test(models.Model):
        name = models.CharField("Title", null=False, blank=False, max_length=300)
    
        def clean(self):
            super().clean()
            # 只校验新创建的记录
            if self._state.adding and Test.objects.filter(name=self.name).exists():
                raise ValidationError("这个名称已经存在,新记录不能重复!")
    
    执行迁移后,数据库和代码层面都会只约束新记录,旧数据的重复完美保留。
方案3:Django 2.2+——带条件的UniqueConstraint

Django 2.2及以上支持带条件的唯一约束,你可以指定约束只对符合条件的新记录生效:

  • 先查一下当前数据库里的最大id,作为新旧数据的分界:
    python manage.py shell -c "from yourappname.models import Test; print(Test.objects.aggregate(models.Max('id'))['id__max'])"
    
    假设得到的最大id是100,然后修改模型:
    from django.db import models
    from django.db.models import Q
    
    class Test(models.Model):
        name = models.CharField("Title", null=False, blank=False, max_length=300)
    
        class Meta:
            constraints = [
                models.UniqueConstraint(
                    fields=['name'],
                    name='unique_name_for_new_records',
                    # 只约束id大于100的新记录
                    condition=Q(id__gt=100)
                )
            ]
    
  • 同样记得在clean()方法里添加代码层面的校验(和方案2一样),确保全面拦截。
  • 最后执行python manage.py makemigrations和migrate完成迁移即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 06:57:37