Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/django/forms/boundfield.py: 33%
169 statements
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
« prev ^ index » next coverage.py v7.0.5, created at 2023-01-17 06:13 +0000
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:
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 if only_initial and self.html_initial_name in self.form.data:
100 # Propagate the hidden initial value.
101 value = self.form._widget_data_value(
102 self.field.hidden_widget(),
103 self.html_initial_name,
104 )
105 else:
106 value = self.value()
107 return widget.render(
108 name=self.html_initial_name if only_initial else self.html_name,
109 value=value,
110 attrs=attrs,
111 renderer=self.form.renderer,
112 )
114 def as_text(self, attrs=None, **kwargs):
115 """
116 Return a string of HTML for representing this as an <input type="text">.
117 """
118 return self.as_widget(TextInput(), attrs, **kwargs)
120 def as_textarea(self, attrs=None, **kwargs):
121 """Return a string of HTML for representing this as a <textarea>."""
122 return self.as_widget(Textarea(), attrs, **kwargs)
124 def as_hidden(self, attrs=None, **kwargs):
125 """
126 Return a string of HTML for representing this as an <input type="hidden">.
127 """
128 return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
130 @property
131 def data(self):
132 """
133 Return the data for this BoundField, or None if it wasn't given.
134 """
135 return self.form._widget_data_value(self.field.widget, self.html_name)
137 def value(self):
138 """
139 Return the value for this BoundField, using the initial value if
140 the form is not bound or the data otherwise.
141 """
142 data = self.initial
143 if self.form.is_bound:
144 data = self.field.bound_data(self.data, data)
145 return self.field.prepare_value(data)
147 def _has_changed(self):
148 field = self.field
149 if field.show_hidden_initial:
150 hidden_widget = field.hidden_widget()
151 initial_value = self.form._widget_data_value(
152 hidden_widget,
153 self.html_initial_name,
154 )
155 try:
156 initial_value = field.to_python(initial_value)
157 except ValidationError:
158 # Always assume data has changed if validation fails.
159 return True
160 else:
161 initial_value = self.initial
162 return field.has_changed(initial_value, self.data)
164 def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
165 """
166 Wrap the given contents in a <label>, if the field has an ID attribute.
167 contents should be mark_safe'd to avoid HTML escaping. If contents
168 aren't given, use the field's HTML-escaped label.
170 If attrs are given, use them as HTML attributes on the <label> tag.
172 label_suffix overrides the form's label_suffix.
173 """
174 contents = contents or self.label
175 if label_suffix is None:
176 label_suffix = (
177 self.field.label_suffix
178 if self.field.label_suffix is not None
179 else self.form.label_suffix
180 )
181 # Only add the suffix if the label does not end in punctuation.
182 # Translators: If found as last label character, these punctuation
183 # characters will prevent the default label_suffix to be appended to the label
184 if label_suffix and contents and contents[-1] not in _(":?.!"):
185 contents = format_html("{}{}", contents, label_suffix)
186 widget = self.field.widget
187 id_ = widget.attrs.get("id") or self.auto_id
188 if id_:
189 id_for_label = widget.id_for_label(id_)
190 if id_for_label:
191 attrs = {**(attrs or {}), "for": id_for_label}
192 if self.field.required and hasattr(self.form, "required_css_class"):
193 attrs = attrs or {}
194 if "class" in attrs:
195 attrs["class"] += " " + self.form.required_css_class
196 else:
197 attrs["class"] = self.form.required_css_class
198 context = {
199 "field": self,
200 "label": contents,
201 "attrs": attrs,
202 "use_tag": bool(id_),
203 "tag": tag or "label",
204 }
205 return self.form.render(self.form.template_name_label, context)
207 def legend_tag(self, contents=None, attrs=None, label_suffix=None):
208 """
209 Wrap the given contents in a <legend>, if the field has an ID
210 attribute. Contents should be mark_safe'd to avoid HTML escaping. If
211 contents aren't given, use the field's HTML-escaped label.
213 If attrs are given, use them as HTML attributes on the <legend> tag.
215 label_suffix overrides the form's label_suffix.
216 """
217 return self.label_tag(contents, attrs, label_suffix, tag="legend")
219 def css_classes(self, extra_classes=None):
220 """
221 Return a string of space-separated CSS classes for this field.
222 """
223 if hasattr(extra_classes, "split"):
224 extra_classes = extra_classes.split()
225 extra_classes = set(extra_classes or [])
226 if self.errors and hasattr(self.form, "error_css_class"):
227 extra_classes.add(self.form.error_css_class)
228 if self.field.required and hasattr(self.form, "required_css_class"):
229 extra_classes.add(self.form.required_css_class)
230 return " ".join(extra_classes)
232 @property
233 def is_hidden(self):
234 """Return True if this BoundField's widget is hidden."""
235 return self.field.widget.is_hidden
237 @property
238 def auto_id(self):
239 """
240 Calculate and return the ID attribute for this BoundField, if the
241 associated Form has specified auto_id. Return an empty string otherwise.
242 """
243 auto_id = self.form.auto_id # Boolean or string
244 if auto_id and "%s" in str(auto_id):
245 return auto_id % self.html_name
246 elif auto_id:
247 return self.html_name
248 return ""
250 @property
251 def id_for_label(self):
252 """
253 Wrapper around the field widget's `id_for_label` method.
254 Useful, for example, for focusing on this field regardless of whether
255 it has a single widget or a MultiWidget.
256 """
257 widget = self.field.widget
258 id_ = widget.attrs.get("id") or self.auto_id
259 return widget.id_for_label(id_)
261 @cached_property
262 def initial(self):
263 return self.form.get_initial_for_field(self.field, self.name)
265 def build_widget_attrs(self, attrs, widget=None):
266 widget = widget or self.field.widget
267 attrs = dict(attrs) # Copy attrs to avoid modifying the argument.
268 if (
269 widget.use_required_attribute(self.initial)
270 and self.field.required
271 and self.form.use_required_attribute
272 ):
273 # MultiValueField has require_all_fields: if False, fall back
274 # on subfields.
275 if (
276 hasattr(self.field, "require_all_fields")
277 and not self.field.require_all_fields
278 and isinstance(self.field.widget, MultiWidget)
279 ):
280 for subfield, subwidget in zip(self.field.fields, widget.widgets):
281 subwidget.attrs["required"] = (
282 subwidget.use_required_attribute(self.initial)
283 and subfield.required
284 )
285 else:
286 attrs["required"] = True
287 if self.field.disabled:
288 attrs["disabled"] = True
289 return attrs
291 @property
292 def widget_type(self):
293 return re.sub(
294 r"widget$|input$", "", self.field.widget.__class__.__name__.lower()
295 )
297 @property
298 def use_fieldset(self):
299 """
300 Return the value of this BoundField widget's use_fieldset attribute.
301 """
302 return self.field.widget.use_fieldset
305@html_safe
306class BoundWidget:
307 """
308 A container class used for iterating over widgets. This is useful for
309 widgets that have choices. For example, the following can be used in a
310 template:
312 {% for radio in myform.beatles %}
313 <label for="{{ radio.id_for_label }}">
314 {{ radio.choice_label }}
315 <span class="radio">{{ radio.tag }}</span>
316 </label>
317 {% endfor %}
318 """
320 def __init__(self, parent_widget, data, renderer):
321 self.parent_widget = parent_widget
322 self.data = data
323 self.renderer = renderer
325 def __str__(self):
326 return self.tag(wrap_label=True)
328 def tag(self, wrap_label=False):
329 context = {"widget": {**self.data, "wrap_label": wrap_label}}
330 return self.parent_widget._render(self.template_name, context, self.renderer)
332 @property
333 def template_name(self):
334 if "template_name" in self.data:
335 return self.data["template_name"]
336 return self.parent_widget.template_name
338 @property
339 def id_for_label(self):
340 return self.data["attrs"].get("id")
342 @property
343 def choice_label(self):
344 return self.data["label"]