Django中的unique_together不起作用

时间:2017-08-25 12:34:30

标签: django django-models

unique_together不起作用,它只在第一个字段上设置唯一约束并忽略第二个字段。有没有办法强制执行唯一约束?

class BaseModel(models.Model):

    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    deleted = models.DateTimeField(db_index=True, null=True, blank=True)
    last_modified_at = models.DateTimeField(auto_now=True)

    class Meta:
        abstract = True


class Book(BaseModel):
    first_form_number = models.CharField(max_length=8)

    class Meta:
        unique_together = (("first_form_number", "deleted"),)

3 个答案:

答案 0 :(得分:3)

您的模型在正确的唯一索引创建的范围内正常工作

$ python manage.py sqlmigrate app 0001_initial
...
CREATE UNIQUE INDEX "app_base_slug_version_a455c5b7_uniq" ON "app_base" ("slug", "version");
...

(预计应用程序名称为“app”)

我必须大致同意user3541631的回答。它一般取决于数据库,但Django直接支持的所有四个数据库引擎都是相似的。他们希望“null在UNIQUE列中是不同的”(参见NULL Handling in SQLite Versus Other Database Engines

我使用和不使用null验证了您的问题:

class Test(TestCase):
    def test_without_null(self):
        timestamp = datetime.datetime(2017, 8, 25, tzinfo=pytz.UTC)
        book_1 = Book.objects.create(deleted=timestamp, first_form_number='a')
        with self.assertRaises(django.db.utils.IntegrityError):
            Book.objects.create(deleted=timestamp, first_form_number='a')

    def test_with_null(self):
        # this test fails !!! (and a duplicate is created)
        book_1 = Book.objects.create(first_form_number='a')
        with self.assertRaises(django.db.utils.IntegrityError):
            Book.objects.create(first_form_number='a')

如果您愿意手动编写迁移以创建两个特殊的部分唯一索引,则可以使用 PostgreSQL 解决方案:

CREATE UNIQUE INDEX book_2col_uni_idx ON app_book (first_form_number, deleted)
WHERE deleted IS NOT NULL;

CREATE UNIQUE INDEX book_1col_uni_idx ON app_book (first_form_number)
WHERE deleted IS NULL;

请参阅:

答案 1 :(得分:1)

取决于您的数据库,NULL可能不等于任何其他NULL。

因此,您创建的行不相同,如果其中一个值为NULL,则只有非空字段才是唯一的,在您的情况下为'first_form_number'。

另外考虑区分大小写,因此“char”和“Char”不一样。

我有类似的情况,我通过覆盖模型上的保存方法进行了自己的检查。

您检查数据库中是否存在,但在更新时也排除当前实例,而不是与自身进行比较..

if not deleted:
exists = model.objects.exclude(pk=instance.pk).filter(first_form_number__iexact=first_form_number).exists()

答案 2 :(得分:-1)

确保实际扩展继承的Meta类,而不是定义自己的Meta类(Django忽略它):

class Meta(BaseModel.Meta):
        unique_together = (("first_form_number", "deleted"),)