| 353 | |
| 354 | == Adding Form Validation == |
| 355 | |
| 356 | We'll use [http://formencode.org/ FormEncode] to do the validation, but we'll keep it all fairly basic. Let's declare our form in a separate file, namely `geddit/form.py`, which will have the following content: |
| 357 | |
| 358 | {{{ |
| 359 | #!python |
| 360 | from formencode import Schema, validators |
| 361 | |
| 362 | |
| 363 | class SubmissionForm(Schema): |
| 364 | username = validators.UnicodeString(not_empty=True) |
| 365 | url = validators.URL(not_empty=True, add_http=True, check_exists=False) |
| 366 | title = validators.UnicodeString(not_empty=True) |
| 367 | }}} |
| 368 | |
| 369 | Now let's use that class in the `Root.submit()` method. First add the `from formencode import Invalid` and `from geddit.form import SubmissionForm` lines to the imports at the top of the file. Then, update the `submit()` method to match the following: |
| 370 | |
| 371 | {{{ |
| 372 | #!python |
| 373 | @cherrypy.expose |
| 374 | def submit(self, cancel=False, **data): |
| 375 | if cherrypy.request.method == 'POST': |
| 376 | if cancel: |
| 377 | raise cherrypy.HTTPRedirect('/') |
| 378 | form = SubmissionForm() |
| 379 | try: |
| 380 | data = form.to_python(data) |
| 381 | submission = Submission(**data) |
| 382 | self.data.append(submission) |
| 383 | raise cherrypy.HTTPRedirect('/') |
| 384 | except Invalid, e: |
| 385 | errors = e.unpack_errors() |
| 386 | else: |
| 387 | errors = {} |
| 388 | |
| 389 | tmpl = loader.load('submit.html') |
| 390 | stream = tmpl.generate(errors=errors) |
| 391 | return stream.render('html', doctype='html') |
| 392 | }}} |
| 393 | |
| 394 | As you can tell, we now only add the submitted link to our database when validation is successful: all fields need to be filled out, and the `url` field needs to contain a valid URL. If the submission is valid, we proceed as before. If it is not valid, we render the submission form template again, passing it the dictionary of validation errors. Let's modify the `submit.html` template so that it displays those error messages: |
| 395 | |
| 396 | {{{ |
| 397 | #!genshi |
| 398 | <html xmlns="http://www.w3.org/1999/xhtml" |
| 399 | xmlns:py="http://genshi.edgewall.org/"> |
| 400 | <head> |
| 401 | <title>Geddit: Submit new link</title> |
| 402 | </head> |
| 403 | <body> |
| 404 | <h1>Geddit</h1> |
| 405 | |
| 406 | <h2>Submit new link</h2> |
| 407 | <form action="" method="post"> |
| 408 | <table summary=""><tr> |
| 409 | <th><label for="username">Your name:</label></th> |
| 410 | <td> |
| 411 | <input type="text" id="username" name="username" /> |
| 412 | <span py:if="'username' in errors" class="error">${errors.username}</span> |
| 413 | </td> |
| 414 | </tr><tr> |
| 415 | <th><label for="url">Link URL:</label></th> |
| 416 | <td> |
| 417 | <input type="text" id="url" name="url" /> |
| 418 | <span py:if="'url' in errors" class="error">${errors.url}</span> |
| 419 | </td> |
| 420 | </tr> |
| 421 | <tr> |
| 422 | <th><label for="title">Title:</label></th> |
| 423 | <td> |
| 424 | <input type="text" name="title" /> |
| 425 | <span py:if="'title' in errors" class="error">${errors.title}</span> |
| 426 | </td> |
| 427 | </tr></table> |
| 428 | <div> |
| 429 | <input type="submit" value="Submit" /> |
| 430 | <input type="submit" name="cancel" value="Cancel" /> |
| 431 | </div> |
| 432 | </form> |
| 433 | |
| 434 | </body> |
| 435 | </html> |
| 436 | }}} |
| 437 | |
| 438 | So now, if you submit the form without enterering a title, and having entered an invalid URL, you'd see something like the following: |
| 439 | |
| 440 | [[Image(tutorial02.png)]] |
| 441 | |
| 442 | But there's a problem here: Note how the input values have vanished from the form! We'd have to repopulate the form manually from the data submitted so far. We could do that by adding the required `value=""` attributes to th text fields in the template, but Genshi provides a more elegant way: the `HTMLFormFiller` steam filter. Given a dictionary of values, it can automatically populate HTML forms in the template output stream. |
| 443 | |
| 444 | To enable this functionality, first you'll need to add the import `from genshi.filters import HTMLFormFiller` to the `genshi/controller.py` file. Next, update the bottom lines of the `Root.submit()` method implementation so that they look as follows: |
| 445 | |
| 446 | {{{ |
| 447 | #!python |
| 448 | tmpl = loader.load('submit.html') |
| 449 | stream = tmpl.generate(errors=errors) | HTMLFormFiller(data=data) |
| 450 | return stream.render('html', doctype='html') |
| 451 | }}} |
| 452 | |
| 453 | Now, all entered values are preserved when validation errors occur. |