Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.10/site-packages/django/forms/boundfield.py: 31%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

186 statements  

1import re 

2 

3from django.core.exceptions import ValidationError 

4from django.forms.utils import RenderableFieldMixin, 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 _ 

9 

10__all__ = ("BoundField",) 

11 

12 

13class BoundField(RenderableFieldMixin): 

14 "A Field plus data" 

15 

16 def __init__(self, form, field, name): 

17 self.form = form 

18 self.field = field 

19 self.name = name 

20 self.html_name = form.add_prefix(name) 

21 self.html_initial_name = form.add_initial_prefix(name) 

22 self.html_initial_id = form.add_initial_prefix(self.auto_id) 

23 if self.field.label is None: 

24 self.label = pretty_name(name) 

25 else: 

26 self.label = self.field.label 

27 self.help_text = field.help_text or "" 

28 self.renderer = form.renderer 

29 

30 @cached_property 

31 def subwidgets(self): 

32 """ 

33 Most widgets yield a single subwidget, but others like RadioSelect and 

34 CheckboxSelectMultiple produce one subwidget for each choice. 

35 

36 This property is cached so that only one database query occurs when 

37 rendering ModelChoiceFields. 

38 """ 

39 id_ = self.field.widget.attrs.get("id") or self.auto_id 

40 attrs = {"id": id_} if id_ else {} 

41 attrs = self.build_widget_attrs(attrs) 

42 return [ 

43 BoundWidget(self.field.widget, widget, self.form.renderer) 

44 for widget in self.field.widget.subwidgets( 

45 self.html_name, self.value(), attrs=attrs 

46 ) 

47 ] 

48 

49 def __bool__(self): 

50 # BoundField evaluates to True even if it doesn't have subwidgets. 

51 return True 

52 

53 def __iter__(self): 

54 return iter(self.subwidgets) 

55 

56 def __len__(self): 

57 return len(self.subwidgets) 

58 

59 def __getitem__(self, idx): 

60 # Prevent unnecessary reevaluation when accessing BoundField's attrs 

61 # from templates. 

62 if not isinstance(idx, (int, slice)): 

63 raise TypeError( 

64 "BoundField indices must be integers or slices, not %s." 

65 % type(idx).__name__ 

66 ) 

67 return self.subwidgets[idx] 

68 

69 @property 

70 def errors(self): 

71 """ 

72 Return an ErrorList (empty if there are no errors) for this field. 

73 """ 

74 return self.form.errors.get( 

75 self.name, self.form.error_class(renderer=self.form.renderer) 

76 ) 

77 

78 @property 

79 def template_name(self): 

80 return self.field.template_name or self.form.renderer.field_template_name 

81 

82 def get_context(self): 

83 return {"field": self} 

84 

85 def as_widget(self, widget=None, attrs=None, only_initial=False): 

86 """ 

87 Render the field by rendering the passed widget, adding any HTML 

88 attributes passed as attrs. If a widget isn't specified, use the 

89 field's default widget. 

90 """ 

91 widget = widget or self.field.widget 

92 if self.field.localize: 

93 widget.is_localized = True 

94 attrs = attrs or {} 

95 attrs = self.build_widget_attrs(attrs, widget) 

96 if self.auto_id and "id" not in widget.attrs: 

97 attrs.setdefault( 

98 "id", self.html_initial_id if only_initial else self.auto_id 

99 ) 

100 if only_initial and self.html_initial_name in self.form.data: 

101 # Propagate the hidden initial value. 

102 value = self.form._widget_data_value( 

103 self.field.hidden_widget(), 

104 self.html_initial_name, 

105 ) 

106 else: 

107 value = self.value() 

108 return widget.render( 

109 name=self.html_initial_name if only_initial else self.html_name, 

110 value=value, 

111 attrs=attrs, 

112 renderer=self.form.renderer, 

113 ) 

114 

115 def as_text(self, attrs=None, **kwargs): 

116 """ 

117 Return a string of HTML for representing this as an <input type="text">. 

118 """ 

119 return self.as_widget(TextInput(), attrs, **kwargs) 

120 

121 def as_textarea(self, attrs=None, **kwargs): 

122 """Return a string of HTML for representing this as a <textarea>.""" 

123 return self.as_widget(Textarea(), attrs, **kwargs) 

124 

125 def as_hidden(self, attrs=None, **kwargs): 

