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

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

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

105 

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) 

111 

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) 

115 

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) 

121 

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) 

128 

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) 

138 

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) 

155 

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. 

161 

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

163 

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) 

197 

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) 

210 

211 @property 

212 def is_hidden(self): 

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

214 return self.field.widget.is_hidden 

215 

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

228 

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

239 

240 @cached_property 

241 def initial(self): 

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

243 

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 

269 

270 @property 

271 def widget_type(self): 

272 return re.sub( 

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

274 ) 

275 

276 

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: 

283 

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

291 

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

293 self.parent_widget = parent_widget 

294 self.data = data 

295 self.renderer = renderer 

296 

297 def __str__(self): 

298 return self.tag(wrap_label=True) 

299 

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) 

303 

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 

309 

310 @property 

311 def id_for_label(self): 

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

313 

314 @property 

315 def choice_label(self): 

316 return self.data["label"]