Skip to content

Commit 4d9f946

Browse files
authored
Merge pull request #497 from MongoEngine/string_fields
New model form generator: Support of StringField based fields
2 parents a57b482 + 721e8c6 commit 4d9f946

File tree

11 files changed

+1167
-97
lines changed

11 files changed

+1167
-97
lines changed

docs/forms.md

Lines changed: 272 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Flask-WTF(WTForms) integration
1+
# WTForms integration
22

33
```{important}
44
Documentation below is related to project version 2.0.0 or higher, old versions has
@@ -48,15 +48,21 @@ For all fields, processed by Flask-Mongoengine integration:
4848
{attr}`validators` extension by Flask-Mongoengine.
4949
- If model field definition have {attr}`wtf_filters` defined, they will be forwarded to
5050
WTForm as {attr}`filters`.
51-
- If model field definition have {attr}`required`, then {class}`~wtforms.validators.InputRequired`
52-
will be added to form {attr}`validators`, otherwise {class}`~wtforms.validators.Optional`
51+
- If model field definition have {attr}`required`, then
52+
{class}`~wtforms.validators.InputRequired`
53+
will be added to form {attr}`validators`, otherwise
54+
{class}`~wtforms.validators.Optional`
5355
added.
5456
- If model field definition have {attr}`verbose_name` it will be used as form field
5557
{attr}`label`, otherwise pure field name used.
5658
- If model field definition have {attr}`help_text` it will be used as form field
5759
{attr}`description`, otherwise empty string used.
58-
- Field's {attr}`default` used as form {attr}`default`, that's why for string fields
59-
special {class}`~.NoneStringField` with `None` value support used.
60+
- Field's {attr}`default` used as form {attr}`default`, that's why special WTForms
61+
fields implementations was created. Details can be found in
62+
{mod}`flask_mongoengine.wtf.fields` module. In new form generator only 'Mongo'
63+
prefixed classes are used for fields, other classes are deprecated and will be
64+
removed in version **3.0.0**. If you have own nesting classes, you should check
65+
inheritance and make an update.
6066
- Field's {attr}`choices`, if exist, used as form {attr}`choices`.
6167

6268
```{warning}
@@ -99,7 +105,53 @@ Not yet documented. Please help us with new pull request.
99105

100106
### EmailField
101107

102-
Not yet documented. Please help us with new pull request.
108+
- API: {class}`.db_fields.EmailField`
109+
- Default form field class: {class}`~.MongoEmailField`
110+
111+
#### Form generation behaviour
112+
113+
Unlike [StringField] WTForm class of the field is not adjusted by normal form
114+
generation sequence and always match {class}`~.MongoEmailField`. All other
115+
adjustments, related to validators insert are work with EmailField in the same way,
116+
as in [StringField].
117+
118+
Additional {class}`~wtforms.validators.Email` validator is also inserted to form
119+
field, to exclude unnecessary database request, if form data incorrect.
120+
121+
Field respect user's adjustments in {attr}`wtf_field_class` option of
122+
{class}`.db_fields.EmailField`. This will change form field display, but will not
123+
change inserted validators.
124+
125+
#### Examples
126+
127+
strings_demo.py in example app contain basic non-requirement example. You can adjust
128+
it to any provided example for test purposes.
129+
130+
##### Not required EmailField
131+
132+
```python
133+
"""strings_demo.py"""
134+
from example_app.models import db
135+
136+
137+
class StringsDemoModel(db.Document):
138+
"""Documentation example model."""
139+
140+
url_field = db.EmailField()
141+
````
142+
143+
##### Required EmailField
144+
145+
```python
146+
"""strings_demo.py"""
147+
from example_app.models import db
148+
149+
150+
class StringsDemoModel(db.Document):
151+
"""Documentation example model."""
152+
153+
required_url_field = db.EmailField(required=True)
154+
````
103155

104156
### EmbeddedDocumentField
105157

@@ -131,11 +183,215 @@ Not yet documented. Please help us with new pull request.
131183

132184
### StringField
133185

