Django: 'unique_together' 和 'blank=True
我有一个Django模型,长得像这样:
class MyModel(models.Model):
parent = models.ForeignKey(ParentModel)
name = models.CharField(blank=True, max_length=200)
... other fields ...
class Meta:
unique_together = ("name", "parent")
这个模型的工作方式是这样的:如果在同一个parent
下,有多个相同的name
,我就会收到一个错误提示:“这个名称和父级的组合已经存在。”
不过,当我尝试保存多个MyModel
,它们的parent
相同,但name
字段是空白的时候,我也会收到错误提示,但这其实是应该允许的。所以,简单来说,当name
字段为空时,我不想看到上面的错误提示。有没有办法做到这一点呢?
5 个回答
你可以使用约束来设置一个部分索引,方法如下:
class MyModel(models.Model):
parent = models.ForeignKey(ParentModel)
name = models.CharField(blank=True, max_length=200)
... other fields ...
class Meta:
constraints = [
models.UniqueConstraint(
fields=['name', 'parent'],
condition=~Q(name='')
name='unique_name_for_parent'
)
]
这样可以让像 UniqueTogether 这样的约束只应用于某些特定的行(这些行是根据你可以用 Q 定义的条件来决定的)。
顺便提一下,这也是 Django 推荐的做法:https://docs.djangoproject.com/en/3.2/ref/models/options/#unique-together
更多文档信息可以参考这里:https://docs.djangoproject.com/en/3.2/ref/models/constraints/#django.db.models.UniqueConstraint
使用 unique_together
的时候,你是在告诉 Django,你不希望有两个 MyModel
的实例,它们的 parent
和 name
属性是一样的——即使 name
是空字符串也一样。
这个规则是在数据库层面上通过在相关的数据库列上使用 unique
属性来强制执行的。所以如果你想要对这个行为做一些例外处理,就得避免在模型中使用 unique_together
。
相反,你可以通过重写模型中的 save
方法来实现你想要的效果,并在这里强制执行唯一性检查。当你尝试保存模型的一个实例时,你的代码可以检查是否已经存在具有相同 parent
和 name
组合的实例,如果有,就拒绝保存这个实例。不过,如果 name
是空字符串,你也可以允许这个实例被保存。一个基本的实现可能看起来像这样:
class MyModel(models.Model):
...
def save(self, *args, **kwargs):
if self.name != '':
conflicting_instance = MyModel.objects.filter(parent=self.parent, \
name=self.name)
if self.id:
# This instance has already been saved. So we need to filter out
# this instance from our results.
conflicting_instance = conflicting_instance.exclude(pk=self.id)
if conflicting_instance.exists():
raise Exception('MyModel with this name and parent already exists.')
super(MyModel, self).save(*args, **kwargs)
希望这对你有帮助。
首先,空字符串(就是没有内容的字符串)和空值(null)是两回事('' != None
)。
其次,在Django中,当你通过表单使用CharField时,如果你把这个字段留空,它会存储一个空字符串。
如果你的字段不是CharField,那你只需要加上null=True
就可以了。但是在这种情况下,你需要做的不止这些。你需要创建一个forms.CharField
的子类,并重写它的clean
方法,让它在遇到空字符串时返回空值(None),大概是这样的:
class NullCharField(forms.CharField):
def clean(self, value):
value = super(NullCharField, self).clean(value)
if value in forms.fields.EMPTY_VALUES:
return None
return value
然后在你的ModelForm中使用这个自定义的字段:
class MyModelForm(forms.ModelForm):
name = NullCharField(required=False, ...)
这样的话,如果你把这个字段留空,它在数据库中就会存储空值,而不是空字符串(''
)。