Created
January 9, 2020 14:05
-
-
Save bsolomon1124/6bb71e62f40b9574592460b94f1a790d to your computer and use it in GitHub Desktop.
Derivative of SimpleArrayField
This file contains hidden or 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
| """Reproduce SplitArrayField for no-psycopg2 environment. | |
| See: | |
| - 1.11.x/django/contrib/postgres/forms/array.py | |
| - 1.11.x/django/forms/fields.py | |
| - 1.11/ref/contrib/postgres/forms/ | |
| - 1.11/ref/forms/validation/ | |
| """ | |
| # Note: no SplitArrayWidget here, no corresponding HTML | |
| import re | |
| import sre_compile | |
| from django import forms | |
| from django.core.exceptions import ValidationError | |
| from django.contrib.postgres.utils import prefix_validation_error | |
| from django.utils.translation import ugettext_lazy as _ | |
| _pattern_type = type(sre_compile.compile("", 0)) | |
| class MultipleFieldBase(forms.CharField): | |
| widget = forms.Textarea | |
| default_error_messages = { | |
| 'item_invalid': _('Item # %(nth)s, "%(val)s" is not valid: '), | |
| } | |
| def __init__( | |
| self, | |
| base_field, | |
| delimiter, | |
| strip=True, | |
| **kwargs | |
| ): | |
| # `delimiter` may be: | |
| # - str: split on this str | |
| # - callable e.g. unicode.splitlines or a lambda | |
| # - compiled regex | |
| self.base_field = base_field | |
| self.delimiter = delimiter | |
| self.strip = strip | |
| if isinstance(delimiter, basestring): | |
| def _split(s): | |
| return s.split(delimiter) | |
| elif callable(delimiter): | |
| def _split(s): | |
| return delimiter(s) | |
| elif isinstance(delimiter, _pattern_type): | |
| def _split(s): | |
| return delimiter.split(s) | |
| else: | |
| raise TypeError( | |
| "`delimiter` should be basestring, callable, or regex" | |
| ) | |
| self._split = _split | |
| super(MultipleFieldBase, self).__init__(**kwargs) | |
| # TODO: may want to implement prepare_value() | |
| def to_python(self, value): | |
| # Coerces the value to a correct datatype and raises ValidationError | |
| # if that is not possible; | |
| # accepts the raw value from the widget and returns the converted value | |
| # This does *not* affect the POST data format | |
| if isinstance(value, list): | |
| items = value | |
| elif value: | |
| items = self._split(value) | |
| else: | |
| items = [] | |
| errors = [] | |
| values = [] | |
| for index, item in enumerate(items, 1): | |
| try: | |
| values.append(self.base_field.to_python(item)) | |
| except ValidationError as error: | |
| errors.append(prefix_validation_error( | |
| error, | |
| prefix=self.error_messages['item_invalid'], | |
| code='item_invalid', | |
| params={'nth': index, 'val': item}, | |
| )) | |
| if errors: | |
| raise ValidationError(errors) | |
| return values | |
| def validate(self, value): | |
| super(MultipleFieldBase, self).validate(value) | |
| errors = [] | |
| for index, item in enumerate(value, 1): | |
| try: | |
| self.base_field.validate(item) | |
| except ValidationError as error: | |
| errors.append( | |
| prefix_validation_error( | |
| error, | |
| prefix=self.error_messages['item_invalid'], | |
| code='item_invalid', | |
| params={'nth': index, 'val': item}, | |
| ) | |
| ) | |
| if errors: | |
| raise ValidationError(errors) | |
| def run_validators(self, value): | |
| super(MultipleFieldBase, self).run_validators(value) | |
| errors = [] | |
| for index, item in enumerate(value, 1): | |
| try: | |
| self.base_field.run_validators(item) | |
| except ValidationError as error: | |
| errors.append(prefix_validation_error( | |
| error, | |
| prefix=self.error_messages['item_invalid'], | |
| code='item_invalid', | |
| params={'nth': index, 'val': item}, | |
| )) | |
| if errors: | |
| raise ValidationError(errors) | |
| def clean(self, value): | |
| if self.strip: | |
| value = value.strip() | |
| return super(MultipleFieldBase, self).clean(value) | |
| # Lets user pass in multiple new lines (e.g. blank lines in between) | |
| # We could also (and probably should) handle in MultipleFieldBase itself | |
| flex_newline_re = re.compile(r"[\r\n]+") | |
| class MyForm(forms.Form): | |
| addr = MultipleFieldBase( | |
| base_field=forms.GenericIPAddressField(protocol="IPv4"), | |
| delimiter=flex_newline_re, | |
| label="IP addresses", | |
| required=False, | |
| help_text="Zero or more dotted-quad IPv4 addresses, one per line", | |
| ) | |
| my_int = forms.IntegerField(label="Just a number") | |
| multiple_ints = MultipleFieldBase( | |
| base_field=forms.IntegerField(), | |
| delimiter=flex_newline_re, | |
| label="A bunch of ints", | |
| required=True, | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment