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!

Original source