On Thu, Sep 30, 2004 at 03:15:33PM -0400, Aaron Switzer wrote:
> I do agree that a "lower-level" extension point would be useful and more
> inline with OO design.  I guess stripping out the HTML specific stuff
> would be a good starting point.

I couldn't make CVS diff honor the -N flag for some reason, so here is a 
first cut at a new class HTTPContent, containing the non-HTML parts 
formerly in Page (and a diff to Page.py).

Please comment.

I'd like to remove preAction and postAction from Page as well, or at 
least make them be called in a more consistent fashion.  Right now they 
get called for _action_ style things, but not when defaultAction() is 
invoked.  I suggest removing Page.preAction() and Page.postAction() and 
letting them fall through to the empty versions in HTTPContent, and then 
consistently calling them before and after *all* actions, including 
defaultAction().  Maybe this should be another diff...

-- 
[EMAIL PROTECTED]
  Some people have a way with words, while others... erm... thingy.

from Common import *
from HTTPServlet import HTTPServlet
from WebUtils import Funcs
from Application import EndResponse


class HTTPContentError(Exception):
        pass


class HTTPContent(HTTPServlet):
        """
        HTTPContent is a type of HTTPServlet that is more convenient for
        Servlets which represent content generated in response to
        GET and POST requests.

        Subclasses typically override defaultAction().

        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.

        If you plan to produce HTML content, you should start by looking
        at Page instead of this lower-level class.
        """

        ## Transactions ##

        def awake(self, transaction):
                """
                Makes instance variables from the transaction.  This is
                where Page becomes unthreadsafe, as the page is tied to
                the transaction.  This is also what allows us to
                implement functions like `write`, where you don't
                need to pass in the transaction or response.
                """
                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):
                """
                Invoked in response to a GET request method.  All methods
                are passed to `_respond`
                """
                self._respond(transaction)

        def respondToPost(self, transaction):
                """
                Invoked in response to a GET request method.  All methods
                are passed to `_respond`
                """
                self._respond(transaction)

        def _respond(self, transaction):
                """
                Handles actions if an ``_action_`` or ``_action_XXX``
                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 HTTPContentError, "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)):
                                self.handleAction(action)
                                return

                # If no action was found, run the default.
                self.defaultAction()

        def defaultAction(self):
                """
                The core method that gets called as a result of requests.
                Subclasses should override this.
                """
                pass

        def sleep(self, transaction):
                """
                :ignore:
                We unset some variables.  Very boring.
                """
                self._session = None
                self._request  = None
                self._response = None
                self._transaction = None
                HTTPServlet.sleep(self, transaction)


        ## Access ##

        def application(self):
                """
                The `Application` instance we're using.
                """
                return self._transaction.application()

        def transaction(self):
                """
                The `Transaction` we're currently handling.
                """
                return self._transaction

        def request(self):
                """
                The request (`HTTPRequest`) we're handling.
                """
                return self._request

        def response(self):
                """
                The response (`HTTPResponse`) we're handling.
                """
                return self._response

        def session(self):
                """
                The session object, i.e., a state for the current
                user (associated with a browser instance, really).
                If no session exists, then a session will be created.
                """
                if not self._session:
                        self._session = self._transaction.session()
                return self._session


        ## Writing ##

        def write(self, *args):
                """
                Writes the arguments, which are turned to strings
                (with `str`) and concatenated before being written
                to the response.
                """
                for arg in args:
                        self._response.write(str(arg))

        def writeln(self, *args):
                """
                Writes the arguments (like `write`), adding a newline
                after.
                """
                for arg in args:
                        self._response.write(str(arg))
                self._response.write('\n')


        ## Threading ##

        def canBeThreaded(self):
                """
                Returns 0 because of the instance variables 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 [].  See `_respond` for more about actions.
                """
                
                return []

        def preAction(self, actionName):
                """
                Invoked by self prior to invoking a action method.
                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.

                By default this does nothing.
                """
                pass

        def postAction(self, actionName):
                """
                Invoked by self after invoking a action method.
                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.

                By default this does nothing.
                """
                pass

        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.
                """
                ## 2003-03 ib @@: old-style actions are gone, so this
                ## method should go too.
                return name

        def urlEncode(self, s):
                """
                Alias for `WebUtils.Funcs.urlEncode`.  Quotes special
                characters using the % substitutions.
                """
                ## @@: urllib.quote, or 
                return Funcs.urlEncode(s)

        def urlDecode(self, s):
                """
                Alias for `WebUtils.Funcs.urlDecode`.  Turns special
                % characters into actual characters.
                """
                return Funcs.urlDecode(s)

        def forward(self, URL, context=None):
                """
                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, context)

        def includeURL(self, URL, context=None):
                """
                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, context)

        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.

                A `context` keyword argument is also possible, even
                though it isn't present in the method signature.
                """
                return apply(self.application().callMethodOfServlet, 
(self.transaction(), URL, method) + args, kwargs)

        def endResponse(self):
                """
                When this method is called during `awake` or
                `respond`, servlet processing will end immediately,  
                and the accumulated response will be sent.

                Note that `sleep` will still be called, providing a 
                chance to clean up or free any resources.               
                """
                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()


        ## 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


        ## Exception Reports ##

        def writeExceptionReport(self, handler):
                """
                Writes extra information to the exception report.
                The `handler` argument is the exception handler, and
                information is written there (using `writeTitle`,
                `write`, and `writeln`).  This information is added
                to the exception report.

                See `WebKit.ExceptionHandler` for more information.
                """
                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.
''')
Index: Page.py
===================================================================
RCS file: /cvsroot/webware/Webware/WebKit/Page.py,v
retrieving revision 1.33
diff -u -r1.33 Page.py
--- Page.py     1 Dec 2003 21:12:09 -0000       1.33
+++ Page.py     2 Oct 2004 18:39:32 -0000
@@ -1,16 +1,12 @@
 from Common import *
-from HTTPServlet import HTTPServlet
+from HTTPContent import HTTPContent, HTTPContentError
 from WebUtils import Funcs
 from Application import EndResponse
 
 
-class PageError(Exception):
-       pass
-
-
-class Page(HTTPServlet):
+class Page(HTTPContent):
        """
