quick search:
 

Clean errors with Formulator and ZPT

Submitted by: macguyver007
Last Edited: 2004-10-14

Category: PageTemplates

Average rating is: 3.5 out of 5 (2 ratings)

Description:
For a while I thought it was impossible to use formultor with ZPT in a neat and clean fashion.
But this recipe solves the problem.
With this you can create a nice form in a ZPT page that will render the error
above the corresponding fields.
The first page takes the longest to write out but after that it is very simple to copy and paste into any form.


Source (Text):
The first part involves writing a script(python). I put this script in my root folder of Zope so any form can use it.

check_form - Script(Python) Parameter List: formulator_object
#This form checks request data against a formulator object.

from Products.Formulator.Form import FormValidationError

req = context.REQUEST
error = ''
formErrors = {}
try:
 container[formulator_object].validate_all_to_request(req)
except FormValidationError, e:
 for i in e.errors:
  title = i.field.get_value('title')
  text = i.error_text
  formErrors[title] = text
 return formErrors
else:
 return doyourstuff()


I then create a formulator object to use call login. I am going to make two formulator fields #1 username #2 password


Next, I create the ZPT that contains the form called sign_in.htm....


<html>
  <head>
    <title>The title</title>
  </head>
  <body>
<form name="sign_in" method="post" action="sign_in.html">

<tal:block define="global formErrors python:container.check_form('login')" />

Username:<br>
<span tal:condition="python:request.REQUEST_METHOD == 'POST'" tal:content="structure python:formErrors['username:']" tal:on-error="string:" style="color:red">error goes here</span>
<input tal:attributes="value request/field_username" tal:on-error="string:" name="field_username" type="text" id="field_username">
<br>

Password:<br>
<span tal:condition="python:request.REQUEST_METHOD == 'POST'" tal:content="structure python:formErrors['password:']" tal:on-error="string:" style="color:red">error goes here</span>
<input tal:attributes="value request/field_password" tal:on-error="string:" name="field_password" type="text" id="field_password">
<br>

</form>
</body>
</html>

Explanation:
Here's how it works:
first the python script takes the parameter "formulator_object"
so you can pass it different forms.
Next, the formulator object is set up with the proper fields.
Finally the ZPT page that works to display the form and serve as the error
page. If you notice it posts to itself. The following tag:
<tal:block define="global formErrors python:container.check_form('login')" />
calls our check_form script every time the page is accessed. But only when
it is posted to do the errors show up by using this tag:
<span tal:condition="python:request.REQUEST_METHOD == 'POST'"
tal:content="structure python:formErrors['username:']"
tal:on-error="string:" style="color:red">error goes here</span>
for each field. FormErrors[] accesses the error by title set in the formulator object
and is built with our check_form script. so formErrors['username'] will give us the error for the username field.
- added info:
You can use a title but you have to use it to reference it in the formErrors dictionary object.
For example a fomulator object for a text field with the id of user_name
without a title would be referenced as formErrors['user_name'].
If you were to supply the same object a title as in User Name:
you would have to reference it using it's title as in: formErrors['User Name:'].

I have also included the following tag:
<input tal:attributes="value request/field_username" tal:on-error="string:" name="field_username" type="text" id="field_username">
That is so when you post and there are errors, the data you posted will still be in the fields so you don't have to re-type.
Now I am sure there has to be a cleaner way of writing some of that but I have yet to find it.
Now every time I create a form, I copy the tags and change them to my liking.
It also lends itself to some nice markup to display the errors so it looks pretty too.
Hope you find this useful!


Comments:

shortcut by upinsmoke - 2004-10-14
After playing with this recipe, I discovered a more straight forward way to render the form field including the submitted value.
You can use the render_from_request method of a form field to fill it with the submitted value::

  <tal:block content="structure python:container.form.form_field.render_from_request(request.REQUEST)" />

You can also get rid of the conditions on each form element, if you dont mind that empty span tag left over.
 
Re: shortcut by macguyver007 - 2004-10-14
Cool info! I didn't know you could do that :^D
 
Re: Re: shortcut by DGoldenESB - 2004-10-14
Note for the casual reader:

Something like that approach seems more in keeping in general 
with the spirit of Formulator,
 as http://www.zope.org/Members/faassen/Formulator/ 
says "field_" names are implementation internals - the
"Formulator_and_ZPT" howto
at http://www.zope.org/Members/beno/HowTo/HowTo/Formulator_With_ZPT 
uses a technique similar to the below

