Create a shortcut for raising ValidationError from within the Form.clean() method

71 views
Skip to first unread message

Lucas Weyne

unread,
Sep 6, 2019, 11:28:29 AM9/6/19
to Django developers (Contributions to Django itself)
I want to create a shortcut method to raise ValidationError inside the Form.clean() method. 

When we need to raise the same ValidationError (same message and same code) many times inside the Form.clean(), I should pass the same message every time. 
Would be cleaner, pass only the error code and let the config Meta.error_messages solve them. Is there a way to solve this with Django:

What I want is something looks like the Serializer.fail() method of Django REST framework (docs here).

This is a sample form with my suggestion:

from django import forms
from django.core.exceptions import NON_FIELD_ERRORS

from .models import Variable


class VariableForm(forms.ModelForm):
   
def clean(self):
        cleaned_data
= super().clean()
        type
= cleaned_data.get('type')
        value_bool
= cleaned_data.get('value_bool')
        value_str
= cleaned_data.get('value_str')
        value_num
= cleaned_data.get('value_num')

       
if type == 'str' and (value_bool is not None or value_num is not None):
           
self.fail('incorrect_assignment', type='str')

       
if type == 'num' and (value_bool is not None or value_str is not None):
           
self.fail('incorrect_assignment', type='num')

       
       
if type == 'bool' and (value_num is not None or value_str is not None):
           
self.fail('incorrect_assignment', type='bool')

   
class Meta:
        model
= Variable
        fields
= ('name', 'type', 'value_bool', 'value_str', 'value_num')
        error_messages
= {
            NON_FIELD_ERRORS
: {
               
'incorrect_assignment': 'Incorrect assignment for variable of type %(type)s'
           
}
       
}

Without this shortcut I need raise a entire ValidationError (with message, code and parms arguments) three times with the same arguments except params. I can put the message inside a variable, but why not use the error_messages provided by the Django forms API?

Another problem is when I want to raise a required error programatically (inside the Form.clean() method) with the defaul message without pass them another time.

Adam Johnson

unread,
Sep 6, 2019, 12:01:16 PM9/6/19
to django-d...@googlegroups.com
Hi Lucas,

Thank you for writing to the list with your proposal.

At first glance, I'm not sure of the value. Shortcuts are nice but they come at the expense of there being two ways to do things.

Your example might not best express your idea, but if I were to refactor it to what Django currently supports, I think the clean() method would look something like this (untested):

def clean(self):
    cleaned_data = super().clean()
    type_ = cleaned_data.get('type')

    value_bool = cleaned_data.get('value_bool')
    value_str = cleaned_data.get('value_str')
    value_num = cleaned_data.get('value_num')
   
    type_error = False
    if type_ == 'str':
        type_error = (value_bool is not None or value_num is not None)
    elif type_ == 'num':
        type_error = (value_bool is not None or value_str is not None)
    elif type_ == 'bool':
        type_error = (value_num is not None or value_str is not None)
    if type_error:
        raise ValidationError('Incorrect assignment for variable of type %(type)s', params={'type': type_})


This is, in my opinion, simpler as it involves less indirection.

But maybe there's a more advanced case you've worked with?

Thanks,

Adam

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/4a526437-a0f5-4c37-bf7e-5868ff870bfd%40googlegroups.com.


--
Adam

Lucas Weyne

unread,
Sep 6, 2019, 12:23:51 PM9/6/19
to Django developers (Contributions to Django itself)
Hi Adam,

Your refactor works great and thats how I was thinking to solve this problem. My idea its was use the Meta.error_messages dictionary to solve default error messages. 

What if I want to raise a ValidationError for a conditional required field and display the default required message This field is required.
I can't see another wat to raise a  default error message like required, blank, invalid, unique_together without pass the message again.


def clean():  
    cleaned_data
= super().clean()
    password
= cleaned_data.get('password')
    password_confirm
= cleaned_data.get('password_confirm')

   
if password and not password_confirm:
       
# Raise the required validation error (with default message  'This field is required')
       
self.fail('password_confirm', 'required')
   
   
return cleaned_data

The example problem is a password confirmation, but can be any type of field that can be required based on another field

Adam Johnson

unread,
Sep 9, 2019, 3:17:24 PM9/9/19
to django-d...@googlegroups.com
Hi Lucas,

In the case that you want to apply an error to a specific field, there's the add_error method (docs: https://docs.djangoproject.com/en/2.2/ref/forms/api/#django.forms.Form.add_error ).

For your example (untested):

def clean(self):

    cleaned_data = super().clean()
    password = cleaned_data.get('password')
    password_confirm = cleaned_data.get('password_confirm')

    if password and not password_confirm:
        self.add_error('password_confirm', self.fields['password_confirm'].error_messages['required'])
   
    return cleaned_data


Yes this isn't quite as concise as saying "reuse the required message from the field." However I think adding the shortcut path would still be too complex.


Whilst skimming them I noticed one which might intersects this discussion (though there may be others) - the addition of multi-field validators: https://code.djangoproject.com/attachment/ticket/12498/complex-validator-docs.diff . Given they carry error messages, maybe some work around that would make forms easier in the way you imagine?

Thanks,

Adam

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.


--
Adam
Reply all
Reply to author
Forward
0 new messages