You are not logged in Log in Join
You are here: Home » Members » mukhsein » Build a Searchable Job Board

Log in
Name

Password

 

Build a Searchable Job Board

How To: Build a searchable Job Board

0.0 Introduction

This short howto is a simple walk-through or tutorial to demonstrate how Zope can be used to build interesting "web-apps". It was inspired by the Job Board example from the doc: A Technical Introduction to Object Publishing with Zope by Brian Lloyd and Amos Latteier from digicool.com. It does not however, use the same methods. It's an alternative Job Board. To view a working example of it, see here.

This Howto is dedicated to all struggling Zope newbies. We were all newbies, once. :-)

Note: If you're using Zope-2.1.0 and above, you'll need to disable SubTransactions for the job board entries' Catalog. Or the searching won't work as expected immediately after you add new entry objects.


Update: If you're using Zope-2.2.0 and above, you'll need an extra step to enable "Anonymous" entry additions. Go to the folder where the entry_addForm is stored, click on the "Security" tab at the top. Check the box for "Add Job Board Entry" on the Anonymous column. This should avoid the authorization failures.

So, who is the intended reader?
  • You are relatively new to zope.
  • You have zope set up and running - you get the "Welcome to Zope" page when you access your zope site.
  • You know some dtml (but you're not a jedi yet :))
  • You know how to add, delete etc. objects from the zope odb.
  • You are new to Z Classes and want to know how to build one.
  • You are new to Z Catalogs and want to see a practical example of searching Z Catalogs.

Obviously, this is not intended for Zope gurus. Some things will be rather simplified and not as 'tidy' as one would like. Also, I will explain a lot of things which would be obvious to the average web guru. I've tried to make this howto accessible to not only Zope newbies but also to those new to programming in general. So, what are we going to do?
Here's the Scope of this howto:

Scope
The Job Board is a board where anyone can post job offers (like you find in newspaper classifieds and job "notice boards"). That is, it is publicly postable and readable. Like a 'real' job board. However, unlike a physical one, you'll also be able to search the board for jobs based on your own search criteria. (say, company or job name)

In summary, a user will be able to:
  • Add a new entry onto the Job Board
  • View all the entries on the Job Board
  • Search the Job Board for jobs

Okay, let's begin. Point your browser to your Zope site and log in to the management interface.

1.0 Create the Job Board Folder

The first thing to do is to create the folder where this little app will live. Add a folder with Id "Job_Board". Leave the default check-boxes on. You'll want an "index_html". Then, add another folder within the Job_Board folder - give it the Id "entries". This folder will store our job board entries. Next add a ZCatalog object. Give it the Id "Catalog" - the name of this catalog is important. The 'C' is capital.

2.0 Create the Job Board Entry ZClass

We want to make a template for adding job offers. For this, we'll create a ZClass. The instances are later created by the users themselves as and when they have new jobs to offer without entering the management interface. Go to the "Products" folder in the "Control_Panel" at the top. Add a new product with Id "JobBoard". Click on it.

Now add a new Z Class. Give it an Id "job_board_entry", Meta Type "Job Board Entry". Make sure "Create constructor objects" is checked. In the Base Classes section, add CatalogAware as a base class. Then click "Add".

3.0 Customize the job_board_entry Z Class

Now that we've created the Z Class, it's time to customize it. Click on the job_board_entry class (it's got that "tin can" icon). Let's create some properties to be the job_board_entry class' attributes. Click on the Property Sheets tab (at the top). Add a new property sheet by clicking Add. Give the property sheet the Id "entry_info". Click on it.

Let's add some properties. Add these:

IdTypeValue
position_namestring[string]
position_aboutlines[lines]
position_payfloat0.0
org_namestring[string]
org_homepagestring[string]
org_abouttext[text]
contact_namestring[string]
contact_emailstring[string]
contact_phonestring[string]
offer_expiresstring[string]
titlestring[string]


Apart from the required "0.0" for float type, why did we put values "[string]" etc in the other properties? It's not required but I find that it's easier to see what types those properties are later. Also, why didn't we use type "date" for the offer_expires property? Well, we could but then you would not be able to put values like "Never" or "Real Soon" in it. The type "date" is an object with methods. Not a simple type.

3.1 Customize job_board_entry_add method

When an entry is created, each entry object needs an "id". A unique identifier. Normally, when you create an object using the management interface, Zope will as you to specify the object's "id". We could do that for our entry object as well, but you'd have to make the user or zope check for conflicting ids at creation time - this is sub-optimal to say the least.

The solution to this is to get Zope to generate ids automatically. For this, we'll use the trick suggested in "HowTo: AutoGenerate Random ID for Objects" by Bill (check the "howto" pages on the zope.org website for more tricks and tips!).

Basically, you use a time-stamp generated from the machine's rtc. So, each job_board_entry object will have a string of numbers for their ids. Something of the form: 940962299. To do that, you need to customize the job_board_entry_add method:
Get to the JobBoardProduct folder. Below the job_board_entry class, you'll see a method labeled "job_board_entry_add". Click on it. Add the lines in bold to the method:

job_board_entry_add
<HTML>
<HEAD><TITLE>Add job_board_entry</TITLE></HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">

<dtml-comment> We add the new object by calling the class in
                a with tag. Not only does this get the thing
                added, it adds the new thing's attributes to
                the DTML name space, so we can call methods
                to initialize the object.
</dtml-comment>

<dtml-comment>
                The dtml-calls below are for auto-id during object
                initialization.
</dtml-comment>
<dtml-call "REQUEST.set('ts', ZopeTime())">
<dtml-call "REQUEST.set('id',_.str(_.int(ts)))">

<dtml-with "job_board_entry.createInObjectManager(REQUEST['id'], REQUEST)">

  <dtml-comment>

     You can ad code that modifies the new instance here.

     For example, if you have a property sheet that you want to update
     from form values, you can call it here:

       <dtml-call "propertysheets.Basic.manage_editProperties(REQUEST)">

  </dtml-comment>
<dtml-call "propertysheets.entry_info.manage_editProperties(REQUEST)">
<dtml-call reindex_object>

</dtml-with>

<dtml-comment> Now we need to return something. We do this via
                a redirect so that the URL is correct.

                Unfortunately, the way we do this depends on
                whether we live in a product or in a class.
                If we live in a product, we need to use DestinationURL
                to decide where to go. If we live in a class,
                DestinationURL won't be available, so we use URL2.
</dtml-comment>
<dtml-if NoRedir>
<dtml-else>
  <dtml-if DestinationURL>

    <dtml-call "RESPONSE.redirect(DestinationURL+'/manage_workspace')">

  <dtml-else>

    <dtml-call "RESPONSE.redirect(URL2+'/manage_workspace')">
  </dtml-if>
</dtml-if>

</BODY>
</HTML>

Note: The 2 lines (dtml-call) in bold above tells Zope to update the properties of the newly created objects and the then re-index the ZCatalog, respectively.

If you've got some experience with Zope you quickly realize that if you used the management interface to browse through your entries that it is difficult to determine the objects just from a number. Not to worry, Zope objects have the attribute "Title" which we will use to allow humans to tell at a glance what the objects are. Yes, they are the titles in parentheses, next to the object ids.

The nice thing about this is that you can have the same titles for two objects with different Ids. This is analogous to two people with the exact same name - but having different identities. We will also create the titles automatically as we'll see later.

3.2 Create job_board_entry Z Class view methods

Now then, wouldn't it be great if our job_board_entry class instances could display themselves? Well, they can! Click on the "Methods" tab. Add these two methods: "index_html" and "content_html". Here's what they look like: (The dtml tags are in bold to highlight them)

content_html
<TABLE border="1" bgcolor="aliceblue">

<TR><TH colspan="2" bgcolor="lightblue">Job Offer Entry Object: <dtml-var id></TH><TR>

<TR><TD bgcolor="lightblue"><B>Organization:</B></TD><TD><dtml-var org_name></TD></TR>
<TR><TD bgcolor="lightblue"><B>Homepage:</B></TD><TD><dtml-var org_homepage></TD></TR>
<TR><TD bgcolor="lightblue"><B>About:</B></TD><TD><dtml-var org_about newline_to_br></TD></TR>

<TR><TD bgcolor="lightblue"><B>Position:</B></TD><TD><dtml-var position_name></TD></TR>
<TR>
<TD bgcolor="lightblue"><B>Description:</B></TD>
<TD>
<UL>
<dtml-in position_about>
 <dtml-let item=sequence-item>
  <dtml-if item>
  <LI><dtml-var item></LI>
  </dtml-if>
 </dtml-let>
</dtml-in>
</UL>
</TD>
</TR>
<TR><TD bgcolor="lightblue"><B>Pay Offered:</B></TD>
<TD>
<dtml-if "position_pay==0.0">
 Contact Us
<dtml-else>
 <dtml-var position_pay>
</dtml-if>
</TD></TR>
<TR><TD bgcolor="lightblue"><B>Offer Expires:</B></TD><TD><dtml-var offer_expires></TD></TR>

<TR><TD bgcolor="lightblue"><B>Contact:</B></TD><TD><dtml-var contact_name></TD></TR>
<TR><TD bgcolor="lightblue"><B>Email:</B></TD><TD><dtml-var contact_email></TD></TR>
<TR><TD bgcolor="lightblue"><B>Phone:</B></TD><TD><dtml-var contact_phone></TD></TR>

</TABLE>
index_html
<dtml-var standard_html_header>
<dtml-var content_html>
<dtml-var standard_html_footer>

You might be wondering, with the index_html being only 3 lines, why not combine the two dtml methods into one? The reason is this: "index_html" is always the default dtml method called when you view an object in zope (this applies to folders as well). What this means is that when you view the object on its own, you'll get the content plus the standard html headers and footers. However, if you're viewing a list of these objects on a page (ie. another dtml method or document), then that page would already have the standard footers and headers. On that page, you only call the objects' "content_html" methods and not the "index_html" one. If you're confused, don't worry, this concept will be clearer as we go along.

4.0 Create methods for public object creation

Normally, you can only add new objects through the Zope management interface with the by-now-familiar "Add" button. This is because you want only users with the right access rights to add objects into you Zope site. (As specified in the various acls in the Zope folders.) Normally, the "job_board_entry_addForm" would be used to fill in the details (attributes) for an entry - if you're going to add entry objects through the management interface, you'll need to check that this method is correct.

However, since this is going to be a public posting board, you'll want to allow anyone to add job offers / notices to the job board. After all, that's what it's for. To do that, we'll add 2 methods in the "entries" folder. (ie. ~/Job_Board/entries/) Go to that folder and "Add" 2 dtml methods, name them as below.

We will need to create two methods. One is the "job_board_entry_addform" equivalent and the other is the addform processor method which will process the data from the webform. (dtml tags are in bold)

entry_addForm
<dtml-var standard_html_header>

<H2>Add Job Board Entry</H2>
<FORM action="entry_addProcessor">
<TABLE border="0">

<TR><TH>Organization</TH>
    <TD><input type=text name=organization size="35"></TD>
</TR>
<TR><TH>Homepage</TH>
    <TD><input type=text name=homepage size="35"></TD>
</TR>
<TR><TH>About</TH>
    <td><textarea name="about:text" rows="8" cols="35"></textarea></TD>
</TR>

<TR><TH>Position</TH>
    <TD><input type=text name=position size="35"></TD>
</TR>
<TR><TH>Description</TH>
    <TD><textarea name="description:text" rows="8" cols="35"></textarea></TD>
</TR>
<TR><TH>Pay Offered</TH>
    <TD><input type=text name=pay value="0.0"></TD>
</TR>
<TR><TH>Offer Expires</TH>
    <TD><input type=text name=expires></TD>
</TR>
<TR><TH>Contact</TH>
    <TD><input type=text name=name size="35"></TD>
</TR>
<TR><TH>Email</TH>
    <TD><input type=text name=email size="35"></TD>
</TR>
<TR><TH>Phone</TH>
    <TD><input type=text name=phone size="35"></TD>
</TR>

<TR>
<TD></TD>
<TD><input type=submit value=" Add Entry "><input type=reset value=" Clear Form "></TD>
</TR>
</TABLE>
</FORM>

<P>
<TABLE border="1">
<TR><TH colspan="2" bgcolor="lightblue">Usage</TH></TR>
<TR><TH>Organization</TH>
    <TD>Name of organization eg. <I>Malaysian Open Source Group</I></TD></TR>
<TR><TH>Homepage</TH>
    <TD>Website address without "http://" eg. <I>www.my-opensource.org/oss/</I></TD></TR>
<TR><TH>About</TH>
    <TD>Short intro to your organization's activities</TD></TR>
<TR><TH>Position</TH>
    <TD>Position offered eg. <I>System Administrator</I></TD></TR>
<TR><TH>Description</TH>
    <TD>Short Job description - each line becomes a bullet list item.</TD></TR>
<TR><TH>Pay Offered</TH>
    <TD>In RM eg. <I>2000.0</I> - entering 0.0 will display "Contact Us" when viewed</TD></TR>
<TR><TH>Offer Expires</TH>
    <TD>When this offer expires eg. <I>dd/mm/yyyy</I> or <I>Never</I></TD></TR>
<TR><TH>Contact</TH>
    <TD>The contact person eg. <I>The Manager</I> or <I>Dr. Nah</I></TD></TR>
<TR><TH>Email</TH>
    <TD>Contact person's email eg. <I>docn@my-opensource.org</I></TD></TR>
<TR><TH>Phone</TH>
    <TD>Contact person's phone number</TD></TR>
</TABLE>
</P>

<dtml-var standard_html_footer>
entry_addProcessor
<dtml-var standard_html_header>

<dtml-call "REQUEST.set('org_name', organization)">
<dtml-call "REQUEST.set('org_homepage', homepage)">
<dtml-call "REQUEST.set('org_about', about)">

<dtml-call "REQUEST.set('position_name', position)">
<dtml-call "REQUEST.set('position_about', description)">
<dtml-call "REQUEST.set('position_pay', pay)">
<dtml-call "REQUEST.set('offer_expires', expires)">

<dtml-call "REQUEST.set('contact_name', name)">
<dtml-call "REQUEST.set('contact_email', email)">
<dtml-call "REQUEST.set('contact_phone', phone)">

<dtml-call "REQUEST.set('title', organization+' - '+position)">

<dtml-with "manage_addProduct['JobBoard']">
<dtml-call "job_board_entry_add(_.None, _, NoRedir=1)">
</dtml-with>

<CENTER>
<H2>Thank you for your entry</H2>
<dtml-var linkto_frontpage>
</CENTER>

<dtml-var standard_html_footer>

The first method is pretty much standard for a webform. Those of you familiar with such things will not find anything out of the ordinary. However, note the line:
<FORM action="entry_addProcessor">

That's right. Normally, you would put here the url of a cgi script. But in Zope, you just call another method. Cool. The interesting stuff is in the entry_addProcessor method. This is where we assign the data from the form's fields to the actual object's attributes. This is done by the many <dtml-call...>.

Look at the dtml-calls. You'll notice that the var-names from the html form are mapped to the objects attributes. Actually, there's no reason why the attribute names and the webform field names cannot be exactly the same. I made them this way so that you can see which part is which in the code.

Look at this line:
<dtml-call "REQUEST.set('title', organization+' - '+position)">

This is where we assign the "title" of the object. (remember when we coded the autogenerate IDs?). Here, we set the title so that is a combination of the organization and the position offered. This makes it easy to spot, at a glance, what those objects are when you're browsing the list of items in the "entries" folder using the management interface. Note that its possible for two different objects to have the same title. This is okay, since they would have different object IDs. (ie. those numbers)

Lastly, look at the code snippet below:
<dtml-with "manage_addProduct['JobBoard']">
<dtml-call "job_board_entry_add(_.None, _, NoRedir=1)">
</dtml-with>

The "entry_addProcessor" actually does call the real method to create an "entry" object. But "NoRedir" is set to "1" so that the browser is not redirected as is the normal case when using the management interface. (Try adding an object normally). So, the user will see the next lines of HTML acknowledging the addition / creation of the new "entry" object. If an error were to occur while processing, you won't get to see the "Thank you" message. (Which is good, btw. We don't want to give a false impression, do we?)

Important:The "entry_addProcessor" must be set to "Manager" proxy for it to work. Click on the "entry_addProcessor" dtml method and click on the "Proxy" tab at the top. Make sure that Proxy Role "Manager" is highlighted and click the "Change" button. This 'proxying' is what enables an anonymous user to add new entry objects. The "entry_addProcessor" method acts with the manager's role. This is a very powerful thing so be sure you know what you're doing when you assign public methods the manager's role. In this Job Board case, we want this ability.

5.0 Viewing the Job Board

Okay, so now we have our class done, we can add new job board entries. First, let's build the actual web page where the job board will be viewed. The "welcome" page you will see on the demo site is the one below. Customize to your own needs.

index_html
<dtml-var standard_html_header>
<TABLE border="0">
<TR>
<TH bgcolor="lightblue">
<H2>Welcome to the my-oss IT Job Board!</H2>
</TH>
</TR>
<TR>
<TD>
<P>
<B>T</B>his is a simple web application using zope. The purpose of this
website is for people to post job offers and for people to look for interesting
jobs in the local IT industry (preferably using or building open source projects).
</P>
<P>
<B>T</B>his site was made primarily to demonstrate the use of <A href="http://www.zope.org">Zope</A>
as a web application tool to the <A href="http://www.my-opensource.org">Malaysian Open Source Group</A> members.
</P>
<P>
<B>H</B>opefully, members of the local open source community will also find paying jobs
to sustain themselves while they hack open source code. It's also for companies that are
looking for knowledgeable IT personnel whose job will entail common
open source systems like Apache site admin or programming with egcs and python.
</P>
<P>
<B>B</B>etter yet, if you happen to be looking for open source developers for
an open source project, you can post an offer here. (maybe contract work?)
</P>
<P>
<B>I</B>t is my hope that this site becomes more than a toy and that both
prospective employers and employees will find it useful.
</P>
</TD>
</TR>
<TR>
<TD>
To see how it works, you can:
<OL>
<LI><A href="viewJobBoard">View the Job Board</A></LI>
<LI><A href="entries/entry_addForm">Add an entry to the Job Board</A></LI>
<LI><A href="entry_search">Search the Job Board</A></LI>
</OL>
</TD>
</TR>
</TABLE>
<dtml-var ZopeAttributionButton>
<dtml-var standard_html_footer>

Notice this line:
<LI><A href="viewJobBoard">View the Job Board</A></LI>

The "href" is actually referencing a dtml method - in this case "viewJobBoard". Let me explain a bit here. The reason why dtml methods are called methods is because they are methods of the object in which they live or which they are a part of. In this case, it is a method of the job board object (which is really just a folder object) that implements a view. Object oriented programmers will now understand this method concept in Zope. (and in the process, acquire some Zope Zen :-))

