Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/forms/boundfield.py: 34%
155 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 re
3from django.core.exceptions import ValidationError
4from django.forms.utils import pretty_name
5from django.forms.widgets import MultiWidget, Textarea, TextInput
6from django.utils.functional import cached_property
7from django.utils.html import format_html, html_safe
8from django.utils.translation import gettext_lazy as _
10__all__ = ("BoundField",)
13@html_safe
14class BoundField:
15 "A Field plus data"
17 def __init__(self, form, field, name):
18 self.form = form
19 self.field = field
20 self.name = name
21 self.html_name = form.add_prefix(name)
22 self.html_initial_name = form.add_initial_prefix(name)
23 self.html_initial_id = form.add_initial_prefix(self.auto_id)
24 if self.field.label is None: 24 ↛ 25line 24 didn't jump to line 25, because the condition on line 24 was never true
25 self.label = pretty_name(name)
26 else:
27 self.label = self.field.label
28 self.help_text = field.help_text or ""
30 def __str__(self):
31 """Render this field as an HTML widget."""
32 if self.field.show_hidden_initial:
33 return self.as_widget() + self.as_hidden(only_initial=True)
34 return self.as_widget()
36 @cached_property
37 def subwidgets(self):
38 """
39 Most widgets yield a single subwidget, but others like RadioSelect and
40 CheckboxSelectMultiple produce one subwidget for each choice.
42 This property is cached so that only one database query occurs when
43 rendering ModelChoiceFields.
44 """
45 id_ = self.field.widget.attrs.get("id") or self.auto_id
46 attrs = {"id": id_} if id_ else {}
47 attrs = self.build_widget_attrs(attrs)
48 return [
49 BoundWidget(self.field.widget, widget, self.form.renderer)
50 for widget in self.field.widget.subwidgets(
51 self.html_name, self.value(), attrs=attrs
52 )
53 ]
55 def __bool__(self):
56 # BoundField evaluates to True even if it doesn't have subwidgets.
57 return True
59 def __iter__(self):
60 return iter(self.subwidgets)
62 def __len__(self):
63 return len(self.subwidgets)
65 def __getitem__(self, idx):
66 # Prevent unnecessary reevaluation when accessing BoundField's attrs
67 # from templates.
68 if not isinstance(idx, (int, slice)):
69 raise TypeError(
70 "BoundField indices must be integers or slices, not %s."
71 % type(idx).__name__
72 )
73 return self.subwidgets[idx]
75 @property
76 def errors(self):
77 """
78 Return an ErrorList (empty if there are no errors) for this field.
79 """
80 return self.form.errors.get(
81 self.name, self.form.error_class(renderer=self.form.renderer)
82 )
84 def as_widget(self, widget=None, attrs=None, only_initial=False):
85 """
86 Render the field by rendering the passed widget, adding any HTML
87 attributes passed as attrs. If a widget isn't specified, use the
88 field's default widget.
89 """
90 widget = widget or self.field.widget
91 if self.field.localize:
92 widget.is_localized = True
93 attrs = attrs or {}
94 attrs = self.build_widget_attrs(attrs, widget)
95 if self.auto_id and "id" not in widget.attrs:
96 attrs.setdefault(
97 "id", self.html_initial_id if only_initial else self.auto_id
98 )
99 return widget.render(
100 name=self.html_initial_name if only_initial else self.html_name,
101 value=self.value(),
102 attrs=attrs,
103 renderer=self.form.renderer,
104 )
106 def as_text(self, attrs=None, **kwargs):
107 """
108 Return a string of HTML for representing this as an <input type="text">.
109 """
110 return self.as_widget(TextInput(), attrs, **kwargs)
112 def as_textarea(self, attrs=None, **kwargs):
113 """Return a string of HTML for representing this as a <textarea>."""
114 return self.as_widget(Textarea(), attrs, **kwargs)
116 def as_hidden(self, attrs=None, **kwargs):
117 """
118 Return a string of HTML for representing this as an <input type="hidden">.
119 """
120 return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
122 @property
123 def data(self):
124 """
125 Return the data for this BoundField, or None if it wasn't given.
126 """
127 return self.form._widget_data_value(self.field.widget, self.html_name)
129 def value(self):
130 """
131 Return the value for this BoundField, using the initial value if
132 the form is not bound or the data otherwise.
133 """
134 data = self.initial
135 if self.form.is_bound:
136 data = self.field.bound_data(self.data, data)
137 return self.field.prepare_value(data)
139 def _has_changed(self):
140 field = self.field
141 if field.show_hidden_initial:
142 hidden_widget = field.hidden_widget()
143 initial_value = self.form._widget_data_value(
144 hidden_widget,
145 self.html_initial_name,
146 )
147 try:
148 initial_value = field.to_python(initial_value)
149 except ValidationError:
150 # Always assume data has changed if validation fails.
151 return True
152 else:
153 initial_value = self.initial
154 return field.has_changed(initial_value, self.data)
156 def label_tag(self, contents=None, attrs=None, label_suffix=None):
157 """
158 Wrap the given contents in a <label>, if the field has an ID attribute.
159 contents should be mark_safe'd to avoid HTML escaping. If contents
160 aren't given, use the field's HTML-escaped label.
162 If attrs are given, use them as HTML attributes on the <label> tag.
164 label_suffix overrides the form's label_suffix.
165 """
166 contents = contents or self.label
167 if label_suffix is None:
168 label_suffix = (
169 self.field.label_suffix
170 if self.field.label_suffix is not None
171 else self.form.label_suffix
172 )
173 # Only add the suffix if the label does not end in punctuation.
174 # Translators: If found as last label character, these punctuation
175 # characters will prevent the default label_suffix to be appended to the label
176 if label_suffix and contents and contents[-1] not in _(":?.!"):
177 contents = format_html("{}{}", contents, label_suffix)
178 widget = self.field.widget
179 id_ = widget.attrs.get("id") or self.auto_id
180 if id_:
181 id_for_label = widget.id_for_label(id_)
182 if id_for_label:
183 attrs = {**(attrs or {}), "for": id_for_label}
184 if self.field.required and hasattr(self.form, "required_css_class"):
185 attrs = attrs or {}
186 if "class" in attrs:
187 attrs["class"] += " " + self.form.required_css_class
188 else:
189 attrs["class"] = self.form.required_css_class
190 context = {
191 "field": self,
192 "label": contents,
193 "attrs": attrs,
194 "use_tag": bool(id_),
195 }
196 return self.form.render(self.form.template_name_label, context)
198 def css_classes(self, extra_classes=None):
199 """
200 Return a string of space-separated CSS classes for this field.
201 """
202 if hasattr(extra_classes, "split"):
203 extra_classes = extra_classes.split()
204 extra_classes = set(extra_classes or [])
205 if self.errors and hasattr(self.form, "error_css_class"):
206 extra_classes.add(self.form.error_css_class)
207 if self.field.required and hasattr(self.form, "required_css_class"):
208 extra_classes.add(self.form.required_css_class)
209 return " ".join(extra_classes)
211 @property
212 def is_hidden(self):
213 """Return True if this BoundField's widget is hidden."""
214 return self.field.widget.is_hidden
216 @property
217 def auto_id(self):
218 """
219 Calculate and return the ID attribute for this BoundField, if the
220 associated Form has specified auto_id. Return an empty string otherwise.
221 """
222 auto_id = self.form.auto_id # Boolean or string
223 if auto_id and "%s" in str(auto_id): 223 ↛ 225line 223 didn't jump to line 225, because the condition on line 223 was never false
224 return auto_id % self.html_name
225 elif auto_id:
226 return self.html_name
227 return ""
229 @property
230 def id_for_label(self):
231 """
232 Wrapper around the field widget's `id_for_label` method.
233 Useful, for example, for focusing on this field regardless of whether
234 it has a single widget or a MultiWidget.
235 """
236 widget = self.field.widget
237 id_ = widget.attrs.get("id") or self.auto_id
238 return widget.id_for_label(id_)
240 @cached_property
241 def initial(self):
242 return self.form.get_initial_for_field(self.field, self.name)
244 def build_widget_attrs(self, attrs, widget=None):
245 widget = widget or self.field.widget
246 attrs = dict(attrs) # Copy attrs to avoid modifying the argument.
247 if (
248 widget.use_required_attribute(self.initial)
249 and self.field.required
250 and self.form.use_required_attribute
251 ):
252 # MultiValueField has require_all_fields: if False, fall back
253 # on subfields.
254 if (
255 hasattr(self.field, "require_all_fields")
256 and not self.field.require_all_fields
257 and isinstance(self.field.widget, MultiWidget)
258 ):
259 for subfield, subwidget in zip(self.field.fields, widget.widgets):
260 subwidget.attrs["required"] = (
261 subwidget.use_required_attribute(self.initial)
262 and subfield.required
263 )
264 else:
265 attrs["required"] = True
266 if self.field.disabled:
267 attrs["disabled"] = True
268 return attrs
270 @property
271 def widget_type(self):
272 return re.sub(
273 r"widget$|input$", "", self.field.widget.__class__.__name__.lower()
274 )
277@html_safe
278class BoundWidget:
279 """
280 A container class used for iterating over widgets. This is useful for
281 widgets that have choices. For example, the following can be used in a
282 template:
284 {% for radio in myform.beatles %}
285 <label for="{{ radio.id_for_label }}">
286 {{ radio.choice_label }}
287 <span class="radio">{{ radio.tag }}</span>
288 </label>
289 {% endfor %}
290 """
292 def __init__(self, parent_widget, data, renderer):
293 self.parent_widget = parent_widget
294 self.data = data
295 self.renderer = renderer
297 def __str__(self):
298 return self.tag(wrap_label=True)
300 def tag(self, wrap_label=False):
301 context = {"widget": {**self.data, "wrap_label": wrap_label}}
302 return self.parent_widget._render(self.template_name, context, self.renderer)
304 @property
305 def template_name(self):
306 if "template_name" in self.data:
307 return self.data["template_name"]
308 return self.parent_widget.template_name
310 @property
311 def id_for_label(self):
312 return self.data["attrs"].get("id")
314 @property
315 def choice_label(self):
316 return self.data["label"]