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.