1. Andrey

    08.03.2010

    0 ↑
    0 ↓
    Подскажите, пожалуйста, как быть с валидацией ModelChoiceField, если в это поле передают неверные данные? Суть проблемы в том, что в форме это поле выглядит как целое число, но клиенту никто не запрещает передать туда строку, и при этом выскакивает ValueError, причем до метода clean_, т.е. поймать его как forms.ValidationError не получится. Вот я сделал проект, чтобы было понятнее:

    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))
    Если это запустить, видим обычную формочку с селектом. Теперь беру и на стороне клиента подменяю в селекте одно из значений int на строку "invalid". При сабмите формы вижу не просьбу исправить ошибки в форме, как хотелось бы, а вот это:
    Environment:

    Request Method: POST
    Request URL: http://192.168.1.2:8001/
    Django Version: 1.2 alpha 1
    Python Version: 2.6.2
    Installed Applications:
    ['django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'modelchoicetest']
    Installed Middleware:
    ('django.middleware.common.CommonMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware')


    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'
    Что делать-то?
  2. Иван Сагалаев

    08.03.2010

    0 ↑
    0 ↓

    Да в принципе ничего делать не надо. Сервис не обязан предоставлять корректный интерфейс в ответ на хак: простая страница с 500-й ошибкой — вполне достаточный ответ для того, кто полез в HTML и что-то там подменил. Никому, кроме себя, он не помешает.

    Хотя Джанга могла бы и не ломаться в этом месте, да... В принципе, неплохо бы тикет сделать.

  3. Это неделю назад в транке починили, см. http://code.djangoproject.com/ticket/11465

  4. хотя ввел в заблуждение, там не совсем это починили)

  5. Андрей

    09.03.2010

    0 ↑
    0 ↓
    С 500-й ошибкой - мне кажется, не очень, тем более если уведомления об ошибках идут на почту :) Не происходит же такого, например, при заполнении honeypot в django comments или при отсутствии csrf_token. Более адекватно было бы отвечать страницей 403.

    Вот мне предложили решение, но я не успел еще попробовать:
    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.
  6. Андрей

    09.03.2010

    0 ↑
    0 ↓
    Я хотел сказать не то чтобы 403 было бы правильно - это нужно делать отдельную реакцию на такую ситуацию, чего никто делать не будет - а просто чтобы поле при этом не проходило валидацию. Просто 403 адекватнее, чем 500.
  7. Иван Сагалаев

    09.03.2010

    0 ↑
    0 ↓

    Правильней, на самом деле, 400 (Bad request). Но да, я согласен, что постоянный спам в лог ли на почту — это не очень правильно. Потому и говорю, что надо тикет сделать.

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.