134-
Not yet documented. Please help us with new pull request.
186+
- API: {class}`.db_fields.StringField`
187+
- Default form field class: Selected by field settings combination
188+
189+
#### Form generation behaviour
190+
191+
By default, during WTForm generation for fields without specified size (
192+
{attr}`min_length` or {attr}`max_length`) class {class}`.MongoTextAreaField` is used,
193+
in case when {attr}`min_length` or {attr}`max_length` set, then
194+
{class}`.MongoStringField` used and {class}`~wtforms.validators.Length` will be added
195+
to form field validators. This allows to keep documents of any size in mongodb.
196+
197+
In some cases class {class}`~.MongoStringField` is not the best choice for field, even
198+
with limited size. In this case user can easily overwrite generated field class by
199+
providing {attr}`wtf_field_class` on {class}`.db_fields.StringField` field declaration,
200+
as on document, as well as on form generation steps.
201+
202+
If database field definition has {attr}`regex` parameter set, then
203+
{class}`~wtforms.validators.Regexp` validator will be added to the form field.
204+
205+
#### Features deprecated
206+
207+
Field declaration step keyword arguments {attr}`password` and {attr}`textarea` are
208+
deprecated in Flask-Mongoengine version **2.0.0** and exist only to make migration
209+
steps easy.
210+
211+
To implement same behaviour, user should use {attr}`wtf_field_class` setting on
212+
{class}`.db_fields.StringField` init.
213+
214+
#### Related WTForm custom fields
215+
216+
Several special WTForms field implementation was created to support mongodb database
217+
behaviour and do not create any values in database, in case of empty fields. They
218+
can be used as {attr}`wtf_field_class` setting or independently. Some of them used
219+
in another database fields too, but all of them based on
220+
{class}`wtforms.fields.StringField` and {class}`~.EmptyStringIsNoneMixin`. You can use
221+
{class}`~.EmptyStringIsNoneMixin` for own field types.
222+
223+
- {class}`~.MongoEmailField`
224+
- {class}`~.MongoHiddenField`
225+
- {class}`~.MongoPasswordField`
226+
- {class}`~.MongoSearchField`
227+
- {class}`~.MongoStringField`
228+
- {class}`~.MongoTelField`
229+
- {class}`~.MongoTextAreaField`
230+
- {class}`~.MongoURLField`
231+
232+
#### Examples
233+
234+
strings_demo.py in example app contain basic non-requirement example. You can adjust
235+
it to any provided example for test purposes.
236+
237+
##### Not limited StringField as MongoTextAreaField
238+
239+
```python
240+
"""strings_demo.py"""
241+
from example_app.models import db
242+
243+
244+
class StringsDemoModel(db.Document):
245+
"""Documentation example model."""
246+
247+
string_field = db.StringField()
248+
```
249+
250+
##### Not limited StringField as MongoTelField
251+
252+
```python
253+
"""strings_demo.py"""
254+
from example_app.models import db
255+
from flask_mongoengine.wtf import fields as mongo_fields
256+
257+
258+
class StringsDemoModel(db.Document):
259+
"""Documentation example model."""
260+
261+
tel_field = db.StringField(wtf_field_class=mongo_fields.MongoTelField)
262+
```
263+
264+
##### Not limited StringField as MongoTextAreaField with https regex
265+
266+
[mongoengine] and [wtforms] projects are not consistent in how they work with regex.
267+
You will be safe, if you use {func}`re.compile` each time, when you work with regex
268+
settings, before parent projects itself.
269+
270+
```python
271+
"""strings_demo.py"""
272+
import re
273+
274+
from example_app.models import db
275+
276+
277+
class StringsDemoModel(db.Document):
278+
"""Documentation example model."""
279+
280+
regexp_string_field = db.StringField(regex=re.compile(
281+
r"^(https:\/\/)[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=]+$"
282+
))
283+
```
284+
285+
##### Size limited StringField as MongoStringField
286+
287+
```python
288+
"""strings_demo.py"""
289+
from example_app.models import db
290+
291+
292+
class StringsDemoModel(db.Document):
293+
"""Documentation example model."""
294+
295+
sized_string_field = db.StringField(min_length=5)
296+
```
297+
298+
##### Required password field with minimum size
299+
300+
```python
301+
"""strings_demo.py"""
302+
from example_app.models import db
303+
from flask_mongoengine.wtf import fields as mongo_fields
304+
305+
306+
class StringsDemoModel(db.Document):
307+
"""Documentation example model."""
308+
309+
password_field = db.StringField(
310+
wtf_field_class=mongo_fields.MongoPasswordField,
311+
required=True,
312+
min_length=5,
313+
)
314+
```
135315

136316
### URLField
137317

