diff options
Diffstat (limited to 'examples/todo')
| -rw-r--r-- | examples/todo/QUICKSTART | 55 | ||||
| -rw-r--r-- | examples/todo/ROADMAP | 35 | ||||
| -rw-r--r-- | examples/todo/SitePage.py | 67 | ||||
| -rw-r--r-- | examples/todo/__init__.py | 17 | ||||
| -rw-r--r-- | examples/todo/api.py | 2 | ||||
| -rw-r--r-- | examples/todo/check.png | bin | 0 -> 2248 bytes | |||
| -rw-r--r-- | examples/todo/favicon.ico | 191 | ||||
| -rw-r--r-- | examples/todo/index.py | 47 | ||||
| -rw-r--r-- | examples/todo/list.py | 24 | ||||
| -rw-r--r-- | examples/todo/lists.py | 98 | ||||
| -rw-r--r-- | examples/todo/login.py | 51 | ||||
| -rw-r--r-- | examples/todo/logout.py | 10 | ||||
| -rw-r--r-- | examples/todo/main.css | 50 | ||||
| -rw-r--r-- | examples/todo/model.py | 92 | ||||
| -rw-r--r-- | examples/todo/register.py | 28 | ||||
| -rw-r--r-- | examples/todo/tests/test_model.py | 71 |
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 Binary files differnew file mode 100644 index 0000000..a7eef69 --- /dev/null +++ b/examples/todo/check.png 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 "/") and security flag. +The expire time can be set with constants "ONCLOSE", "NOW", or "NEVER"; or +you can give an integer timestamp, tuple of integers (as used in the time +module), or string identifier (like "+1w" 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) |
