Searching and Categorizing Content

The ZCatalog is Zope's built in search engine. It allows you to categorize and search all kinds of Zope objects. You can also use it to search external data such as relational data, files, and remote web pages. In addition to searching you can use the ZCatalog to organize collections of objects.

The ZCatalog supports a rich query interface. You can perform full text searching, and can search multiple indexes at once. In addition, the ZCatalog keeps track of meta-data about indexed objects. Here are the two most common ZCatalog usage patterns:

Mass Cataloging
Cataloging a large collection of objects all at once.
Automatic Cataloging
Cataloging objects as they are created and tracking changes made to them.

Getting started with Mass Cataloging

Let's take a look at how to use the ZCatalog to search documents. Cataloging a bunch of objects all at once is called mass Cataloging. Mass Cataloging involves three steps:

Creating a ZCatalog

Choose ZCatalog from the product add list to create a ZCatalog object within a subfolder named Zoo. This takes you to the ZCatalog add form, as shown in the figure below.

ZCatalog add form

Figure 16-1 ZCatalog add form

The Add form asks you for an Id and a Title. Give your ZCatalog the Id AnimalCatalog and click Add to create your new ZCatalog. The ZCatalog icon looks like a folder with a small magnifying glass on it. Select the AnimalCatalog icon to see the Contents view of the ZCatalog.

A ZCatalog looks a lot like a folder, but it has a few more tabs. Six tabs on the ZCatalog are the exact same six tabs you find on a standard folder. ZCatalog have the following views: Contents, Catalog, Properties, Indexes, Metadata, Find Objects, Advanced, Undo, Security, and Ownership. When you click on a ZCatalog, you are on the Contents view. Here, you can add new objects and the ZCatalog will contain them just as any folder does. Although a ZCatalog is like a normal Zope folder, this does not imply that the objects contained within it are automatically searchable. A ZCatalog can catalog objects at any level of your site, and it needs to be told exactly which ones to index.

Creating Indexes

In order to tell Zope what to catalog and where to store the information, we need to create a Lexicon and an Index. A Lexicon is necessary to provide word storage services for full-text searching, and an Index is the object which stores the data necessary to perform fast searching.

In the contents view of the AnimalCatalog ZCatalog, choose ZCTextIndex Lexicon, and give it an id of zooLexicon

ZCTextIndex Lexicon add form

Figure 16-2 ZCTextIndex Lexicon add form

Now we can create an index that will record the information we want to have in the ZCatalog. Click on the Indexes tab of the ZCatalog. A drop down menu lists the available indexes. Choose ZCTextIndex; in the add form fill in the id zooTextIdx. Fill in PrincipiaSearchSource in the "Field name" input. This tells the ZCTextIndex to index the body text of the DTML Documents (PrincipiaSearchSource is an API method of all DTML Document and Method objects). Note that zooLexicon is preselected in the Lexicon menu.

ZCTextIndex add form

Figure 16-3 ZCTextIndex add form

To keep this example short we will skip over some of the options presented here. In the section on indexes below, we will discuss this more thoroughly.

Additionally, we will have to tell the ZCatalog which attributes of each cataloged object that it should store directly. These attributes are called Metadata. For now, just go to the Metadata tab of the ZCatalog and add id and title.

Finding and Cataloging Objects

Now that you have created a ZCatalog and an Index, you can move onto the next step: finding objects and cataloging them. Suppose you have a zoo site with information about animals. To work with these examples, create two DTML Documents along-side the AnimalCatalog object (within the same folder that contains the AnimalCatalog ZCatalog) that contain information about reptiles and amphibians.

The first should have an Id of "chilean_frog", a title "Chilean four-eyed frog" and its body text should read something like this:

        The Chilean four-eyed frog has a bright
        pair of spots on its rump that look like enormous eyes. When
        seated, the frog's thighs conceal these eyespots. When
        predators approach, the frog lowers its head and lifts its
        rump, creating a much larger and more intimidating head.
        Frogs are amphibians.

For the second, fill in an id of "carpet_python" and a title of "Carpet Python"; its body text could be:

        *Morelia spilotes variegata* averages 2.4 meters in length.  It
        is a medium-sized python with black-to-gray patterns of
        blotches, crossbands, stripes, or a combination of these
        markings on a light yellowish-to-dark brown background.  Snakes
        are reptiles.

Visitors to your Zoo want to be able to search for information on the Zoo's animals. Eager herpetologists want to know if you have their favorite snake, so you should provide them with the ability to search for certain words and show all the documents that contain those words. Searching is one of the most useful and common web activities.

The AnimalCatalog ZCatalog you created can catalog all of the documents in your Zope site and let your users search for specific words. To catalog your documents, go to the AnimalCatalog ZCatalog and click on the Find Objects tab.

In this view, you tell the ZCatalog what kind of objects you are interested in. You want to catalog all DTML Documents so select DTML Document from the Find objects of type multiple selection and click Find and Catalog.

The ZCatalog will now start from the folder where it is located and search for all DTML Documents. It will search the folder and then descend down into all of the sub-folders and their sub-folders. For example, if your ZCatalog is located at /Zoo/AnimalCatalog, then the /Zoo folder and all its subfolders will get searched.

If you have lots and lots of objects, this may take a long time to complete, so be patient.

After a period of time, the ZCatalog will take you to the Catalog view automatically, with a status message telling you what it just did.

Below the status information is a list of objects that are cataloged, they are all DTML Documents. To confirm that these are the objects you are interested in, you can click on them to visit them.

