codekingpro/portable-devtools
114k
1import inspect2import itertools3import warnings4 5from markupsafe import escape6from markupsafe import Markup7 8from wtforms import widgets9from wtforms.i18n import DummyTranslations10from wtforms.utils import unset_value11from wtforms.validators import StopValidation12from wtforms.validators import ValidationError13 14 15class Field:16 """17 Field base class18 """19 20 errors = tuple()21 process_errors = tuple()22 raw_data = None23 validators = tuple()24 widget = None25 _formfield = True26 _translations = DummyTranslations()27 do_not_call_in_templates = True # Allow Django 1.4 traversal28 29 def __new__(cls, *args, **kwargs):30 if "_form" in kwargs:31 return super().__new__(cls)32 else:33 return UnboundField(cls, *args, **kwargs)34 35 def __init__(36 self,37 label=None,38 validators=None,39 filters=(),40 description="",41 id=None,42 default=None,43 widget=None,44 render_kw=None,45 name=None,46 _form=None,47 _prefix="",48 _translations=None,49 _meta=None,50 ):51 """52 Construct a new field.53 54 :param label:55 The label of the field.56 :param validators:57 A sequence of validators to call when `validate` is called.58 :param filters:59 A sequence of callable which are run by :meth:`~Field.process`60 to filter or transform the input data. For example61 ``StringForm(filters=[str.strip, str.upper])``.62 Note that filters are applied after processing the default and63 incoming data, but before validation.64 :param description:65 A description for the field, typically used for help text.66 :param id:67 An id to use for the field. A reasonable default is set by the form,68 and you shouldn't need to set this manually.69 :param default:70 The default value to assign to the field, if no form or object71 input is provided. May be a callable.72 :param widget:73 If provided, overrides the widget used to render the field.74 :param dict render_kw:75 If provided, a dictionary which provides default keywords that76 will be given to the widget at render time.77 :param name:78 The HTML name of this field. The default value is the Python79 attribute name.80 :param _form:81 The form holding this field. It is passed by the form itself during82 construction. You should never pass this value yourself.83 :param _prefix:84 The prefix to prepend to the form name of this field, passed by85 the enclosing form during construction.86 :param _translations:87 A translations object providing message translations. Usually88 passed by the enclosing form during construction. See89 :doc:`I18n docs <i18n>` for information on message translations.90 :param _meta:91 If provided, this is the 'meta' instance from the form. You usually92 don't pass this yourself.93 94 If `_form` isn't provided, an :class:`UnboundField` will be95 returned instead. Call its :func:`bind` method with a form instance and96 a name to construct the field.97 """98 if _translations is not None:99 self._translations = _translations100 101 if _meta is not None:102 self.meta = _meta103 elif _form is not None:104 self.meta = _form.meta105 else:106 raise TypeError("Must provide one of _form or _meta")107 108 self.default = default109 self.description = description110 self.render_kw = render_kw111 self.filters = filters112 self.flags = Flags()113 self.name = _prefix + name114 self.short_name = name115 self.type = type(self).__name__116 117 self.check_validators(validators)118 self.validators = validators or self.validators119 120 self.id = id or self.name121 self.label = Label(122 self.id,123 label124 if label is not None125 else self.gettext(name.replace("_", " ").title()),126 )127 128 if widget is not None:129 self.widget = widget130 131 for v in itertools.chain(self.validators, [self.widget]):132 flags = getattr(v, "field_flags", {})133 134 # check for legacy format, remove eventually135 if isinstance(flags, tuple): # pragma: no cover136 warnings.warn(137 "Flags should be stored in dicts and not in tuples. "138 "The next version of WTForms will abandon support "139 "for flags in tuples.",140 DeprecationWarning,141 stacklevel=2,142 )143 flags = {flag_name: True for flag_name in flags}144 145 for k, v in flags.items():146 setattr(self.flags, k, v)147 148 def __str__(self):149 """150 Returns a HTML representation of the field. For more powerful rendering,151 see the `__call__` method.152 """153 return self()154 155 def __html__(self):156 """157 Returns a HTML representation of the field. For more powerful rendering,158 see the :meth:`__call__` method.159 """160 return self()161 162 def __call__(self, **kwargs):163 """164 Render this field as HTML, using keyword args as additional attributes.165 166 This delegates rendering to167 :meth:`meta.render_field <wtforms.meta.DefaultMeta.render_field>`168 whose default behavior is to call the field's widget, passing any169 keyword arguments from this call along to the widget.170 171 In all of the WTForms HTML widgets, keyword arguments are turned to172 HTML attributes, though in theory a widget is free to do anything it173 wants with the supplied keyword arguments, and widgets don't have to174 even do anything related to HTML.175 """176 return self.meta.render_field(self, kwargs)177 178 @classmethod179 def check_validators(cls, validators):180 if validators is not None:181 for validator in validators:182 if not callable(validator):183 raise TypeError(184 "{} is not a valid validator because it is not "185 "callable".format(validator)186 )187 188 if inspect.isclass(validator):189 raise TypeError(190 "{} is not a valid validator because it is a class, "191 "it should be an instance".format(validator)192 )193 194 def gettext(self, string):195 """196 Get a translation for the given message.197 198 This proxies for the internal translations object.199 200 :param string: A string to be translated.201 :return: A string which is the translated output.202 """203 return self._translations.gettext(string)204 205 def ngettext(self, singular, plural, n):206 """207 Get a translation for a message which can be pluralized.208 209 :param str singular: The singular form of the message.210 :param str plural: The plural form of the message.211 :param int n: The number of elements this message is referring to212 """213 return self._translations.ngettext(singular, plural, n)214 215 def validate(self, form, extra_validators=()):216 """217 Validates the field and returns True or False. `self.errors` will218 contain any errors raised during validation. This is usually only219 called by `Form.validate`.220 221 Subfields shouldn't override this, but rather override either222 `pre_validate`, `post_validate` or both, depending on needs.223 224 :param form: The form the field belongs to.225 :param extra_validators: A sequence of extra validators to run.226 """227 self.errors = list(self.process_errors)228 stop_validation = False229 230 # Check the type of extra_validators231 self.check_validators(extra_validators)232 233 # Call pre_validate234 try:235 self.pre_validate(form)236 except StopValidation as e:237 if e.args and e.args[0]:238 self.errors.append(e.args[0])239 stop_validation = True240 except ValidationError as e:241 self.errors.append(e.args[0])242 243 # Run validators244 if not stop_validation:245 chain = itertools.chain(self.validators, extra_validators)246 stop_validation = self._run_validation_chain(form, chain)247 248 # Call post_validate249 try:250 self.post_validate(form, stop_validation)251 except ValidationError as e:252 self.errors.append(e.args[0])253 254 return len(self.errors) == 0255 256 def _run_validation_chain(self, form, validators):257 """258 Run a validation chain, stopping if any validator raises StopValidation.259 260 :param form: The Form instance this field belongs to.261 :param validators: a sequence or iterable of validator callables.262 :return: True if validation was stopped, False otherwise.263 """264 for validator in validators:265 try:266 validator(form, self)267 except StopValidation as e:268 if e.args and e.args[0]:269 self.errors.append(e.args[0])270 return True271 except ValidationError as e:272 self.errors.append(e.args[0])273 274 return False275 276 def pre_validate(self, form):277 """278 Override if you need field-level validation. Runs before any other279 validators.280 281 :param form: The form the field belongs to.282 """283 pass284 285 def post_validate(self, form, validation_stopped):286 """287 Override if you need to run any field-level validation tasks after288 normal validation. This shouldn't be needed in most cases.289 290 :param form: The form the field belongs to.291 :param validation_stopped:292 `True` if any validator raised StopValidation.293 """294 pass295 296 def process(self, formdata, data=unset_value, extra_filters=None):297 """298 Process incoming data, calling process_data, process_formdata as needed,299 and run filters.300 301 If `data` is not provided, process_data will be called on the field's302 default.303 304 Field subclasses usually won't override this, instead overriding the305 process_formdata and process_data methods. Only override this for306 special advanced processing, such as when a field encapsulates many307 inputs.308 309 :param extra_filters: A sequence of extra filters to run.310 """311 self.process_errors = []312 if data is unset_value:313 try:314 data = self.default()315 except TypeError:316 data = self.default317 318 self.object_data = data319 320 try:321 self.process_data(data)322 except ValueError as e:323 self.process_errors.append(e.args[0])324 325 if formdata is not None:326 if self.name in formdata:327 self.raw_data = formdata.getlist(self.name)328 else:329 self.raw_data = []330 331 try:332 self.process_formdata(self.raw_data)333 except ValueError as e:334 self.process_errors.append(e.args[0])335 336 try:337 for filter in itertools.chain(self.filters, extra_filters or []):338 self.data = filter(self.data)339 except ValueError as e:340 self.process_errors.append(e.args[0])341 342 def process_data(self, value):343 """344 Process the Python data applied to this field and store the result.345 346 This will be called during form construction by the form's `kwargs` or347 `obj` argument.348 349 :param value: The python object containing the value to process.350 """351 self.data = value352 353 def process_formdata(self, valuelist):354 """355 Process data received over the wire from a form.356 357 This will be called during form construction with data supplied358 through the `formdata` argument.359 360 :param valuelist: A list of strings to process.361 """362 if valuelist:363 self.data = valuelist[0]364 365 def populate_obj(self, obj, name):366 """367 Populates `obj.<name>` with the field's data.368 369 :note: This is a destructive operation. If `obj.<name>` already exists,370 it will be overridden. Use with caution.371 """372 setattr(obj, name, self.data)373 374 375class UnboundField:376 _formfield = True377 creation_counter = 0378 379 def __init__(self, field_class, *args, name=None, **kwargs):380 UnboundField.creation_counter += 1381 self.field_class = field_class382 self.args = args383 self.name = name384 self.kwargs = kwargs385 self.creation_counter = UnboundField.creation_counter386 validators = kwargs.get("validators")387 if validators:388 self.field_class.check_validators(validators)389 390 def bind(self, form, name, prefix="", translations=None, **kwargs):391 kw = dict(392 self.kwargs,393 name=name,394 _form=form,395 _prefix=prefix,396 _translations=translations,397 **kwargs,398 )399 return self.field_class(*self.args, **kw)400 401 def __repr__(self):402 return "<UnboundField({}, {!r}, {!r})>".format(403 self.field_class.__name__, self.args, self.kwargs404 )405 406 407class Flags:408 """409 Holds a set of flags as attributes.410 411 Accessing a non-existing attribute returns None for its value.412 """413 414 def __getattr__(self, name):415 if name.startswith("_"):416 return super().__getattr__(name)417 return None418 419 def __contains__(self, name):420 return getattr(self, name)421 422 def __repr__(self):423 flags = (424 f"{name}={getattr(self, name)}"425 for name in dir(self)426 if not name.startswith("_")427 )428 return "<wtforms.fields.Flags: {%s}>" % ", ".join(flags)429 430 431class Label:432 """433 An HTML form label.434 """435 436 def __init__(self, field_id, text):437 self.field_id = field_id438 self.text = text439 440 def __str__(self):441 return self()442 443 def __html__(self):444 return self()445 446 def __call__(self, text=None, **kwargs):447 if "for_" in kwargs:448 kwargs["for"] = kwargs.pop("for_")449 else:450 kwargs.setdefault("for", self.field_id)451 452 attributes = widgets.html_params(**kwargs)453 text = escape(text or self.text)454 return Markup(f"<label {attributes}>{text}</label>")455 456 def __repr__(self):457 return f"Label({self.field_id!r}, {self.text!r})"458 