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

136 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:32 +0000

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._prefix = prefix 

34 self._fields = OrderedDict() 

35 

36 if hasattr(fields, "items"): 

37 fields = fields.items() 

38 

39 translations = self.meta.get_translations(self) 

40 extra_fields = [] 

41 if meta.csrf: 

42 self._csrf = meta.build_csrf(self) 

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

44 

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

46 field_name = unbound_field.name or name 

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

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

49 self._fields[name] = field 

50 

51 self.form_errors = [] 

52 

53 def __iter__(self): 

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

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

56 

57 def __contains__(self, name): 

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

59 return name in self._fields 

60 

61 def __getitem__(self, name): 

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

63 return self._fields[name] 

64 

65 def __setitem__(self, name, value): 

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

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

68 

69 def __delitem__(self, name): 

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

71 del self._fields[name] 

72 

73 def populate_obj(self, obj): 

74 """ 

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

76 fields. 

77 

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

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

80 """ 

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

82 field.populate_obj(obj, name) 

83 

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

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

86 

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

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

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

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

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

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

93 not passed. 

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

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

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

97 passed. 

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

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

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

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

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

103 data as parameters. Overwrites any duplicate keys in 

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

105 """ 

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

107 

108 if data is not None: 

109 kwargs = dict(data, **kwargs) 

110 

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

112 

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

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

115 

116 inline_filter = getattr(self, "filter_%s" % name, None) 

117 if inline_filter is not None: 

118 field_extra_filters.append(inline_filter) 

119 

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

121 data = getattr(obj, name) 

122 elif name in kwargs: 

123 data = kwargs[name] 

124 else: 

125 data = unset_value 

126 

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

128 

129 def validate(self, extra_validators=None): 

130 """ 

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

132 

133 :param extra_validators: 

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

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

136 `validate` method. 

137 

138 Returns `True` if no errors occur. 

139 """ 

140 success = True 

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

142 if extra_validators is not None and name in extra_validators: 

143 extra = extra_validators[name] 

144 else: 

145 extra = tuple() 

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

147 success = False 

148 return success 

149 

150 @property 

151 def data(self): 

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

153 

154 @property 

155 def errors(self): 

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

157 if self.form_errors: 

158 errors[None] = self.form_errors 

159 return errors 

160 

161 

162class FormMeta(type): 

163 """ 

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

165 

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

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

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

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

170 re-generated on the next instantiation. 

171 

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

173 instances are ignored by the metaclass. 

174 """ 

175 

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

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

178 cls._unbound_fields = None 

179 cls._wtforms_meta = None 

180 

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

182 """ 

183 Construct a new `Form` instance. 

184 

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

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

187 hierarchy. 

188 """ 

189 if cls._unbound_fields is None: 

190 fields = [] 

191 for name in dir(cls): 

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

193 unbound_field = getattr(cls, name) 

194 if hasattr(unbound_field, "_formfield"): 

195 fields.append((name, unbound_field)) 

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

197 # to ensure a stable sort. 

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

199 cls._unbound_fields = fields 

200 

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

202 if cls._wtforms_meta is None: 

203 bases = [] 

204 for mro_class in cls.__mro__: 

205 if "Meta" in mro_class.__dict__: 

206 bases.append(mro_class.Meta) 

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

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

209 

210 def __setattr__(cls, name, value): 

211 """ 

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

213 """ 

214 if name == "Meta": 

215 cls._wtforms_meta = None 

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

217 cls._unbound_fields = None 

218 type.__setattr__(cls, name, value) 

219 

220 def __delattr__(cls, name): 

221 """ 

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

223 needed. 

224 """ 

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

226 cls._unbound_fields = None 

227 type.__delattr__(cls, name) 

228 

229 

230class Form(BaseForm, metaclass=FormMeta): 

231 """ 

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

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

234 

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

236 and passed to `process()`. 

237 """ 

238 

239 Meta = DefaultMeta 

240 

241 def __init__( 

242 self, 

243 formdata=None, 

244 obj=None, 

245 prefix="", 

246 data=None, 

247 meta=None, 

248 **kwargs, 

249 ): 

250 """ 

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

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

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

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

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

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

257 not passed. 

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

259 prefixed with the value. This is for distinguishing multiple 

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

261 matching input data, not the Python name for matching 

262 existing data. 

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

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

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

266 passed. 

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

268 :attr:`meta` instance. 

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

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

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

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

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

274 data as parameters. Overwrites any duplicate keys in 

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

276 """ 

277 meta_obj = self._wtforms_meta() 

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

279 meta_obj.update_values(meta) 

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

281 

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

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

284 # attributes with the same names. 

285 setattr(self, name, field) 

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

287 

288 def __setitem__(self, name, value): 

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

290 

291 def __delitem__(self, name): 

292 del self._fields[name] 

293 setattr(self, name, None) 

294 

295 def __delattr__(self, name): 

296 if name in self._fields: 

297 self.__delitem__(name) 

298 else: 

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

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

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

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

303 setattr(self, name, None) 

304 else: 

305 super().__delattr__(name) 

306 

307 def validate(self, extra_validators=None): 

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

309 Returns ``True`` if validation passes. 

310 

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

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

313 

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

315 extra validator methods to run. Extra validators run after 

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

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

318 """ 

319 if extra_validators is not None: 

320 extra = extra_validators.copy() 

321 else: 

322 extra = {} 

323 

324 for name in self._fields: 

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

326 if inline is not None: 

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

328 

329 return super().validate(extra)