Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wtforms/form.py: 28%

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

137 statements  

1import itertools 

2from collections import OrderedDict 

3 

4from wtforms.meta import DefaultMeta 

5from wtforms.utils import unset_value 

6 

7__all__ = ("BaseForm", "Form") 

8 

9_default_meta = DefaultMeta() 

10 

11 

12class BaseForm: 

13 """ 

14 Base Form Class. Provides core behaviour like field construction, 

15 validation, and data and error proxying. 

16 """ 

17 

18 def __init__(self, fields, prefix="", meta=_default_meta): 

19 """ 

20 :param fields: 

21 A dict or sequence of 2-tuples of partially-constructed fields. 

22 :param prefix: 

23 If provided, all fields will have their name prefixed with the 

24 value. 

25 :param meta: 

26 A meta instance which is used for configuration and customization 

27 of WTForms behaviors. 

28 """ 

29 if prefix and prefix[-1] not in "-_;:/.": 

30 prefix += "-" 

31 

32 self.meta = meta 

33 self._form_error_key = "" 

34 self._prefix = prefix 

35 self._fields = OrderedDict() 

36 

37 if hasattr(fields, "items"): 

38 fields = fields.items() 

39 

40 translations = self.meta.get_translations(self) 

41 extra_fields = [] 

42 if meta.csrf: 

43 self._csrf = meta.build_csrf(self) 

44 extra_fields.extend(self._csrf.setup_form(self)) 

45 

46 for name, unbound_field in itertools.chain(fields, extra_fields): 

47 field_name = unbound_field.name or name 

48 options = dict(name=field_name, prefix=prefix, translations=translations) 

49 field = meta.bind_field(self, unbound_field, options) 

50 self._fields[name] = field 

51 

52 self.form_errors = [] 

53 

54 def __iter__(self): 

55 """Iterate form fields in creation order.""" 

56 return iter(self._fields.values()) 

57 

58 def __contains__(self, name): 

59 """Returns `True` if the named field is a member of this form.""" 

60 return name in self._fields 

61 

62 def __getitem__(self, name): 

63 """Dict-style access to this form's fields.""" 

64 return self._fields[name] 

65 

66 def __setitem__(self, name, value): 

67 """Bind a field to this form.""" 

68 self._fields[name] = value.bind(form=self, name=name, prefix=self._prefix) 

69 

70 def __delitem__(self, name): 

71 """Remove a field from this form.""" 

72 del self._fields[name] 

73 

74 def populate_obj(self, obj): 

75 """ 

76 Populates the attributes of the passed `obj` with data from the form's 

77 fields. 

78 

79 :note: This is a destructive operation; Any attribute with the same name 

80 as a field will be overridden. Use with caution. 

81 """ 

82 for name, field in self._fields.items(): 

83 field.populate_obj(obj, name) 

84 

85 def process(self, formdata=None, obj=None, data=None, extra_filters=None, **kwargs): 

86 """Process default and input data with each field. 

87 

88 :param formdata: Input data coming from the client, usually 

89 ``request.form`` or equivalent. Should provide a "multi 

90 dict" interface to get a list of values for a given key, 

91 such as what Werkzeug, Django, and WebOb provide. 

92 :param obj: Take existing data from attributes on this object 

93 matching form field attributes. Only used if ``formdata`` is 

94 not passed. 

95 :param data: Take existing data from keys in this dict matching 

96 form field attributes. ``obj`` takes precedence if it also 

97 has a matching attribute. Only used if ``formdata`` is not 

98 passed. 

99 :param extra_filters: A dict mapping field attribute names to 

100 lists of extra filter functions to run. Extra filters run 

101 after filters passed when creating the field. If the form 

102 has ``filter_<fieldname>``, it is the last extra filter. 

103 :param kwargs: Merged with ``data`` to allow passing existing 

104 data as parameters. Overwrites any duplicate keys in 

105 ``data``. Only used if ``formdata`` is not passed. 

106 """ 

107 formdata = self.meta.wrap_formdata(self, formdata) 

108 

109 if data is not None: 

110 kwargs = dict(data, **kwargs) 

