简体   繁体   English

Django:'unique_together'和'blank = True'

[英]Django: 'unique_together' and 'blank=True'

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." 如果有相同name比同一次parent然后我得到一个错误:“为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. 但是,当我使用相同的parent保存多个MyModelname字段为空时,我也会收到错误,但是应该允许这样做。 So basically I don't want to get the above error when the name field is blank. 所以基本上我不想在name字段为空时得到上述错误。 Is that possible somehow? 这有可能吗?

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. 因此,如果你的字段不是CharField,你应该只添加null=True 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: 你需要创建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

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而不是空字符串( ''

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实例具有相同的parentname属性 - 即使name是空字符串也适用。

This is enforced at the database level using the unique attribute on the appropriate database columns. 这是使用相应数据库列上的unique属性在数据库级别强制执行的。 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. 相反,您可以通过覆盖模型上的save方法并在那里强制执行唯一约束来获得所需的内容。 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. 当您尝试保存模型的实例时,您的代码可以检查是否存在具有相同parentname组合的任何现有实例,并且如果存在则拒绝保存实例。 But you can also allow the instance to be saved if the name is an empty string. 但是如果name是空字符串,您还可以允许保存实例。 A basic version of this might look like this: 这个的基本版本可能如下所示:

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. 希望有所帮助。

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. 请注意,引发了ValidationError而不是一般异常。 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. 此解决方案的好处是,在使用.is_valid()验证ModelForm时,会自动调用上面的模型.clean()方法,并将ValidationError字符串保存在.errors中,以便它可以显示在html模板中。

Let me know if you do not agree with this solution. 如果您不同意此解决方案,请与我们联系。

bigmattyh gives a good explanation as to what is happening. bigmattyh对发生的事情给出了很好的解释。 I'll just add a possible save method. 我只想添加一个可能的save方法。

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.')

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM