Last active
February 10, 2016 06:21
-
-
Save aklim007/c325753b3e171adb8674 to your computer and use it in GitHub Desktop.
Django forms ArrayField
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- 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