9.2. Digging Into the Project Controller Methods
In the next section, we take a deeper look at how we can use MochiKit to make sending Ajax requests to the browser easier. Before we get there, let's take a look at the WhatWhat Status project controller and how it handles these Ajax requests, and what it sends back to the browser:
import turbogears as tg import cherrypy as http import commands import socket import md5 import time import random import os from turbogears import identity, flash, validators from datetime import datetime from whatwhat import utils from textile import textile from whatwhat.widgets import widgets from whatwhat.model import (Person, Project, Risk, Note, Question, Answer, Issue, chance_codes, impact_codes, status_codes, ProjectFile) class ProjectController(identity.SecureResource): require = identity.not_anonymous() @tg.expose() def default(self, *args, **kwargs): parts = http.request.path.rsplit('/') if len(parts) == 3 and parts[-1].isdigit(): return self.project(parts[-1]) @tg.expose() def index(self, *args, **kwargs): project_id = self._active_project_id() if project_id: raise tg.redirect('/project/%s' % project_id) return self.locate() @tg.expose(template="whatwhat.templates.project.locate") def locate(self): all_projects = Project.select("parent_project_id is NULL order by upper(name)") projects = [project for project in all_projects if not project.archived] return dict(active_section='project', projects=projects)
Right at the top, notice again that we are requiring that every access to this controller or any of its methods will require that Identity knows who you are, and that you're properly logged in to the system.
The default, index, and expose methods in the preceding code provide ways for the project controller to find the right project in the database and ensure that the project method gets passed the correct values. We've already talked about how the default method works, but here's a quick reminder in case you skipped ahead: The default method is what you get if none of the other exposed methods are called directly. In other words, if your URL is http://localhost:8080/project/project, the project method is called; but if the URL doesn't match locate, project, toggle_closed_risks, or any of the other methods in this controller, the default method is called.
In this case, the default method grabs the URL string from CherryPy, splits it around the / character, and checks to see whether the last element in the parts list is a number. If it is, it assumes that number is the project you want to see and passes you on to the project's file with the correct ID.
The index method is what is called if you use a URL such as localhost:8080/project/.
def _active_project_id(self): if http.request.simpleCookie.has_key('active_project_id'): return int(http.request.simpleCookie['active_project_id'].value) return None
If _active_project_id can't retrieve an active project value from the cookie, it returns None, and the index method passes the user along to locate, which grabs a list of all the current projects and sends the user to whatwhat.templates.project.locate, which then displays a page with a drop-down list of projects. When the user selects a project from that page, the user gets sent to /projects/7 (or whatever project ID the user selected). Because there is no 7 method in the project controller, the default method gets called, and the user gets passed to the project with the correct number.
All of this might seem unnecessary, but it gives WhatWhat Status nice and pretty URLs for the project page, which are easy to use in our templates, and which also have the property of being RESTful. REST stands for REpresentational State Transfer, and it's a term used to describe a particular way of organizing resources on the web. We talk more about REST in Chapter 17, "CherryPy and TurboGears Decorators," but for now, it's probably enough to say that REST is an application design pattern that emphasizes URLs that are universal (every object in the system is represented by a unique URL), stateless (the URL is all that is needed to define that particular resource), and have a well-defined set of operations that can be performed on them.
In this case, FastTrack has a unique URL for every project in the system, and each element in the URL has a specific, well-defined meaning. /project/ indicates that we are looking at a project, and the /7 indicates that we are looking at the project with the ID of 7. If the WhatWhat Status authors implemented a delete mechanism for projects that answered to /project/7/delete, which deleted project 7, that would indicate a fully RESTFul interface.
The simpler alternative to defining these methods is to present WhatWhat Status users with links such as /project/project/7. Not only is this URL redundant, it could also be confusing to the user, and we'll constantly have to type that /project/ into all of our links, which is no fun either.
All of this brings us to the central method of the project controller project():
@tg.expose(template="whatwhat.templates.project.project") @tg.validate(validators=dict(project_id=validators.Int())) def project(self, project_id): project = Project.get(project_id) user_person = Person.get(identity.current.user.id) groupids = utils.getGroups() http.response.simpleCookie['active_project_id'] = project_id http.response.simpleCookie['active_project_id']['path'] = '/' people = Person.select(orderBy='displayName') return dict(active_section='project', project=project, chance_codes=chance_codes, impact_codes=impact_codes, status_codes=status_codes, show_closed_risks=bool(self._show_closed_risks()), show_closed_issues=bool(self._show_closed_issues()), show_all_notes=bool(self._show_all_notes()), people=people, user_person=user_person, groupids=groupids, risks_widget=widgets.risks_widget, issues_widget=widgets.issues_widget, questions_widget=widgets.questions_widget, notes_widget=widgets.notes_widget)
Even though this method is a bit longer than most of the ones we've seen so far, it is still conceptually simple. It gets the project object from the database, and assigns the project_id value to a cookie. Then it gets the current user's ID, and the groups they are a member of, and passes this data to the template in a dictionary. It also passes some codes from the model (which we've already seen), several Boolean values (which will determine whether closed Risks, Issues, and Notes will display), and a few widgets for good measure to the template.
This might seem like a lot of information to pass to one template; as you will see, however, the project template is totally dynamic and has the potential to display a lot of different information.