You have completed the first step of searching your objects, cataloging them into a ZCatalog. Now your documents are in the ZCatalog's database. Now you can move onto the fourth step, creating a web page and result form to query the ZCatalog.

Search and Report Forms

To create search and report forms, make sure you are inside the AnimalCatalog ZCatalog and select Z Search Interface from the add list. Select the AnimalCatalog ZCatalog as the searchable object, as shown in the figure below.

Creating a search form for a ZCatalog

Figure 16-4 Creating a search form for a ZCatalog

Name the Report Id "SearchResults", the Search Input Id "SearchForm", select "Generate Page Templates" and click Add. This will create two new Page Templates in the AnimalCatalog ZCatalog named SeachForm and SearchResults.

These objects are contained in the ZCatalog, but they are not cataloged by the ZCatalog. The AnimalCatalog has only cataloged DTML Documents. The search Form and Report templates are just a user interface to search the animal documents in the ZCatalog. You can verify this by noting that the search and report forms are not listed in the Cataloged Objects tab.

To search the AnimalCatalog ZCatalog, select the SearchForm template and click on its Test tab.

By typing words into the ZooTextIdx form element you can search all of the documents cataloged by the AnimalCatalog ZCatalog. For example, type in the word "Reptiles". The AnimalCatalog ZCatalog will be searched and return a simple table of objects that have the word "Reptiles" in them. The search results should include the carpet python. You can also try specifying multiple search terms like "reptiles OR amphibians". Search results for this query should include both the Chilean four-eyed Frog and the carpet python. Congratulations, you have successfully created a ZCatalog, cataloged content into it and searched it through the web.

Configuring ZCatalogs

The ZCatalog is capable of much more powerful and complex searches than the one you just performed. Let's take a look at how the ZCatalog stores information. This will help you tailor your ZCatalogs to provide the sort of searching you want.

Defining Indexes

ZCatalogs store information about objects and their contents in fast databases called indexes. Indexes can store and retrieve large volumes of information very quickly. You can create different kinds of indexes that remember different kinds of information about your objects. For example, you could have one index that remembers the text content of DTML Documents, and another index that remembers any objects that have a specific property.

When you search a ZCatalog you are not searching through your objects one by one. That would take far too much time if you had a lot of objects. Before you search a ZCatalog, it looks at your objects and remembers whatever you tell it to remember about them. This process is called indexing. From then on, you can search for certain criteria and the ZCatalog will return objects that match the criteria you provide.

A good way to think of an index in a ZCatalog is just like an index in a book. For example, in a book's index you can look up the word Python:

        Python: 23, 67, 227

The word Python appears on three pages. Zope indexes work like this except that they map the search term, in this case the word Python, to a list of all the objects that contain it, instead of a list of pages in a book.

In Zope 2.6, indexes can be added and removed from a ZCatalog using a new, "pluggable" index interface as shown in the figure below:

Managing indexes

Figure 16-5 Managing indexes

Each index has a name, like PrincipiaSearchSource, and a type, like ZCTextIndex.

When you catalog an object the ZCatalog uses each index to examine the object. The ZCatalog consults attributes and methods to find an object's value for each index. For example, in the case of the DTML Documents cataloged with a PrincipiaSearchSource index, the ZCatalog calls each document's PrincipiaSearchSource method and records the results in its PrincipiaSearchSource index. If the ZCatalog cannot find an attribute or method for an index, then it ignores it. In other words it's fine if an object does not support a given index. There are eight kinds of indexes:

ZCTextIndex
Searches text. Use this kind of index when you want a full-text search.
FieldIndex
Searches objects for specific values. Use this kind of index when you want to search date objects, numbers, or specific strings.
KeywordIndex
Searches collections of specific values. This index is like a FieldIndex, but it allows you to search collections rather than single values.
PathIndex
Searches for all objects that contain certain URL path elements. For example, you could search for all the objects whose paths begin with /Zoo/Animals.
TopicIndex
Searches among FilteredSets; each set contains the document IDs of documents which match the set's filter expression. Use this kind of index to optimize frequently-accessed searches.
DateIndex
A subclass of FieldIndex, optimized for date-time values. Use this index for any field known to be a date or a date-time.
DateRangeIndex
Searches objects based on a pair of dates / date-times. Use this index to search for objects which are "current" or "in effect" at a given time.
TextIndex
Old version of a full-text index. Only provided for backward compatibility, use ZCTextIndex instead.

We'll examine these different indexes more closely later in the chapter. New indexes can be created from the Indexes view of a ZCatalog. There, you can enter the name and select a type for your new index. This creates a new empty index in the ZCatalog. To populate this index with information, you need to go to the Advanced view and click the the Update Catalog button. Recataloging your content may take a while if you have lots of cataloged objects. For a ZCTextIndex, you will also need a ZCTextIndex Lexicon object in your ZCatalog - see below for details.

To remove an index from a ZCatalog, select the Indexes and click on the Delete button. This will delete the index and all of its indexed content. As usual, this operation is undoable.

Defining Meta Data

The ZCatalog can not only index information about your object, but it can also store information about your object in a tabular database called the Metadata Table. The Metadata Table works similarly to a relational database table, it consists of one or more columns that define the schema of the table. The table is filled with rows of information about cataloged objects. These rows can contain information about cataloged objects that you want to store in the table. Your meta data columns don't need to match your ZCatalog's indexes. Indexes allow you to search; meta-data allows you to report search results.

