validate_unique вызывает RelatedObjectDoesNotExist при вызове form.is_valid () - PullRequest
0 голосов
/ 13 июня 2018

У меня есть проблема, которую я просто не могу понять.

Предположим, у нас есть следующие 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)

и простая ModelForm на основе MyModel:

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 в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 None или if 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")

Попытки работы

Странно, что любая из этих 2 попыток ДЕЙСТВУЕТ ли :

, добавляя clean метод к MyForm и БЕЗ , вызывая 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

...