15 декабря 2008 г.

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

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

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

Вспомним, как мы сохраняем форму. Примерный процесс проверки POST данных:
  1. if request.method == 'POST'# If the form has been submitted...  
  2.       if form.is_valid():  
  3.        # Process the data in form.cleaned_data  
  4.        # ...  
  5.         form.save()  


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

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

Свойство is_bound:
  1. self.is_bound = data is not None or files is not None  

self.errors:
  1. errors = property(_get_errors)  

Метод свойства:
  1. def _get_errors(self):  
  2.     "Returns an ErrorDict for the data provided for the form"  
  3.     if self._errors is None:  
  4.         self.full_clean()  
  5.     return self._errors  

А вот сама функция
  1. def full_clean(self):  
  2.       """ 
  3.       Cleans all of self.data and populates self._errors and 
  4.       self.cleaned_data. 
  5.       """  
  6.       self._errors = ErrorDict()  
  7.       if not self.is_bound: # Stop further processing.  
  8.           return  
  9.       self.cleaned_data = {}  
  10.       # If the form is permitted to be empty, and none of the form data has  
  11.       # changed from the initial data, short circuit any validation.  
  12.       if self.empty_permitted and not self.has_changed():  
  13.           return  
  14.       for name, field in self.fields.items():  
  15.           # value_from_datadict() gets the data from the data dictionaries.  
  16.           # Each widget type knows how to retrieve its own data, because some  
  17.           # widgets split data over several HTML fields.  
  18.           value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))  
  19.           try:  
  20.               if isinstance(field, FileField):  
  21.                   initial = self.initial.get(name, field.initial)  
  22.                   value = field.clean(value, initial)  
  23.               else:  
  24.                   value = field.clean(value)  
  25.               self.cleaned_data[name] = value  
  26.               if hasattr(self'clean_%s' % name):  
  27.                   value = getattr(self'clean_%s' % name)()  
  28.                   self.cleaned_data[name] = value  
  29.           except ValidationError, e:  
  30.               self._errors[name] = e.messages  
  31.               if name in self.cleaned_data:  
  32.                   del self.cleaned_data[name]  
  33.       try:  
  34.           self.cleaned_data = self.clean()  
  35.       except ValidationError, e:  
  36.           self._errors[NON_FIELD_ERRORS] = e.messages  
  37.       if self._errors:  
  38.           delattr(self'cleaned_data')  

Метод clean поля:
  1.    
  2. class Field(object):  
  3.       def clean(self, value):  
  4.           """ 
  5.           Validates the given value and returns its "cleaned" value as an 
  6.           appropriate Python object. 
  7.    
  8.           Raises ValidationError for any errors. 
  9.           """  
  10.           if self.required and value in EMPTY_VALUES:  
  11.               raise ValidationError(self.error_messages['required'])  
  12.           return value  

Например, для поля CharField
  1. def clean(self, value):  
  2.     "Validates max_length and min_length. Returns a Unicode object."  
  3.     super(CharField, self).clean(value)  
  4.     if value in EMPTY_VALUES:  
  5.         return u''  
  6.     value = smart_unicode(value)  
  7.     value_length = len(value)  
  8.     if self.max_length is not None and value_length > self.max_length:  
  9.         raise ValidationError(self.error_messages['max_length'] % {'max'self.max_length, 'length': value_length})  
  10.     if self.min_length is not None and value_length < self.min_length:  
  11.         raise ValidationError(self.error_messages['min_length'] % {'min'self.min_length, 'length': value_length})  
  12.     return value  

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

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

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

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

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