So what does the dtml method "viewJobBoard" look like? See below. As usual, dtml is in bold. Note that the method resides within the "Job Board" folder. (It is a method of that folder object).

viewJobBoard
<dtml-var standard_html_header>
<H2>The Job Board</H2>

<dtml-with entries>

<TABLE border="0">
<dtml-in "objectValues(['Job Board Entry'])">
  <TR><TD><dtml-var content_html></TD></TR>
</dtml-in>
</TABLE>

</dtml-with>

<dtml-var linkto_frontpage>
<dtml-var standard_html_footer>

The dtml:
<dtml-in "objectValues(['Job Board Entry'])">
  <TR><TD><dtml-var content_html></TD></TR>
</dtml-in>

Instructs Zope to look for objects with that Value attached to them. In this case, all objects that have the "meta-type" of "Job Board Entry". What this dtml-in does is loop through all the objects within that folder and if the object is that meta-type, its content_html method is called.

The "linkto_frontpage" line there is just another dtml method (living in the same folder) as a convenient way to make links to the front page. It is a one liner:

linkto_frontpage
<P><A HREF="Job_Board"><H4>Back to front page.</H4></A></P>

Some of you might be wondering: "Why are we even bothering to create such a simple one liner dtml method?" Well, the reason is that, now we can use a short single line within any other dtml methods or dtml documents as shown in "viewJobBoard" above. This same line can be used anywhere dtml is used and any number of times. The benefit? Should we need to change that link, we only need to do it once (in the actual "linkto_frontpage" dtml method) and it will immediately work throughout the site.

