validate_isique在调用form.is_valid()时导致RelatedObjectDoesNotExist

时间:2018-06-13 10:27:47

标签: python django django-models django-forms

我有一个我无法理解的问题。

假设我们有以下2个型号:

from django.db import models

class OtherModel(models.Model):
    number = models.PositiveIntegerField()

class MyModel(models.Model):
    name = models.CharField(max_length=15)
    other_obj = models.ForeignKey(OtherModel)
    deleted_at = models.DateTimeField(blank=True, null=True)

和一个基于MyModel的简单ModelForm:

from django.forms import ModelForm

class MyForm(ModelForm):
    class Meta:
        model = MyModel
        fields = ['name', 'other_obj']

通过简单的pytest方法测试:

def test_myform():
    form = MyForm(data={})
    assert form.is_valid() is False

这完全正常(测试不会失败),直到我向validate_unique添加MyModel方法:

from django.core.exceptions import ValidationError
# ...

class MyModel(models.Model):
    # ...

    def validate_unique(self, exclude=None):
        super(MyModel, self).validate_unique(exclude=exclude)

        qs = MyModel.objects.filter(other_obj=self.other_obj)

        if qs.exists() and qs[0].pk != self.pk:
            e = qs[0]
            raise ValidationError('This OtherModel ({}) was already used already exists'.format(e.other))

这给了我以下错误消息(RelatedObjectDoesNotExist: MyModel has no other_obj):

F
myapp/tests/test_forms.py:66 (test_myform)
def test_myform():
        form = MyForm(data={})
>       assert form.is_valid() is False

test_forms.py:69: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../venv/lib/python3.6/site-packages/django/forms/forms.py:183: in is_valid
    return self.is_bound and not self.errors
../../venv/lib/python3.6/site-packages/django/forms/forms.py:175: in errors
    self.full_clean()
../../venv/lib/python3.6/site-packages/django/forms/forms.py:386: in full_clean
    self._post_clean()
../../venv/lib/python3.6/site-packages/django/forms/models.py:402: in _post_clean
    self.validate_unique()
../../venv/lib/python3.6/site-packages/django/forms/models.py:411: in validate_unique
    self.instance.validate_unique(exclude=exclude)
test_forms.py:35: in validate_unique
    qs = MyModel.objects.filter(other_obj=self.other_obj)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <django.db.models.fields.related_descriptors.ForwardManyToOneDescriptor object at 0x7f01b905de10>
instance = <MyModel: MyModel object>
cls = <class 'myapp.tests.test_forms.MyModel'>

    def __get__(self, instance, cls=None):
        """
            Get the related instance through the forward relation.

            With the example above, when getting ``child.parent``:

            - ``self`` is the descriptor managing the ``parent`` attribute
            - ``instance`` is the ``child`` instance
            - ``cls`` is the ``Child`` class (we don't need it)
            """
        if instance is None:
            return self

        # The related instance is loaded from the database and then cached in
        # the attribute defined in self.cache_name. It can also be pre-cached
        # by the reverse accessor (ReverseOneToOneDescriptor).
        try:
            rel_obj = getattr(instance, self.cache_name)
        except AttributeError:
            val = self.field.get_local_related_value(instance)
            if None in val:
                rel_obj = None
            else:
                rel_obj = self.get_object(instance)
                # If this is a one-to-one relation, set the reverse accessor
                # cache on the related object to the current instance to avoid
                # an extra SQL query if it's accessed later on.
                if not self.field.remote_field.multiple:
                    setattr(rel_obj, self.field.remote_field.get_cache_name(), instance)
            setattr(instance, self.cache_name, rel_obj)

        if rel_obj is None and not self.field.null:
            raise self.RelatedObjectDoesNotExist(
>               "%s has no %s." % (self.field.model.__name__, self.field.name)
            )
E           django.db.models.fields.related_descriptors.RelatedObjectDoesNotExist: MyModel has no other_obj.

../../venv/lib/python3.6/site-packages/django/db/models/fields/related_descriptors.py:194: RelatedObjectDoesNotExist

我假设它试图访问一个不存在的对象(OtherModel)(是None),但是不应该在clean方法中验证它? 所以我认为编写自定义清理方法可以解决问题但是以下任何一种尝试都 NOT 工作:

无法正常工作

clean方法添加到MyModel

class MyModel(models.Model):
    # ...

    def clean(self):
        if not self.other_obj:
            raise ValidationError("Please set an OtherModel")

    # ...

即使您将if not self.other_obj替换为if self.other_obj is Noneif not self.other_obj_id

clean_other_obj添加到MyModel

 class MyModel(models.Model):
    # ...

    def clean_other_obj(self):
        if not self.other_obj: # why is this not working
            raise ValidationError("other_obj cant be empty!")
    # ...

clean_other_obj添加到MyForm

class MyForm(ModelForm):
    # ...

    def clean_other_obj(self):
        data = self.cleaned_data['other_obj']
        if not data:
            raise forms.ValidationError("Please fill other_obj")
        return data

clean方法添加到MyForm并调用super

class MyForm(ModelForm):
    # ...
    def clean(self):
        cleaned_data = super(MyForm, self).clean()
        other_obj = cleaned_data.get("other_obj")

        if not other_obj:
            raise forms.ValidationError("other_obj cant be empty")

工作尝试次数

奇怪的是,这两次尝试中的任何一次 DID工作

clean方法添加到MyForm WITHOUT 调用super

class MyForm(ModelForm):
    # ...
    def clean(self):
        # cleaned_data = super(MyForm, self).clean()  # This doesnt work!
        other_obj = self.cleaned_data.get("other_obj")

        if not other_obj:
            raise forms.ValidationError("other_obj cant be empty")

检查other_obj_id中的validate_unique是否有效:

from django.core.exceptions import ValidationError
# ...

class MyModel(models.Model):
    # ...

    def validate_unique(self, exclude=None):
        super(MyModel, self).validate_unique(exclude=exclude)

        if not self.other_obj_id:  # why is this working and not other_obj 
            raise ValidationError("other_obj cant be empty!")

        qs = MyModel.objects.filter(other_obj=self.other_obj)

        if qs.exists() and qs[0].pk != self.pk:
            raise ValidationError('This OtherModel ({}) was already used.'.format(qs[0].other))

注意:这仅在您检查other_obj_id时有效,如果您选中other_obj它仍然无法正常工作

由于这两次尝试都是相当令人不满意的解决方案,因为在一种情况下无法调用super方法,而在另一种情况下,validate_unique方法未被用于清理数据而不是只检查重复,我想知道为什么会这样,我做错了什么。

我知道stackoverflow上有类似的问题,但没有一个能给出正确的答案。

非常感谢答案!

包含所有非工作尝试的代码:https://pastebin.com/y10ma8EL

0 个答案:

没有答案