diff options
| author | ianb <devnull@localhost> | 2005-04-22 02:44:48 +0000 |
|---|---|---|
| committer | ianb <devnull@localhost> | 2005-04-22 02:44:48 +0000 |
| commit | 4431828c677e6104b7abc62b31a933d2793ff6cf (patch) | |
| tree | 887f7b87f167a988566657304331ba8bb15ed4d6 /examples | |
| download | paste-4431828c677e6104b7abc62b31a933d2793ff6cf.tar.gz | |
Renamed WSGIKit to Paste
Diffstat (limited to 'examples')
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 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) 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) |