For those who are wondering, yes, that's exactly the reason the dtml methods "standard_html_header" and "standard_html_footer" exist (and work the way they do). But there's more! What if, for the whole site, you want a certain "standard_html_header" except for a few folders you want different "standard_html_header" dtml methods? No problem, just make new ones in those folders. They will override the one in the top level folder. This phenomenon is called "acquisition" in Zope-speak and after you've played around with Zope, you'll agree it's pretty simple but very powerful.

6.0 Searching for Job Board Entries

Our little job board is now pretty functional as it is and you can already put it to good use. However, like I promised earlier, we want to go one-up on the real "paper and pins" job boards. You can scan the board for interesting entries but what if the board became super successful and there are hundreds of entries? This is where automated searching can give our Zope job board the edge over conventional ones.

Searching (for objects) in Zope is done using the ZCatalog. As you'll see, ZCatalog is pretty nifty, to say the least. You can use it to catalog and later search for any object or attribute of objects within the context of the Catalog. ZCatalogs are special objects but still objects nonetheless with their own attributes and methods. We've already created one at the start of this tutorial so all we need to do is customize it!

6.1 Customizing the ZCatalog

The ZCatalog catalogs objects for quick searching. The first thing we need to do is to customize the MetaData Table. Go to the Job Board folder object and click on the Catalog object. At the top of the page, click on the MetaData Table tab.

