Django unique_together不能与ForeignKey = None一起使用

时间:2010-08-15 16:43:29

标签: django django-models django-admin

我看到一些人在我之前有这个问题,但是在旧版本的Django上,我正在运行1.2.1。

我的模型看起来像:

class Category(models.Model):
 objects = CategoryManager()

 name = models.CharField(max_length=30, blank=False, null=False)
 parent = models.ForeignKey('self', null=True, blank=True, help_text=_('The direct parent category.'))

 class Meta:
  unique_together = ('name', 'parent')

每当我尝试在管理员中保存父级设置为“无”的类别时,当其他类别的SAME名称和父级设置为“无”时,它仍然有效。

关于如何优雅地解决这个问题的想法?

3 个答案:

答案 0 :(得分:10)

在数据库级别强制实施唯一的约束,并且您的数据库引擎似乎不会对空值应用约束。

在Django 1.2中,您可以为模型定义clean method以提供自定义验证。在您的情况下,只要父级为None,您就需要检查具有相同名称的其他类别。

class Category(models.Model):
    ...
    def clean(self):
        """
        Checks that we do not create multiple categories with 
        no parent and the same name.
        """
        from django.core.exceptions import ValidationError
        if self.parent and Category.objects.filter(name=self.name).exists():
            raise ValidationError("Another Category with name=%s and no parent already exists % self.name)

如果您通过Django管理员编辑类别,将自动调用clean方法。在您自己的观看中,您必须致电category.fullclean()

答案 1 :(得分:5)

我也遇到了这个问题并通过创建一个带有clean方法的超模来解决它(就像Alasdair建议的那样)并将其用作我所有模型的基类:

class Base_model(models.Model):
  class Meta:
    abstract=True

  def clean(self):
    """
    Check for instances with null values in unique_together fields.
    """
    from django.core.exceptions import ValidationError

    super(Base_model, self).clean()

    for field_tuple in self._meta.unique_together[:]:
        unique_filter = {}
        unique_fields = []
        null_found = False
        for field_name in field_tuple:
            field_value = getattr(self, field_name)
            if getattr(self, field_name) is None:
                unique_filter['%s__isnull'%field_name] = True
                null_found = True
            else:
                unique_filter['%s'%field_name] = field_value
                unique_fields.append(field_name)
        if null_found:
            unique_queryset = self.__class__.objects.filter(**unique_filter)
            if self.pk:
                unique_queryset = unique_queryset.exclude(pk=self.pk)
            if unique_queryset.exists():
                msg = self.unique_error_message(self.__class__, tuple(unique_fields))
                raise ValidationError(msg)

答案 2 :(得分:0)

不幸的是,对于那些使用PostgreSQL作为后端数据库引擎的人来说,永远不会有解决此问题的方法:

“当前,只有B树索引可以声明为唯一。

当索引被声明为唯一时,不允许具有相等索引值的多个表行。空值不视为相等。多列唯一索引只会拒绝多行中所有索引列均相等的情况。

在为表定义唯一约束或主键时,PostgreSQL自动创建唯一索引。该索引涵盖构成主键或唯一约束(如果适用的话,为多列索引)的列,并且是强制执行约束的机制。“

来源: https://www.postgresql.org/docs/9.0/indexes-unique.html