Skip to content

Instantly share code, notes, and snippets.

@bsolomon1124
Created January 9, 2020 14:05
Show Gist options
  • Select an option

  • Save bsolomon1124/6bb71e62f40b9574592460b94f1a790d to your computer and use it in GitHub Desktop.

Select an option

Save bsolomon1124/6bb71e62f40b9574592460b94f1a790d to your computer and use it in GitHub Desktop.
Derivative of SimpleArrayField
"""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