15 декабря 2008 г.

Формы в джанго

Даа, сложновато разобраться формами в django.
Этот топик - заметки в этапах ковыряния в исходниках и понятие структуры (может быть ошибочное мнение).
Итак,
1. форма содержит поля (fields).
2. в полях есть виджеты (widgets).

Инициализация формы:
Для вывода формы (а точнее преобразования значения поля из запроса в вид на форме), а также для обратного преобразования этих данных используются виджеты (которые используют данные instance или initial поля, а также POST данные).
Для проверки POST данных, преобразованных виджетом, используется метод clean поля (см ниже).

Вспомним, как мы сохраняем форму. Примерный процесс проверки POST данных:

if request.method == 'POST': # If the form has been submitted...
if form.is_valid():
# Process the data in form.cleaned_data
# ...
form.save()


Наша форма имеет предка - класс BaseForm, в котором есть функция is_valid():


def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not bool(self.errors)

Свойство is_bound:

self.is_bound = data is not None or files is not None

self.errors:

errors = property(_get_errors)

Метод свойства:

def _get_errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors

А вот сама функция
 
def full_clean(self):
"""
Cleans all of self.data and populates self._errors and
self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.initial.get(name, field.initial)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError, e:
self._errors[name] = e.messages
if name in self.cleaned_data:
del self.cleaned_data[name]
try:
self.cleaned_data = self.clean()
except ValidationError, e:
self._errors[NON_FIELD_ERRORS] = e.messages
if self._errors:
delattr(self, 'cleaned_data')

Метод clean поля:
 
class Field(object):
def clean(self, value):
"""
Validates the given value and returns its "cleaned" value as an
appropriate Python object.

Raises ValidationError for any errors.
"""
if self.required and value in EMPTY_VALUES:
raise ValidationError(self.error_messages['required'])
return value

Например, для поля CharField

def clean(self, value):
"Validates max_length and min_length. Returns a Unicode object."
super(CharField, self).clean(value)
if value in EMPTY_VALUES:
return u''
value = smart_unicode(value)
value_length = len(value)
if self.max_length is not None and value_length > self.max_length:
raise ValidationError(self.error_messages['max_length'] % {'max': self.max_length, 'length': value_length})
if self.min_length is not None and value_length < self.min_length:
raise ValidationError(self.error_messages['min_length'] % {'min': self.min_length, 'length': value_length})
return value

Теперь становится более понятным, в каком месте лучше провести свою проверку или преобразовать данные с формы, переопределяя класс поля или создав свой виджет.

Моя задача: передать для одного поля ModelChoiceField динамический queryset, в зависимости от другого значения в POST данных (это нужно из-за того, что поле ModelChoiceField меняется динамически через AJAX, в зависимости от другого значения).
Изучив вышеприведенный код я нашел несколько путей для реализации:
1. Переопределив метод clean формы forms.ModelChoiceField
2. Передать в поле полный queryset, изменив виджет (чтобы он принимал наш "подставной" queryset), для вывода ограниченного числа значений.
3. Передавать нужный queryset в зависимости от data (то есть POST).

Последний вариант самый предпочтительный - самый простой, плюс доп проверка queryset, в зависимости от поля (сохранится структура базы) и у меня работает)

Комментариев нет:

Отправить комментарий