Current

This document is valid for the current version of Plone.

Build a custom search form with YAFOWIL

by Peter Holzer last modified Mar 24, 2012 09:47 PM
YAFOWIL is framework independent form library written in Python. This tutorial shows how to create a simple installable search form.

Create a basic egg

Let's create your basic egg with paster.

paster create -t basic_namespace

Answer the questions and name it example.searchform, this will get you a new directory with the necessary boilerplate and metadata.

Next add a configure.zcml file, we'll need that soon.

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:five="http://namespaces.zope.org/five">

</configure>

Move your newly created egg to the source folder of your buildout and add register the egg within the develop-part of your buildout

develop =
    src/example.searchform

Also add it to the egg and zcml part of the instance in your buildout.

egg =
    example.searchform

zcml =
    example.searchform

Dependencies

Add yafowil and since we're building a form for Plone also add yafowil.zope2 to the install_requires of your setup.py file.

install_requires=[
    'setuptools',
    # -*- Extra requirements: -*-
    'yafowil',
    'yafowil.zope2',
],

Rerun your buildout and the dependencies will be fetched for you.

Building the Form

Add a searchform.py to you egg and start with the imports for your form.

We import yafowil.zope that will make the bindings for Zope/Plone and yafowil.loader for all the basic YAFOWIL blueprints into the factory. The factory will later build the widgets out of the blueprints. The Controller will be responsible for form processing, delegation of actions and form rendering. Also create a MessageFactory object for the internationalization of your form.

import yafowil.zope2
import yafowil.loader
from yafowil.base import factory, UNSET
from yafowil.controller import Controller
from zope.i18nmessageid import MessageFactory
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile

from Products.CMFCore.utils import getToolByName

_ = MessageFactory('ExampleSearchForm')

Next we create a BrowserView class for your form and create a page template searchform.pt where we will show your form.

class SearchView(BrowserView):

    template = ViewPageTemplateFile('searchform.pt')

Within the searchform.pt we place the result of your SearchView

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      metal:use-macro="context/main_template/macros/master"
      i18n:domain="ExampleSearchForm">

   <body>
      <metal:main fill-slot="main">
          <tal:main-macro metal:define-macro="main">
              <div tal:content="structure python:view.form()">form</div>
          </tal:main-macro>
      </metal:main>
   </body>

</html>

Register your form within your configure.zcml as a browser page.

<browser:page
    for="*"
    name="examplesearch"
    permission="zope.Public"
    class=".searchform.SearchView"
    />

Return to your SearchView and we'll continue to build your form. We create a _form_action that will point back to your form to display the search results later. The _form_handler ist responsible for saving the contents of our form as criterias of our search.

class SearchView(BrowserView):

    template = ViewPageTemplateFile('searchform.pt')

    def _form_action(self, widget, data):
        return '%s/@@examplesearch' % self.context.absolute_url()

    def _form_handler(self, widget, data):
        self.searchterm = data['searchterm'].extracted

Next we'll create the form elements within our SearchView. We define the form, give it a name and tell which action will be executed on submit. For the search term we create a string input field, give it a unicode string for the label which can later be translated to your desired languages. Additionally you can add classes to almost every element of your widget. More widget properties can be find in the YAFOWIL widget documentation. To complete our form we add a controller which returns the rendered form.

...
    def form(self):
        form = factory('form',
            name='search',
            props={
                'action': self._form_action,
            })

        form['searchterm'] = factory(
            'field:label:error:text',
            props={
                'label': _(u'Search term:'),
                'field.class': 'myFieldClass',
                'text.class': 'myInputClass',
                'size': '20',
        })
        form['submit'] = factory(
            'field:submit',
            props={
                'label': _(u'Search'),
                'submit.class': '',
                'handler': self._form_handler,
                'action': 'search'
        })

        controller = Controller(form, self.request)
        return controller.rendered

Restart your Plone instance and point your browser to your form http://localhost:8080/@@examplesearch

The Search Query

For the query we check if our form handler has the attribute and that the attribute is not empty. Now we take our searchterm, create the catalog search and return the results.

def results(self):
    if not hasattr(self,'searchterm') or not self.searchterm:
        return []

    cat = getToolByName(self.context, 'portal_catalog')
    query = {}

    qterm = self.searchterm
    if qterm:
        qterm = '%s' % (qterm)
        query['SearchableText'] = qterm.decode('utf-8')
    print query
    return cat(**query)

Complete your page template with some code to show the results and you are good to go.

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"
      xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      xmlns:i18n="http://xml.zope.org/namespaces/i18n"
      metal:use-macro="context/main_template/macros/master"
      i18n:domain="ExampleSearchForm">

   <body>
      <metal:main fill-slot="main">
          <tal:main-macro metal:define-macro="main">
              <div tal:content="structure python:view.form()">form</div>
            <ul>
               <tal:block repeat="item python:view.results()">
                  <li>
                     <a tal:content="item/Title"
                        tal:attributes="href item/getURL|item/absolute_url"></a>
                  </li>
               </tal:block>
            </ul>
         </tal:main-macro>
      </metal:main>
   </body>

</html>

More information and documentation on http://yafowil.info/

Filed under: , , ,

Contribute

Something wrong or out of date? Anybody can edit or create a new article in the knowledge base. Simply create an account on this site, log in, and click the Edit button to contribute.