summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorianb <devnull@localhost>2005-04-22 02:44:48 +0000
committerianb <devnull@localhost>2005-04-22 02:44:48 +0000
commit4431828c677e6104b7abc62b31a933d2793ff6cf (patch)
tree887f7b87f167a988566657304331ba8bb15ed4d6 /examples
downloadpaste-4431828c677e6104b7abc62b31a933d2793ff6cf.tar.gz
Renamed WSGIKit to Paste
Diffstat (limited to 'examples')
-rw-r--r--examples/__init__.py1
-rwxr-xr-xexamples/helloworld.cgi24
-rw-r--r--examples/helloworld/__init__.py2
-rw-r--r--examples/helloworld/index.py28
-rwxr-xr-xexamples/helloworld_wrap.cgi6
-rw-r--r--examples/login_example.py35
-rw-r--r--examples/todo/QUICKSTART55
-rw-r--r--examples/todo/ROADMAP35
-rw-r--r--examples/todo/SitePage.py67
-rw-r--r--examples/todo/__init__.py17
-rw-r--r--examples/todo/api.py2
-rw-r--r--examples/todo/check.pngbin0 -> 2248 bytes
-rw-r--r--examples/todo/favicon.ico191
-rw-r--r--examples/todo/index.py47
-rw-r--r--examples/todo/list.py24
-rw-r--r--examples/todo/lists.py98
-rw-r--r--examples/todo/login.py51
-rw-r--r--examples/todo/logout.py10
-rw-r--r--examples/todo/main.css50
-rw-r--r--examples/todo/model.py92
-rw-r--r--examples/todo/register.py28
-rw-r--r--examples/todo/tests/test_model.py71
-rw-r--r--examples/todo_sql/__init__.py6
-rw-r--r--examples/todo_sql/db.py12
-rw-r--r--examples/todo_sql/server.conf20
-rw-r--r--examples/todo_sql/sitepage.py35
-rw-r--r--examples/todo_sql/templates/generic_error.pt7
-rw-r--r--examples/todo_sql/templates/index.pt23
-rw-r--r--examples/todo_sql/templates/standard_template.pt43
-rw-r--r--examples/todo_sql/templates/view_list.pt57
-rw-r--r--examples/todo_sql/web/__init__.py17
-rw-r--r--examples/todo_sql/web/edit_list.py29
-rw-r--r--examples/todo_sql/web/index.py7
-rw-r--r--examples/todo_sql/web/static/stylesheet.css13
-rw-r--r--examples/todo_sql/web/view_list.py48
35 files changed, 1251 insertions, 0 deletions
diff --git a/examples/__init__.py b/examples/__init__.py
new file mode 100644
index 0000000..792d600
--- /dev/null
+++ b/examples/__init__.py
@@ -0,0 +1 @@
+#
diff --git a/examples/helloworld.cgi b/examples/helloworld.cgi
new file mode 100755
index 0000000..bcc15a5
--- /dev/null
+++ b/examples/helloworld.cgi
@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+
+import cgi
+
+form = cgi.FieldStorage()
+import os
+
+print 'Content-type: text/html\n'
+
+if form.getvalue('name'):
+ print '<html><head><title>Hello!</title></head>'
+ print '<body>'
+ print '<h1>Hello %s!</h1>' % form['name'].value
+else:
+ print '<html><head><title>Who is there?</title></head>'
+ print '<body>'
+ print '<h1>Who is there?</h1>'
+
+print '<form action="%s" method="POST">' % os.environ['SCRIPT_NAME']
+print 'What is your name?<br>'
+print '<input type="text" name="name" value="%s"><br>' \
+ % cgi.escape(form.getvalue('name', ''), 1)
+print '<input type="submit" value="That is my name"></form>'
+print '</body></html>'
diff --git a/examples/helloworld/__init__.py b/examples/helloworld/__init__.py
new file mode 100644
index 0000000..ef59661
--- /dev/null
+++ b/examples/helloworld/__init__.py
@@ -0,0 +1,2 @@
+# No overrides.
+
diff --git a/examples/helloworld/index.py b/examples/helloworld/index.py
new file mode 100644
index 0000000..b79132f
--- /dev/null
+++ b/examples/helloworld/index.py
@@ -0,0 +1,28 @@
+"""
+WSGI application
+
+Automated greeting application.
+"""
+
+import cgi
+
+def application(environ, start_response):
+ form = cgi.FieldStorage(fp=environ['wsgi.input'],
+ environ=environ,
+ keep_blank_values=1)
+ start_response('200 OK', [('Content-type', 'text/html')])
+
+ if form.getvalue('name'):
+ yield ('<html><head><title>Hello!</title></head>\n')
+ yield ('<body>\n')
+ yield ('<h1>Hello %s!</h1>\n' % form['name'].value)
+ else:
+ yield ('<html><head><title>Who is there?</title></head>\n')
+ yield ('<body>\n')
+ yield ('<h1>Who is there?</h1>\n')
+ yield ('<form action="%s" method="POST">\n' % environ['SCRIPT_NAME'])
+ yield ('What is your name?<br>\n')
+ yield ('<input type="text" name="name" value="%s"><br>\n'
+ % cgi.escape(form.getvalue('name', ''), 1))
+ yield ('<input type="submit" value="That is my name"></form>\n')
+ yield ('</body></html>\n')
diff --git a/examples/helloworld_wrap.cgi b/examples/helloworld_wrap.cgi
new file mode 100755
index 0000000..16793bf
--- /dev/null
+++ b/examples/helloworld_wrap.cgi
@@ -0,0 +1,6 @@
+#!/usr/bin/env python
+
+import cgiserver
+import pycgiwrapper
+cgiserver.run_with_cgi(
+ pycgiwrapper.CGIWrapper('helloworld.cgi'))
diff --git a/examples/login_example.py b/examples/login_example.py
new file mode 100644
index 0000000..ee6e55b
--- /dev/null
+++ b/examples/login_example.py
@@ -0,0 +1,35 @@
+from wsgikit.twisted_wsgi import serve_application
+from wsgikit import echo, login
+
+def twisted_serve(app):
+ serve_application(
+ app, port=8080)
+
+class AuthTest(login.Authenticator):
+ def check_auth (self, username, password):
+ print "checking", username, password
+ return username == password
+
+def logincheck (wrapped_app):
+ def wrapping_app(environ, start_response):
+ k = environ.keys()
+ k.sort()
+ print "environ", k
+ if environ.has_key('REMOTE_USER'):
+ return wrapped_app(environ, start_response)
+ else:
+ start_response('401 Unauthorized',
+ [('Content-type','text/plain')])
+ return ['Not logged in.']
+ return wrapping_app
+
+
+if __name__ == '__main__':
+ app = echo.application
+ app = logincheck (app)
+ app = login.middleware(
+ app, http_login=1, authenticator=AuthTest, secret='foo')
+ twisted_serve (app)
+
+
+
diff --git a/examples/todo/QUICKSTART b/examples/todo/QUICKSTART
new file mode 100644
index 0000000..1de319e
--- /dev/null
+++ b/examples/todo/QUICKSTART
@@ -0,0 +1,55 @@
+WSGIKit Example Application: todo
+=================================
+
+This is a simple application intended to demonstrate the basic usage of
+a WSGIKit site as well as some WSGI features such as a middleware hooks
+that performs URL rewriting.
+
+ROADMAP lists some of the features we intend to implement in the future.
+
+Installing WSGIKit
+------------------
+
+`docs/WSGIKit.txt`__ describes a basic user-level installation of
+WSGIKit and supported WSGI servers.
+
+.. __: http://svn.w4py.org/WSGIKit/trunk/docs/WSGIKit.txt
+
+Your First Run
+--------------
+
+The commands::
+
+ cd WSGIKit/trunk
+ wsgikit/server.py --server=wsgiutils --webkit-dir=example/todo \
+ --port 8080 -D --data-dir='/var/www/data1'
+
+If you do not have $PYTHONPATH set up as described WSGIKit.txt then you
+can add a like such as this::
+
+ env PYTHONPATH=`pwd` wsgikit/server.py ...
+
+Replace 'env' with 'setenv' if you're using csh or tcsh.
+
+Then open up http://localhost:8080/
+
+Running Multiple Instances
+--------------------------
+
+Any options not defined in server.py are passed on to the application.
+In the previous example --data-dir is an optional argument that
+specifies where to put the todo list, which is a pickled instance of the
+Manager class in model.py.
+
+Simply specify an alternate --port and --data-dir to run multiple
+instances simultaneously. The todo lists are saved to disk after each
+modification to user accounts or lists and saved as state.pickle.
+
+Testing
+-------
+
+There is a simple set of tests in examples/todo/tests that you can run
+using py.test (see `PyPy doc`__ for information on how to set up py.test).
+The tests only cover the logic contained in todo/model.py
+
+.. __: http://codespeak.net/pypy/index.cgi?doc/howtopypy.html
diff --git a/examples/todo/ROADMAP b/examples/todo/ROADMAP
new file mode 100644
index 0000000..ef2350e
--- /dev/null
+++ b/examples/todo/ROADMAP
@@ -0,0 +1,35 @@
+=======
+ROADMAP
+=======
+
+Items to do
+===========
+
+- add checkboxes for done/not-done states
+
+- by default, only show not-done items (with a toggle between done/not-done)
+
+- give a link/page to see the completed items
+
+- show the number of items (visually or textually) in a todo list
+
+- move items from one list to another
+
+- "share" a list, i.e. a read-only version for an unauthenticated? user
+
+- email a list
+
+- see multiple lists at once
+
+- "subscribe" to a list for status changes or reminders
+
+- add due date, completion date and creation date to todo items
+
+- nest-able todo lists? (a todo list could be contained in another?)
+
+Bugs to fix
+===========
+
+- list names don't work with spaces in them... let's make it so they can
+
+- /<username>/<listname>/delete doesn't work (can't delete/obliterate a list)
diff --git a/examples/todo/SitePage.py b/examples/todo/SitePage.py
new file mode 100644
index 0000000..178e471
--- /dev/null
+++ b/examples/todo/SitePage.py
@@ -0,0 +1,67 @@
+
+import os
+from wsgikit.webkit.wkservlet import Page
+import api
+
+
+class SitePage(Page):
+
+ manager = None
+
+ def awake(self, trans):
+ super(SitePage, self).awake(trans)
+ if not getattr(self, 'manager', None):
+ datadir = trans.request().environ().get('wsgikit.config',{}).get('datadir','.')
+ self.manager = api.Manager.load(root=datadir)
+ self.baseURL = trans.request().environ()['todo.base_url']
+ self.username = self.session().value('username', None)
+ if not self.username and self.loginRequired():
+ self.transaction().forward('/login')
+
+ def loginRequired(self):
+ return True
+
+ def sleep(self, trans):
+ super(SitePage, self).sleep(trans)
+ if self.manager:
+ self.manager.save()
+
+ def preAction(self, actionName):
+ pass
+
+ def postAction(self, actionName):
+ pass
+
+ def writeStyleSheet(self):
+ self.write("""<meta http-equiv="Content-Type" content="text/html;
+ charset=ISO-8859-1" />\n""")
+ self.write("""<link href="%s/main.css" rel="stylesheet"
+ type="text/css" media="screen"/>\n""" % self.baseURL)
+
+ def writeDocType(self):
+ self.write("""<?xml version="1.0" encoding="iso-8859-1"?>
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n""")
+
+ def title(self):
+ return "WSGIKit ToDo list test"
+
+ def htBodyArgs(self):
+ return ""
+
+ def writeBodyParts(self):
+ self.writePreContent()
+ self.writeContent()
+ self.writePostContent()
+
+ def writePreContent(self):
+ self.write("""<h1><img src="%s/check.png" align="absmiddle" />WSGIKit ToDo
+ List</h1><hr />""" % self.baseURL)
+
+ def writeContent(self):
+ self.write("Blank Page")
+
+ def writePostContent(self):
+ self.write("""<p class="faded">Copyright not needed</p><pre>%s</pre>""" % self.htmlEncode(repr(self.manager)))
+
+
diff --git a/examples/todo/__init__.py b/examples/todo/__init__.py
new file mode 100644
index 0000000..fbf9c0a
--- /dev/null
+++ b/examples/todo/__init__.py
@@ -0,0 +1,17 @@
+import os
+from wsgikit import wsgilib
+
+def urlparser_hook(environ):
+ if not environ.has_key('todo.base_url'):
+ environ['todo.base_url'] = environ['SCRIPT_NAME']
+
+def not_found_hook(environ, start_response):
+
+ p = environ['wsgikit.urlparser.not_found_parser']
+ username, rest = wsgilib.path_info_split(environ.get('PATH_INFO', ''))
+ if username is None:
+ return p.not_found(environ, start_response)
+ environ['todo.username'] = username
+ environ['SCRIPT_NAME'] += '/' + username
+ environ['PATH_INFO'] = rest
+ return p(environ, start_response)
diff --git a/examples/todo/api.py b/examples/todo/api.py
new file mode 100644
index 0000000..afbbd11
--- /dev/null
+++ b/examples/todo/api.py
@@ -0,0 +1,2 @@
+
+from model import User, List, Item, Manager
diff --git a/examples/todo/check.png b/examples/todo/check.png
new file mode 100644
index 0000000..a7eef69
--- /dev/null
+++ b/examples/todo/check.png
Binary files differ
diff --git a/examples/todo/favicon.ico b/examples/todo/favicon.ico
new file mode 100644
index 0000000..3d97672
--- /dev/null
+++ b/examples/todo/favicon.ico
@@ -0,0 +1,191 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html>
+<head>
+ <title>Webware for Python
+</title>
+<link href="css/main.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+
+<table cellspacing="0" cellpadding="0" style="width: 100%; border-spacing: 0px;">
+<tr>
+
+<td style="width: 158px; height: 80px;">
+<a href="/"><img src="graphics/python_top.png" width="158" height="80" alt="Webware for Python" /></a>
+</td>
+
+<td style="width: 243px; height: 80px;">
+<a href="/"><img src="graphics/webware.png" width="243" height="80" alt="Webware" /></a>
+</td>
+
+<td style="width: 100%; height: 80px; text-align: center; background-image: url('graphics/head_middle.png');">
+
+
+<p class="topbar">
+Download
+<a href="http://prdownloads.sourceforge.net/webware/Webware-0.8.1.tar.gz?download"><b>Webware 0.8.1</b></a>
+</p>
+
+
+</td>
+
+<td style="height: 80px; text-align: right; width: 6px;">
+<img src="graphics/head_right.png" width="6" height="80" alt="Webware" />
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: top; width: 158px; height: 400px; background-image: url('graphics/menu.png'); background-repeat: no-repeat;">
+
+
+<p style="padding-top: 41px; margin-top: 0px;" class="menu">
+<strong>Documentation</strong><br />
+<a class="menu" href="Webware/Docs/Overview.html">Overview</a><br />
+<a class="menu" href="Webware/Docs/index.html">Online Docs</a><br />
+<!--<a class="menu" href="#">Development</a><br />-->
+<a class="menu" href="http://wiki.w4py.org/">Wiki</a><br />
+<a class="menu" href="Papers/">Papers</a><br />
+</p>
+
+<p class="menu">
+<strong>Download</strong><br />
+<a class="menu" href="http://prdownloads.sourceforge.net/webware/Webware-0.8.1.tar.gz?download">Webware-0.8.1</a><br />
+<a class="menu" href="OlderVersions">Older versions</a><br />
+</p>
+
+<p class="menu">
+<strong>Mailing lists</strong><br />
+<a class="menu" href="http://lists.sourceforge.net/mailman/listinfo/webware-announce">webware-announce</a><br />
+<a class="menu" href="http://lists.sourceforge.net/mailman/listinfo/webware-discuss">webware-discuss</a><br />
+<a class="menu" href="http://lists.sourceforge.net/mailman/listinfo/webware-devel">webware-devel</a><br />
+</p>
+
+<p class="menu">
+<strong>Development</strong><br />
+<a class="menu" href="Wiki/subversionrepositoryaccess">Source Code</a><br />
+<a class="menu" href="http://www.sourceforge.net/projects/webware">Sourceforge Page</a><br />
+<a class="menu" href="http://sourceforge.net/tracker/?group_id=4866&atid=104866">Bug tracker</a><br />
+</p>
+
+<p class="menu">
+<strong>Support</strong><br />
+<a class="menu" href="Consultants">Consultants</a></p>
+
+
+</td>
+
+<td rowspan="2" colspan="3" id="content" style="vertical-align: top; width: 100%%; height: 100%%;">
+
+<h1>Webware for Python
+</h1>
+
+<div class="document">
+<p><strong>Webware for Python</strong> is a suite of software components for developing object-oriented, web-based applications. The suite uses well known design patterns and includes popular features such as a fast application server, Python Server Pages (PSP), and a CGI wrapper.</p>
+<hr />
+<div class="section" id="news">
+<h1><a name="news">News</a></h1>
+<div class="section" id="zptkit-0-1">
+<h2><a name="zptkit-0-1">2005-03-18 - ZPTKit 0.1</a></h2>
+<p>ZPTKit version 0.1 released. ZPTKit helps make <a class="reference" href="http://www.zope.org/DevHome/Wikis/DevSite/Projects/ZPT/FrontPage">Zope Page Templates</a>
+easy to use with Webware. For more on ZPTKit see
+<a class="reference" href="http://imagescape.com/software/ZPTKit/">http://imagescape.com/software/ZPTKit/</a>, <a class="reference" href="http://imagescape.com/software/ZPTKit/ZPTKit-0.1.tar.gz">download</a> or <a class="reference" href="http://svn.webwareforpython.org/ZPTKit/trunk">view the
+source</a>.</p>
+</div>
+<div class="section" id="component-0-1">
+<h2><a name="component-0-1">2005-03-16 - Component 0.1</a></h2>
+<p><a class="reference" href="http://wiki.webwareforpython.org/component.html">Component</a> version 0.1 released. Component provides a subclass of
+<tt class="literal"><span class="pre">WebKit.Page</span></tt> that makes it easier to reuse code from multiple
+sources that extend the capability of servlets.</p>
+</div>
+<div class="section" id="webware-switches-to-subversion">
+<h2><a name="webware-switches-to-subversion">2005-03-14 - Webware switches to Subversion</a></h2>
+<p>Webware has switched its source-code repository from using Sourceforge's
+CVS service to using a <a class="reference" href="http://subversion.tigris.org/">Subversion</a> hosted on the webwareforpython.org
+(aka w4py.org) server. Here are some <a class="reference" href="Wiki/subversionrepositoryaccess">instructions on how to access the repository</a>.
+Newcomers to subversion may be interested in <a class="reference" href="http://svnbook.red-bean.com/en/1.0/index.html">the Subversion book</a>.</p>
+</div>
+<div class="section" id="wsgi-webware-renamed-to-wsgikit">
+<h2><a name="wsgi-webware-renamed-to-wsgikit">2004-11-17 - WSGI Webware renamed to WSGIKit</a></h2>
+<p>You can now find the WSGI-enabled Webware implementation at
+<tt class="literal"><span class="pre">svn://colorstudy.com/trunk/WSGIKit</span></tt> or <a class="reference" href="http://svn.colorstudy.com/trunk/WSGIKit">view online</a>.
+It is now distutil-installable and includes documentation.</p>
+</div>
+<div class="section" id="webware-wsgikit">
+<h2><a name="webware-wsgikit">2004-10-05 - Webware WSGIKit</a></h2>
+<p>An effort is in the works to port Webware to the new
+<a class="reference" href="http://www.python.org/peps/pep-0333.html">Web Server Gateway Interface (WSGI)</a>. The WSGI is an effort by
+<a class="reference" href="http://www.python.org/sigs/web-sig/">the Python Web-SIG</a> to create a standard
+interface for Python web applications, frameworks, and servers, and
+should allow Webware to be run in different environments, and alongside
+other Python web frameworks. Work can be found in a Subversion repository
+at <tt class="literal"><span class="pre">http://svn.w4py.org/WSGIKit/trunk</span></tt> and <a class="reference" href="http://svn.w4py.org/WSGIKit/trunk">viewed online</a>.</p>
+</div>
+<div class="section" id="website-re-organization-in-progress">
+<h2><a name="website-re-organization-in-progress">2004-06-19 - Website re-organization in progress</a></h2>
+<p>Thanks to Eric Radman for the new site design, and Jacob Hanson for the logo.
+We're in the process of moving content over from the <a class="reference" href="http://webware.sf.net/oldsite">old site</a>,
+re-organizing, and updating it where necessary. Please bear with us.</p>
+</div>
+<div class="section" id="new-server-and-domain">
+<h2><a name="new-server-and-domain">2004-04-05 - New server and domain</a></h2>
+<p>The Webware project has been generously provided with a virtual server and
+hosting package, and has registered webwareforpython.org. We will be moving the
+Wiki to this server and are in the process of redesigning the entire website in
+order to provide more useful and better-organized information -- stay tuned!</p>
+</div>
+<div class="section" id="webware-0-8-1-released">
+<h2><a name="webware-0-8-1-released">2003-08-01 - Webware 0.8.1 Released</a></h2>
+<p>Webware 0.8.1 was released today. It contains an important security fix,
+and all users should upgrade. See the <a class="reference" href="http://www.webwareforpython.org/Webware-0.8.1/Docs/RelNotes-0.8.1.html">0.8.1 release notes</a> for details.</p>
+</div>
+<div class="section" id="webware-0-8-0-released">
+<h2><a name="webware-0-8-0-released">2003-02-09 - Webware 0.8.0 Released</a></h2>
+<ul class="simple">
+<li>All adapters have been moved to Webware/WebKit/Adapters</li>
+<li>LRWPAdapter added, for use with the <a class="reference" href="http://xitami.com">Xitami web server</a></li>
+<li><tt class="literal"><span class="pre">setCookie</span></tt> method in <tt class="literal"><span class="pre">HTTPResponse</span></tt>. New method supports
+setting the expire time, path (which defaults to &quot;/&quot;) and security flag.
+The expire time can be set with constants &quot;ONCLOSE&quot;, &quot;NOW&quot;, or &quot;NEVER&quot;; or
+you can give an integer timestamp, tuple of integers (as used in the time
+module), or string identifier (like &quot;+1w&quot; for 1 week in the future).</li>
+<li>Added an <em>experimental</em> <tt class="literal"><span class="pre">HTTPAdapter.py</span></tt> which serves directly as an
+HTTP server.</li>
+<li>Added an optional AutoReload setting to enable reloading the server
+whenever source files, including servlets and PSP files are changed.</li>
+<li>Fix so PSP works with python 2.3a1.</li>
+<li>Includes an Apache 2 mod_webkit adapter, and pre-compiled DLL for windows.</li>
+<li>Fixed a problem where escapes in PSP source were not being processed correctly.</li>
+<li>Restored 0.7 behavior of Page.writeDocType() to output 4.01 Transitional.
+See the <a class="reference" href="http://webware.sourceforge.net/WebKit/Docs/RelNotes-0.8.html">0.8 release notes</a> for more info.</li>
+<li>Upgraded documentation</li>
+<li>Improvements to profilling.</li>
+<li>Numerous bug fixes.</li>
+</ul>
+</div>
+</div>
+</div>
+<hr /><p>This page <a href="http://wiki.w4py.org/webwarenews.html?_action_=edit">can be updated</a> in the <a href="http://wiki.w4py.org">Webware Wiki.</a></p>
+
+</td>
+</tr>
+
+<tr>
+<td style="vertical-align: bottom;">
+<p style="text-align: center;">
+<a href="http://www.python.org/">
+<img src="graphics/PythonPowered.png" width="88" height="31" alt="python powered" /></a>
+</p>
+
+<p style="text-align: center;">
+Hosted by <a href="http://www.tummy.com/">tummy.com</a>
+</p>
+
+</td></tr>
+
+</table>
+
+</body>
+</html>
diff --git a/examples/todo/index.py b/examples/todo/index.py
new file mode 100644
index 0000000..c6be1f2
--- /dev/null
+++ b/examples/todo/index.py
@@ -0,0 +1,47 @@
+from SitePage import SitePage
+
+import api
+
+class index(SitePage):
+
+ def awake(self, trans):
+ super(index, self).awake(trans)
+ self.sendRedirectAndEnd(self.username + '/lists')
+ return
+ username = self.session().value('username', None)
+ if username is not None:
+ self.sendRedirectAndEnd("%s/lists" % username)
+
+ def writeContent(self):
+ self.write("""<form action="./" method="post"> <p><table>
+ <tr><td>Username:</td> <td><input type="text" name="username">
+ </input></td></tr> <tr><td>Password:</td><td><input type="password"
+ name="password"> </input></td> <tr><td></td> <td><input
+ type="submit" name="_action_submit" value="Log In"></td></tr>
+ <tr><td></td><td>Enter "guest" and "abc123" to test drive or <a
+ href="register">register</a> to get your own user
+ account.</td></tr> </table></p>""")
+
+ def actions(self):
+ return ["submit", "logout"]
+
+ def submit(self):
+ fields = self.request().fields()
+ trans = self.request().transaction()
+ session = self.session()
+ if fields.has_key('username') and fields.has_key('password'):
+ try:
+ user = self.manager.getUser(fields['username'])
+ if fields['password'] == user.password:
+ session.setValue('username', user.name)
+ self.sendRedirectAndEnd("%s/lists" % user.name)
+ return
+ except KeyError:
+ pass
+ self.awake(self.request().transaction())
+ self.writeHTML()
+
+ def logout(self):
+ self.session().setValue('username', None)
+ self.awake(self.request().transaction())
+ self.writeHTML()
diff --git a/examples/todo/list.py b/examples/todo/list.py
new file mode 100644
index 0000000..ecc0cb5
--- /dev/null
+++ b/examples/todo/list.py
@@ -0,0 +1,24 @@
+from SitePage import SitePage
+
+import api
+
+class list(SitePage):
+ def writeContent(self):
+ self.write("""<h1>User's List""")
+ self.write("""<form action="list" method="post"> <p> <table> <tr> <td>
+ <input type="text" name="task"> </input> </td> <td> <input
+ type="submit" name="_action_add" value="Add"> </td> </tr> </table>
+ </p> </form>""")
+
+ def actions(self):
+ return ["add"]
+
+ def add(self):
+ """
+ your code here
+ """
+ self.awake(self.request().transaction())
+ self.writeHTML()
+
+
+
diff --git a/examples/todo/lists.py b/examples/todo/lists.py
new file mode 100644
index 0000000..581e7db
--- /dev/null
+++ b/examples/todo/lists.py
@@ -0,0 +1,98 @@
+from urllib import pathname2url
+from urllib2 import unquote
+from wsgikit.httpexceptions import HTTPNotFound
+
+from SitePage import SitePage
+
+import api
+
+class lists(SitePage):
+
+ def awake(self, trans):
+ super(lists, self).awake(trans)
+ username = self.request().environ()['todo.username']
+ try:
+ self.listUser = self.manager.getUser(username)
+ except KeyError:
+ raise HTTPNotFound
+ extra = self.request().extraURLPath()
+ if extra and \
+ extra[0] == '/' and \
+ extra[1:] in self.listUser.lists:
+ self.listname = unquote(extra[1:])
+ self.writeContent = self.writeContentForOneList
+ else:
+ self.listname = None
+ self.writeContent = self.writeContentForAllLists
+
+ def sleep(self, trans):
+ self.listUser = None
+ super(lists, self).sleep(trans)
+
+ def writeContentForOneList(self):
+ todolist = self.listUser.getList(self.listname)
+ self.write("""
+ <h1>%s</h1>
+ <ul class="todolist">
+ """ % (todolist.name,))
+ for name, item in todolist:
+ if item.done:
+ self.write("""<li class="done">""")
+ self.write("""<input type="checkbox" name="%s" checked="checked" />""" % item.name)
+ else:
+ self.write("""<li class="undone">""")
+ self.write("""<input type="checkbox" name="%s" />""" % item.name)
+ self.write(""" %s</li>""" % item.name)
+ self.write("""
+ </ul>
+ <form action="../lists/%s" method="post">
+ <input type="hidden" name="_action_" value="addItem" />
+ <input type="hidden" name="listname" value="%s" />
+ <p>
+ Add new item: <input type="text" name="itemname" value="" />
+ <input type="submit" value="new item" />
+ </p>
+ </form>
+ <script type="text/javascript">
+ document.forms[0].itemname.focus();
+ </script>
+ """ % (todolist.name, todolist.name,))
+
+ def writeContentForAllLists(self):
+ self.write("""
+ <h1>Select a List</h1>
+ <ul>
+ """)
+ for listname, todolist in self.listUser:
+ self.write("""
+ <li><strong><a href="lists/%(listname)s">%(listname)s</a></strong> (<a href="%(listname)s/delete">obliterate</a>)</li>
+ """ % locals())
+ self.write("""
+ </ul>
+ <form action="lists" method="post">
+ <input type="hidden" name="_action_" value="addList" />
+ <p>
+ Add new list: <input type="text" name="listname" value="%s" />
+ <input type="submit" value="add list" />
+ </p>
+ </form>
+ """ % self.request().fields().get('listname',''))
+
+ def actions(self):
+ return ["addList", "addItem"]
+
+ def addList(self):
+ fields = self.request().fields()
+ newlist = api.List(fields.get('listname', 'new list'), owner=self.listUser)
+ self.sendRedirectAndEnd("/%s/lists/%s" % (self.listUser.name, pathname2url(newlist.name)))
+
+ def addItem(self):
+ fields = self.request().fields()
+ currentlist = self.listUser.getList(fields['listname'])
+ currentlist.addItem(api.Item(fields['itemname']))
+ self.sendRedirectAndEnd(
+ '%(baseURL)s/%(username)s/lists/%(listname)s'
+ % {'baseURL': self.baseURL,
+ 'username': self.listUser.name,
+ 'listname': self.listname})
+
diff --git a/examples/todo/login.py b/examples/todo/login.py
new file mode 100644
index 0000000..d0a9d34
--- /dev/null
+++ b/examples/todo/login.py
@@ -0,0 +1,51 @@
+from SitePage import SitePage
+
+class login(SitePage):
+
+ def loginRequired(self):
+ return False
+
+ def awake(self, trans):
+ super(login, self).awake(trans)
+ self.failedLogin = False
+
+ def actions(self):
+ return ['submit']
+
+ def writeContent(self):
+ if self.failedLogin:
+ self.write("""
+ <div style="font-color: red">Login incorrect</a>""")
+ self.write("""
+ <form action="%s/login" method="post">
+ <table>
+ <tr><td>Username:</td>
+ <td><input type="text" name="username"></td></tr>
+
+ <tr><td>Password:</td>
+ <td><input type="password" name="password"></td></tr>
+
+ <tr><td></td>
+ <td><input type="submit" name="_action_submit" value="Log In">
+ </td></tr>
+
+ <tr><td></td><td>Enter "guest" and "abc123" to test drive or <a
+ href="register">register</a> to get your own user
+ account.</td></tr> </table></p>""" % self.baseURL)
+
+ def submit(self):
+ field = self.request().field
+ trans = self.request().transaction()
+ session = self.session()
+ username = field('username')
+ password = field('password')
+ try:
+ user = self.manager.getUser(username)
+ if password == user.password:
+ session.setValue('username', user.name)
+ self.sendRedirectAndEnd("%s/lists" % user.name)
+ return
+ except KeyError:
+ pass
+ self.failedLogin = True
+ self.writeHTML()
diff --git a/examples/todo/logout.py b/examples/todo/logout.py
new file mode 100644
index 0000000..6c48889
--- /dev/null
+++ b/examples/todo/logout.py
@@ -0,0 +1,10 @@
+from SitePage import SitePage
+
+import api
+
+class logout(SitePage):
+
+ def awake(self, trans):
+ super(logout, self).awake(trans)
+ self.session().setValue('username', None)
+ self.sendRedirectAndEnd("index")
diff --git a/examples/todo/main.css b/examples/todo/main.css
new file mode 100644
index 0000000..67f0fb2
--- /dev/null
+++ b/examples/todo/main.css
@@ -0,0 +1,50 @@
+html {
+ font-size: 12px;
+ color: #000000;
+}
+body {
+ font-size: 12px;
+ background: #FFFFFF;
+}
+p {
+ font-size: 12px;
+ color: #000000;
+}
+h1 {
+ font-size: 18px;
+ color: #000000;
+}
+h2 {
+ font-size: 14px;
+}
+pre {
+ font-weight: normal;
+ font-size: 11px;
+ border: 1px solid #009BB7;
+ padding: 3px;
+ background-color: #D4F5FB;
+}
+
+p.faded {
+ font-size: 10px;
+ color: #666666;
+}
+
+li.done {
+ color:#666666;
+ text-decoration: strikethrough;
+ padding: 0;
+ margin: 0;
+}
+
+li.undone {
+ color:#000000;
+ padding: 0;
+ margin: 0;
+}
+
+ul.todolist {
+ list-style-type: none;
+ margin:0 auto;
+ padding:0 auto;
+ } \ No newline at end of file
diff --git a/examples/todo/model.py b/examples/todo/model.py
new file mode 100644
index 0000000..deba326
--- /dev/null
+++ b/examples/todo/model.py
@@ -0,0 +1,92 @@
+"""
+Simple To-do Lists
+
+TODO:
+ - keep track of the *order* of the items in a list
+ - problems everywhere... why can't you have two items with the same name?
+ - need to use a serial/id number for each object so that we can reference
+ them in the urls by number instead of by name (except for the username
+ which makes sense to have in the URLs...)
+ - __iter__ating through things? does that make sense?
+ - getList(name) vs. list(name) method names...
+ - re-ordering of list items?
+ - I know it's just for the sample app, but pickling the manager
+ in and out of some local directory? Is that okay?
+ - Why in List.__init__ would it be the list's responsibility to add itself
+ to its new owner? Why wouldn't the owner do that?
+"""
+
+import os
+
+
+class DuplicateUserError(Exception): pass
+class DuplicateListError(Exception): pass
+
+
+class User(object):
+ def __init__(self, name, password):
+ self.name = name
+ self.password = password
+ self.lists = {}
+ def addList(self, todolist):
+ if todolist.name in self.lists:
+ raise DuplicateListError("A list by the name %s already exists for user %s" % (todolist.name, self.name))
+ self.lists[todolist.name] = todolist
+ def getList(self, name):
+ return self.lists[name]
+ def __iter__(self):
+ keys = self.lists.keys()
+ keys.sort()
+ for key in keys:
+ yield key, self.lists[key]
+
+
+class List(object):
+ def __init__(self, name, owner):
+ self.name = name
+ self.items = {}
+ self.owner = owner
+ self.owner.addList(self)
+ def addItem(self, item):
+ if isinstance(item, str):
+ item = Item(item)
+ self.items[item.name] = item
+ def __iter__(self):
+ keys = self.items.keys()
+ keys.sort()
+ for key in keys:
+ yield key, self.items[key]
+
+
+class Item(object):
+ def __init__(self, name):
+ self.name = name
+ self.done = False
+
+
+class Manager(object):
+ def __init__(self, root):
+ self.root = os.path.abspath(root)
+ self.users = {}
+
+ def addUser(self, user):
+ if user.name in self.users:
+ raise DuplicateUserError("A user by the name of %s already exists." % `user.name`)
+ self.users[user.name] = user
+
+ def getUser(self, username):
+ return self.users[username]
+
+ def save(self):
+ from cPickle import dump
+ dump(self, file(os.path.join(self.root, 'state.pickle'), 'w'))
+
+ def load(cls, root):
+ from cPickle import load
+ try:
+ return load(file(os.path.join(root, 'state.pickle')))
+ except IOError:
+ return cls(root=root)
+
+ load = classmethod(load)
+
diff --git a/examples/todo/register.py b/examples/todo/register.py
new file mode 100644
index 0000000..36af0da
--- /dev/null
+++ b/examples/todo/register.py
@@ -0,0 +1,28 @@
+from SitePage import SitePage
+
+import api
+
+class register(SitePage):
+
+ def loginRequired(self):
+ return False
+
+ def writeContent(self):
+ self.write("""<form action="%s/register" method="post"> <p><table>
+ <tr><td></td> <td>Enter a username and password to use.</td></tr>
+ <tr><td>Username:</td> <td><input type="text" name="username">
+ </input></td></tr> <tr><td>Password:</td><td><input type="password"
+ name="password"> </input></td> <tr><td></td> <td><input
+ type="submit" name="_action_submit" value="Sign Up"></td></tr>
+ </table></p>""" % self.baseURL)
+
+ def actions(self):
+ return ["submit"]
+
+ def submit(self):
+ fields = self.request().fields()
+ user = api.User(fields['username'], fields['password'])
+ self.manager.addUser(user)
+ self.session().setValue('username', user.name)
+ self.sendRedirectAndEnd("%s/lists" % user.name)
+
diff --git a/examples/todo/tests/test_model.py b/examples/todo/tests/test_model.py
new file mode 100644
index 0000000..e5a9773
--- /dev/null
+++ b/examples/todo/tests/test_model.py
@@ -0,0 +1,71 @@
+
+import os
+import sys
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+
+from model import User, List, Item, Manager, \
+ DuplicateUserError, DuplicateListError
+
+
+def test_user_creation():
+ newuser = User('user1', 'pass')
+ assert not newuser.lists
+ newlist = List('list1', newuser)
+ assert newlist.name in newuser.lists
+
+def test_item_creation():
+ newitem = Item('make a new list')
+ assert newitem.done == False
+
+def test_list_creation():
+ newuser = User('tester', 'pass')
+ newlist = List('', newuser)
+ assert not newlist.items
+ assert newlist.owner.name is newuser.name
+ newitem = Item('make a new list')
+ assert newitem.done == False
+ newlist.addItem(newitem)
+ assert len(newlist.items) == 1
+ assert newitem.name in newlist.items
+
+class TestListManager:
+
+ def setup_class(cls):
+ cls.manager = mgr = Manager(root=os.path.join(os.path.dirname(__file__), 'data'))
+ print 'cls.manager:', cls.manager.root
+ user1 = User('user1', 'pass')
+ mgr.addUser(user1)
+ user2 = User('user2', 'pass')
+ mgr.addUser(user2)
+ list1 = List('list1', owner=user1)
+ list1.addItem('first step')
+ list1.addItem('second step')
+ list1.addItem('third step')
+ list2 = List('list2', owner=user2)
+
+ def test_list_manager(self):
+ assert os.path.exists(self.manager.root)
+ assert len(self.manager.users) == 2
+ assert len(self.manager.getUser('user1').lists) == 1
+
+ def test_list_manipulations(self):
+ user1 = self.manager.getUser('user1')
+ assert user1.name == 'user1'
+
+ def test_duplicates(self):
+ from py.test import raises
+ raises(DuplicateUserError, "self.manager.addUser(User('user1', 'pass'))")
+ user1 = self.manager.getUser('user1')
+ raises(DuplicateListError, "List('list1', owner=user1)")
+
+ def test_simple_persistence(self):
+ self.manager.save()
+ duped = Manager.load(self.manager.root)
+ assert 'user1' in duped.users
+ assert 'list2' in duped.getUser('user2').lists
+
+ def teardown_class(cls):
+ statefile = os.path.join(cls.manager.root, 'state.pickle')
+ if os.path.exists(statefile):
+ os.remove(statefile)
diff --git a/examples/todo_sql/__init__.py b/examples/todo_sql/__init__.py
new file mode 100644
index 0000000..39d0c09
--- /dev/null
+++ b/examples/todo_sql/__init__.py
@@ -0,0 +1,6 @@
+from wsgikit.util.thirdparty import add_package
+
+add_package('ZopePageTemplates')
+add_package('Component')
+add_package('ZPTKit')
+add_package('sqlobject')
diff --git a/examples/todo_sql/db.py b/examples/todo_sql/db.py
new file mode 100644
index 0000000..21f36fb
--- /dev/null
+++ b/examples/todo_sql/db.py
@@ -0,0 +1,12 @@
+from sqlobject import *
+
+class TodoList(SQLObject):
+
+ description = StringCol(notNull=True)
+ items = MultipleJoin('TodoItem')
+
+class TodoItem(SQLObject):
+
+ todo_list = ForeignKey('TodoList')
+ description = StringCol(notNull=True)
+ done = BoolCol(notNull=True, default=False)
diff --git a/examples/todo_sql/server.conf b/examples/todo_sql/server.conf
new file mode 100644
index 0000000..0b38b2a
--- /dev/null
+++ b/examples/todo_sql/server.conf
@@ -0,0 +1,20 @@
+# -*- python -*- Note: this file is in Python syntax
+
+import os
+database = 'sqlite:%s/data.db' % os.path.dirname(__file__)
+
+app_template = 'webkit_zpt'
+app_name = 'todo_sql'
+webkit_dir = '/var/www/todo_sql/web'
+sys_path = ['/var/www']
+
+## Server options:
+
+verbose = True
+# The name of the server-type to start:
+server = 'wsgiutils'
+# If true, files will be regularly polled and the server restarted
+# if files are modified:
+reload = True
+# If true, tracebacks will be shown in the browser:
+debug = True
diff --git a/examples/todo_sql/sitepage.py b/examples/todo_sql/sitepage.py
new file mode 100644
index 0000000..9c88685
--- /dev/null
+++ b/examples/todo_sql/sitepage.py
@@ -0,0 +1,35 @@
+import os
+from Component import CPage
+from Component.notify import NotifyComponent
+from ZPTKit import ZPTComponent
+from todo_sql.db import *
+
+class SitePage(CPage):
+
+ components = [
+ ZPTComponent([os.path.join(os.path.dirname(__file__),
+ 'templates')]),
+ NotifyComponent()]
+
+ def title(self):
+ return self.options.get('title', CPage.title(self))
+
+ def awake(self, trans):
+ CPage.awake(self, trans)
+ self.baseURL = self.request().environ()['todo_sql.base_url']
+ self.baseStaticURL = self.baseURL + '/static'
+ self.options.lists = list(TodoList.select())
+ self.setup()
+
+ def setup(self):
+ pass
+
+ def sleep(self, trans):
+ self.teardown()
+ CPage.sleep(self, trans)
+
+ def teardown(self):
+ pass
+
+ def writeHTML(self):
+ self.writeTemplate()
diff --git a/examples/todo_sql/templates/generic_error.pt b/examples/todo_sql/templates/generic_error.pt
new file mode 100644
index 0000000..7b32820
--- /dev/null
+++ b/examples/todo_sql/templates/generic_error.pt
@@ -0,0 +1,7 @@
+<html metal:use-macro="here/standard_template.pt/macros/page">
+<metal:body fill-slot="body">
+
+<tal:block replace="structure options/error_message" />
+
+</metal:body>
+</html> \ No newline at end of file
diff --git a/examples/todo_sql/templates/index.pt b/examples/todo_sql/templates/index.pt
new file mode 100644
index 0000000..8db6839
--- /dev/null
+++ b/examples/todo_sql/templates/index.pt
@@ -0,0 +1,23 @@
+<html metal:use-macro="here/standard_template.pt/macros/page">
+<metal:body fill-slot="body">
+
+<ul>
+ <li tal:repeat="list options/lists">
+ <a tal:attributes="href python: '%s/view_list?id=%s' % (servlet.baseURL, list.id)"
+ tal:content="list/description">list</a></li>
+</ul>
+
+<p tal:condition="not: options/lists">
+ There are no lists to display
+</p>
+
+<form action="./edit_list" method="POST">
+ Create a new list:<br>
+ <input type="hidden" name="id" value="new">
+ <input type="hidden" name="_action_" value="save">
+ <input type="text" name="description">
+ <input type="submit" value="Create!!!!">
+</form>
+<tal:block replace="servlet/request/environ" />
+</metal:body>
+</html>
diff --git a/examples/todo_sql/templates/standard_template.pt b/examples/todo_sql/templates/standard_template.pt
new file mode 100644
index 0000000..e3ff01d
--- /dev/null
+++ b/examples/todo_sql/templates/standard_template.pt
@@ -0,0 +1,43 @@
+<metal:tpl define-macro="page">
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+<html lang="en-US">
+<head>
+<title tal:content="servlet/title">title</title>
+<link rel="stylesheet" type="text/css"
+ tal:attributes="href python: servlet.baseStaticURL + '/stylesheet.css'">
+
+<metal:slot define-slot="extra_head"></metal:slot>
+
+</head>
+<body>
+
+<h1 id="header"><tal:mark replace="servlet/title"/></h1>
+
+<table width="100%">
+<tr>
+<td id="sidenav" width="20%">
+<a tal:attributes="href servlet/baseURL">Home</a><br>
+<tal:for repeat="list options/lists">
+ <a tal:attributes="href python: '%s/view_list?id=%s' %
+ (servlet.baseURL, list.id)"
+ tal:content="list/description">link</a><br>
+</tal:for>
+</td>
+
+<td>
+<span tal:replace="structure servlet/messageText">
+This is where the notification messages go.
+</span>
+
+<div id="content">
+<metal:tpl define-slot="body">
+[This page has not customized its "body" slot]
+</metal:tpl>
+</div>
+
+</td></tr></table>
+
+</body>
+</html>
+</metal:tpl>
diff --git a/examples/todo_sql/templates/view_list.pt b/examples/todo_sql/templates/view_list.pt
new file mode 100644
index 0000000..2a85b13
--- /dev/null
+++ b/examples/todo_sql/templates/view_list.pt
@@ -0,0 +1,57 @@
+<html metal:use-macro="here/standard_template.pt/macros/page">
+<metal:body fill-slot="body">
+
+<form action="view_list?_action_=check" method="POST">
+<input type="hidden" name="id"
+ tal:attributes="value options/list/id">
+
+<table>
+<tr>
+ <th>Done?</th>
+ <th>Description</th>
+</tr>
+
+<tr tal:repeat="item options/list_items">
+ <tal:let define="name python: 'item_%s' % item.id">
+ <td>
+ <input type="checkbox"
+ tal:attributes="name name;
+ id name;
+ checked item/done">
+ </td>
+ <td>
+ <label tal:attributes="for name"
+ tal:content="item/description">description</label>
+ </td>
+ <td><a tal:attributes="
+ href python: 'view_list?_action_=destroy&item_id=%s'
+ % item.id">[delete]</a></td>
+ </tal:let>
+</tr>
+
+<tr tal:condition="not: options/list_items">
+ <td colspan=2>No items to display</td>
+</tr>
+
+</table>
+
+<input type="submit" value="Update item status">
+</form>
+
+<form action="view_list?_action_=add" method="POST">
+<input type="hidden" name="id"
+ tal:attributes="value options/list/id">
+Add item:<br>
+<input type="text" style="width: 100%" name="description"><br>
+<input type="submit" value="Add">
+</form>
+
+<form action="edit_list?_action_=destroy" method="POST">
+<input type="hidden" name="id"
+ tal:attributes="value options/list/id">
+<input type="submit" value="Delete this list"
+ onclick="return window.confirm('Really delete this list?')">
+</form>
+
+</metal:body>
+</html>
diff --git a/examples/todo_sql/web/__init__.py b/examples/todo_sql/web/__init__.py
new file mode 100644
index 0000000..835887d
--- /dev/null
+++ b/examples/todo_sql/web/__init__.py
@@ -0,0 +1,17 @@
+import os
+from wsgikit import wsgilib
+from wsgikit.util.thirdparty import add_package
+add_package('sqlobject')
+import sqlobject
+
+sql_set = False
+
+def urlparser_hook(environ):
+ global sql_set
+ if not environ.has_key('todo_sql.base_url'):
+ environ['todo_sql.base_url'] = environ['SCRIPT_NAME']
+ if not sql_set:
+ sql_set = True
+ db_uri = environ['wsgikit.config']['database']
+ sqlobject.sqlhub.processConnection = sqlobject.connectionForURI(
+ db_uri)
diff --git a/examples/todo_sql/web/edit_list.py b/examples/todo_sql/web/edit_list.py
new file mode 100644
index 0000000..0bd3372
--- /dev/null
+++ b/examples/todo_sql/web/edit_list.py
@@ -0,0 +1,29 @@
+from todo_sql.sitepage import SitePage
+from todo_sql.db import *
+
+class edit_list(SitePage):
+
+ def setup(self):
+ self.list_id = self.request().field('id')
+ if self.list_id != 'new':
+ self.list = TodoList.get(int(self.list_id))
+ self.options.title = 'Edit list '
+
+ def actions(self):
+ return ['save', 'destroy']
+
+ def save(self):
+ desc = self.request().field('description')
+ if self.list_id == 'new':
+ self.list = TodoList(description=desc)
+ self.message('List created')
+ else:
+ self.list.description = desc
+ self.message('List updated')
+ self.sendRedirectAndEnd('./view_list?id=%s' % self.list.id)
+
+ def destroy(self):
+ desc = self.list.description
+ self.list.destroySelf()
+ self.message('List %s deleted' % desc)
+ self.sendRedirectAndEnd('./')
diff --git a/examples/todo_sql/web/index.py b/examples/todo_sql/web/index.py
new file mode 100644
index 0000000..55b1f98
--- /dev/null
+++ b/examples/todo_sql/web/index.py
@@ -0,0 +1,7 @@
+from todo_sql.sitepage import SitePage
+from todo_sql.db import *
+
+class index(SitePage):
+
+ def setup(self):
+ self.options.title = 'List of Lists'
diff --git a/examples/todo_sql/web/static/stylesheet.css b/examples/todo_sql/web/static/stylesheet.css
new file mode 100644
index 0000000..d70e9ec
--- /dev/null
+++ b/examples/todo_sql/web/static/stylesheet.css
@@ -0,0 +1,13 @@
+body {
+ font-family: Helvetica, Arial, sans-serif;
+}
+
+pre {
+ overflow: auto;
+}
+
+div.notifyMessage {
+ border: 2px solid black;
+ background-color: #007700;
+ color: #ffffff;
+}
diff --git a/examples/todo_sql/web/view_list.py b/examples/todo_sql/web/view_list.py
new file mode 100644
index 0000000..a7105d6
--- /dev/null
+++ b/examples/todo_sql/web/view_list.py
@@ -0,0 +1,48 @@
+from todo_sql.sitepage import SitePage
+from todo_sql.db import *
+
+class view_list(SitePage):
+
+ def setup(self):
+ self.options.list = TodoList.get(int(self.request().field('id')))
+ self.options.list_items = list(self.options.list.items)
+ self.options.title = 'List: %s' % self.options.list.description
+
+ def actions(self):
+ return ['check', 'add', 'destroy']
+
+ def check(self):
+ field = self.request().field
+ for item in self.options.list.items:
+ checked = field('item_%s' % item.id, False)
+ if not checked and item.done:
+ self.message('Item %s marked not done'
+ % item.description)
+ item.done = False
+ if checked and not item.done:
+ self.message('Item %s marked done'
+ % item.description)
+ item.done = True
+ self.sendRedirectAndEnd(
+ './view_list?id=%s' % self.options.list.id)
+
+ def add(self):
+ desc = self.request().field('description')
+ if not desc:
+ self.message('You must give a description')
+ else:
+ TodoItem(todo_list=self.options.list,
+ description=desc)
+ self.message('Item added')
+ self.sendRedirectAndEnd(
+ './view_list?id=%s' % self.options.list.id)
+
+ def destroy(self):
+ id = int(self.request.field('item_id'))
+ item = TodoItem.get(id)
+ assert item.todo_list.id == self.options.list.id
+ desc = description
+ item.destroySelf()
+ self.message("Item %s removed" % desc)
+ self.sendRedirectAndEnd(
+ './view_list?id=%s' % self.options.list.id)