The MetaData Table stores bits of information you want to have ready to display in the results page. They are taken from the objects themselves and stored in the table. So, each object will have it's own row. You'll want to have a minimum of items stored here since it's only needed to give a quick look at the objects. For the real details, we will call the objects themselves (via their content_html method). Data stored in this table will be used to populate the results table generated from the search.

Make sure these items are in the list:
(Some are already listed)

  • id
  • contact_phone
  • offer_expires
  • position_name
  • org_homepage
  • contact_email
  • meta_type
  • contact_name
  • org_name

The next thing to do is to index the catalog with items that will be used to search it. That sounds complicated than it really is. Basically, the items you specify to be indexed will be used in the search. Click on the Indexes tab at the top.
Add these items to the index list:
(They are all text-indexes)

  • position_name
  • position_about
  • org_name
  • org_about
  • org_homepage
These will also be the fields for the search form as we shall see later.

Next click on the Status tab. Make sure that Substransactions is Disabled. Disable it if it isn't.

6.2 Creating the ZCatalog Search Interfaces

Now that we have a catalog, we'll want to make interfaces for it so we can use it. Go back 'out' of the Catalog. Make sure you can see the "Catalog" and "Entries" folders on this level. (this is the Job Board folder). Select Z Search Interface and click Add to add the Z Search Interface.

