summaryrefslogtreecommitdiff
path: root/examples/todo
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/todo
downloadpaste-4431828c677e6104b7abc62b31a933d2793ff6cf.tar.gz
Renamed WSGIKit to Paste
Diffstat (limited to 'examples/todo')
-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
16 files changed, 838 insertions, 0 deletions
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)