Почему django model.save () не вызывает full_clean ()?

150

Мне просто любопытно, если кто-нибудь знает, есть ли веская причина, почему в django orm не вызывает 'full_clean' на модели, если она не сохраняется как часть формы модели.

Обратите внимание, что full_clean () не будет вызываться автоматически при вызове метода save () вашей модели. Вам нужно будет вызывать его вручную, если вы хотите запустить одношаговую проверку моделей для ваших собственных моделей, созданных вручную. полный чистый документ Джанго

(ПРИМЕЧАНИЕ: цитата обновлена ​​для Django 1.6 ... предыдущие документы django также содержали предупреждение о ModelForms.)

Есть ли веские причины, по которым люди не хотят такого поведения? Я думаю, что если бы вы потратили время на добавление проверки в модель, вы бы хотели, чтобы проверка выполнялась при каждом сохранении модели.

Я знаю, как заставить все работать правильно, я просто ищу объяснение.

Аарон
источник
11
Большое спасибо за этот вопрос, он помешал мне биться головой об стену гораздо больше времени. Я создал миксин, который может помочь другим. Проверьте суть: gist.github.com/glarrain/5448253
glarrain
И я наконец использую сигнал, чтобы поймать pre_saveкрючок и сделать full_cleanна всех пойманных моделях.
Альфред Хуан,

Ответы:

59

AFAIK, это из-за обратной совместимости. Есть также проблемы с ModelForms с исключенными полями, моделями со значениями по умолчанию, сигналами pre_save () и т. Д.

Источники, которые могут вас заинтересовать:

LQC
источник
3
Наиболее полезный отрывок (IMHO) из второй ссылки: «Разработка« автоматической »опции проверки, которая достаточно проста, чтобы быть полезной, и достаточно надежна для обработки всех крайних случаев, - если это вообще возможно - гораздо больше, чем может быть достигнуто на временном интервале 1.2. Следовательно, на данный момент Django не имеет ничего подобного и не будет иметь его в 1.2. Если вы думаете, что сможете заставить его работать на 1.3, лучше всего поднять предложение, включая, по крайней мере, несколько примеров кода, а также объяснение того, как вы будете делать его простым и надежным ".
Джош
30

Из-за совместимости автоматическая очистка при сохранении не включена в ядре django.

Если мы начинаем новый проект и хотим, чтобы saveметод по умолчанию для Model мог очищаться автоматически, мы можем использовать следующий сигнал для очистки перед сохранением каждой модели.

from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save

@receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
    instance.full_clean()
Альфред Хуан
источник
2
Почему это лучше (или хуже), чем переопределение метода save в некоторой BaseModel (от которой все другие наследуют), чтобы сначала вызвать full_clean, а затем вызвать super ()?
J__
7
Я вижу две проблемы с этим подходом 1) в случае, если ModelForm метод full_clean () будет вызван дважды: по форме и по сигналу 2) Если форма исключает некоторые поля, они все равно будут проверены сигналом.
Мехмет
1
@ Mehmet Так что, может быть, вы можете добавить их if send == somemodel, then exclude some fieldsвpre_save_handler
Симин Цзе
4
Для тех, кто использует или рассматривает возможность использования этого подхода: имейте в виду, что этот подход официально не поддерживается Django и не будет поддерживаться в обозримом будущем (см. Этот комментарий в трекере ошибок Django: code.djangoproject.com/ticket/ 29655 # комментарий: 3 ), поэтому вы можете наткнуться на некоторые недостатки, такие как остановка аутентификации для работы ( code.djangoproject.com/ticket/29655 ), если вы включите проверку для всех моделей. Вам придется решать такие проблемы самостоятельно. Однако лучшего подхода нет.
Евгений А.
2
Начиная с Django 2.2.3, это вызывает проблему с базовой системой аутентификации. Вы получите ValidationError: Session with this Session key already exists. Чтобы избежать этого, вам нужно добавить оператор if для sender in list_of_model_classesпредотвращения переопределения сигнала моделями аутентификации Django по умолчанию. Определите, как list_of_model_classesвы выберете
Аддисон Клинке
15

Самый простой способ вызвать full_cleanметод - просто переопределить saveметод в вашем model:

def save(self, *args, **kwargs):
    self.full_clean()
    return super(YourModel, self).save(*args, **kwargs)
M.Void
источник
Почему это лучше (или хуже), чем использовать сигнал?
J__
6
Я вижу две проблемы с этим подходом: 1) в случае, если в ModelForm метод full_clean () будет вызван дважды: формой и сохранением 2) Если форма исключает некоторые поля, они все равно будут проверены при сохранении.
Мехмет
3

Вместо вставки фрагмента кода, который объявляет получателя, мы можем использовать приложение в качестве INSTALLED_APPSраздела вsettings.py

INSTALLED_APPS = [
    # ...
    'django_fullclean',
    # your apps here,
]

Перед этим вам может потребоваться установка django-fullcleanс использованием PyPI:

pip install django-fullclean
Альфред Хуан
источник
13
Зачем вам pip installкакое-то приложение с 4 строками кода (проверить исходный код ) вместо того, чтобы писать эти строки самостоятельно?
Дэвид Д.
Еще одна библиотека, которую я сам не пробовал: github.com/danielgatis/django-smart-save
Flimm
2

Если у вас есть модель, которую вы хотите гарантировать, чтобы она имела хотя бы одно отношение FK, и вы не хотите использовать ее, null=Falseпотому что для этого требуется установить FK по умолчанию (который будет представлять собой данные для мусора), лучший способ, который я придумаю, это добавить кастом .clean()и .save()методы. .clean()вызывает ошибку проверки и .save()вызывает чистую. Таким образом, целостность обеспечивается как из форм, так и из другого вызывающего кода, командной строки и тестов. Без этого (AFAICT) нет способа написать тест, который гарантирует, что модель имеет отношение FK к специально выбранной (не по умолчанию) другой модели.

class Payer(models.Model):

    name = models.CharField(blank=True, max_length=100)
    # Nullable, but will enforce FK in clean/save:
    payer_group = models.ForeignKey(PayerGroup, null=True, blank=True,)

    def clean(self):
        # Ensure every Payer is in a PayerGroup (but only via forms)
        if not self.payer_group:
            raise ValidationError(
                {'payer_group': 'Each Payer must belong to a PayerGroup.'})

    def save(self, *args, **kwargs):
        self.full_clean()
        return super().save(*args, **kwargs)

    def __str__(self):
        return self.name
shacker
источник
1

Комментируя ответ @Alfred Huang и комментируя его. Можно привязать хук pre_save к приложению, определив список классов в текущем модуле (models.py) и проверив его в хуке pre_save:

CUSTOM_CLASSES = [obj for name, obj in
        inspect.getmembers(sys.modules[__name__])
        if inspect.isclass(obj)]

@receiver(pre_save)
def pre_save_handler(sender, instance, **kwargs):
    if type(instance) in CUSTOM_CLASSES:
        instance.full_clean()
Питер Шеннон
источник