SVN: r25609 - trunk/quixote/demo

David Binger <dbinger-fVcApmY9cLvQ3/1i3zOLAti2O/[email protected]> Tue, 16 Nov 2004 16:24:52 -0500
Newsgroups gmane.comp.web.quixote.cvs
Message-ID <[email protected]>
Author: dbinger
Date: 2004-11-16 14:18:02 -0500 (Tue, 16 Nov 2004)
New Revision: 25609

Modified:
   trunk/quixote/demo/altdemo.py
Log:
Revise altdemo to show basic session operation.


Modified: trunk/quixote/demo/altdemo.py
===================================================================
--- trunk/quixote/demo/altdemo.py	2004-11-16 19:12:25 UTC (rev 25608)
+++ trunk/quixote/demo/altdemo.py	2004-11-16 19:18:02 UTC (rev 25609)
@@ -9,88 +9,194 @@
 
 The server listens on localhost:8080 by default.  Debug and error output
 will be sent to the terminal.
+
+If you have installed durus, you can run the same demo, except with
+persistent sessions stored in a durus database, by running:
+
+    $ server/simple_server.py --factory quixote.demo.altdemo.create_durus_publisher
+
 """
-from quixote import get_response
-from quixote.publish import Publisher
+
+from quixote import get_user, get_session, get_session_manager, get_field
 from quixote.directory import Directory
-from quixote.errors import PublishError
-from quixote.html import href
+from quixote.html import href, htmltext
+from quixote.publish import SessionPublisher
+from quixote.session import Session, SessionManager
 from quixote.util import dump_request
 
-def format_page(title, content, style_sheet="/base.css"):
-    return ('<html><head><title>%(title)s</title>'
-            '<link rel="stylesheet" href="%(style_sheet)s" type="text/css">'
-            '</head><body>%(content)s</body></html>') % locals()
+def format_page(title, content):
+    request = htmltext(
+        '<div style="font-size: smaller;background:#eee">'
+        '<h1>Request:</h1>%s</div>') % dump_request()
+    return htmltext(
+        '<html><head><title>%(title)s</title>'
+        '<style type="text/css">\n'
+        'body { border: thick solid green; padding: 2em; }\n'
+        'h1 { font-size: larger; }\n'
+        'th { background: #aaa; text-align:left; font-size: smaller; }\n'
+        'td { background: #ccc; font-size: smaller; }\n'
+        '</style>'
+        '</head><body>%(content)s%(request)s</body></html>') % locals()
 
 def format_request():
     return format_page('Request', dump_request())
 
 def format_link_list(targets):
-    return '<ul>%s</ul>' % ''.join([
-        '<li>%s</li>' % href(target, target) for target in targets])
+    return htmltext('<ul>%s</ul>') % htmltext('').join([
+        htmltext('<li>%s</li>') % href(target, target) for target in targets])
 
-class SubDirectory(Directory):
+class RootDirectory(Directory):
 
-    _q_exports = ["", "dumpreq"]
+    _q_exports = ['', 'login', 'logout']
 
-    dumpreq = format_request
-
     def _q_index(self):
-        return format_page('Lookup', format_link_list(range(5)))
+        content = htmltext('')
+        if not get_user():
+            content += htmltext('<p>%s</p>' % href('login', 'login'))
+        else:
+            content += htmltext(
+                '<p>Hello, %s.</p>') % get_user()
+            content += htmltext('<p>%s</p>' % href('logout', 'logout'))
+        sessions = get_session_manager().items()
+        if sessions:
+            sessions.sort()
+            content += htmltext('<table><tr>'
+                                '<th></th>'
+                                '<th>Session</th>'
+                                '<th>User</th>'
+                                '<th>Number of Requests</th>'
+                                '</tr>')
+            this_session = get_session()
+            for index, (id, session) in enumerate(sessions):
+                if session is this_session:
+                    formatted_id = htmltext(
+                        '<span style="font-weight:bold">%s</span>' % id)
+                else:
+                    formatted_id = id
+                content += htmltext(
+                    '<tr><td>%s</td><td>%s</td><td>%s</td><td>%d</td>' % (
+                    index,
+                    formatted_id,
+                    session.user or htmltext("<em>None</em>"),
+                    session.num_requests))
+            content += htmltext('</table>')
+        return format_page("Quixote Session Management Demo", content)
 
-    def _q_lookup(self, component):
-        return format_page(
-            'Found: %s' % component,
-            '<div style="font-size:x-large">%s</div>' % component)
-        
-class RootDirectory(Directory):
+    def login(self):
+        content = htmltext('')
+        if get_field("name"):
+            session = get_session()
+            session.set_user(get_field("name")) # This is the important part.
+            content += htmltext(
+                '<p>Welcome, %s!  Thank you for logging in.</p>') % get_user()
+            content += href("..", "go back")
+        else:
+            content += htmltext(
+                '<p>Please enter your name here:</p>\n'
+                '<form method="POST" action="login">'
+                '<input name="name" />'
+                '<input type="submit" />'
+                '</form>')
+        return format_page("Quixote Session Demo: Login", content)
 
-    _q_exports = ["",
-                  "simple",
-                  "error",
-                  "publish_error",
-                  "dumpreq",
-                  ("base.css", "css"),
-                  "sub"]
+    def logout(self):
+        if get_user():
+            content = htmltext('<p>Goodbye, %s.</p>') % get_user()
+        else:
+            content = htmltext('<p>That would be redundant.</p>') 
+        content += href("..", "start over")
+        get_session_manager().expire_session() # This is the important part.
+        return format_page("Quixote Session Demo: Logout", content)
 
-    def _q_index(self):
+
+class DemoSession(Session):
+
+    def __init__(self, id):
+        Session.__init__(self, id)
+        self.num_requests = 0
+
+    def start_request(self):
         """