126 """ 

127 Return a string of HTML for representing this as an <input type="hidden">. 

128 """ 

129 return self.as_widget(self.field.hidden_widget(), attrs, **kwargs) 

130 

131 @property 

132 def data(self): 

133 """ 

134 Return the data for this BoundField, or None if it wasn't given. 

135 """ 

136 return self.form._widget_data_value(self.field.widget, self.html_name) 

137 

138 def value(self): 

139 """ 

140 Return the value for this BoundField, using the initial value if 

141 the form is not bound or the data otherwise. 

142 """ 

143 data = self.initial 

144 if self.form.is_bound: 

145 data = self.field.bound_data(self.data, data) 

146 return self.field.prepare_value(data) 

147 

148 def _has_changed(self): 

149 field = self.field 

150 if field.show_hidden_initial: 

151 hidden_widget = field.hidden_widget() 

152 initial_value = self.form._widget_data_value( 

153 hidden_widget, 

154 self.html_initial_name, 

155 ) 

156 try: 

157 initial_value = field.to_python(initial_value) 

158 except ValidationError: 

159 # Always assume data has changed if validation fails. 

160 return True 

161 else: 

162 initial_value = self.initial 

163 return field.has_changed(initial_value, self.data) 

164 

165 def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None): 

166 """ 

167 Wrap the given contents in a <label>, if the field has an ID attribute. 

168 contents should be mark_safe'd to avoid HTML escaping. If contents 

169 aren't given, use the field's HTML-escaped label. 

170 

171 If attrs are given, use them as HTML attributes on the <label> tag. 

172 

173 label_suffix overrides the form's label_suffix. 

174 """ 

175 contents = contents or self.label 

176 if label_suffix is None: 

177 label_suffix = ( 

178 self.field.label_suffix 

179 if self.field.label_suffix is not None 

180 else self.form.label_suffix 

181 ) 

182 # Only add the suffix if the label does not end in punctuation. 

183 # Translators: If found as last label character, these punctuation 

184 # characters will prevent the default label_suffix to be appended to the label 

185 if label_suffix and contents and contents[-1] not in _(":?.!"): 

186 contents = format_html("{}{}", contents, label_suffix) 

187 widget = self.field.widget 

188 id_ = widget.attrs.get("id") or self.auto_id 

189 if id_: 

190 id_for_label = widget.id_for_label(id_) 

191 if id_for_label: 

192 attrs = {**(attrs or {}), "for": id_for_label} 

193 if self.field.required and hasattr(self.form, "required_css_class"): 

194 attrs = attrs or {} 

195 if "class" in attrs: 

196 attrs["class"] += " " + self.form.required_css_class 

197 else: 

198 attrs["class"] = self.form.required_css_class 

199 context = { 

200 "field": self, 

201 "label": contents, 

202 "attrs": attrs, 

203 "use_tag": bool(id_), 

204 "tag": tag or "label", 

205 } 

206 return self.form.render(self.form.template_name_label, context) 

207 

208 def legend_tag(self, contents=None, attrs=None, label_suffix=None): 

209 """ 

210 Wrap the given contents in a <legend>, if the field has an ID 

211 attribute. Contents should be mark_safe'd to avoid HTML escaping. If 

212 contents aren't given, use the field's HTML-escaped label. 

213 

214 If attrs are given, use them as HTML attributes on the <legend> tag. 

215 

216 label_suffix overrides the form's label_suffix. 

217 """ 

218 return self.label_tag(contents, attrs, label_suffix, tag="legend") 

219 

220 def css_classes(self, extra_classes=None): 

221 """ 

222 Return a string of space-separated CSS classes for this field. 

223 """ 

224 if hasattr(extra_classes, "split"): 

225 extra_classes = extra_classes.split() 

226 extra_classes = set(extra_classes or []) 

227 if self.errors and hasattr(self.form, "error_css_class"): 

228 extra_classes.add(self.form.error_css_class) 

229 if self.field.required and hasattr(self.form, "required_css_class"): 

230 extra_classes.add(self.form.required_css_class) 

231 return " ".join(extra_classes) 

232 

233 @property 

234 def is_hidden(self): 

235 """Return True if this BoundField's widget is hidden.""" 

236 return self.field.widget.is_hidden 

237 

238 @property 

239 def auto_id(self): 

