Django:“unique_together”和“空白= True”

时间:2021-05-20 11:47:46

I have a Django model which looks like this:

我有一个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")

This works as expected; If there is the same name more than once in the same parent then I get an error: "MyModel with this Name and Parent already exists."

这是预期;如果在同一父类中有多个相同的名称,则会出现一个错误:“具有该名称和父类的MyModel已经存在”。

However, I also get an error when I save more than one MyModel with the same parent but with the name field blank, but this should be allowed. So basically I don't want to get the above error when the name field is blank. Is that possible somehow?

但是,当我用相同的父元素保存多个MyModel但名称字段为空时,也会出现错误,但这是允许的。所以基本上,当name字段为空时,我不想得到上面的错误。这有可能吗?

4 个解决方案

#1


14  

Firstly, blank (empty string) IS NOT same as null ('' != None).

首先,空格(空字符串)不等于null (" != None)。

Secondly, Django CharField when used through forms will be storing empty string when you leave field empty.

其次,通过窗体使用的Django CharField将在字段为空时存储空字符串。

So if your field was something else than CharField you should just add null=True to it. But in this case you need to do more than that. You need to create subclass of forms.CharField and override it's clean method to return None on empty string, something like this:

所以如果你的字段不是CharField你应该给它加上null=True。但在这种情况下,你需要做更多。您需要创建表单的子类。CharField和override在空字符串中返回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

and then use it in form for your ModelForm:

然后在你的ModelForm中使用:

class MyModelForm(forms.ModelForm):
    name = NullCharField(required=False, ...)

this way if you leave it blank it will store null in database instead of empty string ('')

这样,如果你让它为空,它将在数据库中存储null而不是空字符串(")

#2


11  

Using unique_together, you're telling Django that you don't want any two MyModel instances with the same parent and name attributes -- which applies even when name is an empty string.

使用unique_together,您将告诉Django,您不希望任何两个MyModel实例具有相同的父属性和名称属性——即使名称是空字符串,这也适用。

This is enforced at the database level using the unique attribute on the appropriate database columns. So to make any exceptions to this behavior, you'll have to avoid using unique_together in your model.

这是在数据库级别上使用适当的数据库列上的惟一属性执行的。因此,要对这种行为进行任何异常,您必须避免在模型中使用unique_together。

Instead, you can get what you want by overriding the save method on the model and enforcing the unique restraint there. When you try to save an instance of your model, your code can check to see if there are any existing instances that have the same parent and name combination, and refuse to save the instance if there are. But you can also allow the instance to be saved if the name is an empty string. A basic version of this might look like this:

相反,您可以通过覆盖模型上的save方法并在模型上实施惟一的约束来获得所需的内容。当您尝试保存模型的实例时,您的代码可以检查是否有任何现有实例具有相同的父实例和名称组合,如果有实例,则拒绝保存。但是,如果名称为空字符串,也可以保存实例。一个基本的版本可能是这样的:

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)

Hope that helps.

希望有帮助。

#3


2  

This solution is very similar to the one given by @bigmattyh, however, i found the below page which describes where the validation should be done:

这个解决方案与@bigmattyh所给出的解决方案非常相似,但是,我发现下面的页面描述了应该在哪里进行验证:

http://docs.djangoproject.com/en/1.3/ref/models/instances/#validating-objects

http://docs.djangoproject.com/en/1.3/ref/models/instances/ validating-objects

The solution i ended up using is the following:

我最后使用的解决方案如下:

from django    import forms

class MyModel(models.Model):
...

def clean(self):
    if self.name != '':
        instance_exists = MyModel.objects.filter(parent=self.parent,
                                                 name=self.name).exists()
        if instance_exists:
            raise forms.ValidationError('MyModel with this name and parent already exists.')

Notice that a ValidationError is raised instead of a generic exception. This solution has the benefit that when validating a ModelForm, using .is_valid(), the models .clean() method above is automatically called, and will save the ValidationError string in .errors, so that it can be displayed in the html template.

注意,会引发ValidationError而不是泛型异常。这个解决方案的好处是,当使用.is_valid()验证一个ModelForm时,会自动调用上面的model .clean()方法,并将ValidationError字符串保存在.errors中,以便在html模板中显示它。

Let me know if you do not agree with this solution.

如果你不同意这个解决办法,请告诉我。

#4


-1  

bigmattyh gives a good explanation as to what is happening. I'll just add a possible save method.

bigmattyh对发生的事情给出了很好的解释。我将添加一个可能的保存方法。

def save(self, *args, **kwargs):
    if self.parent != None and MyModels.objects.filter(parent=self.parent, name=self.name).exists():
        raise Exception('MyModel with this name and parent exists.')
    super(MyModel, self).save(*args, **kwargs)

I think I chose to do something similar by overriding my model's clean method and it looked something like this:

我想我选择了类似的做法,推翻我的模型的清洁方法,它看起来是这样的:

from django.core.exceptions import ValidationError
def clean(self):
    if self.parent != None and MyModels.objects.filter(parent=self.parent, name=self.name).exists():
        raise ValidationError('MyModel with this name and parent exists.')

#1


14  

Firstly, blank (empty string) IS NOT same as null ('' != None).

首先,空格(空字符串)不等于null (" != None)。

Secondly, Django CharField when used through forms will be storing empty string when you leave field empty.

其次,通过窗体使用的Django CharField将在字段为空时存储空字符串。

So if your field was something else than CharField you should just add null=True to it. But in this case you need to do more than that. You need to create subclass of forms.CharField and override it's clean method to return None on empty string, something like this:

所以如果你的字段不是CharField你应该给它加上null=True。但在这种情况下,你需要做更多。您需要创建表单的子类。CharField和override在空字符串中返回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

and then use it in form for your ModelForm:

然后在你的ModelForm中使用:

class MyModelForm(forms.ModelForm):
    name = NullCharField(required=False, ...)

this way if you leave it blank it will store null in database instead of empty string ('')

这样,如果你让它为空,它将在数据库中存储null而不是空字符串(")

#2


11  

Using unique_together, you're telling Django that you don't want any two MyModel instances with the same parent and name attributes -- which applies even when name is an empty string.

使用unique_together,您将告诉Django,您不希望任何两个MyModel实例具有相同的父属性和名称属性——即使名称是空字符串,这也适用。

This is enforced at the database level using the unique attribute on the appropriate database columns. So to make any exceptions to this behavior, you'll have to avoid using unique_together in your model.

这是在数据库级别上使用适当的数据库列上的惟一属性执行的。因此,要对这种行为进行任何异常,您必须避免在模型中使用unique_together。

Instead, you can get what you want by overriding the save method on the model and enforcing the unique restraint there. When you try to save an instance of your model, your code can check to see if there are any existing instances that have the same parent and name combination, and refuse to save the instance if there are. But you can also allow the instance to be saved if the name is an empty string. A basic version of this might look like this:

相反,您可以通过覆盖模型上的save方法并在模型上实施惟一的约束来获得所需的内容。当您尝试保存模型的实例时,您的代码可以检查是否有任何现有实例具有相同的父实例和名称组合,如果有实例,则拒绝保存。但是,如果名称为空字符串,也可以保存实例。一个基本的版本可能是这样的:

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)

Hope that helps.

希望有帮助。

#3


2  

This solution is very similar to the one given by @bigmattyh, however, i found the below page which describes where the validation should be done:

这个解决方案与@bigmattyh所给出的解决方案非常相似,但是,我发现下面的页面描述了应该在哪里进行验证:

http://docs.djangoproject.com/en/1.3/ref/models/instances/#validating-objects

http://docs.djangoproject.com/en/1.3/ref/models/instances/ validating-objects

The solution i ended up using is the following:

我最后使用的解决方案如下:

from django    import forms

class MyModel(models.Model):
...

def clean(self):
    if self.name != '':
        instance_exists = MyModel.objects.filter(parent=self.parent,
                                                 name=self.name).exists()
        if instance_exists:
            raise forms.ValidationError('MyModel with this name and parent already exists.')

Notice that a ValidationError is raised instead of a generic exception. This solution has the benefit that when validating a ModelForm, using .is_valid(), the models .clean() method above is automatically called, and will save the ValidationError string in .errors, so that it can be displayed in the html template.

注意,会引发ValidationError而不是泛型异常。这个解决方案的好处是,当使用.is_valid()验证一个ModelForm时,会自动调用上面的model .clean()方法,并将ValidationError字符串保存在.errors中,以便在html模板中显示它。

Let me know if you do not agree with this solution.

如果你不同意这个解决办法,请告诉我。

#4


-1  

bigmattyh gives a good explanation as to what is happening. I'll just add a possible save method.

bigmattyh对发生的事情给出了很好的解释。我将添加一个可能的保存方法。

def save(self, *args, **kwargs):
    if self.parent != None and MyModels.objects.filter(parent=self.parent, name=self.name).exists():
        raise Exception('MyModel with this name and parent exists.')
    super(MyModel, self).save(*args, **kwargs)

I think I chose to do something similar by overriding my model's clean method and it looked something like this:

我想我选择了类似的做法,推翻我的模型的清洁方法,它看起来是这样的:

from django.core.exceptions import ValidationError
def clean(self):
    if self.parent != None and MyModels.objects.filter(parent=self.parent, name=self.name).exists():
        raise ValidationError('MyModel with this name and parent exists.')