|
5 | 5 | "ModelSelectField", |
6 | 6 | "QuerySetSelectField", |
7 | 7 | ] |
8 | | -from gettext import gettext as _ |
9 | | -from typing import Optional |
| 8 | +from typing import Callable, Optional |
10 | 9 |
|
11 | 10 | from flask import json |
12 | 11 | from mongoengine.queryset import DoesNotExist |
@@ -63,6 +62,7 @@ def __init__( |
63 | 62 | label_modifier=None, |
64 | 63 | **kwargs, |
65 | 64 | ): |
| 65 | + """Init docstring placeholder.""" |
66 | 66 |
|
67 | 67 | super(QuerySetSelectField, self).__init__(label, validators, **kwargs) |
68 | 68 | self.label_attr = label_attr |
@@ -122,7 +122,7 @@ def pre_validate(self, form): |
122 | 122 | :param form: The form the field belongs to. |
123 | 123 | """ |
124 | 124 | if (not self.allow_blank or self.data is not None) and not self.data: |
125 | | - raise wtf_validators.ValidationError(_("Not a valid choice")) |
| 125 | + raise wtf_validators.ValidationError(self.gettext("Not a valid choice")) |
126 | 126 |
|
127 | 127 | def _is_selected(self, item): |
128 | 128 | return item == self.data |
@@ -414,3 +414,72 @@ class MongoFloatField(wtf_fields.FloatField): |
414 | 414 | """ |
415 | 415 |
|
416 | 416 | widget = wtf_widgets.NumberInput(step="any") |
| 417 | + |
| 418 | + |
| 419 | +class MongoDictField(MongoTextAreaField): |
| 420 | + """Form field to handle JSON in :class:`~flask_mongoengine.db_fields.DictField`.""" |
| 421 | + |
| 422 | + def __init__( |
| 423 | + self, |
| 424 | + json_encoder: Optional[Callable] = None, |
| 425 | + json_encoder_kwargs: Optional[dict] = None, |
| 426 | + json_decoder: Optional[Callable] = None, |
| 427 | + json_decoder_kwargs: Optional[dict] = None, |
| 428 | + *args, |
| 429 | + **kwargs, |
| 430 | + ): |
| 431 | + """ |
| 432 | + Special WTForms field for :class:`~flask_mongoengine.db_fields.DictField` |
| 433 | +
|
| 434 | + Configuration available with providing :attr:`wtf_options` on |
| 435 | + :class:`~flask_mongoengine.db_fields.DictField` initialization. |
| 436 | +
|
| 437 | + :param json_encoder: Any function, capable to transform dict to string, by |
| 438 | + default :func:`json.dumps` |
| 439 | + :param json_encoder_kwargs: Any dictionary with parameters to |
| 440 | + :func:`json_encoder`, by default: `{"indent":4}` |
| 441 | + :param json_decoder: Any function, capable to transform string to dict, by |
| 442 | + default :func:`json.loads` |
| 443 | + :param json_decoder_kwargs: Any dictionary with parameters to |
| 444 | + :func:`json_decoder`, by default: `{}` |
| 445 | + """ |
| 446 | + |
| 447 | + self.json_encoder = json_encoder or json.dumps |
| 448 | + self.json_encoder_kwargs = json_encoder_kwargs or {"indent": 4} |
| 449 | + self.json_decoder = json_decoder or json.loads |
| 450 | + self.json_decoder_kwargs = json_decoder_kwargs or {} |
| 451 | + self.data = None |
| 452 | + super().__init__(*args, **kwargs) |
| 453 | + |
| 454 | + def _parse_json_data(self): |
| 455 | + """Tries to load JSON data with python internal JSON library.""" |
| 456 | + try: |
| 457 | + self.data = self.json_decoder(self.data, **self.json_decoder_kwargs) |
| 458 | + except ValueError as error: |
| 459 | + raise wtf_validators.ValidationError( |
| 460 | + self.gettext(f"Cannot load data: {error}") |
| 461 | + ) from error |
| 462 | + |
| 463 | + def _ensure_data_is_dict(self): |
| 464 | + """Ensures that saved data is dict, not a list or other valid parsed JSON.""" |
| 465 | + if not isinstance(self.data, dict): |
| 466 | + raise wtf_validators.ValidationError( |
| 467 | + self.gettext("Not a valid dictionary (list input detected).") |
| 468 | + ) |
| 469 | + |
| 470 | + def process_formdata(self, valuelist): |
| 471 | + """Process text form data to dictionary or raise JSONDecodeError.""" |
| 472 | + super().process_formdata(valuelist) |
| 473 | + if self.data is not None: |
| 474 | + self._parse_json_data() |
| 475 | + self._ensure_data_is_dict() |
| 476 | + |
| 477 | + def _value(self): |
| 478 | + """Show existing data as pretty-formatted, or show raw data/empty dict.""" |
| 479 | + if self.data: |
| 480 | + if isinstance(self.data, dict): |
| 481 | + return self.json_encoder(self.data, **self.json_encoder_kwargs) |
| 482 | + else: |
| 483 | + # This allows to fix/see input errors, without escaping. |
| 484 | + return self.data |
| 485 | + return "{}" |
0 commit comments