Вызвать ошибку проверки в методе сохранения модели в Django

Я не уверен, как правильно вызвать ошибку проверки в методе сохранения модели и отправить пользователю четкое сообщение.

По сути, я хочу знать, как должна заканчиваться каждая часть «если», та, где я хочу вызвать ошибку, и та, где она фактически сохраняется:

def save(self, *args, **kwargs):
    if not good_enough_to_be_saved:
        raise ValidationError
    else:
        super(Model, self).save(*args, **kwargs)

Затем я хочу знать, что делать, чтобы отправить ошибку проверки, которая точно сообщает пользователю, что не так, точно так же, как тот, который Django автоматически возвращает, если, например, значение не является уникальным. Я использую (ModelForm) и настраиваю все из модели.


person Bastian    schedule 07.01.2012    source источник
comment
использовать метод clean ()   -  person Luv33preet    schedule 27.08.2018


Ответы (7)


Большинство представлений Django, например администратор Django не сможет обработать ошибку проверки в методе сохранения, поэтому ваши пользователи получат 500 ошибок.

Вы должны выполнить валидацию в форме модели или модели и поднять там ValidationError. Затем вызывайте save(), только если данные формы модели «достаточно хороши для сохранения».

person Alasdair    schedule 07.01.2012
comment
Вы правы, я перенесу свою проверку в форму, так проще. Мне просто понравилась идея иметь все в модели. - person Bastian; 08.01.2012
comment
@bastian, мне тоже понравилось, что в модели есть все. Когда вы пишете новую форму, легко забыть бизнес-правило, но не в том случае, если бизнес-правила присутствуют в модели. По этой причине я переместил проверки из форм в модель, как я объясняю в своем сообщении. Я готов узнать о новых методах, позволяющих сделать это более элегантным способом, если он существует. В любом случае я избегаю писать код проверки в формах. - person dani herrera; 08.01.2012
comment
Допустимо включать валидацию в вашу модель с помощью валидаторов или написания clean() метода. Все, что я сказал, это то, что метод save() - неправильное место. Ознакомьтесь с документами по проверке объектов . - person Alasdair; 08.01.2012
comment
Хо да! Я только что понял, что в моей модели работает clean (). Большое спасибо. - person Bastian; 09.01.2012
comment
Я не понимаю, почему проверка должна выполняться только на стороне формы, а не на стороне сохранения модели. Как будто других способов создания объекта нет. Что, если вы хотите инстанцировать и создать объект без использования формы и все же хотите гарантировать определенное состояние? - person dabadaba; 09.01.2019
comment
@dabadaba, вы можете поместить проверку в чистый метод модели, я только сказал, что не следует помещать ее в метод save() модели. Если вы поместите проверку в метод save(), вы получите 500 ошибок из большинства представлений, потому что они не будут обрабатывать ValidationError. Обратите внимание, что выполнение проверки в методе save() не является абсолютной гарантией - вы все равно можете написать Model.objects.filter(...).update(...) или вручную SQL, что приведет к сохранению недействительных данных. - person Alasdair; 09.01.2019

Бастиан, я объясняю вам мой шаблон кода, надеюсь, это вам поможет:

Начиная с django 1.2, он может написать код проверки на модели. Когда мы работаем с модельными формами, instance.full_clean () вызывается при проверке формы.

В каждой модели я перезаписываю метод clean() пользовательской функцией (этот метод автоматически вызывается из full_clean () при проверке формы модели):

from django.db import models
 
class Issue(models.Model):
    ....
    def clean(self): 
        rules.Issue_clean(self)  #<-- custom function invocation

from issues import rules
rules.connect()

Затем в rules.py файле я пишу правила ведения бизнеса. Также я подключаю pre_save() к своей пользовательской функции, чтобы предотвратить сохранение модели с неправильным состоянием:

из issues.models import Issue

def connect():    
    from django.db.models.signals import post_save, pre_save, pre_delete
    #issues 
    pre_save.connect(Issue_pre_save, sender = Incidencia ) 
    post_save.connect(Issue_post_save, sender = Incidencia )
    pre_delete.connect(Issue_pre_delete, sender= Incidencia) 