240 """ 

241 Calculate and return the ID attribute for this BoundField, if the 

242 associated Form has specified auto_id. Return an empty string otherwise. 

243 """ 

244 auto_id = self.form.auto_id # Boolean or string 

245 if auto_id and "%s" in str(auto_id): 

246 return auto_id % self.html_name 

247 elif auto_id: 

248 return self.html_name 

249 return "" 

250 

251 @property 

252 def id_for_label(self): 

253 """ 

254 Wrapper around the field widget's `id_for_label` method. 

255 Useful, for example, for focusing on this field regardless of whether 

256 it has a single widget or a MultiWidget. 

257 """ 

258 widget = self.field.widget 

259 id_ = widget.attrs.get("id") or self.auto_id 

260 return widget.id_for_label(id_) 

261 

262 @cached_property 

263 def initial(self): 

264 return self.form.get_initial_for_field(self.field, self.name) 

265 

266 def build_widget_attrs(self, attrs, widget=None): 

267 widget = widget or self.field.widget 

268 attrs = dict(attrs) # Copy attrs to avoid modifying the argument. 

269 if ( 

270 widget.use_required_attribute(self.initial) 

271 and self.field.required 

272 and self.form.use_required_attribute 

273 ): 

274 # MultiValueField has require_all_fields: if False, fall back 

275 # on subfields. 

276 if ( 

277 hasattr(self.field, "require_all_fields") 

278 and not self.field.require_all_fields 

279 and isinstance(self.field.widget, MultiWidget) 

280 ): 

281 for subfield, subwidget in zip(self.field.fields, widget.widgets): 

282 subwidget.attrs["required"] = ( 

283 subwidget.use_required_attribute(self.initial) 

284 and subfield.required 

285 ) 

286 else: 

287 attrs["required"] = True 

288 if self.field.disabled: 

289 attrs["disabled"] = True 

290 if not widget.is_hidden and self.errors: 

291 attrs["aria-invalid"] = "true" 

292 # Preserve aria-describedby provided by the attrs argument so user 

293 # can set the desired order. 

294 if not attrs.get("aria-describedby") and not self.use_fieldset: 

295 if aria_describedby := self.aria_describedby: 

296 attrs["aria-describedby"] = aria_describedby 

297 return attrs 

298 

299 @property 

300 def aria_describedby(self): 

301 # Preserve aria-describedby set on the widget. 

302 if self.field.widget.attrs.get("aria-describedby"): 

303 return None 

304 aria_describedby = [] 

305 if self.auto_id and not self.is_hidden: 

306 if self.help_text: 

307 aria_describedby.append(f"{self.auto_id}_helptext") 

308 if self.errors: 

309 aria_describedby.append(f"{self.auto_id}_error") 

310 return " ".join(aria_describedby) 

311 

312 @property 

313 def widget_type(self): 

314 return re.sub( 

315 r"widget$|input$", "", self.field.widget.__class__.__name__.lower() 

316 ) 

317 

318 @property 

319 def use_fieldset(self): 

320 """ 

321 Return the value of this BoundField widget's use_fieldset attribute. 

322 """ 

323 return self.field.widget.use_fieldset 

324 

325 

326@html_safe 

327class BoundWidget: 

328 """ 

329 A container class used for iterating over widgets. This is useful for 

330 widgets that have choices. For example, the following can be used in a 

331 template: 

332 

333 {% for radio in myform.beatles %} 

334 <label for="{{ radio.id_for_label }}"> 

335 {{ radio.choice_label }} 

336 <span class="radio">{{ radio.tag }}</span> 

337 </label> 

338 {% endfor %} 

339 """ 

340 

341 def __init__(self, parent_widget, data, renderer): 

342 self.parent_widget = parent_widget 

343 self.data = data 

344 self.renderer = renderer 

345 

346 def __str__(self): 

347 return self.tag(wrap_label=True) 

348 

349 def tag(self, wrap_label=False): 

350 context = {"widget": {**self.data, "wrap_label": wrap_label}} 

351 return self.parent_widget._render(self.template_name, context, self.renderer) 

352 

353 @property 

354 def template_name(self): 

355 if "template_name" in self.data: 

356 return self.data["template_name"] 

357 return self.parent_widget.template_name 

358 

359 @property 

360 def id_for_label(self): 

361 return self.data["attrs"].get("id") 

362 

363 @property 

364 def choice_label(self): 

365 return self.data["label"]