Section 5.3. Updating Our Form

5.3. Updating Our Form

Now that we have bookmarks and categories, however, we notice that there's no easy way to add the categories for a bookmark when we create them through our form. We could approach this problem in several ways. We could try to do this with CatWalk by mounting it inside our application; or we could write a standard HTML form, parse the post results in our controller, do whatever validation we need, and have our controller pass the data on to our SQLObject class, which will automatically inject it into the database. But the current manifestation of CatWalk isn't easy to modify to do what we want, and handling validation, displaying error messages, and all of that seems like a lot of work.

So, let's explore a third option that uses a TurboGears feature you haven't seen yet: widgets. You can always do things by hand when you need to, just like we did in Chapter 4, "Creating a Simple Application," but most of the time widgets are going to make life a lot easier. The form widget definitely makes it easier to handle data input and validation in your application. Previous web-based widget interfaces have tended to be either too complex for the simple cases or impossible to use for the complex cases. But TurboGears widgets have been designed to make easy things easy, and complex things possible.

So, now for a whirlwind tour of widgets.

On the simplest level, you can think of widgets as an easy way to reuse "packages" of HTML, JavaScript, and CSS on your pages in a smart way. So, for example, if you reuse a form widget that has associated JavaScript functions and CSS styles 100 times on one of your pages, TurboGears is "smart" enough to only inject those JavaScript and CSS chunks to your page header one time. We talk about writing your own widgets in Chapter 16, "TurboGears Widgets: Bringing CSS, XHTML, and JavaScript Together in Reusable Components," but for now let's take a look at some of the widgets that come prebuilt in TurboGears.

There's a widget for every kind of standard form element from simple text areas to multiple select boxes. For now, we'll explore simple form element widgets. We cover more-complex widgets with more JavaScript and CSS later in this book. By the time we're done, you'll be able to build your own widgets that can encapsulate large swaths of complex and dynamic view code.

Fundamentally, there are two types of widgets: simple and compound. A compound widget is one that contains any number of member widgets. In this chapter, we use a few of the simple form element widgets to build a compound form widget.

Before we get started, we need to import a few more turbogears modules for this next section.

Because we're using widgets, we want the turbogears.widgets module, and because we want validation and we want to handle our own errors, we also want to import validators and error_handler from the TurboGears module. Although it might add a bit of convenience if we automatically imported all these modules into our controller, TurboGears avoids that kind of thing. In Chapter 1, "Introduction to TurboGears," we mentioned the Zen of Pythonin Python and TurboGears we value "explicit over implicit." We want you to be able to understand and control your namespace; after all, you might be importing other modules from other projects or other frameworks. Also, generally we think you should be the one in control of your life and your application.

Anyway, now our imports look something like this:

import logging import cherrypy import turbogears from turbogears import controllers, expose, validate, redirect, \                        widgets, validators, error_handler from bookmarker import json from model import Bookmarks, Categories

A form widget is a way to automatically create forms by bundling together a bunch of widgets and validators into something easy to inject into the Kid template. So, the first thing we do is create a list of the widgets we'll be using to build our form. Before we tackle the project of moving a newly created form that handles the relationship between Bookmarks and Categories, we'll just replace the existing form with a new widget-based form that validates the URL we send in and ensures that the name and description are filled out before we save anything to the database.

To do that, we create a new BookmarkFields class, which subclasses WidgetsList, and then use that class to set up a bookmark_form object:

class BookmarkFields(widgets.WidgetsList):     bookmarkname = widgets.TextField(validator=validators.NotEmpty)     link = widgets.TextField(validator=validators.URL)     description = widgets.TextArea(validator=validators.NotEmpty) bookmark_form = widgets.TableForm(fields=bookmark_fields(),                                   submit_text="Save Bookmark")

There's a lot of new stuff going on in this little piece of code. We create a class BookmarkFields that subclasses WidgetsList and contains several individual widgets. Each of these widgets is defined with a reference to the validator that will govern the use of that widget. Even if you aren't requiring a particular field or validating its contents, you might still want to use a validator because that's what converts the raw strings passed back from the browser into the appropriate Python type. Of course, if what you want is a string, you don't need a validator at all.

Following the standard TurboGears philosophy of reusing existing Python libraries wherever possible, TurboGears incorporates Ian Bicking's FormEncode validators here; so, if you have experience with FormEncode, all the same things apply. In Chapter 16, we talk about some of the more complex things you can do with validators that interact with more than one field, or that return custom object types.

If you don't know FormEncode, the only thing you need to know for now is that passing a validator to a form field widget is all you need to do to convert the string received from the browser into the proper Python type. All three of our fields are text strings, and we want to force the user to fill out all three; however, one of them is supposed to contain a URL, so we set that validator equal to validators.URL, and the other two to NotEmpty.

The last thing we did in the sample code snippet was to define our form widget. Forms require that we define a fields parameter with a list of widgets or a WidgetsList object, and they can also accept a submit_text variable, which will be turned into a label for the Submit button. In this example, we set the fields equal to our bookmark_form WidgetsList.

We can then send this form out to a template from one of our controllers:

@expose(template="bookmarker.templates.form") def bookmark(self, parameter_1, tg_errors=None):     b = model.Bookmarks.get(parameter_1)     submit_action = "/save_bookmark/%s" %parameter_1     return dict(form=bookmark_form, values=b, action=submit_action)

We're mounting this bookmark method under the root class, and using CherryPy's positional parameter handling to let us handle requests such as /bookmark/1. The 1 is loaded into the parameter_1 variable, which we use to retrieve the bookmark row with an ID of 1. Then, we set up a values dictionary where we set the names of each of the widgets in our phone equal to the values we get from the database. We're going to need this values dictionary in our template so that we can prepopulate the form with data.

We also want to pass a submit action into our form, so we're setting that up here, too; and then, as usual, we return a dictionary to our template.

Now that we've got our data set up, all the hard work is done, and all we need to do is create a new template by copying one of the existing templates and replacing the body text with something that looks like the following:

<body>    ${form(value=values, action=action)} </body>

That's pretty simple.

Not only do we have less code, our new form is better. If we pass it an existing bookmark, it will display the fields properly and allow you to edit them.

Of course, in a lot of cases you want to have control over exactly how your form layout works. We get to that in good time, but for now I'll just say that the form widget provides hooks to get very customizable (and designer-friendly) forms exactly the way you want them to look. The default format for the form can be seen in Figure 5.2, and you can learn more about customizing your forms in Chapter 16.

Figure 5.2. Edit bookmark form

This code instantiates a form widget and sets the values for each of the member widgets. We also configure the post button with the action we want it to take when the form is submitted. Widgets are stateless and can be reused at any time (even on the same page). This allows for all kinds of cool things such as repeating widgetsso we could have a bunch of bookmark forms on one page. But, it also means that you shouldn't try to store your data in a widget.

If you remember, we set up our submit_action as /save_bookmark/ followed by the ID number of the bookmark we're editing. This means we need to update the save_bookmark method to handle this:

@expose() @error_handler(bookmark) @validate(form=bookmark_form) def save_bookmark(self, parameter_1, **kwargs):     return "Bookmark ID = (" + parameter_1 + "                            ".join(["%s : %s " %item                            for item in kwargs.iteritems()]))

This is just a mockup of the save form, but it's useful if you want to check to make sure everything is working before moving on.

If you test this code in your browser by going to http://localhost:8080/bookmark/1, you should get a form with data from the first bookmark you have defined in the database.

If you edit it, but leave the name blank, you should get the same page backwith an error message next to your name field. The same goes for your link (which not only has to exist, but is required to be a valid URL) and description fields. You can see this in action in Figure 5.3.

Figure 5.3. Edit bookmark form with Failing Validator

This might seem like magic, but it's actually not hard to understand what's going on here. We used the @turbogears.validate decorator to declare that the save_bookmark method validates a bookmark_form, and the @turbogears.error_handler decorator to tell our method where to send our form errors to. This means that when there is a validation error, everything gets sent back to the same method that created the form. However, one thing we didn't tell you about form element widgets comes into play here: When they render themselves to HTML, they check to see whether the failing validators have attached an error message, and they inject that message into HTML they create, in addition to the form elements they originally injected.

So, now that we know our form works, with validation and everything, we can think about saving data into the database. This is as easy as updating our model objects with the values from the form:

@expose() @error_handler(bookmark) @validate(form=bookmark_form) def save_bookmark(self, *args, **kwargs):     b=Bookmarks.get(*args[0])     b.set(**kwargs)     raise redirect("/index")

Because our bookmark form elements have the same names as our bookmark class attributes, we can make things simple by just passing **kwargs to the .set method on our bookmark.

We're also using positional parameters to grab the rest of the URL, but this time we are catching them with *args. For those of you who are new to Python, the *name syntax grabs all the positional parameters and puts them into a list. This is an incredibly useful Python feature, because you can use it to handle any number of positional parameters. In this case, it would be easy to use named parameters, but using *args makes it easy for our code to handle the case where no index value is being passed into our method without blowing up. This is going to be important soon when we use the edit_bookmark controller to handle adds as well as updates.

When the form is submitted, the user is sent back to the index page (which we haven't created yet, but we'll get back to that in a minute).

First, let's go back to our bookmark controller and add the ability to handle adding new bookmarks to the database:

def bookmark(self, *args, **kwargs):     if args and args[0] == "add":         values = ""         submit_action= "/save_bookmark/"         b="     if args and args[0] == "edit":         from sqlobject import SQLObjectNotFound         try:             b = Bookmarks.get(args[1])             values = kwargs         except SQLObjectNotFound:             values = ""             flash("That's not a valid Bookmark, do you want to add one now?")         submit_action = "/save_bookmark/edit/%s" %args[1]     return dict(form=bookmark_form, values=b, action=submit_action)

This isn't that much different from what we had before, but we're now checking for either an add or a save parameter in the URL. We also converted to just grabbing the keyword arguments via **kwargs.

So, now if the user enters a URL such as bookmark/edit/3, arg[0] will be edit and arg[1] will be 3. Our function will then try to get Bookmark with the ID of 3 from the database, and assign the form values to the values dictionary. If the Bookmarks.get() method fails, SQLObject will raise an SQLObjectNotFound exception, which we catch, and create values as an empty string, set the flash message to an error message, and call the form.

We haven't discussed turbogears.flash yet, but it's useful for letting the user know the results of their actions. If you extend master.kid in all your templates (as we do in a quickstarted project by default), the TurboGears flash message will always show up on the next page rendered. In this case, our error message will show up at the top of the next form.

Now, if we add a new bookmark, our save_bookmark function is going to break, because it only handles adding new bookmarks to the database and has no means to edit existing bookmarks. So, let's add that:

def save_bookmark(self, ID, **kwargs):     from sqlobject import SQLObjectNotFound     try:         b=Bookmarks.get(ID)         b.set(**kwargs)     except SQLObjectNotFound:     Bookmarks(**kwargs) raise redirect("/index")

We just added another TRy/except block to mirror the one in our form-creation method. If the bookmark ID passed in isn't found in the database (because it doesn't exist yet), our except block creates a new Bookmarks object.

Rapid Web Applications with TurboGears(c) Using Python to Create Ajax-Powered Sites
Rapid Web Applications with TurboGears: Using Python to Create Ajax-Powered Sites
ISBN: 0132433885
EAN: 2147483647
Year: 2006
Pages: 202

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: