In this part of the series we’re going to take a step back from SQLAlchemy and have a look at making the user experience a little more fluid. We’re going to convert all our hand-coded forms to widgets, and we’re also going to use widgets as a display mechanism.
Widget-me-do!
Widgets are a wonderful part of TurboGears. Using them in their default incarnation they offer a means of creating forms with automatic validation and error handling, but they can also be customised in almost every way possible. We’re going to start by converting our two forms using standard widgets.
I generally find the best way to create your widgets, especially if you intend to have a large number of them and/or custom widgets is to create a new module specifically for widgets.
To do this, create a new directory called widgets from your project’s root directory (where def.cfg is stored) and turn it into a python module using an __init__.py:
mkdir simpleblog/widgets
cp simpleblog/templates/__init__.py simpleblog/widgets/
These commands should work for windows users too, although I haven’t tested it.
Now open your favourite editor and create simpleblog/widgets/widgets.py:
from turbogears import widgets, validators
class CommentFields(widgets.WidgetsList):
author_name = widgets.TextField(validator=validators.NotEmpty(),
label="Name")
author_email = widgets.TextField(validator=validators.Email(not_empty=True),
label="Email")
author_url = widgets.TextField(label="URL")
content = widgets.TextArea(validator=validators.NotEmpty(),
attrs=dict(rows=30, cols=70))
post_id = widgets.HiddenField(validator=validators.Int(not_empty=True))
comment_form = widgets.TableForm(fields=CommentFields(),
name="w_commentform",
action="/comment",
method="post",
submit_text="Comment!")
Let us take a moment to see what we’ve done here. CommentFields is a WidgetsList. This is just syntactic sugar to make life a little easier, and the results are exactly the same as defining a list of widgets. So we could define the above like this:
comment_fields = [widgets.TextField("author_name", validator=validators.NotEmpty(), label="Name"),
widgets.TextField("author_email", validator=validators.Email(not_empty=True), label="Email"
...
# You get the idea... the rest have been snipped
)]
As you can see, using a WidgetList is a lot easier to read and write, especially when you start dealing with very large forms.
Each widget also has it’s own validator. If you have more complex validation requirements you can also create your own validator schema.
Finally we have the creation of the comment_form itself. I think the keyword arguments speak for themselves.
Note: some of these keywords are can be overridden when the widget is displayed, but not all! The fields that you are allowed to override are:
action,method,form_attrs,submit_textandtable_attrs. An attempt to modify anything else after instantiating will result in an error.
You will now need to add the following line to simpleblog/widgets/__init__.py:
from widgets import *
And, in your controllers.py:
from simpleblog.widgets import comment_form
from turbogears import error_handler
Now the magic happens. We need to add two to our comment method:
@expose()
@validate(form=comment_form)
@error_handler(post)
def comment(self, post_id, author_name, author_email,
content, author_url=None, *args, **kw):
referer = cherrypy.request.headerMap.get("Referer", "/")
p = model.Post.get(int(post_id))
c = model.Comment(post=p, author_name=author_name,
author_email=author_email,
content=content)
if author_url:
c.author_url = author_url
c.flush()
raise redirect(referer)
The @validate decorator takes our comment_form as an argument, using the validator schema that was automatically created when we created the form in widgets.py. The @error_handler() decorator takes our post method as it’s argument. We’ll see what this does for us once we have the form working correctly.
We now need to display the form on our post page:
@expose(template="simpleblog.templates.post")
def post(self, id):
p = model.Post.get(int(id))
return dict(post=p,
comment_form=comment_form)
and index.kid needs a little change too. We can replace all of our hand written form with the widget:
<div id="commentform">
<h3>Add your comments</h3>
${comment_form(value=dict(post_id=post.id))}
</div>
Now, lets restart the app and take a look at the new comment form. When you enter everything as you would for a normal comment everything is hunky-dory. If, however, you enter an invalid email address, or leave out a required field we get a nice little error message!
Not only do we not have to write any HTML forms, we get free server side validation too. In a word, awesome.
This pony has another trick
Widgets can also be used simply for display purposes. They are excellent to prevent template repetition when you have to display the same elements in multiple templates. We’re going to use it in a slightly contrived manner, but it will serve the purpose of showing how widgets can be used in this way.
Back to widgets.py for a few more lines of code:
class PostDisplayWidget(widgets.Widget):
template = 'simpleblog.templates.post_display_widget'
params = ['post']
post_display = PostDisplayWidget()
We’ve added a template here that doesn’t exist, so - lets create it. This template is going to be a little different to a standard kid template as it will be a ‘fragment’. Open the new, blank file, post_display_widget.kid and use the following markup:
<div xmlns:py="http://purl.org/kid/ns#" id="post_${post.id}">
<h2><a href="/post?id=${post.id}" py:content="post.title">Post title here</a></h2>
<div class="postmeta">
Posted on <span class="postdate" py:content="post.post_date">01/01/01</span>.<br/>
<span py:if="post.comment_count > 0" py:strip=''>${post.comment_count}
<span py:if="post.comment_count == 1">comment.</span>
<span py:if="post.comment_count > 1">comments.</span>
</span>
<span py:if="post.comment_count == 0" py:strip=''>No comments</span>
</div>
<div class="content" py:content="XML(post.html_content)">
This is where your post's content is displayed.
</div>
</div>
Look familiar? That’s because this code was lifted straight from index.kid, with the exception of the containing <div> tags. The span tags need to have the kid XML namespace so that this HTML fragment can be displayed properly.
Just the changes to index.kid and our controller’s index method left. We need to remove the display code we just put into our display widget and replace it with… well… the widget:
...
<div id="posts">
<span py:for="post in posts" py:strip="True">
${post_display(post=post)}
</span>
</div>
...
and the controller:
from simpleblog.widgets import comment_form, post_display
...
@expose(template="simpleblog.templates.index")
def index(self):
posts = model.Post.select()
return dict(posts=posts, post_display=post_display)
All we need to do now is make sure the app is still running (we made a few changes there and it’s probable that during those changes there was a syntax error or a missing import) and look at the first page.
It looks exactly like it did before! Well, I did say that the example was slightly contrived, but lets look at the possible uses for a widget used in this way.
Imagine you have an application with many pages and templates and a percentage of those templates have common page elements. You can use widgets to display these common display elements and cut out bugs if you need to change the markup slightly. Instead of having to go through every template to make the same change you just stop at one template - the widget’s - et voila; instant change wherever the widget is used.
Well, that’s the end of another SimpleBlog tutorial. SimpleBlog itself is getting closer to becoming a usable application, so watch this space!
While I have a large range of topics I’d still like to go over, my time is being devoured by my current work load. This means that the tutorials will be released on a slower cycle until after the new year.
links for 2006-11-20 at Blue Sky On Mars | 20-Nov-06 at 10:24 am | Permalink
[...] splee.blog :: SimpleBlog Part 4 - Widgets (tags: web programming turbogears python) [...]
Joseph Tate | 20-Nov-06 at 2:32 pm | Permalink
It should be noted that the use of widgets in this tutorial breaks the Model/View/Controller paradigm. The controller method is tied to an HTML view. The widget cannot be dumped as json or xmlrpc. If this isn’t important to you, then use it as described, if it is, the instantiation of the widget can be done directly in the template, rather than returned by the controller.
Otherwise a great tutorial.
jtate
Splee | 20-Nov-06 at 8:20 pm | Permalink
@Joseph: Excellent point, and it is something I will bring up later in the series in detail.
For anyone who doesn’t want to wait, I generally create a simple object, adding each widget as an attribute. I then add this object to TurboGears’ variable providers:
This means that I can then access every widget from the template via
${tg.mywidgets.widget_name}or${tg.mywidgets.another_widget}.This preserves the designed use of widgets (i.e. one widget shared between all of the apps threads) and the MVC paradigm.
Graham Higgins | 22-Nov-06 at 1:44 am | Permalink
This is a /really/ nice walkthrough. Your effort is much appreciated.
Matthew Webber | 28-Nov-06 at 5:00 pm | Permalink
Thanks for all your excellent work on this series, much appreciated.
Part 4 needs one change, where it says to Open the new, blank file, postdisplaytemplate.kid, the name should be postdisplaywidget.kid.
Also, on your web site the “simpleblog” category is missing part 2 (tag missing I guess).
Splee | 28-Nov-06 at 10:34 pm | Permalink
@Matthew: Fixed. Thanks again for your feedback.
dax | 29-Nov-06 at 5:51 pm | Permalink
Just wondering, where is the best place to put the code for ProviderObj to achieve MVC? Should this also be on the widgets.py?
Your tutorial is way coool! Learned a lot. Thanks a lot! :)
Alberto Valverde | 30-Nov-06 at 9:20 pm | Permalink
Nice article!
I just wanted to comment Lee’s comment a couple of lines back… The best way to have widgets available at every template is to list them in the
tg.include_widgetsconfig variable.For two reasons:
1) Any JS and CSS needed by widgets listed there will get included in the template too (doesn\’t happen with
variable_providers, unfortunately)2) You can control the order in which those resources are included (only in the 1.0 branch in Subversion, ATM, 1.0b2 will include it). This doesn\’t happen when you dump widgets in the dict returned by the controller because iterating it doesn\’t guarantee any ordering and could be the cause (has bitten in the past) of some mysterious bugs. For example, if mochikit gets included after a JSSource that needs it.
Widgets listed at
tg.include_allwill appear in the template astg_${widget_name}. For example,myapp.widgets.show_userwill betg_show_userin the template.Splee | 05-Dec-06 at 3:04 pm | Permalink
@Alberto: That’s something that I’ve not come across yet as all my AJAX stuff is currently done without widgets.
I didn’t think about using the
tg.include_widgetsconfig option, but I don’t particularly like it as a solution to getting JSLink/JSSource or CSSLink/CSSSource widgets to show up in the templates - if you have a lot of widgets that could end up being a lot of code that is inserted on every page view.It might be an idea to have a conversation about this on the mailing list with regards to ToscaWidgets to see if there’s any way around this potential issue.
Alberto Valverde | 07-Dec-06 at 2:30 pm | Permalink
@splee
I’m not sure my message got through as I intended it…
It’s not a solution to include *Links and *Sources in templates, but a solution to make sure that widgets that we really need in every template to be treated in the same way as if they were returned in the dict controllers return, that is, their resources automatically included so the widget behaves as intended.
For example, I have an app. where master.html (using genshi) includes a search form widget that shows up in every page. That search form needs to include an async.js link (it shows results asyncronously in the main div) and autocomplete.js (it autocomplete’s results a là AutoCompleteField).
If I just dump this widget in variableproviders it’s required resources won’t be included because the code in turbogears.controlles.process_output that picks up widget’s resources from the dict that the controller returns doesn’t see those variables (as they’re sent directly to the template engine plugin).
I agree that it should be used with care as we don’t want unneeded links included in every page, but sometimes this is what we need.
It’s a solution for the problem mentioned in http://trac.turbogears.org/turbogears/ticket/847
(which I’m about to close ;)
Regards,
Alberto
Andre | 20-Sep-07 at 8:53 am | Permalink
Very nice tutorial. Thanks. Hope to see more coming - if you find the time.
I just started with TG and wonder whether there is something special you wanted to tell about self-reference, as promised at the end of part 3, that I could not figure out (most probably).