Log in

tipfy.ext.wtforms

This extension provides enhanced WTForms support in tipfy. WTForms is a library for form validation and rendering. It is easy to use, extensible, explicit and magic-free, like tipfy. :)

tipfy.ext.wtforms extends WTForms to provide:

  • Built-in support for <input type="file"> (not handled by WTForms for being framework dependent).
  • Built-in ReCaptcha support.
  • Built-in CSRF protection support.

In the future it will also provide i18n capability.

Creating forms

Let's see how it works in a basic contact form example. For a complete reference, check WTForms documentation.

You define a form in a class that extends wtforms.Form. We wrap it in the tipfy.ext.wtforms so that it also accepts a request object in the constructor. Each form field is an attribute of the class:

handlers.py

from tipfy.ext.wtforms import Form, fields

class ContactForm(Form):
    name = fields.TextField('Name')
    email = fields.TextField('Email')
    message = fields.TextAreaField('Message')

That's it. Our first form is defined. Let's use it in a handler:

handlers.py

from tipfy import RequestHandler, cached_property, redirect
from tipfy.ext.jinja2 import Jinja2Mixin

class MyHandler(RequestHandler, Jinja2Mixin):
    def get(self, **kwargs):
        """To display a form, we simply pass the form instance to the template.
        """
        context = {
            'form': self.form,
        }
        # Returns a rendered template, passing the 'form' instance as variable.
        return self.render_response('test_form.html', **context)

    def post(self, **kwargs):
        """To process a form, we validate it and pass the form instance to the
        template.
        """
        # Validate the form.
        if self.form.validate():
            # Form is valid. Use the form data and redirect the user to the
            # final destination.
            name = self.form.name.data
            email = self.form.email.data
            message = self.form.message.data

            # ... do something with the collected data ...

            return redirect('/')

        # Since the form didn't validate, render it again using self.get().
        return self.get(**kwargs)

    @cached_property
    def form(self):
        """We define a form as a cached property instantiated on first call.
        It is constructed passing the request object to populate it.
        """
        return ContactForm(self.request)

Now we have a handler that displays and processes our form. The form template is still missing, though, so let's create one:

test_form.html

{% from '_form_macros.html' import form_field %}
<html>
    <title>Contact Form</title>
    <body>
        <h1>Contact form</h1>
        <form method="post" action="" enctype="multipart/form-data" class="tipfy-form">
            <ol>
                <li>{{ form_field(form.name, class='medium') }}</li>
                <li>{{ form_field(form.email, class='medium') }}</li>
                <li>{{ form_field(form.message, class='large') }}</li>
            </ol>
            <fieldset class="submit">
                <input type="submit" name="submit" value="Send" class="submit">
            </fieldset>
        </form>    
    </body>
</html>

In this template, we use these Jinja2 Macros for forms to build the form markup.

Also, for this example, try using these form stylesheets. They're some basic styles for a good looking form.

CSRF protection

The Form object also has the ability to protect against CSRF. To enable it, set csrf_protection to True when defining the form, or pass csrf_protection=True when instantiating it.

from tipfy.ext.wtforms import Form, fields

class ContactForm(Form):
    csrf_protection = True
    name = fields.TextField('Name')
    email = fields.TextField('Email')
    message = fields.TextAreaField('Message')

A hidden field csrf_token will be added to the form and automatically populated. You must render this field when displaying the form.

Also, to use CSRF you must have sessions enabled, and you must instantiate the form passing the request object. This is because the token is validated using the current session, which requires a Request object.

Extension Reference


Powered by Moe. Yeah, the name is Moe. Powered by Google App Engine