Skip to content

Instantly share code, notes, and snippets.

@aklim007
Last active February 10, 2016 06:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aklim007/c325753b3e171adb8674 to your computer and use it in GitHub Desktop.
Save aklim007/c325753b3e171adb8674 to your computer and use it in GitHub Desktop.
Django forms ArrayField
# -*- coding=utf8 -*-
"""Django forms ArrayField"""
# Changelog:
# 2016-02-10 версия 0.1.3 Очепятки
# 2016-02-05 версия 0.1.2 Информация о лицензии
# 2015-07-20 версия 0.1.1 Мета данные модуля
from __future__ import unicode_literals
VERSION = (0, 1, 3)
__all__ = ['ArrayField']
__title__ = 'FormsArrayField'
__version__ = '.'.join(map(str, VERSION if VERSION[-1] else VERSION[:2]))
__author__ = 'Klimenko Artyem'
__contact__ = 'aklim007@gmail.com'
__homepage__ = 'https://gist.github.com/aklim007/c325753b3e171adb8674'
__copyright__ = 'Клименко Артём <aklim007@gmail.com>'
__license__ = 'MIT'
from django.contrib.postgres.validators import ArrayMinLengthValidator, ArrayMaxLengthValidator
from django.core.exceptions import ValidationError
from django.utils import six
from django.utils.translation import ugettext_lazy as _, string_concat
from django import forms
class ArrayField(forms.Field):
"""
Поле для форм джанги, описывающее заранее неизвестный массив данных
указанного, при создании поля, типа
REQUIREMENTS:
django>=1.8
EXAMPLES:
class TestForm(forms.Form):
numarray = ArrayField(base_field=forms.IntegerField)
test_form = TestForm(data=QueryDict('numarray=1&numarray=&numarray=5'))
test_form.is_valid() # False, Item 1 in the array did not validate: This field is required.
... ArrayField(base_field=forms.IntegerField(required=False))
test_form = TestForm(data={'numarray': [1, '', 5]})
test_form.is_valid() # True
test_form.cleaned_data # {'numarray': [1, None, 5]}
... ArrayField(base_field=forms.IntegerField(required=False),
remove_empty_values=True)
test_form = TestForm(data={'numarray': [1, '', 5]})
test_form.is_valid() # True
test_form.cleaned_data # {'numarray': [1, 5]}
... ArrayField(base_field=forms.IntegerField(required=False),
min_length=3, remove_empty_values=True)
test_form = TestForm(data={'numarray': [1, '', 5]})
test_form.is_valid() # False, List contains 2 items, it should contain no fewer than 3.
... data=QueryDict('numarray=1&numarray=2&numarray=5')
... data={'numarray': [1, 2, 5]}
... data={'numarray': '1,2,5'}
test_form.is_valid() # True
test_form.cleaned_data # {'numarray': [1, 2, 5]}
****************************************
test_form = TestForm(initial={'numarray': [1, 2]})
test_form.as_p()
<input id="id_numarray_0" name="numarray" type="hidden" value="1" />
<input id="id_numarray_1" name="numarray" type="hidden" value="2" />
... ArrayField(base_field=forms.IntegerField, , widget=forms.SelectMultiple)
test_form = TestForm(initial={'numarray': [1, 2]})
test_form.as_p()
<p>
<label for="id_numarray">Numarray:</label>
<select multiple="multiple" id="id_numarray" name="numarray"></select>
</p>
# Empty =(
... ArrayField(base_field=forms.IntegerField, , widget=forms.SelectMultiple, generate_choises=True)
test_form = TestForm(initial={'numarray': [(1, 'one'), (2, 'two')]})
test_form.as_p()
<p>
<label for="id_numarray">Numarray:</label>
<select multiple="multiple" id="id_numarray" name="numarray">
<option value="1" selected="selected">one</option>
<option value="2" selected="selected">two</option>
</select>
</p>
# AWESOME :3
"""
widget = forms.MultipleHiddenInput
default_error_messages = {
'invalid': _('Enter a list of values.'),
'item_invalid': _('Item %(nth)s in the array did not validate: '),
}
def __init__(self, base_field, delimiter=',', remove_empty_values=False,
max_length=None, min_length=None, generate_choises=False, *args, **kwargs):
"""
ARGUMENTS:
:param base_field: Поле на основе которого будет происходить валидация
:param str | unicode delimiter: делитель текста
:param remove_empty_values: Удалять или нет пустые значения
:param None | int max_length: максимальная длинна выходного массива
:param None | int min_length: минимальная длинна выходного массива
:param bool generate_choises: если виджет является Select
"""
self.base_field, self.generate_choises = base_field, generate_choises
self.remove_empty_values, self.delimiter = remove_empty_values, delimiter
super(ArrayField, self).__init__(*args, **kwargs)
if isinstance(self.base_field, type):
self.base_field = self.base_field()
if min_length is not None:
self.min_length = min_length
self.validators.append(ArrayMinLengthValidator(int(min_length)))
if max_length is not None:
self.max_length = max_length
self.validators.append(ArrayMaxLengthValidator(int(max_length)))
def to_python(self, value):
"""
Вернёт уже питоновский массив с провалидированными данными
Или выдаст исключение при наличии проблем
Если выбрана опция remove_empty_values, то пустых значений
уже не будет на выходе данной функции
ARGUMENTS:
:type value: str | unicode | collections.Iterable
"""
if value in self.empty_values:
return []
if isinstance(value, six.string_types):
items = value.split(self.delimiter)
elif hasattr(value, '__iter__'):
items = value
else:
raise ValidationError(self.error_messages['invalid'], code='invalid')
errors = []
values = []
for i, item in enumerate(items):
try:
values.append(self.base_field.clean(item))
except ValidationError as e:
for error in e.error_list:
errors.append(ValidationError(
string_concat(self.error_messages['item_invalid'], error.message),
code='item_invalid',
params={'nth': i},
))
if errors:
raise ValidationError(errors)
if self.remove_empty_values:
values = [v for v in values if v not in self.empty_values]
return values
def prepare_value(self, value):
"""
При рендере виджета формы, если включена опция generate_choises
то ожидаем, что исходные данные были поданы в формате choises
и тогда у виджета прописываем эти значения, и выбираем первые значения
тюпла для последующей передачи
ARGUMENTS:
:param value: входные данные
:rtype: list
"""
if self.generate_choises and value is not None:
self.widget.choices = value
return [x[0] for x in value]
return value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment