If you arrived here, you probably know mdform. If not, briefly it allows you to write a form in an easy-to-read plain text format that can be parsed to produce a fully functional form. You can checkout its expressive syntax in the mdform repo.
Flask-mdform brings this power to Flask by providing functions and decorators to generate Flask-WTF forms within your app.
pip install flask-mdform
Just put your markdwn form files in templates/md/, for example an hypothetical form to collect information named personal.md might look like this:
name* = ___
age* = ###
and then add the on_get_form
decorator to your route.
@app.route("/by_endpoint", methods=["GET"])
@on_get_form()
def personal():
"""As the endpoint is named `personal`, flask-mdform
will load, parse and display:
templates/md/personal.md
This function should return a dictionary
that maps form labels to the values to show.
(missing values in the dict will just leave
the form field as is)
"""
return dict(name="John", age=42)
If you want to set the name of the form file independently of the endpoint
just use the mdfile
keyword argument:
@app.route("/by_arg", methods=["GET"])
@on_get_form(mdfile="personal")
def by_arg():
"""This will also load, parse and display:
templates/md/personal.md
"""
return dict(name="John", age=42)
To handle the form submission, use the on_submit_form
decorator.
@app.route("/submit", methods=["POST"])
@on_submit_form(mdfile="personal")
def submit(form):
"""This will get the form definition from:
templates/md/personal.md
The argument `form` is contains the filled form after validation.
Use `form.to_plain_dict()` to obtain a dictionary mapping label names
to values.
By the way, you can can call `Form.from_plain_dict(values)` to fill
obtain a filled form object from Form class.
This function should return the webpage to show after. Any flask
response will work (a string, rendering a template, a redirect, etc).
"""
# If you have a save function, you can:
# save(form.to_plain_dict())
return "Thanks for submitting!"
If you just want to render a read-only version of the form with the submitted
data, just raise NotImplementedError
@app.route("/submit", methods=["POST"])
@on_submit_form(mdfile="personal")
def submit(form):
# If you have a save function, you can:
# save(form.to_plain_dict())
raise NotImplementedError
In certain cases, you might want load a form depending on the route. Just provide a
route argument named mdfile
.
@app.route("/form/<mdfile>", methods=["GET"])
@on_get_form()
def by_view_arg(mdfile):
return dict(name="John", age=42)
this will return the templates/md/personal.md if you navigate to /form/personal.
Arguments of these decorators (on_get_form
and on_submit_form
) can
be used to customize the output:
- mdfile: (str) Allows you to customize the mdform file name, do not use
the extension here.
All files will be looked in templates/md/ folder and should have the
extension
.md
(Default:None
, which means that defaults first to ``mdform` view argument or then to endpoint) - read_only: (bool) If True, the form will be displayed as non-editable readonly form. (Default: False)
- block: (str) Name of the Jinja block where the form will be inserted. (Default: None, which means it should use the config value in MDFORM_BLOCK)
- extends: (str) Name of the Jinja template to use. (Default: None, which means it should use the config value in MDFORM_EXTENDS)
- formatter: (callable) Function to write a field to a template. mdform (Default: None, which means it should use the config value in MDFORM_FORMATTER)
- flash_form_errors: (bool) If True, calls FlashError for the form arguments. Showing the errors must be called in the template. (Default: True)
In certain cases you might want to add additional variables to be rendered by jinja. This can be achieved by returning a second dictionary from on_get_form:
@app.route("/by_endpoint", methods=["GET"])
@on_get_form()
def personal():
return dict(name="John", age=42), dict(version="1.3.2)
These extra variables can be inserted in your jinja template (
Version: {{ version }}
) but also within the mdform file itself:
name* = ___
age* = ###
Version: {{ version }}
Remember that mdform just convert your markdown to html leaving these flask items (and any unknown field) untouched.
It is important to realize that these variables will be passed onwards to
render_template in the context variables and therefore their keys must
be valid identifiers. Additionally, they cannot be form
or meta
as
they are reserved by flask-mdform
.
Why? you might ask. Well, form
contains the Flask-WTF object. And
meta
contains a dictionary with metadata information parsed from the
markdown file using the Meta-data extension.
Flask allows to write application wide configurations. Flask-mdforms has the following keys and values by default:
MDFORM_EXTENDS = "form.html"
MDFORM_BLOCK = "innerform"
MDFORM_FORMATTER = formatters.flask_wtf
In certain cases you want to handle your the routes yourself. The function
render_mdform is analogous to the Flask render_template
but it allows
you to show and mdform. Ito has the same arguments as on_get_form
and
on_submit_form
with two additional arguments
- data: (dict) mapping from labels to values to fill the form with.
- on_submit: (callable) function to be called upon submission.
Arguments are
on_submit(form, **request.view_args)
and should return the page to show. - tmpl_context: (dict) variables that should be available in the context of the template.
Finally, you can check some simple demonstrations in the the examples for folder.
Enjoy!
See AUTHORS for a list of the maintainers.
To review an ordered list of notable changes for each version of a project, see CHANGES