Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/postgres/forms/array.py: 18%
167 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import copy
2from itertools import chain
4from django import forms
5from django.contrib.postgres.validators import (
6 ArrayMaxLengthValidator,
7 ArrayMinLengthValidator,
8)
9from django.core.exceptions import ValidationError
10from django.utils.translation import gettext_lazy as _
12from ..utils import prefix_validation_error
15class SimpleArrayField(forms.CharField):
16 default_error_messages = {
17 "item_invalid": _("Item %(nth)s in the array did not validate:"),
18 }
20 def __init__(
21 self, base_field, *, delimiter=",", max_length=None, min_length=None, **kwargs
22 ):
23 self.base_field = base_field
24 self.delimiter = delimiter
25 super().__init__(**kwargs)
26 if min_length is not None:
27 self.min_length = min_length
28 self.validators.append(ArrayMinLengthValidator(int(min_length)))
29 if max_length is not None:
30 self.max_length = max_length
31 self.validators.append(ArrayMaxLengthValidator(int(max_length)))
33 def clean(self, value):
34 value = super().clean(value)
35 return [self.base_field.clean(val) for val in value]
37 def prepare_value(self, value):
38 if isinstance(value, list):
39 return self.delimiter.join(
40 str(self.base_field.prepare_value(v)) for v in value
41 )
42 return value
44 def to_python(self, value):
45 if isinstance(value, list):
46 items = value
47 elif value:
48 items = value.split(self.delimiter)
49 else:
50 items = []
51 errors = []
52 values = []
53 for index, item in enumerate(items):
54 try:
55 values.append(self.base_field.to_python(item))
56 except ValidationError as error:
57 errors.append(
58 prefix_validation_error(
59 error,
60 prefix=self.error_messages["item_invalid"],
61 code="item_invalid",
62 params={"nth": index + 1},
63 )
64 )
65 if errors:
66 raise ValidationError(errors)
67 return values
69 def validate(self, value):
70 super().validate(value)
71 errors = []
72 for index, item in enumerate(value):
73 try:
74 self.base_field.validate(item)
75 except ValidationError as error:
76 errors.append(
77 prefix_validation_error(
78 error,
79 prefix=self.error_messages["item_invalid"],
80 code="item_invalid",
81 params={"nth": index + 1},
82 )
83 )
84 if errors:
85 raise ValidationError(errors)
87 def run_validators(self, value):
88 super().run_validators(value)
89 errors = []
90 for index, item in enumerate(value):
91 try:
92 self.base_field.run_validators(item)
93 except ValidationError as error:
94 errors.append(
95 prefix_validation_error(
96 error,
97 prefix=self.error_messages["item_invalid"],
98 code="item_invalid",
99 params={"nth": index + 1},
100 )
101 )
102 if errors:
103 raise ValidationError(errors)
105 def has_changed(self, initial, data):
106 try:
107 value = self.to_python(data)
108 except ValidationError:
109 pass
110 else:
111 if initial in self.empty_values and value in self.empty_values:
112 return False
113 return super().has_changed(initial, data)
116class SplitArrayWidget(forms.Widget):
117 template_name = "postgres/widgets/split_array.html"
119 def __init__(self, widget, size, **kwargs):
120 self.widget = widget() if isinstance(widget, type) else widget
121 self.size = size
122 super().__init__(**kwargs)
124 @property
125 def is_hidden(self):
126 return self.widget.is_hidden
128 def value_from_datadict(self, data, files, name):
129 return [
130 self.widget.value_from_datadict(data, files, "%s_%s" % (name, index))
131 for index in range(self.size)
132 ]
134 def value_omitted_from_data(self, data, files, name):
135 return all(
136 self.widget.value_omitted_from_data(data, files, "%s_%s" % (name, index))
137 for index in range(self.size)
138 )
140 def id_for_label(self, id_):
141 # See the comment for RadioSelect.id_for_label()
142 if id_:
143 id_ += "_0"
144 return id_
146 def get_context(self, name, value, attrs=None):
147 attrs = {} if attrs is None else attrs
148 context = super().get_context(name, value, attrs)
149 if self.is_localized:
150 self.widget.is_localized = self.is_localized
151 value = value or []
152 context["widget"]["subwidgets"] = []
153 final_attrs = self.build_attrs(attrs)
154 id_ = final_attrs.get("id")
155 for i in range(max(len(value), self.size)):
156 try:
157 widget_value = value[i]
158 except IndexError:
159 widget_value = None
160 if id_:
161 final_attrs = {**final_attrs, "id": "%s_%s" % (id_, i)}
162 context["widget"]["subwidgets"].append(
163 self.widget.get_context(name + "_%s" % i, widget_value, final_attrs)[
164 "widget"
165 ]
166 )
167 return context
169 @property
170 def media(self):
171 return self.widget.media
173 def __deepcopy__(self, memo):
174 obj = super().__deepcopy__(memo)
175 obj.widget = copy.deepcopy(self.widget)
176 return obj
178 @property
179 def needs_multipart_form(self):
180 return self.widget.needs_multipart_form
183class SplitArrayField(forms.Field):
184 default_error_messages = {
185 "item_invalid": _("Item %(nth)s in the array did not validate:"),
186 }
188 def __init__(self, base_field, size, *, remove_trailing_nulls=False, **kwargs):
189 self.base_field = base_field
190 self.size = size
191 self.remove_trailing_nulls = remove_trailing_nulls
192 widget = SplitArrayWidget(widget=base_field.widget, size=size)
193 kwargs.setdefault("widget", widget)
194 super().__init__(**kwargs)
196 def _remove_trailing_nulls(self, values):
197 index = None
198 if self.remove_trailing_nulls:
199 for i, value in reversed(list(enumerate(values))):
200 if value in self.base_field.empty_values:
201 index = i
202 else:
203 break
204 if index is not None:
205 values = values[:index]
206 return values, index
208 def to_python(self, value):
209 value = super().to_python(value)
210 return [self.base_field.to_python(item) for item in value]
212 def clean(self, value):
213 cleaned_data = []
214 errors = []
215 if not any(value) and self.required:
216 raise ValidationError(self.error_messages["required"])
217 max_size = max(self.size, len(value))
218 for index in range(max_size):
219 item = value[index]
220 try:
221 cleaned_data.append(self.base_field.clean(item))
222 except ValidationError as error:
223 errors.append(
224 prefix_validation_error(
225 error,
226 self.error_messages["item_invalid"],
227 code="item_invalid",
228 params={"nth": index + 1},
229 )
230 )
231 cleaned_data.append(None)
232 else:
233 errors.append(None)
234 cleaned_data, null_index = self._remove_trailing_nulls(cleaned_data)
235 if null_index is not None:
236 errors = errors[:null_index]
237 errors = list(filter(None, errors))
238 if errors:
239 raise ValidationError(list(chain.from_iterable(errors)))
240 return cleaned_data
242 def has_changed(self, initial, data):
243 try:
244 data = self.to_python(data)
245 except ValidationError:
246 pass
247 else:
248 data, _ = self._remove_trailing_nulls(data)
249 if initial in self.empty_values and data in self.empty_values:
250 return False
251 return super().has_changed(initial, data)