The Metadata Table is useful for generating search reports. It keeps track of information about objects that goes on your report forms. For example, if you create a Metadata Table column called Title, then your report forms can use this information to show the titles of your objects that are returned in search results instead of requiring that you actually obtain the object to show its title.

To add a new Metadata Table column, type in the name of the column on the Metadata Table view and click Add. To remove a column from the Metadata Table, select the column check box and click on the Delete button. This will delete the column and all of its content for each row. As usual, this operation is undoable. Next let's look more closely at how to search a ZCatalog.

Searching ZCatalogs

You can search a ZCatalog by passing it search terms. These search terms describe what you are looking for in one or more indexes. The ZCatalog can glean this information from the web request, or you can pass this information explicitly from DTML or Python. In response to a search request, a ZCatalog will return a list of records corresponding to the cataloged objects that match the search terms.

Searching with Forms

In this chapter you used the Z Search Interface to automatically build a Form/Action pair to query a ZCatalog (the Form/Action pattern is discussed in the chapter entitled Advanced Page Templates ). The Z Search Interface builds a very simple form and a very simple report. These two methods are a good place to start understanding how ZCatalogs are queried and how you can customize and extend your search interface.

Suppose you have a ZCatalog that holds news items named NewsCatalog. Each news item has content, an author and a date attribute. Your ZCatalog has three indexes that correspond to these attributes, namely "contentTextIdx", "author" and "date". The contents index is a ZCTextIndex, and the author and date indexes are a FieldIndex and a DateIndex. For the ZCTextIndex you will need a ZCTextIndexLexicon, and to display the search results in the Report template, you should add the author, date and absolute_url attributes as Metadata. Here is a search form that would allow you to query such a ZCatalog:

        <html><body>
        <form action="Report" method="get">
        <h2 tal:content="template/title_or_id">Title</h2>
        Enter query parameters:<br><table>
        <tr><th>Author</th>
        <td><input name="author" width=30 value=""></td></tr>
        <tr><th>Content</th>
        <td><input name="contentTextIdx" width=30 value=""></td></tr>
        <tr><th>Date</th>
        <td><input name="date" width=30 value=""></td></tr>
        <tr><td colspan=2 align=center>
        <input type="SUBMIT" name="SUBMIT" value="Submit Query">
        </td></tr>
        </table>
        </form>
        </body></html>

This form consists of three input boxes named contentTextIdx, author, and date. These names must match the names of the ZCatalog's indexes for the ZCatalog to find the search terms. Here is a report form that works with the search form:

        <html>
        <body tal:define="searchResults here/NewsCatalog;">
        <table border>
          <tr>
            <th>Item no.</th>
            <th>Author</th>
            <th>Absolute url</th>
            <th>Date</th>
          </tr>
          <div tal:repeat="item searchResults">
          <tr>
            <td>
              <a href="link to object" tal:attributes="href item/absolute_url">
                #<span tal:replace="repeat/item/number">
                  search item number goes here
                </span>
              </a>
            </td>
            <td><span tal:replace="item/author">author goes here</span></td>
            <td><span tal:replace="item/date">date goes here</span></td>
          </tr>
          </div>
        </table>
        </body></html>

There are a few things going on here which merit closer examination. The heart of the whole thing is in the definition of the searchResults variable:

        <body tal:define="searchResults here/NewsCatalog;">

This calls the NewsCatalog ZCatalog. Notice how the form parameters from the search form ( contentTextIdx , author, date ) are not mentioned here at all. Zope automatically makes sure that the query parameters from the search form are given to the ZCatalog. All you have to do is make sure the report form calls the ZCatalog. Zope locates the search terms in the web request and passes them to the ZCatalog.

The ZCatalog returns a sequence of Record Objects (just like ZSQL Methods). These record objects correspond to search hits, which are objects that match the search criteria you typed in. For a record to match a search, it must match all criteria for each specified index. So if you enter an author and some search terms for the contents, the ZCatalog will only return records that match both the author and the contents.

ZSQL Record objects have an attribute for every column in the database table. Record objects for ZCatalogs work very similarly, except that a ZCatalog Record object has an attribute for every column in the Metadata Table. In fact, the purpose of the Metadata Table is to define the schema for the Record objects that ZCatalog queries return.

Searching from Python

Page Templates make querying a ZCatalog from a form very simple. For the most part, Page Templates will automatically make sure your search parameters are passed properly to the ZCatalog.

Sometimes though you may not want to search a ZCatalog from a web form; some other part of your application may want to query a ZCatalog. For example, suppose you want to add a sidebar to the Zope Zoo that shows news items that only relate to the animals in the section of the site that you are currently looking at. As you've seen, the Zope Zoo site is built up from Folders that organize all the sections according to animal. Each Folder's id is a name that specifies the group or animal the folder contains. Suppose you want your sidebar to show you all the news items that contain the id of the current section. Here is a Script called relevantSectionNews that queries the news ZCatalog with the currentfolder's id:

        ## Script (Python) "relevantSectionNews"
        ##
        """ Returns news relevant to the current folder's id """
        id=context.getId()
        return context.NewsCatalog({'contentTextIdx' : id})

This script queries the NewsCatalog by calling it like a method. ZCatalogs expect a mapping as the first argument when they are called. The argument maps the name of an index to the search terms you are looking for. In this case, the contentTextIdx index will be queried for all news items that contain the name of the current Folder. To use this in your sidebar place you could insert this snippet where appropriate in the main ZopeZoo Page Template:

        ...
        <ul>
          <li tal:repeat="item here/relevantSectionNews">
            <a href="news link" tal:attributes="href item/absolute_url">
              <span tal:replace="item/title">news title</span>
            </a>
          </li>
        </ul>
        ...     

