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
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:32 +0000
1import itertools
2from collections import OrderedDict
4from wtforms.meta import DefaultMeta
5from wtforms.utils import unset_value
7__all__ = ("BaseForm", "Form")
9_default_meta = DefaultMeta()
12class BaseForm:
13 """
14 Base Form Class. Provides core behaviour like field construction,
15 validation, and data and error proxying.
16 """
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 += "-"
32 self.meta = meta
33 self._prefix = prefix
34 self._fields = OrderedDict()
36 if hasattr(fields, "items"):
37 fields = fields.items()
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))
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
51 self.form_errors = []
53 def __iter__(self):
54 """Iterate form fields in creation order."""
55 return iter(self._fields.values())
57 def __contains__(self, name):
58 """Returns `True` if the named field is a member of this form."""
59 return name in self._fields
61 def __getitem__(self, name):
62 """Dict-style access to this form's fields."""
63 return self._fields[name]
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)
69 def __delitem__(self, name):
70 """Remove a field from this form."""
71 del self._fields[name]
73 def populate_obj(self, obj):
74 """
75 Populates the attributes of the passed `obj` with data from the form's
76 fields.
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)
84 def process(self, formdata=None, obj=None, data=None, extra_filters=None, **kwargs):
85 """Process default and input data with each field.
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)
108 if data is not None:
109 kwargs = dict(data, **kwargs)
111 filters = extra_filters.copy() if extra_filters is not None else {}
113 for name, field in self._fields.items():
114 field_extra_filters = filters.get(name, [])
116 inline_filter = getattr(self, "filter_%s" % name, None)
117 if inline_filter is not None:
118 field_extra_filters.append(inline_filter)
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
127 field.process(formdata, data, extra_filters=field_extra_filters)
129 def validate(self, extra_validators=None):
130 """
131 Validates the form by calling `validate` on each field.
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.
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
150 @property
151 def data(self):
152 return {name: f.data for name, f in self._fields.items()}
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
162class FormMeta(type):
163 """
164 The metaclass for `Form` and any subclasses of `Form`.
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.
172 Any properties which begin with an underscore or are not `UnboundField`
173 instances are ignored by the metaclass.
174 """
176 def __init__(cls, name, bases, attrs):
177 type.__init__(cls, name, bases, attrs)
178 cls._unbound_fields = None
179 cls._wtforms_meta = None
181 def __call__(cls, *args, **kwargs):
182 """
183 Construct a new `Form` instance.
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
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)
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)
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)
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.
235 In addition, form and instance input data are taken at construction time
236 and passed to `process()`.
237 """
239 Meta = DefaultMeta
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)
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)
288 def __setitem__(self, name, value):
289 raise TypeError("Fields may not be added to Form instances, only classes.")
291 def __delitem__(self, name):
292 del self._fields[name]
293 setattr(self, name, None)
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)
307 def validate(self, extra_validators=None):
308 """Validate the form by calling ``validate`` on each field.
309 Returns ``True`` if validation passes.
311 If the form defines a ``validate_<fieldname>`` method, it is
312 appended as an extra validator for the field's ``validate``.
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 = {}
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)
329 return super().validate(extra)