| Name |
Space |
Section |
Page |
Version |
Status |
Reviewed |
Author(s) |
| An Alternative ToscaWidgets Setup with Mako |
Pylons CookBook |
Getting Started With ToscaWidgets and Pylons |
An Alternative ToscaWidgets Setup with Mako |
1.0 |
Draft |
False |
James Gardner |
Follow the Getting Started With ToscaWidgets and Pylons example up to the point where the WSGI example is running then continue the Pylons integration from here.
Getting Started with Pylons Integration
Now that we have the WSGI version working we can try full Pylons integration.
First install Pylons:
easy_install "Pylons>=0.9.4"
Then create a project:
paster create --template=pylons TWTest
Middleware Setup
 |
This section is obsolete! Current version of Toscawidgets does all this boiler plate for you, even providing py_c, h, g, etc... (although you should not use them you plan to distribute your widgets since they are Pylons' dependent).
To achieve the same effect as the the code below you can just do:
1
2
3
4
5
6
7 | host_framework = PylonsHostFramework(
default_view = "mako",
helpers = twtest.lib.helpers,
template_paths = config.paths['templates']
)
app = TGWidgetsMiddleware(app, host_framework)
|
Notice the helpers and template_paths keys. The former makes your helpers available at the widgets' templates h symbol; the later makes sure Mako finds your widgets templates inside the same directories it would find your page templates. |
Now we need to integrate the ToscaWigets middleware. We're going to use a slightly different setup from the default because we want to be able to use Mako templates to override the way the widgets themselves are generated. Furthemore we also want the standard Pylons variables such as h and g to be available in the widget templates so that we can use the full power of Pylons in our templates.
First we need to change Pylons so that it uses Mako as the default templating language. Change your config.init_app line to this (adjusting the package name if you've chosen a different name for your project):
1 | config.init_app(global_conf, app_conf, package='twtest', template_engine='mako')
|
Next we setup the ToscaWidgets middleware. Edit TWTest/twtest/config/middleware.py so that immediately after these lines:
1
2
3
4
5
6
7
8
9 | # Load our default Pylons WSGI app and make g available
app = pylons.wsgiapp.PylonsApp(config, helpers=twtest.lib.helpers,
g=app_globals.Globals)
g = app.globals
app = ConfigMiddleware(app, conf)
# YOUR MIDDLEWARE
# Put your own middleware here, so that any problems are caught by the error
# handling middleware underneath
|
you add this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | # Setup ToscaWidgets
from toscawidgets.view import EngineManager
from toscawidgets.middleware import TGWidgetsMiddleware
from toscawidgets.mods.pylonshf import PylonsHostFramework
from pylons.templating import render
def extra_vars():
return dict(
py_c=pylons.c._current_obj(),
g=pylons.g._current_obj(),
h=twtest.lib.helpers,
render=render,
request=pylons.request._current_obj(),
session=pylons.session._current_obj(),
translator=pylons.translator,
ungettext=pylons.i18n.ungettext,
_=pylons.i18n._,
N_=pylons.i18n.N_
)
class CustomHostFramework(PylonsHostFramework):
engines = EngineManager(extra_vars_func=extra_vars)
engines.load_all({'mako.directories':config.paths['templates']})
app = TGWidgetsMiddleware(app, CustomHostFramework)
# Finsihed the ToscaWidgets setup
|
 |
In this example we've set the Pylons c object to be called py_c in the templates. This is so that it doesn't conflict with ToscaWidgets use of c for children. This means that you would have to use py_c.somevar in your ToscaWidget templates instead of c.somevar. Of course this only applies in templates used to override the defaults in custom widgets. You can continue to use c normally in the templates used by Pylons. |
You should now be setup to use ToscaWidgets.
 |
ToscaWidgets also has a handy Paste Deploy plugin that allows you to add this middleware via the config file. Ordinarily since your application will rely on ToscaWidgets it is better to keep this middleware in the application itself so that you don't give your end users the chance to remove it when they alter the config file. |
Setting Up Application Templates
Now we need to setup the templates. First make the templates/base.tmpl file look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | <html>
<head>
% for rsrc in c.resources.get('head', []):
${rsrc.display()}
% endfor
</head>
<body>
% for rsrc in c.resources.get('bodytop', []):
${rsrc.display()}
% endfor
${next.body()}
% for rsrc in c.resources.get('bodybottom', []):
${rsrc.display()}
% endfor
</body>
</html>
|
We can then derive all our other application templates containing widget data from this one and be sure that all the various scripts required by the widgets are inserted in the correct places.
Your First Form
Now that Pylons is setup you can create your first form. Lets add a new controller:
 |
For some reason the paste controller command isn't working for me at the moment. If it doesn't work for you either you should just create the file manually from the description below. |
The TWTest/twtest/controllers/form.py file looks like this::
1
2
3
4
5 | from twtest.lib.base import *
class FormController(BaseController):
def index(self):
return Response('')
|
Add the following after the import:
1
2
3
4
5
6
7
8
9
10
11 | from formencode import Invalid
from toscawidgets.widgets.forms.samples import AddUserForm
from toscawidgets.mods.pylonshf import validate, render_response
from toscawidgets.api import WidgetBunch
form = AddUserForm('form')
class Person(object):
name = "Peter"
email = "peter@example"
age = 2
|
This sets up a sample form based on AddUserForm and a Person class that will be use to create a person to populate that form. twForms uses the class attributes as the values to populate the form with. Of course ordinarily the data to populate the form would come from the model. In this case we are just hard-coding the Person class here as an example.
Rather than using a @validate decorator as you would in TubroGears, a more flexible setup is to use a simple valid() function. This is more flexible because it means you can create your form when the action is run rather than having to create it before hand so that it can be used in the decorator. Here is what our valid() function looks like, it returns True if the data is valid, False otherwise. Add it just after the Person class:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 | def valid(self, form=None, validators=None, post_only=True, state_factory=None):
# These two lines added based on Steven's comments below
if not request.method == 'POST':
return False
# end the lines added based on Steven's comment
if post_only:
params = request.POST.copy()
else:
params = request.params.copy()
errors = {}
state = None
if state_factory:
state = state_factory()
if form:
try:
self.form_result = form.validate(params, state=state)
except Invalid, e:
self.validation_exception = e
errors = e.error_dict
if validators:
if isinstance(validators, dict):
if not hasattr(self, 'form_result'):
self.form_result = {}
for field, validator in validators.iteritems():
try:
self.form_result[field] = \
validator.to_python(decoded[field] or None, state)
except Invalid, error:
errors[field] = error
if errors:
self.errors = errors
return False
return True
|
Now change the two controller actions so they look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | def index(self):
c.title = "Add User Form"
c.w = WidgetBunch()
c.w.form = form
c.value = Person()
c.action = h.url_for(action='save')
return render_response('/widget.tmpl')
def save(self, id="sample"):
if not valid(self, form):
return self.index()
c.input_values = self.form_result
c.title = "Validated data"
return render_response('/widget.tmpl')
|
Now we need to create a template to display the form and the validated data.
Form Template
Add a file called templates/widget.tmpl which looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13 | <%inherit file="/base.tmpl" />
% if c.input_values:
<h1>Validated data:</h1>
<dl>
% for k,v in c.input_values.iteritems():
<dt>${k}</dt>
<dd><pre>${repr(v)}</pre></dd>
% endfor
</dl>
% elif c.w and c.w.form and c.action:
${c.w.form.display(c.value, action=c.action)}
% endif
|
Notice how we inherit the base.tmpl file which sets up the basic page and the code necessary to display the widget information.
Testing The Application
That's it! You can now test your application:
paster serve --reload development.ini
You should find the Pylons version you just developed behaves identically to the WSGI sample.
Placing "c" at stdvars will probably break some widgets as they use "c" as shortcut for "children" in the templates (sorry, didn't use Pylons before deciding that
Maybe you could change it to "py_c" or something or I could deprecate passing "c" to widget templates and send "ch" instead? Maybe both, "ch" for children and "py_c" for pylons' c 'til the deprecation is over?
Nice write up!
Alberto