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"),)
答案 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"),)