views:

234

answers:

2

I'm trying to validate a form containing a ModelChoiceField:

forms.py:

from django import forms

from modelchoicetest.models import SomeObject

class SomeObjectAddForm(forms.ModelForm):
    class Meta:
        model = SomeObject

models.py:

from django.db import models

class SomeChoice(models.Model):
    name = models.CharField(max_length=16)

    def __unicode__(self):
        return self.name

class SomeObject(models.Model):
    choice = models.ForeignKey(SomeChoice)

views.py:

from django.shortcuts import render_to_response
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse

from forms import SomeObjectAddForm

def add(request):
    if request.method == 'POST':
        form = SomeObjectAddForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse('modelchoicetest_add'))
    else:
        form = SomeObjectAddForm()

    return render_to_response('modelchoicetest/index.html',
                              {'form': form},
                              context_instance=RequestContext(request))

When it is used in normal circumstances, everything goes just fine. But I'd like to protect the form from the invalid input. It's pretty obvious that I must get forms.ValidationError when I put invalid value in this field, isn't it? But if I try to submit a form with a value 'invalid' in 'somechoice' field, I get

ValueError: invalid literal for int() with base 10: 'invalid'

and not the expected forms.ValidationError. What should I do? I tried to place a def clean_somechoice(self) to check this field but that didn't work: ValueError happens before it comes to clean_somechoice()

Plus I don't think this is a good solution, there must be something more simple but I just missed that.

here's the full traceback:

Traceback:
File "/usr/local/lib/python2.6/dist-packages/django/core/handlers/base.py" in get_response
  101.                     response = callback(request, *callback_args, **callback_kwargs)
File "/home/andrey/public_html/example/modelchoicetest/views.py" in add
  11.         if form.is_valid():
File "/usr/local/lib/python2.6/dist-packages/django/forms/forms.py" in is_valid
  120.         return self.is_bound and not bool(self.errors)
File "/usr/local/lib/python2.6/dist-packages/django/forms/forms.py" in _get_errors
  111.             self.full_clean()
File "/usr/local/lib/python2.6/dist-packages/django/forms/forms.py" in full_clean
  276.                     value = field.clean(value)
File "/usr/local/lib/python2.6/dist-packages/django/forms/fields.py" in clean
  154.         value = self.to_python(value)
File "/usr/local/lib/python2.6/dist-packages/django/forms/models.py" in to_python
  911.             value = self.queryset.get(**{key: value})
File "/usr/local/lib/python2.6/dist-packages/django/db/models/query.py" in get
  330.         clone = self.filter(*args, **kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/query.py" in filter
  536.         return self._filter_or_exclude(False, *args, **kwargs)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/query.py" in _filter_or_exclude
  554.             clone.query.add_q(Q(*args, **kwargs))
File "/usr/local/lib/python2.6/dist-packages/django/db/models/sql/query.py" in add_q
  1109.                             can_reuse=used_aliases)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/sql/query.py" in add_filter
  1048.                 connector)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/sql/where.py" in add
  66.             value = obj.prepare(lookup_type, value)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/sql/where.py" in prepare
  267.             return self.field.get_prep_lookup(lookup_type, value)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/fields/__init__.py" in get_prep_lookup
  314.             return self.get_prep_value(value)
File "/usr/local/lib/python2.6/dist-packages/django/db/models/fields/__init__.py" in get_prep_value
  496.         return int(value)

Exception Type: ValueError at /
Exception Value: invalid literal for int() with base 10: 'invalid'
+1  A: 

It looks to me like the exception is being raised by the clean method of the actual ModelChoiceField object. Because it's a foreign key, Django is expecting an int, which would be representative of the pk for SomeChoice. How exactly are you passing invalid into the form?

RESPONSE TO COMMENT

If you really feel you need to catch this, you can try overriding the default ModelChoiceField by creating a new field called choice and pass in the to_field_name kwarg into the ModelChoiceField __init__ method. This way Django won't be filtering on pk, and won't raise that exception.

Personally I wouldn't use this solution. There is no need to accommodate user who are hacking your form.

Zach
I can easily pass `invalid` by modifying the form on the client side (lots of tools can do this, I used FF Webdeveloper "Edit HTML" feature). I understand that it isn't something that every client will do but I don't want to give anybody an opportunity to raise unhandled exception on my site, and potentially bomb me with email notifications etc.
Andrey
I just updated my answer with a response to you comment
Zach
Thanks! I will try that. Well it's not about accomodating, it's rather handling the errors correctly. I think that similar cases are for example honeypot field in django comments form or csrf_token. If a hacker posts incorrect data to these, first will give you validation error and second will give you 403 Denied, but none will raise an unhandled exception, right?
Andrey
I'm not really sure
Zach
A: 

This is known Django bug:

http://code.djangoproject.com/ticket/11716

And while this bug is not fixed, you can only handle ValueError manually.

sirex