Latest Version: 0.9.6.2
  Dashboard > Pylons Cookbook > ... > Getting Started With ToscaWidgets and Pylons > An Alternative ToscaWidgets Setup with Mako
  Pylons Cookbook Log In | Sign Up   View a printable version of the current page.  
  An Alternative ToscaWidgets Setup with Mako
Added by James Gardner, last edited by Alberto Valverde on Oct 24, 2007  (view change)
Labels: 
(None)

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:

paster controller form

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

Posted by Alberto Valverde at Mar 16, 2007 01:57 | Permalink

Why is the Person class in the controller file? Isn't this part of the model?

Posted by Jon Rosebaugh at Mar 19, 2007 22:12 | Permalink

Well, I wanted to show a generic example because it doesn't matter to ToscaWidgets where the data comes from. In a real example it is likely the data would come from a model, so yes!

Do you think this is confusing enough to warrant re-wording that section?

Posted by James Gardner at Mar 19, 2007 23:38 | Permalink

I don't know.

I have read this tutorial, and the other one on this site, and the ToscaWidgets PyCon presentation, and the docs on the TW site, and I still can't grok how it's supposed to work.

I think what I would like is for someone to take something like the QuickWiki (or the MiniBlog I'm developing for the Pylons screencasts I plan to make) and make that ToscaWidgets-y.

Posted by Jon Rosebaugh at Mar 20, 2007 04:30 | Permalink

The inline validate given here seems to nuke values passed to form widgets at render time. A Comparison with pylonshf.py shows the following test isn't present (at the start of the function):

if not request.method == 'POST':
return False

This stops validation attempts if the request method is not POST. Adding this fixed the value passing issue for me.

Posted by Steven Holmes at Apr 07, 2007 17:21 | Permalink

Hi!
It is unclear (to me) what is meant by

  • config.paths[pysckbook:'templates']
  • ...[pysckbook:name]
  • ...[pysckbook:field]
  • and other occurencies of var[pysckbook:something]
    At least python2.5 says it is an "TypeError: unhashable type"
    It is either an error in the code or it is not explained enough.
Posted by Henrik Kröger at Apr 17, 2007 18:41 | Permalink

I had the same problem as well. The references to pysckbook shouldn't be there, and I'm guessing they are some type of markup accident. I removed them, and the code works for me now.

Posted by Chuck Thier at Apr 18, 2007 01:10 | Permalink

Hi!
When submitting non-ascii-characters to a form I get the following error:
Module genshi.template.base:416 in _eval:
<type 'exceptions.UnicodeDecodeError'>: 'ascii' codec can't decode byte 0xc3 in position 0: ordinal not in range(128)
Seems like genshi needs to be called to use unicode in some way.

Does anyone have this error, too?

(I also followed (with no effect) "Why do I get a UnicodeDecodeError when using Mako?", which should be noted in here, too.)

Posted by Henrik Kröger at Apr 19, 2007 14:50 | Permalink

Yes, I had the same error.

There is a two steps fix:

+ Add "# encoding: utf-8" at the start of the template. I added this line to the main template (base.tmpl)
+ (From http://docs.pythonweb.org/pages/viewpage.action?pageId=5439551, Why do I get a UnicodeDecodeError when using Mako?)
Make Mako input and output UTF8

If you use Mako and want to output UTF8 encoded strings (e.g. entries from the database) then you need to tell Mako the encoding. Add these lines to your YOURPROJECT/config/environment.py

tmpl_options'mako.input_encoding' = 'UTF-8'
tmpl_options'mako.output_encoding' = 'UTF-8'
tmpl_options'mako.default_filters' = 'decode.utf8'
Posted by Agustin Villena at May 02, 2007 16:09 | Permalink

Don't forget the square parentheses on the last line or you'll get a nasty "can't concatenate str and list values" error.

tmpl_options [ 'mako.input_encoding' ] = 'UTF-8'
tmpl_options [ 'mako.output_encoding' ] = 'UTF-8'
tmpl_options [ 'mako.default_filters' ] = [ 'decode.utf8' ]

Posted by Adal Chiriliuc at Jun 15, 2007 11:46 | Permalink

In Pylons 0.96rc1 you will need to add

import pylons
import twTest.lib.helpers

after
# Setup ToscaWidgets
[...]
from pylons.templating import render

in middleware.py

Posted by Henrik Kröger at Jul 31, 2007 10:59 | Permalink

I've been able to get this example working except for the datepicker widget.

Resources appear to be getting included (both css and javascript) but no javascript action is getting attached to the button for the datepicker field.

If anyone could point me in the right direction to try to find where the disconnect might be I would appreciate it.

Posted by Chris Emery at Aug 15, 2007 18:32 | Permalink

OK - A silly and in retrospect obvious mistake. For those foolhardy enough to follow in my footsteps, the placement of the resource.get functions in your layout (or base) template is important. I had gotten the head and body-top, but placing the body-bottom too high broke the datepicker. Thanks to Alberto for his quick point in the right direction.

Posted by Chris Emery at Aug 16, 2007 00:53 | Permalink
Site running on a free Atlassian Confluence Open Source Project License granted to Pylons. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.3.3 Build:#645 Feb 13, 2007) - Bug/feature request - Contact Administrators
Top