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

1import re 

2 

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 _ 

9 

10__all__ = ("BoundField",) 

11 

12 

13@html_safe 

14class BoundField: 

15 "A Field plus data" 

16 

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 "" 

29 

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() 

35 

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. 

41 

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 ] 

54 

55 def __bool__(self): 

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

57 return True 

58 

59 def __iter__(self): 

60 return iter(self.subwidgets) 

61 

62 def __len__(self): 

63 return len(self.subwidgets) 

64 

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] 

74 

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 ) 

83 

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 ) 

113 

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) 

119 

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) 

123 

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) 

129 

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) 

136 

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) 

146 

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) 

163 

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. 

169 

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

171 

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) 

206 

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. 

212 

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

214 

215 label_suffix overrides the form's label_suffix. 

216 """ 

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

218 

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) 

231 

232 @property 

233 def is_hidden(self): 

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

235 return self.field.widget.is_hidden 

236 

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 "" 

249 

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_) 

260 

261 @cached_property 

262 def initial(self): 

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

264 

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 

290 

291 @property 

292 def widget_type(self): 

293 return re.sub( 

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

295 ) 

296 

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 

303 

304 

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: 

311 

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 """ 

319 

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

321 self.parent_widget = parent_widget 

322 self.data = data 

323 self.renderer = renderer 

324 

325 def __str__(self): 

326 return self.tag(wrap_label=True) 

327 

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) 

331 

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 

337 

338 @property 

339 def id_for_label(self): 

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

341 

342 @property 

343 def choice_label(self): 

344 return self.data["label"]