This template assumes that you have defined absolute_url and title as Metadata columns in the NewsCatalog. Now, when you are in a particular section, the sidebar will show a simple list of links to news items that contain the id of the current animal section you are viewing.

Searching and Indexing Details

Earlier you saw that the ZCatalog supports eight types of indexes. Let's examine these indexes more closely to understand what they are good for and how to search them.

Searching ZCTextIndexes

A ZCTextIndex is used to index text. After indexing, you can search the index for objects that contain certain words. ZCTextIndexes support a rich search grammar for doing more advanced searches than just looking for a word.

Boolean expressions

Search for Boolean expressions like:

          word1 AND word2

This will search for all objects that contain both "word1" and "word2". Valid Boolean operators include AND, OR, and NOT. A synonym for NOT is a leading hyphen:

          word1 -word2

which would search for occurences of "word1" but would exclude documents which contain "word2". A sequence of words without operators implies AND. A search for "carpet python snakes" translates to "carpet AND python AND snakes".

Parentheses

Control search order with parenthetical expressions:

          (word1 AND word2) OR word3)

This will return objects containing "word1" and "word2" or just objects that contain the term "word3".

Wild cards

Search for wild cards like:

          Z*

which returns all words that begin with "Z", or:

           Zop?

which returns all words that begin with "Zop" and have one more character - just like in a Un*x shell. Note though that wild cards cannot be at the beginning of a search phrase. "?ope" is an illegal search term and will be ignored.

Phrase search

Double-quoted text implies phrase search, for example:

          "carpet python" OR frogs 

will search for all occurences of the phrase "carpet python" or of the word "frogs"

All of these advanced features can be mixed together. For example:

        ((bob AND uncle) NOT Zoo*)

will return all objects that contain the terms "bob" and "uncle" but will not include any objects that contain words that start with "Zoo" like "Zoologist", "Zoology", or "Zoo" itself.

Similarly, a search for:

        snakes OR frogs -"carpet python"

will return all objects which contain the word "snakes" or "frogs" but do not contain the phrase "carpet python".

Querying a ZCTextIndex with these advanced features works just like querying it with the original simple features. In the HTML search form for DTML Documents, for example, you could enter "Koala AND Lion" and get all documents about Koalas and Lions. Querying a ZCTextIndex from Python with advanced features works much the same; suppose you want to change your relevantSectionNews Script to not include any news items that contain the word "catastrophic":

        ## Script (Python) "relevantSectionNews"
        ##
        """ Returns relevant, non-catastropic news """"
        id=context.getId()
        return context.NewsCatalog(
                 {'contentTextIdx' : id + ' -catastrophic'}
                )

ZCTextIndexes are very powerful. When mixed with the Automatic Cataloging pattern described later in the chapter, they give you the ability to automatically full-text search all of your objects as you create and edit them.

Lexicons

Lexicons are used by ZCTextIndexes. Lexicons process and store the words from the text and help in processing queries.

Lexicons can:

Normalize Case
Often you want search terms to be case insensitive, eg. a search for "python", "Python" and "pYTHON" should return the same results. The lexicons' Case Normalizer does exactly that.
Remove stop words
Stop words are words that are very common in a given language and should be removed from the index. They would only cause bloat in the index and add little information.
Split text into words
A splitter parses text into words. Different texts have different needs of word splitting - if you are going to process HTML documents, you might want to use the HTML aware splitter which effectively removes HTML tags. On the other hand, if you are going to index plain text documents about HTML, you don't want to remove HTML tags - people might want to look them up. Also, an eg. chinese language document has a different concept of words and you might want to use a different splitter.

The Lexicon uses a pipeline architecture. This makes it possible to mix and match pipeline components. For instance, you could implement a different splitting strategy for your language and use this pipeline element in conjunction with the standard text processing elements. Implementing a pipeline element is out of the scope of this book; for examples of implementing and registering a pipeline element see eg. lib/python/Products/ZCTextIndex/Lexicon.py. A pipeline element should conform to the IPipelineElement interface.

To create a ZCTextIndex, you first have to create a Lexicon object. Multiple ZCTextIndexes can share the same lexicon.

Searching Field Indexes

FieldIndexes differ slightly from ZCTextIndexes. A ZCTextIndex will treat the value it finds in your object, for example the contents of a News Item, like text. This means that it breaks the text up into words and indexes all the individual words.

A FieldIndex does not break up the value it finds. Instead, it indexes the entire value it finds. This is very useful for tracking objects that have traits with fixed values.

In the news item example, you created a FieldIndex author. With the existing search form, this field is not very useful. Unless you know exactly the name of the author you are looking for, you will not get any results. It would be better to be able to select from a list of all the unique authors indexed by the author index.

There is a special method on the ZCatalog that does exactly this called uniqueValuesFor. The uniqueValuesFor method returns a list of unique values for a certain index. Let's change your search form and replace the original author input box with something a little more useful:

        <html><body>
        <form action="Report" method="get">
        <h2 tal:content="template/title_or_id">Title</h2>
        Enter query parameters:<br><table>
        <tr><th>Author</th>
        <td>
          <select name="author:list" size="6" multiple>             
            <option 
              tal:repeat="item python:here.NewsCatalog.uniqueValuesFor('author')" 
              tal:content="item"
              value="opt value">
            </option>
          </select>
        </td></tr>
        <tr><th>Content</th>
        <td><input name="content_index" width=30 value=""></td></tr>
        <tr><th>Date</th>
        <td><input name="date_index" width=30 value=""></td></tr>
        <tr><td colspan=2 align=center>
        <input type="SUBMIT" name="SUBMIT" value="Submit Query">
        </td></tr>
        </table>
        </form>
        </body></html>