-       Page is a type of HTTPServlet that is more convenient for
+       Page is a type of HTTPContent 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.
@@ -20,13 +16,6 @@
 
        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
@@ -36,120 +25,11 @@
 
        ## Transactions ##
 
-       def awake(self, transaction):
-               """
-               Makes instance variables from the transaction.  This is
-               where Page becomes unthreadsafe, as the page is tied to
-               the transaction.  This is also what allows us to
-               implement functions like `write`, where you don't
-               need to pass in the transaction or response.
-               """
-               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):
-               """
-               Invoked in response to a GET request method.  All methods
-               are passed to `_respond`
-               """
-               self._respond(transaction)
-
-       def respondToPost(self, transaction):
-               """
-               Invoked in response to a GET request method.  All methods
-               are passed to `_respond`
-               """
-               self._respond(transaction)
-
-       def _respond(self, transaction):
-               """
-               Handles actions if an ``_action_`` or ``_action_XXX``
-               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)):
-                               self.handleAction(action)
-                               return
-
-               self.defaultAction()
-
        def defaultAction(self):
-               self.writeHTML()
-
-       def sleep(self, transaction):
-               """
-               :ignore:
-               We unset some variables.  Very boring.
-               """
-               self._session = None
-               self._request  = None
-               self._response = None
-               self._transaction = None
-               HTTPServlet.sleep(self, transaction)
-
-
-       ## Access ##
-
-       def application(self):
-               """
-               The `Application` instance we're using.
-               """
-               return self._transaction.application()
-
-       def transaction(self):
-               """
-               The `Transaction` we're currently handling.
-               """
-               return self._transaction
-
-       def request(self):
-               """
-               The request (`HTTPRequest`) we're handling.
-               """
-               return self._request
-
-       def response(self):
-               """
-               The response (`HTTPResponse`) we're handling.
-               """
-               return self._response
-
-       def session(self):
                """