-        This is exported as "".
+        This is called from the main object publishing loop whenever
+        we start processing a new request.  Obviously, this is a good
+        place to track the number of requests made.  (If we were
+        interested in the number of *successful* requests made, then
+        we could override finish_request(), which is called by
+        the publisher at the end of each successful request.)
         """
-        s = ('This demonstation is intended to show the basic '
-             'Quixote traversal pattern in action. ')
-        s += '<ul>'
-        for item in self._q_exports:
-            if item:
-                if type(item) is tuple:
-                    name = item[0]
-                elif callable(getattr(self, item)):
-                    name = item
-                else:
-                    name = item + '/'
-                s += '<li>%s</li>' % href(name, name)
-        s += '</ul>'
-        return format_page('Demo1', s)
+        Session.start_request(self)
+        self.num_requests += 1
 
-    def simple(self):
-        # This function returns a plain text document, not HTML.
-        get_response().set_content_type("text/plain")
-        return "This is the Python function 'quixote.demo.simple'.\n"
+    def has_info(self):
+        """
+        Overriding has_info() is essential but non-obvious.  The
+        session manager uses has_info() to know if it should hang on
+        to a session object or not: if a session is "dirty", then it
+        must be saved.  This prevents saving sessions that don't need
+        to be saved, which is especially important as a defensive
+        measure against clients that don't handle cookies: without it,
+        we might create and store a new session object for every
+        request made by such clients.  With has_info(), we create the
+        new session object every time, but throw it away unsaved as
+        soon as the request is complete.
+        
+        (Of course, if you write your session class such that
+        has_info() always returns true after a request has been
+        processed, you're back to the original problem -- and in fact,
+        this class *has* been written that way, because num_requests
+        is incremented on every request, which makes has_info() return
+        true, which makes SessionManager always store the session
+        object.  In a real application, think carefully before putting
+        data in a session object that causes has_info() to return
+        true.)
+        """
+        return (self.num_requests > 0) or Session.has_info(self)
 
-    def error(self):
-        raise ValueError, "this is a Python exception"
+    is_dirty = has_info
 
-    def publish_error(self):
-        raise PublishError("Publishing error raised by publish_error")
 
-    def css(self):
-        get_response().set_content_type("text/css")
-        return 'body { border: thick solid green; padding: 2em; }'
+def create_publisher():
+    return SessionPublisher(RootDirectory(),
+                            SessionManager(session_class=DemoSession),
+                            display_exceptions='plain')
 
-    def dumpreq(self):
-        return format_request()
+try:
+    # If durus is installed, define a create_durus_publisher() that
+    # uses a durus database to store persistent sessions.
+    from durus.persistent import Persistent
+    from durus.persistent_dict import PersistentDict
+    from durus.file_storage import FileStorage
+    from durus.connection import Connection
+    connection = None # set in create_durus_publisher()
 
-    sub = SubDirectory()
-        
-def create_publisher():
-    return Publisher(RootDirectory(), display_exceptions='plain')
+    class PersistentSession(DemoSession, Persistent):
+        pass
+
+    class PersistentSessionManager(SessionManager, Persistent):
+        def __init__(self):
+            sessions = PersistentDict()
+            SessionManager.__init__(self,
+                                    session_class=PersistentSession,
+                                    session_mapping=sessions)
+        def forget_changes(self, session):
+            print 'abort changes', get_session()
+            connection.abort()
+
+        def commit_changes(self, session):
+            print 'commit changes', get_session()
+            connection.commit()
+
+    def create_durus_publisher():
+        global connection
+        connection = Connection(FileStorage('/tmp/quixote-demo.durus'))
+        root = connection.get_root()
+        session_manager = root.get('session_manager', None)
+        if session_manager is None:
+            session_manager = PersistentSessionManager()
+            connection.get_root()['session_manager'] = session_manager
+            connection.commit()
+        return SessionPublisher(RootDirectory(),
+                                session_mgr=session_manager,
+                                display_exceptions='plain')
+except ImportError:
+    pass # durus not installed.