add custom DynamicArrayField to better handle arrays
This commit is contained in:
@ -0,0 +1,44 @@
|
||||
"""passbook lib fields"""
|
||||
from itertools import chain
|
||||
|
||||
from django import forms
|
||||
from django.contrib.postgres.utils import prefix_validation_error
|
||||
|
||||
from passbook.lib.widgets import DynamicArrayWidget
|
||||
|
||||
|
||||
class DynamicArrayField(forms.Field):
|
||||
"""Show array field as a dynamic amount of textboxes"""
|
||||
|
||||
default_error_messages = {"item_invalid": "Item %(nth)s in the array did not validate: "}
|
||||
|
||||
def __init__(self, base_field, **kwargs):
|
||||
self.base_field = base_field
|
||||
self.max_length = kwargs.pop("max_length", None)
|
||||
kwargs.setdefault("widget", DynamicArrayWidget)
|
||||
super().__init__(**kwargs)
|
||||
|
||||
def clean(self, value):
|
||||
cleaned_data = []
|
||||
errors = []
|
||||
value = [x for x in value if x]
|
||||
for index, item in enumerate(value):
|
||||
try:
|
||||
cleaned_data.append(self.base_field.clean(item))
|
||||
except forms.ValidationError as error:
|
||||
errors.append(
|
||||
prefix_validation_error(
|
||||
error, self.error_messages["item_invalid"],
|
||||
code="item_invalid", params={"nth": index}
|
||||
)
|
||||
)
|
||||
if errors:
|
||||
raise forms.ValidationError(list(chain.from_iterable(errors)))
|
||||
if not cleaned_data and self.required:
|
||||
raise forms.ValidationError(self.error_messages["required"])
|
||||
return cleaned_data
|
||||
|
||||
def has_changed(self, initial, data):
|
||||
if not data and not initial:
|
||||
return False
|
||||
return super().has_changed(initial, data)
|
||||
|
17
passbook/lib/templates/lib/arrayfield.html
Normal file
17
passbook/lib/templates/lib/arrayfield.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% load utils %}
|
||||
|
||||
{% spaceless %}
|
||||
<div class="dynamic-array-widget">
|
||||
{% for widget in widget.subwidgets %}
|
||||
<div class="array-item input-group">
|
||||
{% include widget.template_name %}
|
||||
<div class="input-group-btn">
|
||||
<button class="array-remove btn btn-danger" type="button">
|
||||
<span class="pficon-delete"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div><button type="button" class="add-array-item btn btn-default">Add another</button></div>
|
||||
</div>
|
||||
{% endspaceless %}
|
36
passbook/lib/widgets.py
Normal file
36
passbook/lib/widgets.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""Dynamic array widget"""
|
||||
from django import forms
|
||||
|
||||
|
||||
class DynamicArrayWidget(forms.TextInput):
|
||||
"""Dynamic array widget"""
|
||||
|
||||
template_name = "lib/arrayfield.html"
|
||||
|
||||
def get_context(self, name, value, attrs):
|
||||
value = value or [""]
|
||||
context = super().get_context(name, value, attrs)
|
||||
final_attrs = context["widget"]["attrs"]
|
||||
id_ = context["widget"]["attrs"].get("id")
|
||||
|
||||
subwidgets = []
|
||||
for index, item in enumerate(context["widget"]["value"]):
|
||||
widget_attrs = final_attrs.copy()
|
||||
if id_:
|
||||
widget_attrs["id"] = "{id_}_{index}".format(id_=id_, index=index)
|
||||
widget = forms.TextInput()
|
||||
widget.is_required = self.is_required
|
||||
subwidgets.append(widget.get_context(name, item, widget_attrs)["widget"])
|
||||
|
||||
context["widget"]["subwidgets"] = subwidgets
|
||||
return context
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
try:
|
||||
getter = data.getlist
|
||||
return [value for value in getter(name) if value]
|
||||
except AttributeError:
|
||||
return data.get(name)
|
||||
|
||||
def format_value(self, value):
|
||||
return value or []
|
Reference in New Issue
Block a user