TurboGears relies on ToscaWidgets2 for Widgets and Forms definition. Widgets are small reusable HTML components with attached values, and a form is a collection of one or more widgets (one for each field) that display the fields.
ToscaWidgets2 Widgets provide:
- Templating support to generate the HTML
- Support for widget configuration options
- Resources declaration and injection (for css & JS required by the widget)
For additional details about how ToscaWidgets2 Forms work, refer to the ToscaWidgets2 Documentation
Widgets can be created by subclassing tw2.core.Widget
and defining
a template
argument for it. By default the template argument has to be
the dotted format path of a template file (same syntax you provide to @expose
),
but an inline_engine_name
option can be provided to tell ToscaWidgets2
that the template argument is actually the template itself.
import tw2.core as twc
class UserAvatarWidget(twc.Widget):
inline_engine_name = 'kajiki'
template = '<div class="username">${w.name}</div>'
To display the widget just call the .display(**kwargs)
method of the
widget passing any parameter you want to provide to the widget:
>>> UserAvatarWidget.display(name='John Doe')
Markup(u'<div class="username">John Doe</div>')
The passed name
is available inside the template as w.name
.
All the arguments passed to the tw2.core.Widget.display()
function will be available
as properties of the widget instance inside the template itself.
While our previous example worked as expected, you probably noticed that when
the name
argument to display is omitted it will lead to a crash and we
didn’t provide any detail to developers that they must provide it.
to solve this ToscaWidgets provides parameters support through the tw2.core.Param
class.
To make the name
parameter explicit and provide a default value for it we
can add it to the widget as a Param
:
import tw2.core as twc
class UserAvatarWidget(twc.Widget):
name = twc.Param(default='Unknown User', description='Name of the logged user')
inline_engine_name = 'kajiki'
template = '<div class="username">${w.name}</div>'
Trying to display the widget without a name
argument will now just provide the
default value instead of leading to a crash:
>>> UserAvatarWidget.display()
Markup(u'<div class="username">Unknown User</div>')
>>> UserAvatarWidget.display(name='John Doe')
Markup(u'<div class="username">John Doe</div>')
The passed value will be available inside the template as properties
of the widget instance and the widget instance will be available as w
.
So in the previous example the user name was available as w.name
.
Note
Whenever you need to know which options a widget provides you want to have a look at its parameters. They will usually provide a short description of the parameter purpose.
To implement more advanced widgets you will probably need to add styling and javascript to them. This can easily be done through resources support provided by ToscaWidgets.
A resource is an instance of tw2.core.JSLink
, tw2.core.JSSource
,
tw2.core.CSSLink
and tw2.core.CSSSource
, which allow to provide
access to a CSS and Javascript file or to inline code for the widget.
Resources are injected for you in the <head>
tag by a middleware that
handles resources injection for widgets. Each resource has an id
attribute
so the same resource won’t be injected twice as far as all instances of the
resources share the same id
.
The following example adds an inline CSS to make user avatars bold and provides jQuery as a dependency to add a click function that shows a dialog with the username inside when clicked:
import tw2.core as twc
class UserAvatarWidget(twc.Widget):
name = twc.Param(default='Unknown User', description='Name of the logged user')
inline_engine_name = 'kajiki'
template = '''
<div class="useravatar" id="${w.id}">
<div class="username">${w.name}</div>
<script>$('#${w.id}').click(function() { alert('${w.name}') })</script>
</div>
'''
resources = [
twc.CSSSource(id='useravatarstyle',
src='.useravatar .username { font-weight: bold; }'),
twc.JSLink(id='jquery',
link='https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js')
]
Calling UserAvatarWidget.display
will generate the short html snippet:
<div class="useravatar" id="id_a9a9fbba90744ff2a894f5ea5ae99f44">
<div class="username">John Doe</div>
<script>$('#id_a9a9fbba90744ff2a894f5ea5ae99f44').click(function() { alert('John Doe') })</script>
</div>
but will also inject the required resources into the head
tag:
<head>
<style type="text/css">.useravatar .username { font-weight: bold; }</style>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.0.0/jquery.min.js" type="text/javascript" id="jquery"></script>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
...
</head>
Note that display the widgets twice on the page won’t inject the resources twice. That’s
because ToscaWidgets will recognize that useravatarstyle
and jquery
resources already
got injected and won’t insert them again.
Widgets also provide an .id
attribute automatically generated by ToscaWidgets2
(it can be overwritten at display time), this allows to uniquely identify
each widget instance from any javascript injected into the page.
In the previous example we leveraged this feature to point jQuery to each specific
widget instance through $('#${w.id}')
. If you display two UserAvatarWidget
with different names on the same page, you will notice that clicking each one of them
will properly show the right name thanks to this.
Forms are actually a particular kind of Widget, actually a particular kind of
tw2.core.CompoundWidget
as they can contain more widgets inside themselves.
A Forms is actually a widget that provides a template to displays through a
Layout
all the other widgets that are provided inside the form.
To create a form you will have to declare it specifying:
- the form action (where to submit the form data)
- the form layout (how the form will be displayed)
- the form fields
The action can be specified as an attribute of the form itself, while the layout
must be a class named child which has to inherit from tw2.forms.BaseLayout
.
Any of tw2.forms.TableLayout
or tw2.forms.ListLayout
will usually do, but you
can easily write your own custom layouts. The form fields can then be specified
inside the child class.
import tw2.core as twc
import tw2.forms as twf
class MovieForm(twf.Form):
class child(twf.TableLayout):
title = twf.TextField()
director = twf.TextField(value='Default Director')
genres = twf.CheckBoxList(options=['Action', 'Comedy', 'Romance', 'Sci-fi'])
action = '/save_movie'
To display the form we can return it from the controller where it must be rendered:
@expose('tw2test.templates.index')
def index(self, *args, **kw):
return dict(page='index', form=MovieForm)
and display it inside the template itself.
Any field of the form can be filled using the value
argument passed to the
display function. The values provided inside this argument will override the
field default ones.
<div id="getting_started">
${form.display(value=dict(title='default title'))}
</div>
When submitting the form the save_movie controller declared in the action attribute of the form will receive the submitted values as any other provided GET or POST parameter.
@expose()
def save_movie(self, **kw):
return str(kw)
ToscaWidgets2 is able to use any FormEncode validator for validation of
both fields and forms. More validators are also provided inside the
tw2.core.validators
module.
To start using validation we have to declare the validator for each form field.
For example to block submission of our previous form when no title or director
is provided we can use the tw2.core.Required
validator:
class MovieForm(twf.Form):
class child(twf.TableLayout):
title = twf.TextField(validator=twc.Required)
director = twf.TextField(value="Default Director", validator=twc.Required)
genres = twf.CheckBoxList(options=['Action', 'Comedy', 'Romance', 'Sci-fi'])
action = '/save_movie'
Now the forms knows how to validate the title and director fields,
but those are not validated in any way.
To enable validation in TurboGears we must use the validate
decorator
and place it at our form action:
@expose()
@validate(MovieForm, error_handler=index)
def save_movie(self, *args, **kw):
return str(kw)
Now every submission to /save_movie url will be validated against the MovieForm and if it doesn’t pass validation will be redirected to the index method where the form will display an error for each field not passing validation.
Note
TurboGears keeps track of the form that failed validation
when running the error_handler
, so if we display that
form during the error_handler
it will automatically
display the validation error messages, nothing particular
is required to show errors apart displaying the form after
validation failed.
More about TurboGears support for validation is available inside the Parameters Validation page.
Suppose that you are afraid that people might enter a wrong director name for your movies. The most simple solution would be to require them to enter the name two times to be sure that it is actually the correct one.
How can we enforce people to enter two times the same name inside our form?
Apart from fields, ToscaWidgets permits to set validators to forms.
Those can be used to validate form fields together instead of one by one.
To check that our two directors equals we will use the
formencode.validators.FieldsMatch
validator:
import tw2.core as twc
import tw2.forms as twf
from formencode.validators import FieldsMatch
class MovieForm(twf.Form):
class child(twf.TableLayout):
title = twf.TextField(validator=twc.Required)
director = twf.TextField(value="Default Director", validator=twc.Required)
director_verify = twf.TextField()
genres = twf.CheckBoxList(options=['Action', 'Comedy', 'Romance', 'Sci-fi'])
action = '/save_movie'
validator = FieldsMatch('director', 'director_verify')
Nothing else of our code needs to be changed, our /save_movie controller already has validation for the MovieForm and when the form is submitted after checking that there is a title and director will also check that both director and director_verify fields equals.
Usually you will rely on the @validate
decorator to check for form errors
on submission and display the form with proper error messages, but there might
be cases where you want to manually validate the form and then pass the errors
to it.
To validate a Form just call the tw2.forms.Form.validate()
method on it
with the dictionary of values to validate:
MovieForm.validate({})
That will raise a tw2.core.ValidationError
in case the validation failed,
the validation error itself will contain an instance of the widget already configured
to display the error messages:
try:
MovieForm.validate(dict())
except twc.ValidationError as e:
# Display widget with error messages inside.
e.widget.display()
Whenever you run your application on a mount point which is not the root of the domain name your actions will have to poin to the right path inside the mount point.
In TurboGears2 this is usually achieved using the tg.url
function which
checks the SCRIPT_NAME inside the request environment to see where
the application is mounted. The issue with widget actions is that widgets
actions are globally declared and tg.url
cannot be called outside of
a request.
Calling tg.url
while declaring a form and its action will cause a crash
to avoid this TurboGears provides a lazy version of the url method which
is evaluated only when the widget is displayed (tg.lurl
):
from tg import lurl
class MovieForm(twf.Form):
class child(twf.TableLayout):
title = twf.TextField(validator=twc.Required)
director = twf.TextField(value="Default Director", validator=twc.Required)
genres = twf.CheckBoxList(options=['Action', 'Comedy', 'Romance', 'Sci-fi'])
action = lurl('/save_movie')
Using tg.lurl
the form action will be correctly written depending on
where the application is mounted.
Please pay attention that usually when registering resources on ToscaWidgets (both
tw1 and tw2) it won’t be necessary to call neither tg.url
or tg.lurl
as
all the Link
subclasses like JSLink
, CSSLink
and so on will already
serve the resource using the application mount point.
While using tw2.forms.TableLayout
and tw2.forms.ListLayout
it’s easy to perform
most simple styling and customization of your forms, for more complex widgets
a custom template is usually the way to go.
You can easily provide your custom layout by subclassing tw2.forms.widgets.BaseLayout
and declaring a template for it inside your forms.
For example it is possible to create a name/surname form with a side field for notes using the bootstrap CSS framework:
from tw2.core import Validator
from tw2.forms.widgets import Form, BaseLayout, TextField, TextArea, SubmitButton
class SubscribeForm(Form):
action = '/submit'
class child(BaseLayout):
inline_engine_name = 'kajiki'
template = '''
<div py:strip="">
<py:for each="c in w.children_hidden">
${c.display()}
</py:for>
<div class="form form-horizontal">
<div class="form-group">
<div class="col-md-7">
<div py:with="c=w.children.name"
class="form-group ${c.error_msg and 'has-error' or ''}">
<label for="${c.compound_id}" class="col-md-3 control-label">${c.label}</label>
<div class="col-md-9">
${c.display()}
<span class="help-block" py:content="c.error_msg"/>
</div>
</div>
<div py:with="c=w.children.surname"
class="form-group ${c.error_msg and 'has-error' or ''}">
<label for="${c.compound_id}" class="col-md-3 control-label">${c.label}</label>
<div class="col-md-9">
${c.display()}
<span class="help-block" py:content="c.error_msg"/>
</div>
</div>
</div>
<div class="col-md-4 col-md-offset-1">
${w.children.notes.display()}
</div>
</div>
</div>
</div>
'''
name = TextField(label=l_('Name'), validator=Validator(required=True),
css_class="form-control")
surname = TextField(label=l_('Surname'), validator=Validator(required=True),
css_class="form-control")
notes = TextArea(label=None, placeholder=l_("Notes"),
css_class="form-control", rows=8)
submit = SubmitButton(css_class='btn btn-primary', value=l_('Create'))