The new, important bit of code added to the search form is:

          <select name="author:list" size="6" multiple>             
            <option 
              tal:repeat="item python:here.NewsCatalog.uniqueValuesFor('author')" 
              tal:content="item"
              value="opt value">
            </option>
          </select>

In this example, you are changing the form element author from just a simple text box to an HTML multiple select box. This box contains a unique list of all the authors that are indexed in the author FieldIndex. When the form gets submitted, the select box will contain the exact value of an authors name, and thus match against one or more of the news objects. Your search form should look now like the figure below.

Range searching and unique Authors

Figure 16-6 Range searching and unique Authors

That's it. You can continue to extend this search form using HTML form elements to be as complex as you'd like. In the next section, we'll show you how to use the next kind of index, keyword indexes.

Searching Keyword Indexes

A KeywordIndex indexes a sequence of keywords for objects and can be queried for any objects that have one or more of those keywords.

Suppose that you have a number of Image objects that have a keywords property. The keywords property is a lines property that lists the relevant keywords for a given Image, for example, "Portraits", "19th Century", and "Women" for a picture of Queen Victoria.

The keywords provide a way of categorizing Images. Each Image can belong in one or more categories depending on its keywords property. For example, the portrait of Queen Victoria belongs to three categories and can thus be found by searching for any of the three terms.

You can use a Keyword index to search the keywords property. Define a Keyword index with the name keywords on your ZCatalog. Then catalog your Images. Now you should be able to find all the Images that are portraits by creating a search form and searching for "Portraits" in the keywords field. You can also find all pictures that represent 19th Century subjects by searching for "19th Century".

It's important to realize that the same Image can be in more than one category. This gives you much more flexibility in searching and categorizing your objects than you get with a FieldIndex. Using a FieldIndex your portrait of Queen Victoria can only be categorized one way. Using a KeywordIndex it can be categorized a couple different ways.

Often you will use a small list of terms with KeywordIndexes. In this case you may want to use the uniqueValuesFor method to create a custom search form. For example here's a snippet of a Page Template that will create a multiple select box for all the values in the keywords index:

        <select name="keywords:list" multiple>
          <option 
            tal:repeat="item python:here.uniqueValuesFor('keywords')"
            tal:content="item">
              opt value goes here
          </option>
        </select>

Using this search form you can provide users with a range of valid search terms. You can select as many keywords as you want and Zope will find all the Images that match one or more of your selected keywords. Not only can each object have several indexed terms, but you can provide several search terms and find all objects that have one or more of those values.

Searching Path Indexes

Path indexes allow you to search for objects based on their location in Zope. Suppose you have an object whose path is /zoo/animals/Africa/tiger.doc. You can find this object with the path queries: /zoo, or /zoo/animals, or /zoo/animals/Africa. In other words, a path index allows you to find objects within a given folder (and below).

If you place related objects within the same folders, you can use path indexes to quickly locate these objects. For example:

        <h2>Lizard Pictures</h2>
        <p tal:repeat="item
            python:here.AnimalCatalog(pathindex='/Zoo/Lizards', 
            meta_type='Image')">
          <a href="url" tal:attributes="href item/absolute_url" tal:content="item/title">
            document title
          </a>
        </p>    

This query searches a ZCatalog for all images that are located within the /Zoo/Lizards folder and below. It creates a link to each image. To make this work, you will have to create a FieldIndex meta_type and Metadata entries for absolute_url and title.

Depending on how you choose to arrange objects in your site, you may find that a path indexes are more or less effective. If you locate objects without regard to their subject (for example, if objects are mostly located in user "home" folders) then path indexes may be of limited value. In these cases, key word and field indexes will be more useful.

Searching DateIndexes

DateIndexes work like FieldIndexes, but are optimised for DateTime values. To minimize resource usage, DateIndexes have a resolution of one minute, which is considerably lower than the resolution of DateTime values.

DateIndexes are used just like FieldIndexes; below in the section on "Advanced Searching with Records" we present an example of searching them.

Searching DateRangeIndexes

DateRangeIndexes are specialised for searching for ranges of DateTime values. An example application would be NewsItems which have two DateTime attributes effective and expiration, and which should only be published if the current date would fall somewhere in between these two date values. Like DateIndexes, DateRangeIndexes have a resolution of one minute.

Searching TopicIndexes

A TopicIndex is a container for so-called FilteredSets. A FilteredSet consists of an expression and a set of internal ZCatalog document identifiers that represent a pre-calculated result list for performance reasons. Instead of executing the same query on a ZCatalog multiple times it is much faster to use a TopicIndex instead.

Building up FilteredSets happens on the fly when objects are cataloged and uncatalogued. Every indexed object is evaluated against the expressions of every FilteredSet. An object is added to a FilteredSet if the expression with the object evaluates to 1. Uncatalogued objects are removed from the FilteredSet.

A built-in type of FilteredSet is the PythonFilteredSet - it would be possible to construct custom types though.

A PythonFilteredSet evaluates using the eval() function inside the context of the FilteredSet class. The object to be indexes must be referenced inside the expression using "o.". Below are some examples of expressions.

This would index all DTML Methods:

        o.meta_type=='DTML Method'