In the Add form, Click on "Catalog" (ie. make sure it is highlighted), report style set to "Tabular". For the text fields:

FieldValue
Report Identry_report
Report TitleEntry Search Result
Search Input Identry_search
Search Input TitleSearch for Entries

The two dtml methods will be created. We will need to customize the two methods. So, click on them and edit both methods so they look similar to the examples below.

entry_search
<dtml-var standard_html_header>

<FORM action="entry_report" method="get">
<H2><dtml-var document_title></H2>
Enter query parameters:<BR>
<TABLE>
<TR><TH>Organization</TH>
    <TD><INPUT name="org_name"
               width=30 value=""></TD></TR>
<TR><TH>Homepage</TH>
    <TD><INPUT name="org_homepage"
               width=30 value=""></TD></TR>
<TR><TH>About</TH>
    <TD><INPUT name="org_about"
               width=30 value=""></TD></TR>
<TR><TH>Position</TH>
    <TD><INPUT name="position_name"
               width=30 value=""></TD></TR>
<TR><TH>Description</TH>
    <TD><INPUT name="position_about"
               width=30 value=""></TD></TR>

<TR><TD colspan="2" align="center">
<INPUT type="SUBMIT" name="SUBMIT" value="Submit Query">
</TD></TR>
</TABLE>
</FORM>
<P>
<TABLE border="1">
<TR><TH colspan="2" bgcolor="lightblue">Usage</TH></TR>
<TR><TH>Organization</TH><TD>Name of organization eg. <I>Malaysian Open Source Group</I></TD></TR>
<TR><TH>Homepage</TH><TD>Website address without "http://" eg. <I>www.my-opensource.org/oss/</I></TD></TR>
<TR><TH>About</TD><TD>Keywords regarding organization's activities eg. <I>computers</I></TD></TR>
<TR><TH>Position</TD><TD>Position you're interested in eg. <I>System Administrator</I></TD></TR>
<TR><TH>Description</TH><TD>Keyword from job description eg. <I>flexible hours</I></TD></TR>
</TABLE>
</P>
<dtml-var standard_html_footer>
entry_report
<dtml-var standard_html_header>
<dtml-in Catalog size=50 start=query_start>
   <dtml-if sequence-start>

      <dtml-if previous-sequence>

        <A href="<dtml-var URL><dtml-var sequence-query
                 >query_start=<dtml-var previous-sequence-start-number>">
        (Previous <dtml-var previous-sequence-size> results)
        </A>

      </dtml-if previous-sequence>

      <TABLE border>
        <TR>
          <TH>Organization</TH>
          <TH>Homepage</TH>
          <TH>Position</TH>
          <TH>Offer Expires</TH>
          <TH>Contact</TH>
          <TH>Email</TH>
          <TH>Phone</TH>
        </TR>

   </dtml-if sequence-start>

        <TR>
          <TD><dtml-var org_name></TD>
          <TD>
          <A href="http://<dtml-var org_homepage>">
          <dtml-var org_homepage></A>
          </TD>
          <TD>
          <A href="<dtml-var "Catalog.getpath(data_record_id_)">">
          <dtml-var position_name></A>
          </TD>
          <TD><dtml-var offer_expires></TD>
          <TD><dtml-var contact_name></TD>
          <TD><dtml-var contact_email></TD>
          <TD><dtml-var contact_phone></TD>
        </TR>

   <dtml-if sequence-end>

      </TABLE>
      <dtml-if next-sequence>

         <A href="<dtml-var URL><dtml-var sequence-query
            >query_start=<dtml-var next-sequence-start-number>">
         (Next <dtml-var next-sequence-size> results)
         </A>

      </dtml-if next-sequence>
   </dtml-if sequence-end>

