Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django clean method throwing KeyError on POST

I'm getting a KeyError for 'password' when I try to submit my form.

trace:

Request Method: POST
Request URL: http://localhost:8000/register/
Django Version: 1.2.1
Python Version: 2.7.0
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'djangoproject1.authentication']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware')


Traceback:
File "C:\Python27\lib\site-packages\django\core\handlers\base.py" in get_response
  100.                     response = callback(request, *callback_args, **callback_kwargs)
File "C:\Users\jec23\My Java Projects\djangoproject1\src\djangoproject1\authentication\views.py" in register
  20.         if rf.is_valid() and pf.is_valid():
File "C:\Python27\lib\site-packages\django\forms\forms.py" in is_valid
  121.         return self.is_bound and not bool(self.errors)
File "C:\Python27\lib\site-packages\django\forms\forms.py" in _get_errors
  112.             self.full_clean()
File "C:\Python27\lib\site-packages\django\forms\forms.py" in full_clean
  268.         self._clean_form()
File "C:\Python27\lib\site-packages\django\forms\forms.py" in _clean_form
  296.             self.cleaned_data = self.clean()
File "C:\Users\jec23\My Java Projects\djangoproject1\src\djangoproject1\authentication\forms.py" in clean
  16.         if self.cleaned_data['cpassword']!=self.cleaned_data['password']:

Exception Type: KeyError at /register/
Exception Value: 'password'

views:

def register(request):
    if request.method == 'POST':
        rf = forms.RegisterForm(request.POST)
        pf = forms.ProfileForm(request.POST)
        if rf.is_valid() and pf.is_valid():
            newuser = User(username=rf.cleaned_data['username'],email=rf.cleaned_data['email'])
            newuser.set_password(rf.cleaned_data['password'])
            newuser.save()
            profile = pf.save(commit=False)
            profile.user = newuser
            profile.save()
            return HttpResponseRedirect("/register-success/")
        else:
            return render_to_response("authentication/index.html", {'form1': rf, 'form2':pf})
    else:
        return main(request)

forms:

class RegisterForm(forms.Form):
    username = forms.CharField(min_length=6,max_length=15)
    password = forms.CharField(min_length=6,max_length=15,widget = forms.PasswordInput())
    cpassword = forms.CharField(label='Confirm Password',widget = forms.PasswordInput())
    email = forms.EmailField(label='E-mail Address')

    def clean(self):
        if self.cleaned_data['cpassword']!=self.cleaned_data['password']:
            raise forms.ValidationError("Passwords don't match")
        return self.cleaned_data

class ProfileForm(forms.ModelForm):
    phonenumber = forms.CharField(label='Phone Number')

    class Meta:
        model = UserProfile
        exclude = ('user')
like image 799
JPC Avatar asked Aug 20 '10 17:08

JPC


2 Answers

From my own experience, I found that if you want to do some validation in multiple fields, even if they are marked as required=True, when you override the clean() method in your form, if the fields you want to validate are not filled when submitting it and you try to acess them as cleaned_data["field_name"] your code will explode with a KeyError. To avoid this, just access the field in cleaned_data through the get() dictionary method and check if is None, or pass a default value. As a corollary:

my_field = cleaned_data.get("field_name")  # This is safe and it will work! :)
my_filed = cleaned_data["field_name"]  # This will crash when the field was not filled! :(

I hope this helps someone else, I lost a great amount of time because of this silly thing!

like image 164
Caumons Avatar answered Oct 05 '22 04:10

Caumons


Aha! The validation error message that you are seeing is actually not a validation error message. Let me explain. When you render the model form instance using as_p, it renders each field in the following way:

<p><label ...>fieldname</label> <input ... name="fieldname" /> HELP TEXT IF ANY</p> 

The string Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters that you are seeing to the right hand side of the field is nothing but the help text. This help text is taken from the model definition - you can verify this by visiting django/contrib/auth/models.py and inspecting the definition of User class.

When you override the username field you are omitting any help text. Naturally the "error" disappears.

In order to verify this theory do the following in your main method.

def main(request):
    uf = forms.UserForm()
    upf = forms.UserProfileForm()
    print "User form is bound:%s errors:%s" % (uf.is_bound, uf.errors)
    return render_to_response("authentication/index.html", {'form1': uf, 'form2':upf})

Update

if self.cleaned_data['cpassword']!=self.cleaned_data['password']:

This line can cause trouble when the user doesn't supply one or both of password and cpassword. For example, try this from the Django shell:

>>> data = dict(username = 'admin', cpassword = 'foo', email='[email protected]')
>>> f = RegisterForm(data)
>>> f.is_bound
True
>>> f.is_valid()

Traceback (most recent call last):
  ...
  File "<pyshell#2>", line 8, in clean
    if self.cleaned_data['cpassword']!=self.cleaned_data['password']:
KeyError: 'password'

Change your form's clean method to make sure that both values are present before comparing. Something like this:

def clean(self):
    password = self.cleaned_data.get('password', None)
    cpassword = self.cleaned_data.get('cpassword', None)
    if password and cpassword and (password == cpassword):
        return self.cleaned_data
    else:
        raise forms.ValidationError("Passwords don't match")
like image 36
Manoj Govindan Avatar answered Oct 05 '22 02:10

Manoj Govindan