-               The session object, i.e., a state for the current
-               user (associated with a browser instance, really).
-               If no session exists, then a session will be created.
+               The default action in a Page is to writeHTML().
                """
-               if not self._session:
-                       self._session = self._transaction.session()
-               return self._session
+               self.writeHTML()
 
 
        ## Generating results ##
@@ -180,10 +60,12 @@
                tag. Invoked by writeBody().
 
                With the prevalence of stylesheets (CSS), you can
-               probably skip this particular HTML feature.
+               probably skip this particular HTML feature, but for
+               historical reasons this sets the page to black text
+               on white.
                """
 
-               return 'color=black bgcolor=white'
+               return 'color="black" bgcolor="white"'
 
        def writeHTML(self):
                """
@@ -267,7 +149,7 @@
                implementation does nothing. Subclasses should override if
                necessary. A typical implementation is:
 
-                   self.writeln('\t<link rel=stylesheet href=StyleSheet.css 
type=text/css>')
+                   self.writeln('\t<link rel="stylesheet" href="StyleSheet.css" 
type="text/css">')
                """
                pass
 
@@ -317,109 +199,23 @@
                self.writeln('<p> This page has not yet customized its content. </p>')
 
 
-       ## Writing ##
-
-       def write(self, *args):
-               """
-               Writes the arguments, which are turned to strings
-               (with `str`) and concatenated before being written
-               to the response.
-               """
-               for arg in args:
-                       self._response.write(str(arg))
-
-       def writeln(self, *args):
-               """
-               Writes the arguments (like `write`), adding a newline
-               after.
-               """
-               for arg in args:
-                       self._response.write(str(arg))
-               self._response.write('\n')
-
-
-       ## Threading ##
-
-       def canBeThreaded(self):
-               """
-               Returns 0 because of the instance variables 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 [].  See `_respond` for more about actions.
-               """
-               
-               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.
+               For a page, we first writeDocType(), <html>, and then
+               writeHead().
                """
-               ## 2003-03 ib @@: Whyzit a bad idea to rely on it?
                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.
+               Simply close the html tag (</html>).
                """
                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.
-               """
-               ## 2003-03 ib @@: old-style actions are gone, so this
-               ## method should go too.
-               return name
-
-       """
-       **Convenience Methods**
-       """
+       ## Convenience Methods ##
 
        def htmlEncode(self, s):
                """
@@ -434,168 +230,3 @@
                HTML entities.
                """
                return Funcs.htmlDecode(s)
-
-       def urlEncode(self, s):
-               """
-               Alias for `WebUtils.Funcs.urlEncode`.  Quotes special
-               characters using the % substitutions.
-               """
-               ## @@: urllib.quote, or 
-               return Funcs.urlEncode(s)
-
-       def urlDecode(self, s):
-               """
-               Alias for `WebUtils.Funcs.urlDecode`.  Turns special
-               % characters into actual characters.
-               """
-               return Funcs.urlDecode(s)
-
-       def forward(self, URL, context=None):
-               """
-               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, context)
-
-       def includeURL(self, URL, context=None):
-               """
-               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, context)
-
-       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.
-
-               A `context` keyword argument is also possible, even
-               though it isn't present in the method signature.
-               """
-               return apply(self.application().callMethodOfServlet, 
(self.transaction(), URL, method) + args, kwargs)
-
-       def endResponse(self):
-               """
-               When this method is called during `awake` or
-               `respond`, servlet processing will end immediately,  
-               and the accumulated response will be sent.
-
-               Note that `sleep` will still be called, providing a 
-               chance to clean up or free any resources.               
-               """
-               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()
-
-       """
-       **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):
-               """
-               Writes extra information to the exception report.
-               The `handler` argument is the exception handler, and
-               information is written there (using `writeTitle`,
-               `write`, and `writeln`).  This information is added
-               to the exception report.
-
-               See `WebKit.ExceptionHandler` for more information.
-               """
-               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.
-''')

Reply via email to