<dtml-else>

  There was no data matching this <dtml-var title_or_id> query.

</dtml-in>
<dtml-var linkto_frontpage>
<dtml-var standard_html_footer>

I have not marked any bold items this time since there are many instances where the generated dtml methods differ from the ones above. Either customize it to your needs or simply copy and paste.

Now that we've got all the pieces done, all we need to do now is create some Job Board Entry objects. (ie. instances of the class) Go to the Job Board folder and click on the View tab. This will call that folder's index_html method. Select the add new entry and add one. (You can also call the "enter_addForm" dtml method from within the "Entries" folder directly.) Add another entry. Make it 3 or 4. Check that the objects are actually generated in the "Entries" folder.

View the created objects and make some searches. This is where you might need to fix some bugs. It happens. I know this is obvious to many of you but please do check and test your web apps before you make it "live" on the internet or intranet. That's about it, really. Congratulations for staying with it!

7.0 Conclusion

We have actually covered quite a lot of ground. Specifically:
  • Defining new ZClasses
  • Creating object "publishing" methods (content_html)
  • "Public access" object creation (or class instantiation)
  • Cataloging objects
  • Creating search interface
Which pretty much covers the basics of using Zope and its features. Of course, this tutorial only scratches the surface of what Zope can do but it is my hope that you, the reader, at least come to appreciate the power and simplicity of the Zope web application environment.

Where to from here? Well, there are a few things you can try like tweaking the methods or maybe add a hyperlink to the email address to "mailto:" that address. Or make a real product where you can instantiate an "instant" job board. (hint: you'll need to create another class for this. An "Object Manager" subclass, to be exact.) Or better yet, make a different app altogether. Check the Zope.org site for user contributed products and howtos. There's quite a few. :-)
Good luck and above all, have fun!

8.0 Appendix

Over the months that this Howto has been published, a fair number of folks have written in with comments and questions and thanks. So, I'd like to express my gratitude here to all those who have written in - you've all made this howto that much better. Cheers!