8.1. Cookies and RecentChanges
One critical feature of the WhatWhat Status software is the recent changes page, which enables users to see anything new on any of the projects in the system. And Recent-Changes is also worth a look because it uses a number of new TurboGears features, which can make your life easier.
In particular, we look at how to use CherryPy's response object to set cookies, how to define your own widgets, and how to use Kid template non-XHTML XML formats (in this case, RSS and Atom).
The cherrypy.response object enables you to add special information to the response that CherryPy will pipe out to the user. The response object has headerMap, SimpleCookie, body, sendResponse, and wfile attributes; and we discuss each of these in more detail in Chapter 17, "CherryPy and TurboGears Decorators." But for now, we focus on cookies because they are the most commonly used component.
For those of you familiar with the cookie module from the Python Standard Library, this explanation is going to be short: cherrypy.response.simple_cookie is just an instance of the SimpleCookie class in the standard library's cookie module.
For the rest of us, a simple_cookie just stores a cookie name and value, and sends it to a user's browser. The browser then sends that same name value pair back to the web server on every subsequent request.
Cookies are easy to use in TurboGears. You normally already have CherryPy imported, but if not, you must import cherrypy in your controller and then you can write code like this in your controller methods:
cherrypy.response.simple_cookie['user_name'] = 'Billy Bob Thorton-Wilder'
As long as you create different names for your cookies, you can set several different cookies, which will all be returned by the web browser on the next page request.
Cookies commonly store user state information. The main advantages of using cookies to maintain user state are as follows:
The disadvantages of using cookies are as follows:
If you are writing a web application targeted to web security experts, or hard-core privacy activists, you will definitely want to avoid cookies. But, if you take reasonable precautions, they can be an incredibly useful tool.
WhatWhat Status sets a cookie to remember how much "new" project information to display for a particular user session:
@tg.expose() def change_time(self, change_hours): http.response.simple_cookie['time_frame_id'] = change_hours http.response.simple_cookie['time_frame_id']['path'] = '/' raise tg.redirect('/recentchanges') def _timeframe(self): if http.request.simple_cookie.has_key('time_frame_id'): return int(http.request.simpleCookie['time_frame_id'].value) return 24
In addition to name value pairs, (sometimes called morsels) a simple_cookie can take several optional attributes. The most important attributes are ['domain'], ['path'], and, ['expires']. Because these are optional, and WhatWhat Status has no particular reason to restrict access to this cookie information, the only one they are setting is the path, which ensures that every user request to the WhatWhat Status server gets access to this cookie's information. This function takes a number that is passed in change_hours and puts it in the cookie.
After the cherrypy.response.simple_cookie object has been set, this function calls raise tg.redirect, which stops the execution of the function and sends the user on to the link indicated (in this case, to the index method of RecentChangesController).
But before we move on to the meat of the Recent Changes page, let's take a look at the _timeframe method, which accesses the cookie information we set in change_time. In the same way you set the cookie using CherryPy's response object, you can check it via CherryPy's request object. The .value method of simple_cookie returns the string bound to the key you give it. And in this case, the WhatWhat Status authors are converting that string into an int and returning it. If the cookie does not exist, they just return 24 hours (1 day).
With all that cookie stuff out of the way, let's delve into the controller code:
import turbogears as tg import cherrypy as http from turbogears import identity from whatwhat.model import Project, status_codes, impact_codes, chance_ codes from whatwhat.utils import getRecentChanges from whatwhat import utils, widgets class RecentChangesController(identity.SecureResource): require = identity.not_anonymous() @tg.expose(template="whatwhat.templates.recentchanges.index") def index(self): utils.checkIdentity(http.request.identity.user) all_projects = Project.select("parent_project_id is NULL " + "order by upper(name)") projects = [project for project in all_projects if not project.archived] recent_proj = getRecentChanges(projects, self._timeframe()) return dict(active_section='recentchanges', recent_proj=recent_proj, status_codes=status_codes, chance_codes=chance_codes, impact_codes=impact_codes, time_hours=self._timeframe(), questions_widget=widgets.questions_widget, issues_widget=widgets.issues_widget, risks_widget=widgets.risks_widget, notes_widget=widgets.notes_widget)
The nice thing at this point in our journey through TurboGears and WhatWhat Status is that there's not going to be much new going on in the controller.
Calling the index method will return a page based on the templates.recentchanges.index Kid template (which we look at in a second), and again we construct a dictionary to pass into the template. The only difference here from what we've seen before is that the authors of WhatWhat Status moved a couple of functions into whatwhat.utils, and they are importing a bunch of widgets of their own design from the WhatWhat Status widgets module.
We'll get back to widgets in a minute, but for now let's take a look at the utils function getrecentChanges. This particular function takes a list of projects and the number of hours into the past that the user wants to see. In this case, the number of hours is pulled out of a cookie or set to a default value by the _timeframe() function.
The getrecentChanges method call goes through all the projects in the database and creates a list of projects that have recent updates to their issues, risks, notes, files, or questions, and returns a list of project objects.
The code for all of that looks like this:
As the method iterates over each of the projects, it calls all of those model methods that we looked at in Chapter 6, "Exploring More Complex Models in WhatWhat Status." If there are new issues, notes, or whatever, it appends them to the appropriate list. Notice that new_proj is assigned by another method call to RecentProj, which constructs project objects. It is these project objects that are going to be passed into the template.
RecentProj takes the project name, id, and the lists of recent notes, issues, risks, files, and questions, and creates attributes for each of them:
class RecentProj(object): def __init__(self, proj_name, proj_id, recent_notes, recent_issues, recent_risks, recent_files, recent_questions): self.proj_name = proj_name self.proj_id = proj_id self.recent_notes = recent_notes self.recent_issues = recent_issues self.recent_risks = recent_risks self.recent_files = recent_files self.recent_questions = recent_questions
The getrecentProjects method in our controller returns a list of these RecentProj objects. This list gets passed on to the Template as the list recent_proj. You can see the results of all this in Figure 8.1.
Figure 8.1. Recent Changes example