This would index all folderish objects which have a non-empty title:

        o.isPrincipiaFolderish and o.title

Querying of TopicIndexes is done much in the same way as with other Indexes. Eg., if we named the last FilteredSet above folders_with_titles, we could query our TopicIndex with a Python snippet like:

        zcat = context.AnimalCatalog
        results = zcat(topicindex='folders_with_titles')

Provided our AnimalCatalog contains a TopicIndex topicindex, this would return all folderish objects in AnimalCatalog which had a non-empty title.

TopicIndexes also support the operator parameter with Records. More on Records below.

Advanced Searching with Records

A new feature in Zope 2.4 is the ability to query indexes more precisely using record objects. Record objects contain information about how to query an index. Records are Python objects with attributes, or mappings. Different indexes support different record attributes.

Keyword Index Record Attributes

query
Either a sequence of words or a single word. (mandatory)
operator
Specifies whether all keywords or only one need to match. Allowed values: and, or. (optional, default: 'or')

For example:

        # big or shiny
        results=ZCatalog(categories=['big, 'shiny'])

        # big and shiny
        results=ZCatalog(categories={'query':['big','shiny'], 
                                             'operator':'and'})

The second query matches objects that have both the keywords "big" and "shiny". Without using the record syntax you can only match objects that are big or shiny.

FieldIndex Record Attributes

query
Either a sequence of objects or a single value to be passed as query to the index (mandatory)
range
Defines a range search on a Field Index (optional, default: not set).

Allowed values:

min
Searches for all objects with values larger than the minimum of the values passed in the query parameter.
max
Searches for all objects with values smaller than the maximum of the values passed in the query parameter.
minmax
Searches for all objects with values smaller than the maximum of the values passed in the query parameter and larger than the minimum of the values passwd in the query parameter.

For example, here is a PythonScript snippet using a range search:

        # animals with population count greater than 5
        zcat = context.AnimalCatalog
        results=zcat(population_count={
                         'query' : 5,
                         'range': 'min'}
                    )

This query matches all objects in the AnimalCatalog which have a population count greater than 5 (provided that there is a FieldIndex population_count and an attribute population_count present).

Path Index Record Attributes

query
Path to search for either as a string (e.g. "/Zoo/Birds") or list (e.g. ["Zoo", "Birds"]). (mandatory)
level
The path level to begin searching at. Level defaults to 0, which means searching from the root. A level of -1 means start from anywhere in the path.

Suppose you have a collection of objects with these paths:

  1. /aa/bb/aa
  2. /aa/bb/bb
  3. /aa/bb/cc
  4. /bb/bb/aa
  5. /bb/bb/bb
  6. /bb/bb/cc
  7. /cc/bb/aa
  8. /cc/bb/bb
  9. /cc/bb/cc

Here are some examples queries and their results to show how the level attribute works:

query="/aa/bb", level=0
This gives the same behaviour as our previous examples, ie. searching absolute from the root, and results in:
query="/bb/bb", level=0
Again, this returns the default:
query="/bb/bb", level=1
This searches for all objects which have /bb/bb one level down from the root:
query="/bb/bb", level=-1
Gives all objects which have /bb/bb anywhere in their path:
query="/xx", level=-1
Returns None

You can use the level attribute to flexibly search different parts of the path.

As of Zope 2.4.1, you can also include level information in a search without using a record. Simply use a tuple containing the query and the level. Here's an example tuple: ("/aa/bb", 1).

DateIndex Record Attributes

The supported Record Attributes are the same as those of the FieldIndex:

query
Either a sequence of objects or a single value to be passed as query to the index (mandatory)
range
Defines a range search on a DateIndex (optional, default: not set).

Allowed values:

min
Searches for all objects with values larger than the minimum of the values passed in the query parameter.
max
Searches for all objects with values smaller than the maximum of the values passed in the query parameter.
minmax
Searches for all objects with values smaller than the maximum of the values passed in the query parameter and larger than the minimum of the values passwd in the query parameter.

As an example, we go back to the NewsItems we created in the Section Searching with Forms. For this example, we created news items with attributes content, author, and date. Additionally, we created a search form and a report template for viewing search results.

Searching for dates of NewsItems was not very comfortable though - we had to type in exact dates to match a document.

With a range query we are now able to search for ranges of dates. Take a look at this PythonScript snippet:

        # return NewsItems newer than a week
        zcat = context.NewsCatalog
        results = zcat( date={'query' : ZopeTime() - 7,
                              'range' : 'min'
                      })

DateRangeIndex Record Attributes

DateRangeIndexes only support the query attribute on Record objects. The query attribute results in the same functionality as querying directly.

TopicIndex Record Attributes

Like KeywordIndexes, TopicIndexes support the operator attribute:

operator
Specifies whether all FieldSets or only one need to match. Allowed values: and, or. (optional, default: 'or')

ZCTextIndex Record Attributes

Because ZCTextIndex operators are embedded in the query string, there are no additional Record Attributes for ZCTextIndexes.

Creating Records in HTML

You can also perform record queries using HTML forms. Here's an example showing how to create a search form using records:

        <form action="Report" method="get">
        <table>
        <tr><th>Search Terms (must match all terms)</th>
            <td><input name="content.query:record" width=30 value=""></td></tr>
            <input type="hidden" name="content.operator:record" value="and">
        <tr><td colspan=2 align=center>
        <input type="SUBMIT" value="Submit Query">
        </td></tr>
        </table>
        </form>

For more information on creating records in HTML see the section "Passing Parameters to Scripts" in Chapter 14, Advanced Zope Scripting.

Automatic Cataloging

Automatic Cataloging is an advanced ZCatalog usage pattern that keeps objects up to date as they are changed. It requires that as objects are created, changed, and destroyed, they are automatically tracked by a ZCatalog. This usually involves the objects notifying the ZCatalog when they are created, changed, or deleted.

This usage pattern has a number of advantages in comparison to mass cataloging. Mass cataloging is simple but has drawbacks. The total amount of content you can index in one transaction is equivalent to the amount of free virtual memory available to the Zope process, plus the amount of temporary storage the system has. In other words, the more content you want to index all at once, the better your computer hardware has to be. Mass cataloging works well for indexing up to a few thousand objects, but beyond that automatic indexing works much better.

Another major advantage of automatic cataloging is that it can handle objects that change. As objects evolve and change, the index information is always current, even for rapidly changing information sources like message boards.

In this section, we'll show you an example that creates "news" items that people can add to your site. These items will get automatically cataloged. This example consists of two steps:

As of Zope 2.3, none of the "out-of-the-box" Zope objects support automatic cataloging. This is for backwards compatibility reasons. For now, you have to define your own kind of objects that can be cataloged automatically. One of the ways this can be done is by defining a ZClass.

A ZClass is a Zope object that defines new types of Zope objects. In a way, a ZClass is like a blueprint that describes how new Zope objects are built. Consider a news item as discussed in examples earlier in the chapter. News items not only have content, but they also have specific properties that make them news items. Often these Items come in collections that have their own properties. You want to build a News site that collects News Items, reviews them, and posts them online to a web site where readers can read them.

In this kind of system, you may want to create a new type of object called a NewsItem. This way, when you want to add a new NewsItem to your site, you just select it from the product add list. If you design this object to be automatically cataloged, then you can search your news content very powerfully. In this example, you will just skim a little over ZClasses, which are described in much more detail in Chapter 22, "Extending Zope."

New types of objects are defined in the Products section of the Control Panel. This is reached by clicking on the Control Panel and then clicking on Product Management. Products contain new kinds of ZClasses. On this screen, click "Add" to add a New product. You will be taken to the Add form for new Products.

Name the new Product NewsItem and click "Generate". This will take you back to the Products Management view and you will see your new Product.

Select the NewsItem Product by clicking on it. This new Product looks a lot like a Folder. It contains one object called Help and has an Add menu, as well as the usual Folder "tabs" across the top. To add a new ZClass, pull down the Add menu and select ZClass. This will take you to the ZClass add form, as shown in the figure below.

ZClass add form

Figure 16-7 ZClass add form

This is a complicated form which will be explained in much more detail in Chapter 14, "Extending Zope". For now, you only need to do three things to create your ZClass:

When you're done, don't change any of the other settings in the Form. To create your new ZClass, click Add. This will take you back to your NewsItem Product. Notice that there is now a new object called NewsItem as well as several other objects. The NewsItem object is your new ZClass. The other objects are "helpers" that you will examine more in Chapter 14, "Extending Zope".

Select the NewsItem ZClass object. Your view should now look like the figure below.

A ZClass Methods View

Figure 16-8 A ZClass Methods View

This is the Methods View of a ZClass. Here, you can add Zope objects that will act as methods on your new type of object. Here, for example, you can create Page Templates or Scripts and these objects will become methods on any new News Items that are created. Before creating any methods however, let's review the needs of this new "News Item" object:

News Content
The news Item contains news content, this is its primary purpose. This content should be any kind of plain text or marked up content like HTML or XML.
Author Credit
The News Item should provide some kind of credit to the author or organization that created it.
Date
News Items are timely, so the date that the item was created is important.

You may want your new News Item object to have other properties, these are just suggestions. To add new properties to your News Item click on the Property Sheets tab. This takes you to the Property Sheets view.

Properties are added to new types of objects in groups called Property Sheets. Since your object has no property sheets defined, this view is empty. To add a New Property Sheet, click Add Common Instance Property Sheet, and give the sheet the name "News". Now click Add. This will add a new Property Sheet called News to your object. Clicking on the new Property Sheet will take you to the Properties view of the News Property Sheet, as shown in the figure below.

The properties screen for a Property Sheet

Figure 16-9 The properties screen for a Property Sheet

This view is almost identical to the Properties view found on Folders and other objects. Here, you can create the properties of your News Item object. Create three new properties in this form:

content
This property's type should be text. Each newly created News Item will contain its own unique content property.
author
This property's type should be string. This will contain the name of the news author.
date
This property's type should be date. This will contain the time and date the news item was last updated. A date property requires a value, so for now you can enter the string "01/01/2000".

That's it! Now you have created a Property Sheet that describes your News Items and what kind of information they contain. Properties can be thought of as the data that an object contains. Now that we have the data all set, you need to create an interface to your new kind of objects. This is done by creating a new Form/Action pair to change the data and assigning it to a new View for your object.

The Form/Action pair will give you the ability to edit the data defined in the propertysheet, while the View binds the form to a tab of the Zope Management Interface.

Propertysheets come with built-in forms for editing their data; however we need to build our own so we can signal changes to the ZCatalog.

First we are going to create a form to display and edit properties. Click on the Methods tab. Select "Page Template" from the add drop-down menu, name it editPropertiesForm and fill it with:

      <html><head>
      <title tal:content="here/title_or_id">title</title>
      <link rel="stylesheet" type="text/css" href="/manage_page_style.css">
      </head>
      <body bgcolor="#FFFFFF" link="#000099" vlink="#555555">
      <span 
        tal:define="manage_tabs_message options/manage_tabs_message | nothing"
        tal:replace="structure here/manage_tabs"> 
        prefab management tabs 
      </span>
      <form action="manage_editNewsProps" method="get">
      <table>
      <tr>
        <th valign="top">Content</th>
        <td>
          <textarea 
            name="content:text" rows="6" cols="35"
            tal:content="here/content">content text</textarea>
        </td>
      </tr>
      <tr>
        <th>Author</th>
        <td>
          <input name="author:string" 
                 value="author string"
                 tal:attributes="value here/author">
        </td>
      </tr>
      <tr>
        <th>Date</th>
        <td>
          <input name="date:date"
                 value="the date"
                 tal:attributes="value here/date">
        </td>
      </tr>
      <tr><td></td><td>
      <input type="submit">
      </td></tr>
      </form>
      </body>
      </html>

This is the Form part of the Form/Action pair. Note the call of manage_tabs at the top of the form - this will give your form the standard ZMI tabs.

We will add the Action part now. Add a Script (Python) object and fill in the id manage_editNewsProps and the following code:

      # first get the request
      req = context.REQUEST
      # change the properties in the zclass' propertysheet
      context.propertysheets.News.manage_editProperties(req)
      # signal the change to the zcatalog 
      context.reindex_object()
      # now return a message
      form = context.editPropertiesForm
      return form(REQUEST=req, 
                  manage_tabs_message="Saved changes.",
                  )

  1. The next step will be to define the View. Click on the Views tab. This will take you to the Views view.

Here, you can see that Zope has created three default Views for you. These views will be described in much more detail in Chapter 14, "Extending Zope", but for now, it suffices to say that these views define the tabs that your objects will eventually have.

To create a new view, use the form at the bottom of the Views view. Create a new View with the name "News" and select "editPropertiesForm" from the select box and click Add. This will create a new View on this screen under the original three Views, as shown in the figure below.

The Views view

Figure 16-10 The Views view

We want to make our View the first view that you see when you select a News Item object. To change the order of the views, select the newly created News view and click the First button. This should move the new view from the bottom to the top of the list.

The final step in creating a ZClass is defining a method for displaying the class. Click on the Methods tab, select 'Page Template' from the add list and add a new Page Template with the id "index_html". This will be the default view of your news item. Add the following to the new template:

      <html><head>
      <title tal:content="template/title">The title</title>
      </head><body>
      <h1>News Flash</h1>
      <p tal:content="here/date">
        date goes here
      </p>
      <p tal:content="here/author">
        author goes here
      </p>
      <p tal:content="here/content">
        content goes here
      </p>
      </body></html>

Finally, we will add a new management tab for the display method. Once again, click on the Views tab, and create a View named "View", and assign the index_html to it. Reorder the views so that the News view comes first, followed by the View method.

That's it! You've created your own kind of object called a News Item. When you go to the root folder, you will now see a new entry in your add list.

But don't add any new News Items yet, because the second step in this exercise is to create a ZCatalog that will catalog your new News Items. Go to the root folder and create a new ZCatalog with the id Catalog. The ZClass finds the ZCatalog by looking for a catalog named Catalog through acquisition, so this ZCatalog should be where it can be acquired by all NewsItems you plan to create.

Like the previous two examples of using a ZCatalog, you need to create Indexes and a Metadata Table that make sense for your objects. Create the following indexes:

content
This should be a ZCTextIndex. This will index the content of your News Items.
author
This should be a FieldIndex. This will index the author of the News Item.
date
This should be a DateIndex. This will index the date of the News Item.

After creating these Indexes, add these Metadata columns:

After creating the Indexes and Metadata Table columns, the automatic cataloguing is basically working. The last step is creating a search interface for the ZCatalog using the Z Search Interface tool described previously in this chapter:

Now you are ready to go. Start by adding some new News Items to your Zope. Go anywhere in Zope and select News Item from the add list. This will take you to the add Form for News items.

Give your new News Item the id "KoalaGivesBirth" and click Add. This will create a new News Item. Select the new News Item.

Notice how it has four tabs that match the five Views that were in the ZClass. The first View is News, this view corresponds to the News Property Sheet you created in the News Item ZClass.

Enter your news in the contents box:

      Today, Bob the Koala bear gave birth to little baby Jimbo.

Enter your name in the Author box, and today's date in the Date box.

Click Change and your News Item should now contain some news. Because the News Item object is CatalogPathAware, it is automatically cataloged when it is changed or added. Verify this by looking at the Cataloged Objects tab of the ZCatalog you created for this example.

The News Item you added is the only object that is cataloged. As you add more News Items to your site, they will automatically get cataloged here. Add a few more items, and then experiment with searching the ZCatalog. For example, if you search for "Koala" you should get back the KoalaGivesBirth News Item.

At this point you may want to use some of the more advanced search forms that you created earlier in the chapter. You can see for example that as you add new News Items with new authors, the authors select list on the search form changes to include the new information.

Conclusion

The cataloging features of ZCatalog allow you to search your objects for certain attributes very quickly. This can be very useful for sites with lots of content that many people need to be able to search in an efficient manner.

Searching the ZCatalog works a lot like searching a relational database, except that the searching is more object-oriented. Not all data models are object-oriented however, so in some cases you will want to use the ZCatalog, but in other cases you may want to use a relational database. The next chapter goes into more details about how Zope works with relational databases, and how you can use relational data as objects in Zope.