def Incidencia_clean( instance ):    #<-- custom function 
    import datetime as dt    
    errors = {}

    #dia i hora sempre informats     
    if not instance.dia_incidencia:   #<-- business rules
        errors.setdefault('dia_incidencia',[]).append(u'Data missing: ...')
        
    #dia i hora sempre informats     
    if not  instance.franja_incidencia: 
        errors.setdefault('franja_incidencia',[]).append(u'Falten Dades: ...')
 
    #Només es poden posar incidències més ennlà de 7 dies 
    if instance.dia_incidencia < ( dt.date.today() + dt.timedelta( days = -7) ): 
        errors.setdefault('dia_incidencia 1',[]).append(u'''blah blah error desc)''')
 
    #No incidències al futur. 
    if instance.getDate() > datetime.now(): 
        errors.setdefault('dia_incidencia 2',[]).append(u'''Encara no pots ....''') 
    ... 

    if len( errors ) > 0: 
        raise ValidationError(errors)  #<-- raising errors

def Issue_pre_save(sender, instance, **kwargs): 
    instance.clean()     #<-- custom function invocation

Затем modelform вызывает метод модели clean, а моя функция custon проверяет правильное состояние или вызывает ошибку, которая обрабатывается формой модели.

Чтобы отображать ошибки в форме, вы должны включить это в шаблон формы:

{% if form.non_field_errors %}
      {% for error in form.non_field_errors %}
        {{error}}
      {% endfor %}
{% endif %}  

Причина в том, что ошибки проверки модели связаны с записью словаря ошибок non_field_errors.

Когда вы сохраняете или удаляете модель вне формы, вы должны помнить, что может возникнуть ошибка:

try:
    issue.delete()
except ValidationError, e:
    import itertools
    errors = list( itertools.chain( *e.message_dict.values() ) )

Кроме того, вы можете добавлять ошибки в словарь форм без форм:

    try:
        #provoco els errors per mostrar-los igualment al formulari.
        issue.clean()
    except ValidationError, e:
        form._errors = {}
        for _, v in e.message_dict.items():
            form._errors.setdefault(NON_FIELD_ERRORS, []).extend(  v  )

Помните, что этот код не выполняется в методе save (): обратите внимание, что full_clean () не будет вызываться автоматически ни при вызове метода save () вашей модели, ни в результате проверки ModelForm. Затем вы можете добавить ошибки в словарь форм на без модельных форм:

    try:
        #provoco els errors per mostrar-los igualment al formulari.
        issue.clean()
    except ValidationError, e:
        form._errors = {}
        for _, v in e.message_dict.items():
            form._errors.setdefault(NON_FIELD_ERRORS, []).extend(  v  )
person dani herrera    schedule 07.01.2012
comment
Moltes gràcies за ваше пространное объяснение. Я искал что-нибудь автоматическое, Джангоиш. Ваш пример может заинтересовать меня в других ситуациях, но тот, который я сейчас пишу, представляет собой всего лишь однострочную проверку, поэтому я не буду реализовывать здесь все. - person Bastian; 08.01.2012
comment
вы всегда можете переопределить метод clean с помощью 1-строчной проверки ... - person Sidhin S Thomas; 28.08.2017
comment
хм .. это не работает для меня. Я использую всплывающую форму, и вместо ошибки проверки отображается исключение. Я должен отметить, что, поскольку у меня есть форма, которая работает с двумя моделями, я расширяю forms.Form вместо models.Form - person geoidesic; 05.04.2018

Я думаю, что это более понятный способ сделать это для Django 1.2+.

В формах он будет отображаться как non_field_error, в других случаях, таких как DRF, вы должны проверить это руководство по этому случаю, потому что это будет ошибка 500.

class BaseModelExt(models.Model):
is_cleaned = False

def clean(self):
    # check validation rules here

    self.is_cleaned = True

def save(self, *args, **kwargs):
    if not self.is_cleaned:
        self.clean()

    super().save(*args, **kwargs)
person megajoe    schedule 20.08.2019
comment
Мне это кажется очень простым и эффективным, когда вам нужно проверить объект, созданный программно, а именно: отправка формы не участвует в процессе. Спасибо - person Mario Orlandi; 24.04.2020

В документации Django они вызывают ValueError в методе .save, это может быть полезно для вас. https://docs.djangoproject.com/en/3.1/ref/models/instances/

person jorge4larcon    schedule 28.03.2021

Если вы хотите выполнить проверку модели, вы можете использовать для модели методы clean() или clean_fields. Они вызываются django перед выполнением save(), а ошибки проверки обрабатываются удобным для пользователя способом - django docs здесь.

person thclark    schedule 03.05.2021

