Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom attributes for Flask WTForms

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!

like image 499
kuynik Avatar asked Dec 07 '13 10:12

kuynik


People also ask

What is WTForms in flask?

WTForms is a Python library that provides flexible web form rendering. You can use it to render text fields, text areas, password fields, radio buttons, and others. WTForms also provides powerful data validation using different validators, which validate that the data the user submits meets certain criteria you define.

Should I use WTForms?

WTForms are really useful it does a lot of heavy lifting for you when it comes to data validation on top of the CSRF protection . Another useful thing is the use combined with Jinja2 where you need to write less code to render the form. Note: Jinja2 is one of the most used template engines for Python.

How do I import WTForms into a flask?

From version 0.9. 0, Flask-WTF will not import anything from wtforms, you need to import fields from wtforms. If your form has multiple hidden fields, you can render them in one block using hidden_tag() .

What is StringField?

StringField (default field arguments)[source]¶ This field is the base for most of the more complicated fields, and represents an <input type="text"> .


1 Answers

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="">
like image 165
Martijn Pieters Avatar answered Oct 16 '22 05:10

Martijn Pieters