e.g.

    <tal:block replace="structure python:here.testform1.header()" />
    <table>
      <tr tal:repeat="field python:here.testform1.get_fields()">
        <td tal:content="structure python:field.get_value('title')">Example Title</td>
        <td tal:content="structure python:field.render_from_request(request.REQUEST)">Example Value</td>
      </tr>
      <tr>
        <td><input type="submit" value=" OK "></td>
      </tr>
    </table>
    <tal:block replace="structure python:here.testform1.footer()" />


Example not fully working by arnogross - 2004-10-14
I found the example very useful, but had to struggle some
time to get it running.

If you create the formulator object 'login' create only
the fields with name 'username' and 'password' without a title!.

In 'sign_in.html' use these lines.
There are two changes:
 1. tal:condition="python:test(...)"
 2. tal:content="structure python:formErrors['username']"
I don't know why the original example doesn't worked for me
using Zope 2.5.1.
=============================================================
 Username:<br>
 <span 
  tal:condition="python:test(request.REQUEST_METHOD == 'POST', 1, 0)"
  tal:content="structure python:formErrors['username']" 
  tal:on-error="string:" style="color:red">error goes here
 </span>
 <input tal:attributes="value request/field_username" 
        tal:on-error="string:" name="field_username" type="text"
        id="field_username">
 <br/>

  Password:<br>
  <span 
   tal:condition="python:test(request.REQUEST_METHOD == 'POST', 1, 0)"
   tal:content="structure python:formErrors['password']" 
   tal:on-error="string:" style="color:red">error goes here</span>
  <input tal:attributes="value request/field_password" 
    tal:on-error="string:" 
    name="field_password" type="text" id="field_password">
 <br/>
<input type="submit"/>
 
Re: Example not fully working by macguyver007 - 2004-10-14
Ahh yes, I forgot to mention the title part. 
You can use a title but you have to use it to reference it in the formErrors dictionary object. 
For example a fomulator object for a text field with the id of user_name 
without a title would be referenced as formErrors['user_name']. 
If you were to supply the same object a title as in User Name: 
you would have to reference it using it's title as in: formErrors['User Name:']. 
I should have mentioned that.

I have had no problems using the example for the formErrors 
using "<span tal:condition" without the "python:test" 
I am also running 2.5.1. The condition itself should return true or false when the
expression is evaluated.
So if I say python:request.HTTP_REFERER == 'http://www.google.com'"
and the referer is not google, the condition will end up being false or 0.
The tal:condition itself is the test for that tag. I believe the python:test
expression is used to do different things other than return true or false.


Another Shortcut by Greg - 2004-10-14
Thank you for the great recipe! I found a shortcut in the 
Python script so that you don't have to use the tal:condition 
in each span for each field. Place
   if req.REQUEST_METHOD != 'POST':
      return {}
just below the assignment to req. This change allows you to 
accomplish the same thing as the tal:condition in the template 
by returning no errors when not responding to a POST, but you 
only have to code it in one place.

Also, what is the error variable used for in the Python script? 
I didn't see it being used, and removing it didn't seem to harm 
anything. Did I miss something?

Finally, using context instead of container in the Python 
script seems more flexible. By using context you don't have 
to have the form in the same folder as the script.


general message on top by jiggsfoo - 2004-10-14
I'd ike to put the error messages on the top the form
instead of placing each one on top of each field then
just color the field labels red on the fields with errors.

How do i do this with ZPT?
 
Re: general message on top by wbell539 - 2004-10-14
I wanted to be able to do that too. I adjusted the Python script so that it always returns a dictionary called 'formErrors'. The dictionary is empty if no errors are detected.

## Script (Python) "check_form"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=formulator_object
##title=
##
from Products.Formulator.Form import FormValidationError

req = context . REQUEST
error = ''
formErrors = { }
try :
    try :
        container [ formulator_object ] . validate_all_to_request ( req )
    except FormValidationError, e:
        for i in e . errors :
            title = i . field . get_value ( 'title' )
            text = i . error_text
            formErrors [ title ] = text
finally :
    return formErrors

Near the beginning of the ZPT that will display the form I include code that includes the following lines to list all of the diagnostics that came originally from 'validate_all_to_request'.

<span tal:define="global formErrors python:container.check_form(here.form);global errorFields python:formErrors.keys()" ></span>
<table>
    <tr tal:repeat="errorField python:errorFields">
        <td><span tal:content="structure python:errorField">errorField</span>: <span tal:content="structure python:formErrors[errorField]">formError</span></td>
    </tr>
</table>

The first line obtains the dictionary as a global from the script listed by me above; then it obtains the dictionary's key list, again as a global. The keys are field names.

tal:repeat iterates throught the dictionary's keys.

For each of these keys the key and its corresponding dictionary item are displayed.