debian-quixote3/quixote/demo/altdemo.py

204 lines
8.1 KiB
Python

+#!/usr/bin/env python3
+
"""An alternative Quixote demo. This version is contained in a single module
and does not use PTL. The easiest way to run this demo is to use the
simple HTTP server included with Quixote. For example:
$ server/simple_server.py --factory quixote.demo.altdemo.create_publisher
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_user, get_session, get_session_manager, get_field
from quixote.directory import Directory
from quixote.html import href, htmltext
from quixote.publish import Publisher
from quixote.session import Session, SessionManager
from quixote.util import dump_request
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 htmltext('<ul>%s</ul>') % htmltext('').join([
htmltext('<li>%s</li>') % href(target, target) for target in targets])
class RootDirectory(Directory):
_q_exports = ['', 'login', 'logout']
def _q_index(self):
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 = sorted(get_session_manager().items())
if sessions:
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 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)
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)
class DemoSession(Session):
def __init__(self, id):
Session.__init__(self, id)
self.num_requests = 0
def start_request(self):
"""
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.)
"""
Session.start_request(self)
self.num_requests += 1
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)
is_dirty = has_info
def create_publisher():
return Publisher(RootDirectory(),
session_manager=SessionManager(session_class=DemoSession),
display_exceptions='plain')
try:
# If durus is installed, define a create_durus_publisher() that
# uses a durus database to store persistent sessions.
import os, tempfile
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()
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
filename = os.path.join(tempfile.gettempdir(), 'quixote-demo.durus')
print('Opening %r as a Durus database.' % filename)
connection = Connection(FileStorage(filename))
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 Publisher(RootDirectory(),
session_manager=session_manager,
display_exceptions='plain')
except ImportError:
pass # durus not installed.