from Common import * from HTTPServlet import HTTPServlet from WebUtils import Funcs from Application import EndResponse class PageError(Exception): pass class Page(HTTPServlet): """ Page is a type of HTTPServlet that is more convenient for Servlets which represent HTML pages generated in response to GET and POST requests. In fact, this is the most common type of Servlet. Subclasses typically override writeHeader(), writeBody() and writeFooter(). They might also choose to override writeHTML() entirely. In awake(), the page sets self attributes, _transaction, _response and _request which subclasses should use as appropriate. For the purposes of output, the write() and writeln() convenience methods are provided. When developing a full-blown website, it's common to create a subclass of Page called SitePage which defines the common look and feel of the website and provides site-specific convenience methods. Then all other page classes inherit from SitePage. """ ## Transactions ## def awake(self, transaction): HTTPServlet.awake(self, transaction) self._response = transaction.response() self._request = transaction.request() self._session = None # don't create unless needed assert self._transaction is not None assert self._response is not None assert self._request is not None def respondToGet(self, transaction): """ Invokes _respond() to handle the transaction. """ self._respond(transaction) def respondToPost(self, transaction): """ Invokes _respond() to handle the transaction. """ self._respond(transaction) def _respond(self, transaction): """ Handles actions if an _action_ field is defined, otherwise invokes writeHTML(). Invoked by both respondToGet() and respondToPost(). """ req = transaction.request() # Support old style actions from 0.5.x and below. # Use setting OldStyleActions in Application.config # to use this. if self.transaction().application().setting('OldStyleActions', ) \ and req.hasField('_action_'): action = self.methodNameForAction(req.field('_action_')) actions = self._actionSet() if actions.has_key(action): self.preAction(action) apply(getattr(self, action), (transaction,)) self.postAction(action) return else: raise PageError, "Action '%s' is not in the public list of actions, %s, for %s." % (action, actions.keys(), self) # Check for actions for action in self.actions(): if req.hasField('_action_%s' % action) or \ req.field('_action_', None) == action or \ (req.hasField('_action_%s.x' % action) and \ req.hasField('_action_%s.y' % action)): if self._actionSet().has_key(action): self.handleAction(action) return self.writeHTML() def sleep(self, transaction): self._session = None self._request = None self._response = None self._transaction = None HTTPServlet.sleep(self, transaction) ## Access ## def application(self): return self._transaction.application() def transaction(self): return self._transaction def request(self): return self._request def response(self): return self._response def session(self): if not self._session: self._session = self._transaction.session() return self._session ## Generating results ## def title(self): """ Subclasses often override this method to provide a custom title. This title should be absent of HTML tags. This implementation returns the name of the class, which is sometimes appropriate and at least informative. """ return self.__class__.__name__ def htTitle(self): """ Return self.title(). Subclasses sometimes override this to provide an HTML enhanced version of the title. This is the method that should be used when including the page title in the actual page contents. """ return self.title() def htBodyArgs(self): """ Returns the arguments used for the HTML <body> tag. Invoked by writeBody(). With the prevalence of stylesheets (CSS), you can probably skip this particular HTML feature. """ return 'color=black bgcolor=white' def writeHTML(self): """ Writes all the HTML for the page. Subclasses may override this method (which is invoked by respondToGet() and respondToPost()) or more commonly its constituent methods, writeDocType(), writeHead() and writeBody(). """ self.writeDocType() self.writeln('<html>') self.writeHead() self.writeBody() self.writeln('</html>') def writeDocType(self): """ Invoked by writeHTML() to write the <!DOCTYPE ...> tag. @@ sgd-2003-01-29 - restored the 4.01 transitional as per discussions on the mailing list for the 0.8 release. # This implementation USED TO specify HTML 4.01 Transitional, but # some versions of Mozilla acted strangely with that. The current # implementation does nothing. Subclasses may override to specify something else. You can find out more about doc types by searching for DOCTYPE on the web, or visiting: http://www.htmlhelp.com/tools/validator/doctype.html """ self.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">') pass def writeHead(self): """ Writes the <head> portion of the page by writing the <head>...</head> tags and invoking writeHeadParts() in between. """ wr = self.writeln wr('<head>') self.writeHeadParts() wr('</head>') def writeHeadParts(self): """ Writes the parts inside the <head>...</head> tags. Invokes writeTitle() and writeStyleSheet(). Subclasses can override this to add additional items and should invoke super. """ self.writeTitle() self.writeStyleSheet() def writeTitle(self): """ Writes the <title> portion of the page. Uses title(). """ self.writeln('\t<title>%s</title>' % self.title()) def writeStyleSheet(self): """ Writes the style sheet for the page, however, this default implementation does nothing. Subclasses should override if necessary. A typical implementation is: self.writeln('\t<link rel=stylesheet href=StyleSheet.css type=text/css>') """ pass def writeBody(self): """ Writes the <body> portion of the page by writing the <body>...</body> (making use of self.htBodyArgs()) and invoking self.writeBodyParts() in between. """ wr = self.writeln bodyArgs = self.htBodyArgs() if bodyArgs: wr('<body %s>' % bodyArgs) else: wr('<body>') self.writeBodyParts() wr('</body>') def writeBodyParts(self): """ Invokes writeContent(). Subclasses should only override this method to provide additional page parts such as a header, sidebar and footer, that a subclass doesn't normally have to worry about writing. For writing page-specific content, subclasses should override writeContent() instead. See SidebarPage for an example override of this method. Invoked by writeBody(). """ self.writeContent() def writeContent(self): """ Writes the unique, central content for the page. Subclasses should override this method (not invoking super) to write their unique page content. Invoked by writeBodyParts(). """ self.writeln('<p> This page has not yet customized its content. </p>') ## Writing ## def write(self, *args): for arg in args: self._response.write(str(arg)) def writeln(self, *args): for arg in args: self._response.write(str(arg)) self._response.write('\n') ## Threading ## def canBeThreaded(self): """ Returns 0 because of the ivars we set up in awake(). """ return 0 ## Actions ## def handleAction(self, action): """ Invoked by _respond() when a legitimate action has been found in a form. Invokes preAction(), the actual action method and postAction(). Subclasses rarely override this method. """ self.preAction(action) getattr(self, action)() self.postAction(action) def actions(self): """ Returns a list of method names that are allowable actions from HTML forms. The default implementation returns []. """ return [] def preAction(self, actionName): """ Invoked by self prior to invoking a action method. The implementation basically writes everything up to but not including the body tag. Subclasses may override to customize and may or may not invoke super as they see fit. The actionName is passed to this method, although it seems a generally bad idea to rely on this. However, it's still provided just in case you need that hook. """ self.writeDocType() self.writeln('<html>') self.writeHead() def postAction(self, actionName): """ Invoked by self after invoking a action method. The implementation basically writes everything after the close of the body tag (in other words, just the </html> tag). Subclasses may override to customize and may or may not invoke super as they see fit. The actionName is passed to this method, although it seems a generally bad idea to rely on this. However, it's still provided just in case you need that hook. """ self.writeln('</html>') def methodNameForAction(self, name): """ This method exists only to support "old style" actions from WebKit 0.5.x and below. Invoked by _respond() to determine the method name for a given action name (which usually comes from an HTML submit button in a form). This implementation simple returns the name. Subclasses could "filter" the name by altering it or looking it up in a dictionary. Subclasses should override this method when action names don't match their method names. """ return name ## Convenience ## def htmlEncode(self, s): return Funcs.htmlEncode(s) def htmlDecode(self, s): return Funcs.htmlDecode(s) def urlEncode(self, s): return Funcs.urlEncode(s) def urlDecode(self, s): return Funcs.urlDecode(s) def forward(self, URL): """ Forwards this request to another servlet. See Application.forward() for details. The main difference is that here you don't have to pass in the transaction as the first argument. """ self.application().forward(self.transaction(), URL) def includeURL(self, URL): """ Includes the response of another servlet in the current servlet's response. See Application.includeURL() for details. The main difference is that here you don't have to pass in the transaction as the first argument. """ self.application().includeURL(self.transaction(), URL) def callMethodOfServlet(self, URL, method, *args, **kwargs): """ Call a method of another servlet. See Application.callMethodOfServlet() for details. The main difference is that here you don't have to pass in the transaction as the first argument. """ return apply(self.application().callMethodOfServlet, (self.transaction(), URL, method) + args, kwargs) def endResponse(self): """ If this is called during sleep() or awake() then the rest of awake(), response(), and sleep() are skipped and the accumulated response is sent immediately with no further processing. If this is called during respond() then the rest of respond() is skipped but sleep() is called, then the accumulated response is sent. """ raise EndResponse def sendRedirectAndEnd(self, url): """ Sends a redirect back to the client and ends the response. This is a very popular pattern. """ self.response().sendRedirect(url) self.endResponse() ## Self utility ## def sessionEncode(self, url=None): """ Utility function to access session.sessionEncode. Takes a url and adds the session ID as a parameter. This is for cases where you don't know if the client will accepts cookies. """ if url == None: url = self.request().uri() return self.session().sessionEncode(url) ## Private utility ## def _actionSet(self): """ Returns a dictionary whose keys are the names returned by actions(). The dictionary is used for a quick set-membership-test in self._respond. Subclasses don't generally override this method or invoke it. """ if not hasattr(self, '_actionDict'): self._actionDict = {} for action in self.actions(): self._actionDict[action] = 1 return self._actionDict ## Validate HTML output (developer debugging) ## def validateHTML(self, closingTags='</body></html>'): """ Validate the current response data using Web Design Group's HTML validator available at http://www.htmlhelp.com/tools/validator/ Make sure you install the offline validator (called "validate") which can be called from the command-line. The "validate" script must be in your path. Add this method to your SitePage (the servlet from which all your servlets inherit), override Page.writeBodyParts() in your SitePage like so: def writeBodyParts(self): Page.writeBodyParts() self.validateHTML() The "closingtags" param is a string which is appended to the page before validation. Typically, it would be the string "</body></html>". At the point this method is called (e.g. from writeBodyParts() the page is not yet 100% complete, so we have to fake it. """ # don't bother validating if the servlet has redirected status = self.response().header('status', None) if status and status.find('Redirect') != -1: return response = self.response().rawResponse() contents = response['contents'] + closingTags from WebUtils import WDGValidator errorText = WDGValidator.validateHTML(contents) if not errorText: return self.write(errorText) ## Exception reports ## def writeExceptionReport(self, handler): handler.writeTitle(self.__class__.__name__) handler.writeln('''Servlets can provide debugging information here by overriding writeExceptionReport().<br>For example: <pre> exceptionReportAttrs = 'foo bar baz'.split() def writeExceptionReport(self, handler): handler.writeTitle(self.__class__.__name__) handler.writeAttrs(self, self.exceptionReportAttrs) handler.write('any string') </pre> See WebKit/ExceptionHandler.py for more information. ''')