111 

112 filters = extra_filters.copy() if extra_filters is not None else {} 

113 

114 for name, field in self._fields.items(): 

115 field_extra_filters = filters.get(name, []) 

116 

117 inline_filter = getattr(self, f"filter_{name}", None) 

118 if inline_filter is not None: 

119 field_extra_filters.append(inline_filter) 

120 

121 if obj is not None and hasattr(obj, name): 

122 data = getattr(obj, name) 

123 elif name in kwargs: 

124 data = kwargs[name] 

125 else: 

126 data = unset_value 

127 

128 field.process(formdata, data, extra_filters=field_extra_filters) 

129 

130 def validate(self, extra_validators=None): 

131 """ 

132 Validates the form by calling `validate` on each field. 

133 

134 :param extra_validators: 

135 If provided, is a dict mapping field names to a sequence of 

136 callables which will be passed as extra validators to the field's 

137 `validate` method. 

138 

139 Returns `True` if no errors occur. 

140 """ 

141 success = True 

142 for name, field in self._fields.items(): 

143 if extra_validators is not None and name in extra_validators: 

144 extra = extra_validators[name] 

145 else: 

146 extra = tuple() 

147 if not field.validate(self, extra): 

148 success = False 

149 return success 

150 

151 @property 

152 def data(self): 

153 return {name: f.data for name, f in self._fields.items()} 

154 

155 @property 

156 def errors(self): 

157 errors = {name: f.errors for name, f in self._fields.items() if f.errors} 

158 if self.form_errors: 

159 errors[self._form_error_key] = self.form_errors 

160 return errors 

161 

162 

163class FormMeta(type): 

164 """ 

165 The metaclass for `Form` and any subclasses of `Form`. 

166 

167 `FormMeta`'s responsibility is to create the `_unbound_fields` list, which 

168 is a list of `UnboundField` instances sorted by their order of 

169 instantiation. The list is created at the first instantiation of the form. 

170 If any fields are added/removed from the form, the list is cleared to be 

171 re-generated on the next instantiation. 

172 

173 Any properties which begin with an underscore or are not `UnboundField` 

174 instances are ignored by the metaclass. 

175 """ 

176 

177 def __init__(cls, name, bases, attrs): 

178 type.__init__(cls, name, bases, attrs) 

179 cls._unbound_fields = None 

180 cls._wtforms_meta = None 

181 

182 def __call__(cls, *args, **kwargs): 

183 """ 

184 Construct a new `Form` instance. 

185 

186 Creates the `_unbound_fields` list and the internal `_wtforms_meta` 

187 subclass of the class Meta in order to allow a proper inheritance 

188 hierarchy. 

189 """ 

190 if cls._unbound_fields is None: 

191 fields = [] 

192 for name in dir(cls): 

193 if not name.startswith("_"): 

194 unbound_field = getattr(cls, name) 

195 if hasattr(unbound_field, "_formfield"): 

196 fields.append((name, unbound_field)) 

197 # We keep the name as the second element of the sort 

198 # to ensure a stable sort. 

199 fields.sort(key=lambda x: (x[1].creation_counter, x[0])) 

200 cls._unbound_fields = fields 

201 

202 # Create a subclass of the 'class Meta' using all the ancestors. 

203 if cls._wtforms_meta is None: 

204 bases = [] 

205 for mro_class in cls.__mro__: 

206 if "Meta" in mro_class.__dict__: 

207 bases.append(mro_class.Meta) 

208 cls._wtforms_meta = type("Meta", tuple(bases), {}) 

209 return type.__call__(cls, *args, **kwargs) 

210 

211 def __setattr__(cls, name, value): 

212 """ 

213 Add an attribute to the class, clearing `_unbound_fields` if needed. 

214 """ 

215 if name == "Meta": 

216 cls._wtforms_meta = None 

217 elif not name.startswith("_") and hasattr(value, "_formfield"): 

218 cls._unbound_fields = None 

219 type.__setattr__(cls, name, value) 

220 

221 def __delattr__(cls, name): 

222 """ 

223 Remove an attribute from the class, clearing `_unbound_fields` if 

224 needed. 

225 """ 

