Custom attributes for Flask WTForms
angularjs, flask, javascript, python, wtforms
Solution
Dashes are not permitted in Python identifiers, and only Python identifiers can be used as `keyword_argument=value` pairs in a call.
But you have several options to work around that here; you can pass in the `ng-` prefixed options in a `**kwargs` mapping, have the `Meta` class you use for the form translate `_` to `-` for `ng_` attributes, or use a custom widget to do the same translation.
Pass in a **kwargs mapping
With `**kwargs` you can pass in arguments that are not Python identifiers, as long as they are strings. Use that to render your form fields:
{{ form.name(placeholder="Name", **{'ng-model': 'NameModel'}) }}
You can put the same information in the `render_kw` mapping on the field definition:
class MyForm(Form):
name = StringField(u'Full Name', render_kw={'ng-model': 'NameModel'})
and it'll be used every time you render the field; `render_kw` is added to whatever arguments you pass in when you render, so:
{{ form.name(placeholder="Name") }}
would render both `placeholder` and `ng-model` attributes.
Subclass Meta and use that in your form
As of WTForm 2.0, the `Meta` class you attach to your form is actually asked to render fields with the `Meta.render_field()` hook:
import wtform.meta
class AngularJSMeta:
def render_field(self, field, render_kw):
ng_keys = [key for key in render_kw if key.startswith('ng_')]
for key in ng_keys:
render_kw['ng-' + key[3:]] = render_kw.pop(key)
# WTForm dynamically constructs a Meta class from all Meta's on the
# form MRO, so we can use super() here:
return super(AngularJSMeta, self).render_field(field, render_kw)
Either use that directly on your form:
class MyForm(Form):
Meta = AngularJSMeta
name = StringField(u'Full Name')
or subclass the `Form` class:
class BaseAngularJSForm(Form):
Meta = AngularJSMeta
and use that as the base for all your forms:
class MyForm(BaseAngularJSForm):
name = StringField(u'Full Name')
and now you can use this is your template with:
{{ form.name(placeholder="Name", ng_model='NameModel') }}
Subclass widgets
You could subclass the widget of your choice with:
class AngularJSMixin(object):
def __call__(self, field, **kwargs):
for key in list(kwargs):
if key.startswith('ng_'):
kwargs['ng-' + key[3:]] = kwargs.pop(key)
return super(AngularJSMixin, self).__call__(field, **kwargs)
class AngularJSTextInput(AngularJSMixin, TextInput):
pass
This translates any keyword argument starting with `ng_` to a keyword argument starting with `ng-`, ensuring the right HTML attributes can be added. The `AngularJSMixin` can be used with any of the widget classes.
Use this as a `widget` attribute on your field:
class MyForm(Form):
name = StringField(u'Full Name', widget=AngularJSTextInput())
and again you can use `ng_model` when renderig your template:
{{ form.name(placeholder="Name", ng_model='NameModel') }}
In all cases the attributes will be added as `placeholder="Name" ng-model="NameModel"` in the rendered HTML:
<input id="name" name="name" ng-model="NameModel" placeholder="Name" type="text" value="">
Problem
I develop website on Flask and AngularJS. I need to send a form whith AJAX using AngularJS but it requires a custom attribute for input field. For example I have a form in Jinja2 template: ``` <form method="post" action="/"> {{ form.hidden_tag() }} {{ form.name(placeholder="Name") }} </form> ``` So how can I add an attribute from AngularJS lets say "ng-model" for my "name" field? Thanks for your help!