Чому model.save () django не викликає full_clean ()?


150

Мені просто цікаво, якщо хтось знає, чи є вагомі причини, чому орлан джанго не називає 'full_clean' на моделі, якщо вона не зберігається як частина форми моделі.

Зауважте, що full_clean () не буде викликано автоматично, коли ви викликаєте метод збереження () вашої моделі. Вам потрібно буде викликати його вручну, коли ви хочете запустити одномоментну перевірку моделі для власних створених вручну моделей. django повністю чистий doc

(ПРИМІТКА. Цитата оновлена ​​для Django 1.6 ... попередні документи django мали також попередження щодо ModelForms.)

Чи є вагомі причини, чому люди не хочуть такої поведінки? Я думаю, якщо ви знайшли час, щоб додати валідацію до моделі, ви хочете, щоб ця перевірка запускалася кожного разу, коли модель зберігається.

Я знаю, як змусити все працювати належним чином, я просто шукаю пояснення.


11
Дуже дякую за це запитання, це змусило мене стукати головою об стіну набагато більше часу. Я створив міксин, який може допомогти іншим. Ознайомтесь із суттю: gist.github.com/glarrain/5448253
glarrain

І я нарешті використовую сигнал, щоб спіймати pre_saveгачок і робити full_cleanна всіх спійманих моделях.
Альфред Хуанг

Відповіді:


59

AFAIK, це через зворотну сумісність. Також існують проблеми з ModelForms з виключеними полями, моделями зі значеннями за замовчуванням, сигналами pre_save () тощо.

Джерела, які можуть вас зацікавити:


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
Чому це краще (або гірше), ніж перекриття методу збереження на якомусь BaseModel (від якого будуть успадковані всі інші) спочатку викликати full_clean, а потім викликати super ()?
J__

7
Я бачу дві проблеми з таким підходом 1) у випадку, якщо full_clean () ModelForm буде викликаний двічі: за формою та за сигналом 2) Якщо форма виключає деякі поля, вони все одно будуть підтверджені сигналом.
mehmet

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 ), якщо ви включите перевірку для всіх моделей. Вам доведеться вирішувати такі проблеми самостійно. Однак кращого підходу atm немає.
Євген А.

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)

Чому це краще (або гірше), ніж використання сигналу?
J__

6
Я бачу дві проблеми з цим підходом 1) у випадку, якщо full_clean () ModelForm буде викликаний двічі: за формою та за збереженням 2) Якщо форма виключає деякі поля, вони все одно будуть підтверджені збереженням.
mehmet

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

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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.