226 if not name.startswith("_"): 

227 cls._unbound_fields = None 

228 type.__delattr__(cls, name) 

229 

230 

231class Form(BaseForm, metaclass=FormMeta): 

232 """ 

233 Declarative Form base class. Extends BaseForm's core behaviour allowing 

234 fields to be defined on Form subclasses as class attributes. 

235 

236 In addition, form and instance input data are taken at construction time 

237 and passed to `process()`. 

238 """ 

239 

240 Meta = DefaultMeta 

241 

242 def __init__( 

243 self, 

244 formdata=None, 

245 obj=None, 

246 prefix="", 

247 data=None, 

248 meta=None, 

249 **kwargs, 

250 ): 

251 """ 

252 :param formdata: Input data coming from the client, usually 

253 ``request.form`` or equivalent. Should provide a "multi 

254 dict" interface to get a list of values for a given key, 

255 such as what Werkzeug, Django, and WebOb provide. 

256 :param obj: Take existing data from attributes on this object 

257 matching form field attributes. Only used if ``formdata`` is 

258 not passed. 

259 :param prefix: If provided, all fields will have their name 

260 prefixed with the value. This is for distinguishing multiple 

261 forms on a single page. This only affects the HTML name for 

262 matching input data, not the Python name for matching 

263 existing data. 

264 :param data: Take existing data from keys in this dict matching 

265 form field attributes. ``obj`` takes precedence if it also 

266 has a matching attribute. Only used if ``formdata`` is not 

267 passed. 

268 :param meta: A dict of attributes to override on this form's 

269 :attr:`meta` instance. 

270 :param extra_filters: A dict mapping field attribute names to 

271 lists of extra filter functions to run. Extra filters run 

272 after filters passed when creating the field. If the form 

273 has ``filter_<fieldname>``, it is the last extra filter. 

274 :param kwargs: Merged with ``data`` to allow passing existing 

275 data as parameters. Overwrites any duplicate keys in 

276 ``data``. Only used if ``formdata`` is not passed. 

277 """ 

278 meta_obj = self._wtforms_meta() 

279 if meta is not None and isinstance(meta, dict): 

280 meta_obj.update_values(meta) 

281 super().__init__(self._unbound_fields, meta=meta_obj, prefix=prefix) 

282 

283 for name, field in self._fields.items(): 

284 # Set all the fields to attributes so that they obscure the class 

285 # attributes with the same names. 

286 setattr(self, name, field) 

287 self.process(formdata, obj, data=data, **kwargs) 

288 

289 def __setitem__(self, name, value): 

290 raise TypeError("Fields may not be added to Form instances, only classes.") 

291 

292 def __delitem__(self, name): 

293 del self._fields[name] 

294 setattr(self, name, None) 

295 

296 def __delattr__(self, name): 

297 if name in self._fields: 

298 self.__delitem__(name) 

299 else: 

300 # This is done for idempotency, if we have a name which is a field, 

301 # we want to mask it by setting the value to None. 

302 unbound_field = getattr(self.__class__, name, None) 

303 if unbound_field is not None and hasattr(unbound_field, "_formfield"): 

304 setattr(self, name, None) 

305 else: 

306 super().__delattr__(name) 

307 

308 def validate(self, extra_validators=None): 

309 """Validate the form by calling ``validate`` on each field. 

310 Returns ``True`` if validation passes. 

311 

312 If the form defines a ``validate_<fieldname>`` method, it is 

313 appended as an extra validator for the field's ``validate``. 

314 

315 :param extra_validators: A dict mapping field names to lists of 

316 extra validator methods to run. Extra validators run after 

317 validators passed when creating the field. If the form has 

318 ``validate_<fieldname>``, it is the last extra validator. 

319 """ 

320 if extra_validators is not None: 

321 extra = extra_validators.copy() 

322 else: 

323 extra = {} 

324 

325 for name in self._fields: 

326 inline = getattr(self.__class__, f"validate_{name}", None) 

327 if inline is not None: 

328 extra.setdefault(name, []).append(inline) 

329 

330 return super().validate(extra)