Personal tools
You are here: Home Documentation Tutorials Grok Poller Tutorial Poll View

Poll View

Now it's time to implement a view for the poll items which allows the user to vote on options.
This tutorial shows how to implement a simple polling application using Grok.
Page 12 of 14.

As before, we must implement a new view class, but this time it will be a bit different. We will implement the form by hand for learning purposes, and we will also support some basic error message handling in the view. There are two new concepts that will be introduced in this view, the update() method, and view attributes; add the following code to the poll.py module:

class Index(grok.View):
    grok.context(Poll)

    def update(self, option=None):
        self.error = None
        if option:
            try:
                self.context.choose(option)
                return self.redirect(self.url(self.context, 'results'))
            except KeyError:
                self.error = u'Invalid option, please choose again.'
        elif self.request.form:
            self.error = u'Choose an option'

before a view is rendered, Grok calls the update() method of the view class, and provides any HTTP parameters as method arguments. In this view, the form will send the voting selection as option, thus we added that as an attribute for the method. We can also leave it out and access the form through self.request.form, which we will use to handle empty selections in this example.

We had to specify the context manually, as otherwise Grok wouldn't know which model this view is meant for, Poll or PollOption; the auto-detection is only possible if there is just a single model in the same module. Grok refuses to guess the models, as that would result in an unexpected behaviour.

In the update() method, we define attribute self.error which can be accessed through the view in the template, this will contain a error which will be shown to the user on the form; then we simply check if option is set and call the voting method of our poll, if it doesn't throw an exception the vote succeeded and we redirect to the results view, which we will implement after this view. The elif clause checks if the form was submitted (the dictionary is not empty because the form button sends a value), but the option was not provided, in this case we use the error message to tell the user that no option was provided.

To implement the template, write the following code in poll_templates/index.pt:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:tal="http://xml.zope.org/namespaces/tal">
  <body>
    <h1 tal:content="context/question">Poll question</h1>

    <p tal:condition="view/error" tal:content="view/error">
      Possible errors go here
    </p>

    <form action=""
          tal:attributes="action python: view.url(context)"
          method="post">
      <tal:block repeat="option context/options">
        <tal:block define="index string:choice${repeat/option/index}">
          <input type="radio" name="option" id="" value=""
                 tal:attributes="id index; value option/label" />
          <label for="" tal:attributes="for index">
            <span tal:replace="option/label">Option content</span>
            -- <i tal:content="option/description">Description</i>
          </label>
          <br />
        </tal:block>
      </tal:block>
      <input type="submit" name="submit" value="Vote" />
    </form>

    <a href="" tal:attributes="href python: view.url(context.__parent__)">
      Back to polls
    </a>
  </body>
</html>

Most of the code here should be familiar now, but there are some parts which needs explanation. First new TAL command is condition, if the expression in it evaluates to boolean true, then this element is rendered to the output. In the same element, we also use the error variable we defined in the view class. So in plain english, if error attribute in the view evaluates to true, then the <p> element is rendered with the content of that variable.

tal:block is a complex XML element defined by the template system, which can be used to use TAL commands without rendering any actual elements. Here we use it to run a loop of options, and the loop consists of multiple elements, not just a single looping one as with previous templates. Then we use define command to make a shortcut for numbering our options by repeat index. When using repeat it automatically defines a path for the loop, the path format is repeat/<loop name>/ under which there are different variables of the loop, such as the index; see the TALES specification for the full variable listing.

replace command works almost like a combination of tal:block and content command, it replaces the whole element with the result of the TALES expression. In Python the following template code:

<span tal:replace="option/label">Option content</span>
-- <i tal:content="option/description">Description</i>

equals to:

'%s -- <i>%s</i>' % (option.label, option.description)

ife we didn't want to italize the description, we could have used TALES string expression as follows:

<span tal:content="string:${option/label} -- ${option/description}">
  Option label and description here
</span>

The last new thing is in the "Back to polls" link's Python code, the context.__parent__ part. Every model in Grok containes these two automatic attributes, __parent__ and __name__. The former is the container for this model, and the latter is the name of this model in that container. So this expression returns a link to the poller container.