Изменить: в этом ответе предполагается, что у вас есть сценарий, который не позволяет вам редактировать реализованный в настоящее время класс User, поскольку вы не начинаете проект с нуля, текущая реализация еще не использует пользовательский класс пользователя, и вы вместо этого нужно выяснить, как выполнить эту задачу, изменив поведение модели Django, встроенное в модель User.

Вы можете просто прикрепить clean метод к своей модели большую часть времени, но у вас не обязательно есть эта опция со встроенной auth.User моделью. Это решение позволит вам создать clean метод для модели auth.User таким образом, чтобы ValidationErrors распространялись на формы, в которых вызывается чистый метод (включая формы администратора).

В приведенном ниже примере возникает ошибка, если кто-то пытается создать или изменить экземпляр auth.User, чтобы иметь тот же адрес электронной почты, что и существующий экземпляр auth.User. Заявление об отказе от ответственности: если вы открываете регистрационную форму новым пользователям, вы не хотите, чтобы ваша ошибка проверки вызывала имена пользователей, как у меня ниже.

from django.contrib.auth.models import User
from django.forms import ValidationError as FormValidationError

def clean_user_email(self):
    instance = self
    super(User, self).clean()
    if instance.email:
        if User.objects.filter(id=instance.id, email=instance.email).exists():
            pass  # email was not modified
        elif User.objects.filter(email=instance.email).exists():
            other_users = [*User.objects.filter(email=instance.email).values_list('username', flat=True)]
            raise FormValidationError(f'At least one other user already has this email address: '
                                      f'{", ".join(other_users)}'
                                      , code='invalid')

# assign the above function to the User.clean method
User.add_to_class("clean", clean_user_email)

У меня это есть в конце my_app.models, но я уверен, что он будет работать, если вы вставите его куда-нибудь, загруженное до рассматриваемой формы.

person DragonBobZ    schedule 06.11.2018
comment
Если вам не нравится мой ответ, объясните почему. - person DragonBobZ; 20.03.2019
comment
Я не голосовал против, но предполагаю, что голос против вызван тем, что вы отвечаете на вопрос 2012 года чем-то, что [A] (хотя и интересно) не является ответом на заданный вопрос, [B] не называет никаких существующий User.clean(), а [C] использует исправление обезьяны вместо наследования от AbstractUser и реализации clean() в вашем собственном классе ... - person thebjorn; 02.06.2019
comment
Это не мой класс. Модель User определяется Django. Мне пришлось сделать этот патч обезьяны, чтобы изменить методы во встроенной пользовательской модели Django, потому что после того, как вы запустили проект и он находится в производстве без реализации AbstractUser пользовательской модели User, практически невозможно успешно модернизировать вашу собственную модель User. Обратите внимание, что первые два предложения моего ответа прямо касаются вашего беспокойства. - person DragonBobZ; 03.06.2019
comment
Вдобавок я ответил на вопрос 2012 года тем ответом, который работал в моей ситуации, потому что, когда я искал решения для моей конкретной проблемы, это был вопрос, который возник в 2018 году. Итак, допустим, кто-то вроде меня приходит и сталкивается с этой проблемой. Что ж, есть возможное решение, на поиск которого у меня ушло немало времени, и которое могло бы сэкономить кому-то почти эквивалентное количество времени. Насколько я понимаю, Stack Overflow предназначен для полезного объединения решений. Рассмотрение потенциальных крайних случаев - очень важная часть этого. - person DragonBobZ; 03.06.2019
comment
Как я уже сказал, я не голосовал против, но все ваши доводы в пользу того, почему это классное решение (оно есть), уводят вас дальше от ответа на этот вопрос. Вместо того, чтобы добавлять решение вашей собственной проблемы к старому, частично связанному с вами вопросу, который вы нашли при поиске решений вашей проблемы (и получили отрицательное голосование), могу ли я предложить вам создать свой собственный новый вопрос? Совершенно нормально ответить на свой собственный вопрос, поэтому, если у вас есть с трудом завоеванный опыт, которым вы можете поделиться, вы можете ответить самостоятельно (и, возможно, получить голоса за как вопрос, так и ответ). - person thebjorn; 03.06.2019
comment
Справедливо. Спасибо что нашли время ответить. - person DragonBobZ; 03.06.2019

person    schedule
comment
Публикационного кода недостаточно, вы должны предоставить некоторые пояснения. - person StaceyGirl; 01.12.2017
comment
вы можете вызвать метод full_clean () в функции сохранения, он отлично работает в Django == 1.11, я не уверен в более старой версии. - person Asif Akhtar; 01.12.2017