138-
Not yet documented. Please help us with new pull request.
318+
- API: {class}`.db_fields.URLField`
319+
- Default form field class: {class}`~.MongoURLField`
320+
321+
#### Form generation behaviour
322+
323+
Unlike [StringField] WTForm class of the field is not adjusted by normal form
324+
generation sequence and always match {class}`~.MongoURLField`. All other
325+
adjustments, related to validators insert are work with EmailField in the same way,
326+
as in [StringField].
327+
328+
Additional {class}`~wtforms.validators.Regexp` validator is also inserted to form
329+
field, to exclude unnecessary database request, if form data incorrect. This
330+
validator use regexp, provided in {attr}`url_regex` of {class}`.db_fields.URLField`,
331+
or default URL regexp from [mongoengine] project. This is different from
332+
Flask-Mongoengine version **1.0.0** or earlier, where {class}`~wtforms.validators.URL`
333+
was inserted. This was changed, to exclude validators conflicts.
334+
335+
```{important}
336+
{func}`~.model_form` is still use {class}`~wtforms.validators.URL` for
337+
compatibility with old setups.
338+
```
339+
340+
Field respect user's adjustments in {attr}`wtf_field_class` option of
341+
{class}`.db_fields.URLField`. This will change form field display, but will not
342+
change inserted validators.
343+
344+
#### Examples
345+
346+
strings_demo.py in example app contain basic non-requirement example. You can adjust
347+
it to any provided example for test purposes.
348+
349+
##### Not required URLField
350+
351+
```python
352+
"""strings_demo.py"""
353+
from example_app.models import db
354+
355+
356+
class StringsDemoModel(db.Document):
357+
"""Documentation example model."""
358+
359+
url_field = db.URLField()
360+
````
361+
362+
##### Required URLField with minimum size
363+
364+
```python
365+
"""strings_demo.py"""
366+
from example_app.models import db
367+
368+
369+
class StringsDemoModel(db.Document):
370+
"""Documentation example model."""
371+
372+
required_url_field = db.URLField(required=True, min_length=25)
373+
````
374+
375+
##### URLField with https only regexp check, if data exist
376+
377+
Regexp for {attr}`url_regex` should be prepared by {mod}`re`.
378+
379+
```python
380+
"""strings_demo.py"""
381+
import re
382+
383+
from example_app.models import db
384+
385+
386+
class StringsDemoModel(db.Document):
387+
"""Documentation example model."""
388+
389+
https_url_field = db.URLField(
390+
url_regex=re.compile(
391+
r"^(https:\/\/)[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=]+$"
392+
),
393+
)
394+
````
139395

140396
## Unsupported fields
141397

@@ -234,7 +490,15 @@ Not yet documented. Please help us with new pull request.
234490
Not yet documented. Please help us with new pull request.
235491

236492
[mongoengine]: https://docs.mongoengine.org/
493+
237494
[supported fields]: #supported-fields
495+
238496
[#379]: https://github.com/MongoEngine/flask-mongoengine/issues/379
497+
239498
[integration]: forms
499+
240500
[global transforms]: #global-transforms
501+
502+
[stringfield]: #stringfield
503+
504+
[wtforms]: https://wtforms.readthedocs.io/en/3.0.x/

example_app/app.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from example_app import views
66
from example_app.models import db
7+
from example_app.strings_demo import strings_demo_view
78
from flask_mongoengine.panels import mongo_command_logger
89

910
app = flask.Flask("example_app")
@@ -40,6 +41,10 @@
4041

4142
app.add_url_rule("/", view_func=views.index, methods=["GET", "POST"])
4243
app.add_url_rule("/pagination", view_func=views.pagination, methods=["GET", "POST"])
44+
app.add_url_rule("/strings_demo", view_func=strings_demo_view, methods=["GET", "POST"])
45+
app.add_url_rule(
46+
"/strings_demo/<pk>/", view_func=strings_demo_view, methods=["GET", "POST"]
47+
)
4348

4449
if __name__ == "__main__":
4550
app.run(host="0.0.0.0", port=8000)

example_app/strings_demo.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Strings and strings related fields demo model."""
2+
import re
3+
4+
from flask import render_template, request
5+
6+
from example_app.models import db
7+
from flask_mongoengine.wtf import fields as mongo_fields
8+
9+
10+
class StringsDemoModel(db.Document):
11+
"""Documentation example model."""
12+
13+
string_field = db.StringField()
14+
regexp_string_field = db.StringField(
15+
regex=re.compile(
16+
r"^(https:\/\/)[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=]+$"
17+
)
18+
)
19+
sized_string_field = db.StringField(min_length=5)
20+
tel_field = db.StringField(wtf_field_class=mongo_fields.MongoTelField)
21+
password_field = db.StringField(
22+
wtf_field_class=mongo_fields.MongoPasswordField,
23+
required=True,
24+
min_length=5,
25+
)
26+
email_field = db.EmailField()
27+
url_field = db.URLField()
28+
29+
30+
StringsDemoForm = StringsDemoModel.to_wtf_form()
31+
32+
33+
def strings_demo_view(pk=None):
34+
"""Return all fields demonstration."""
35+
form = StringsDemoForm()
36+
obj = None
37+
if pk:
38+
obj = StringsDemoModel.objects.get(pk=pk)
39+
form = StringsDemoForm(obj=obj)
40+
41+
if request.method == "POST" and form.validate_on_submit():
42+
if pk:
43+
form.populate_obj(obj)
44+
obj.save()
45+
else:
46+
form.save()
47+
page_num = int(request.args.get("page") or 1)
48+
page = StringsDemoModel.objects.paginate(page=page_num, per_page=100)
49+
50+
return render_template(
51+
"strings_demo.html", page=page, form=form, model=StringsDemoModel
52+
)

0 commit comments

Comments
 (0)