sgarza62
sgarza62

Reputation: 6238

Django: How to raise an exception when a user submits an unfinished form?

I've got a relatively standard RegistrationForm that looks like this:

class RegisterForm(forms.Form):
    username = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'username'}), initial='')
    email = forms.EmailField(widget=forms.TextInput(attrs={'placeholder': 'email'}), initial='')
    password = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'password'}), initial='')
    password_repeat = forms.CharField(widget=forms.PasswordInput(attrs={'placeholder': 'retype password'}), initial='')

How could I create a clean method that returns an error when a user forgets to fill in one or more fields? (ie. "You forgot to fill in the email field")

I've tried the following two options in my clean() method (I'll use the password and password_repeat fields as examples):

password = self.cleaned_data['password']
password_repeat = self.cleaned_data['password_repeat']
# initial values are set to '' for all fields, see above.
if password == '':
    raise forms.ValidationError("You forgot to type in a password.")
elif password_repeat == '':
        raise forms.ValidationError("You forgot to retype your password.")

The first option returns:

KeyError at /homepage/

'password'


try:
    password = self.cleaned_data['password']
    password_repeat = self.cleaned_data['password_repeat']
except KeyError(password):
    raise forms.ValidationError("You forgot to fill in the password field.")

The second option returns:

UnboundLocalError at /homepage/

local variable 'password' referenced before assignment


Bonus points if you can provide a solution that allows for the remaining fields to be checked as well (so that I can return an form bound to the data the user successfully submitted).

Upvotes: 6

Views: 23003

Answers (3)

Max Spencer
Max Spencer

Reputation: 1719

You can use the required property available for all Field types, which automatically does this type of validation. So your code would look like:

class RegisterForm(forms.Form):
    username = forms.CharField(
        widget = forms.TextInput(attrs = {'placeholder': 'username'}),
        required = True)
    email = forms.EmailField(
        widget = forms.TextInput(attrs = {'placeholder': 'email'}),
        required = True)
    password = forms.CharField(
        widget = forms.PasswordInput(attrs = {'placeholder': 'password'}),
        required = True)
    password_repeat = forms.CharField(
        widget = forms.PasswordInput(attrs = {'placeholder': 'retype password'}),
        required = True)

Note: I think you can leave out those initial = '' parameters as well, as shown above.

I'm actually not sure why you're getting the errors you mentioned in your question, perhaps you could post the relevant code from your views.py? It could be because you need to return cleaned_data at the end of any clean method you implement.

I would also just say that your use of the clean method is not quite right. If you refer to this page of the documentation on form and field validation you see that to validate a single field you use the specific clean_<fieldname> method e.g. clean_password_repeat. Using the clean method is appropriate when validation involves multiple field simultaneously, one example of this you may like to use is checking the inputs for the two password fields match.

class RegisterForm(forms.Form):
    # field definitions (above)

    def clean(self):
        password = self.cleaned_data['password']
        password_repeat = self.cleaned_data['password_repeat']
        if password != password_repeat:
            raise forms.ValidationError(u"Passwords do not match.")
        return cleaned_data

Note: Code is not tested.

I hope this was helpful!

Upvotes: 6

Benjamin Hodgson
Benjamin Hodgson

Reputation: 44634

Here is a snippet that steps through the keys in a dict and raises an exception if any of them are mapped to the empty string or any other False-like value. The exception tells you which key in the dict was missing an entry.

for key in self.cleaned_data:
    if not self.cleaned_data[key]:
        raise forms.ValidationError("You didn't fill in the {} form".format(key))

If you exclusively want to test for empty strings (and not False, 0, [], etc), change if not self.cleaned_data[key]: to if self.cleaned_data[key] == '':.

Upvotes: 0

dani herrera
dani herrera

Reputation: 51715

Since django 1.2 it is able to write validation code on model. I allways write business rules in model side:

This is your model:

from django.db import models
class Issue(models.Model):
    ....

    def clean(self): 
        rules.Issue_clean(self)

#I write business rules into another file ...
def Incidencia_clean( instance ): 
    import datetime as dt

    errors = {}

    #some business rules:     
    if not instance.dia_incidencia: 
        errors.setdefault('b1',[]).append(u'Falten...ranja')

    if not  instance.franja_incidencia: 
        errors.setdefault('b2',[]).append(u'Falten..nja')

    if instance.dia_incidencia < ( dt.date.today() + 
                                   dt.timedelta( days = -7) ): 
        errors.setdefault('b3',[]).append(u'''No es ... setmana)''')

    if instance.getDate() > datetime.now(): 
        errors.setdefault('b4',[]).append(u'''Encara ...itzat.''') 

    if len( errors ) > 0: 
        raise ValidationError(errors) 

In template:

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

I prefer to write business rule one time in model than repeat rules into each form.

Upvotes: 1

Related Questions