Latest version of this document can be found here.

Formulator_With_ZPT

Created by . Last modified on 2003/08/05.

Most of us are beginning to realize the importance ZPT (Zope Page Templates) plays in the future of Zope. Many of us have begun to migrate toward converting how we build sites from DTML to ZPT as a result. For the most part, this is a less painful experience than most things in programming :) There is one notable exception to this rule: Formulator. It is to make this one_last_hurdle easy that I have written this HowTo. I would like to thank all the people on the discussion list for putting up with me and answering my questions. It is due to you—not me—that this HowTo has come together (and my sanity remained somewhat intact). I would especially like to thank runyaga who wrote an excellent HowTo for creating a Python script to use MailHost, and Clemens Robbenhaar, who took pity on the density of my skull and continued to guide this blind man to the obvious.

Here are the steps to follow to create a Formulator form using ZPT and Python and steering clear of DTML (yay!):

I will give an example of how to create a feedback form for a website, asking the user to fill in their name, email, phone and comments. When submitted, this form sends an email to you, and shows a page confirming that the feedback has been sent. In the case that the form was not filled in correctly, an error page is displayed.

The feedback form is written using ZPT, the action method points to another ZPT, which displays the result page. The result page contains a call to a Python Script that calls Formulator to check the form. If the form has been filled in correctly, the Mailhost service is used to send the feedback email and a confirmation message is returned. If the form as not correctly filled in, an error page is shown.

1: (Optional) Create a macro that you can call from any page. I prefer to do this because it compartimentalizes information and therefore makes reusability easy. I also have all of this material built into my "skel" directory, so that when I take on a new client, I simply copy the skel, rename it, and all this functionality is intact. So, let's presume you want to do the same. You have a page called "Contact_Us.zpt" with the following line of code:

<p tal:replace="here/common_pages/macros/contact_us" />


where "here" lets the parser know to start looking from the present position, "common_pages" is a Zope Page Template you've created somewhere in your acquisition path, "macros" is a required word in ZPT you must use if you're calling a macro and "contact_us" is the name of the macro you've created.

Then you create a page called "common_pages" somewhere in your acquisition path with the following macro:

<div metal:define-macro="contact_us">
<form name="email_us_formulator" method="post" action="email_us.zpt">
<center><font size=5><font face="verdana">
 Thank you for interest. Please let us know how we can serve you!
</font></font><br>&nbsp;<br>
<table tal:define="form here/email_us_formulator" align=center>
  <tr tal:define="yourname request/YourName|nothing">
   <th align="left">
    <font size=5><font face="verdana"><b>
     Your Name:
    </b></font></font>
   </th>
   <td><input type="text" name="YourName"
       tal:replace="structure python:form.YourName.render(yourname)" />
   </td>
 </tr>
  <tr tal:define="youremail request/YourEmail|nothing">
   <th align="left">
    <font size=5><font face="verdana"><b>
     Your Email Address:
    </b></font></font>
   </th>
   <td><input type="text" name="YourEmail"
              tal:replace="structure python:form.YourEmail.render(youremail)">
   </td>
 </tr>
  <tr tal:define="yourphone request/YourPhone|nothing">
   <th align="left">
    <font size=4><font face="verdana"><b>
     Your Phone Number:
    </b></font></font>
   </th>
   <td><input type="text" name="YourPhone"
              tal:replace="structure python:form.YourPhone.render(yourphone)">
   </td>
 </tr>
  <tr tal:define="yourcomments request/YourComments|nothing">
   <th align="left">
    <font size=4><font face="verdana"><b>
     Your Comments:
    </b></font></font>
   </th>
   <td><textarea type="text" name="YourComments"
              tal:replace="structure python:form.YourComments.render(yourcomments)"></textarea>
   </td>
 </tr><tr cols="*,350,*">
   <td> </td>
   <td align=center><input type="submit" value="  Submit  "></td>
   <td> </td>
 </tr>
</table><br>

</form>
</div>

 
Now, here's an explanation for the harder-to-understand components of the above:
2. Next, we'll need to construct the page to which we are posting. In my example, this is called "email_us.zpt". Here is my file:
 
<html>
<head>
<style type="text/css">
<-- Put your styling here. -->
</head>
<body bgcolor="#ffffff">
<p tal:replace="structure python:here.mailScript()" />
</body>
</html>

 
So, we'll need to place a Python script called "mailScript" somewhere in our acquisition path.
 
3: Here's my mailScript() script:
from Products.Formulator.Errors import ValidationError, FormValidationError

# Change the next line to the name of your form, i.e. "context.name_of_my_form"
form = context.email_us_formulator
# Here I initialize a couple of variables
i = 0
myField = []
for field in form.get_fields():
myField.append(field.get_value('title'))
i = i + 1

# Here we'll catch errors. BE SURE TO CHANGE THE NAME OF YOUR FORM!
try:
result = context.email_us_formulator.validate_all(context.REQUEST)
except FormValidationError, errlist:
print "<font style='font: 17px'>I'm sorry. Some of the information you entered "
print "was either incorrect or incomplete. Please use the "back" "
print "button and fill it in correctly. Thank you!<br><br>"
for error in errlist.errors:
print '<b>' + error.field.get_value('title') + ': '+ error.error_text
print '</b><br></font>'
return printed

# If we didn't get an error, then let's print the page thanking the visitor for 
# filling out the form.
if result:
print "<font style='font: 17px'>"
print "<br><br><br><br><br><br>\n"
print "<center>\n"
# 'YourName' is a variable I create in my Formulator forms. Substitute whatever you use.
print "Thank you, " + result['YourName'] + "! We'll be sure to follow up "
print "with you shortly!<br><br>\n"
print "Sincerely,<br>\n"
print "<b>Homer Simpson, owner</b>\n"
print "</center></font>"

# Now, let's see if we can connect to the MailHost.
try:
mailhost=getattr(context, context.superValues('Mail Host')[0].id)
except:
raise AttributeError, "Can't find a Mail Host object."

# Here we fill in the variables for the email we'll send. Change the 
# values to your needs.
mTo = 'Batman@GothamCity.com'
# Once again, I use the variables (such as YourEmail) that I create in my 
# Formulator form. Substitute accordingly.
mFrom = result['YourEmail']
mSubj = 'Feedback From Your Web Site!'
message = []
message.append("Hi, Snoopy! You've just been sent an email from your ")
message.append("Web site!\n")
message.append("Sender's name: %s" % result['YourName'])
message.append("Sender's phone: %s" % result['YourPhone'])
message.append("Sender's email: %s" % result['YourEmail'])
message.append("Sender's comments: %s" % result['YourComments'])
mMsg = '\n'.join(message)

mailhost.send(mMsg, mto=mTo, mfrom=mFrom, subject=mSubj)
return printed

 
Since I'm not trying to give a HowTo on writing Python scripts, I'll limit my explanations to the comments already included. This should be sufficient for anyone's needs that's trying to get Formulator to work with ZPT. If not, please send me an email and I'll see what I can do!
 
beno
 
Download
You can download an example of this here:
 
formulator_example
 
Even Better!
Would you like an easy way to develop a killer Web site for "brochureware" sites that has all this functionality built in? Check out my HowTo for EasySite!