From 4431828c677e6104b7abc62b31a933d2793ff6cf Mon Sep 17 00:00:00 2001 From: ianb Date: Fri, 22 Apr 2005 02:44:48 +0000 Subject: Renamed WSGIKit to Paste --- examples/todo/QUICKSTART | 55 +++++++++++ examples/todo/ROADMAP | 35 +++++++ examples/todo/SitePage.py | 67 +++++++++++++ examples/todo/__init__.py | 17 ++++ examples/todo/api.py | 2 + examples/todo/check.png | Bin 0 -> 2248 bytes examples/todo/favicon.ico | 191 ++++++++++++++++++++++++++++++++++++++ examples/todo/index.py | 47 ++++++++++ examples/todo/list.py | 24 +++++ examples/todo/lists.py | 98 +++++++++++++++++++ examples/todo/login.py | 51 ++++++++++ examples/todo/logout.py | 10 ++ examples/todo/main.css | 50 ++++++++++ examples/todo/model.py | 92 ++++++++++++++++++ examples/todo/register.py | 28 ++++++ examples/todo/tests/test_model.py | 71 ++++++++++++++ 16 files changed, 838 insertions(+) create mode 100644 examples/todo/QUICKSTART create mode 100644 examples/todo/ROADMAP create mode 100644 examples/todo/SitePage.py create mode 100644 examples/todo/__init__.py create mode 100644 examples/todo/api.py create mode 100644 examples/todo/check.png create mode 100644 examples/todo/favicon.ico create mode 100644 examples/todo/index.py create mode 100644 examples/todo/list.py create mode 100644 examples/todo/lists.py create mode 100644 examples/todo/login.py create mode 100644 examples/todo/logout.py create mode 100644 examples/todo/main.css create mode 100644 examples/todo/model.py create mode 100644 examples/todo/register.py create mode 100644 examples/todo/tests/test_model.py (limited to 'examples/todo') 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 + +- ///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("""\n""") + self.write("""\n""" % self.baseURL) + + def writeDocType(self): + self.write(""" + \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("""

WSGIKit ToDo + List


""" % self.baseURL) + + def writeContent(self): + self.write("Blank Page") + + def writePostContent(self): + self.write("""

Copyright not needed

%s
""" % 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 Binary files /dev/null and b/examples/todo/check.png 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 @@ + + + + + + Webware for Python + + + + + + + + + + + + + + + + + + + + + + + + + + +
+Webware for Python + +Webware + + + +

+Download +Webware 0.8.1 +

+ + +
+Webware +
+ + + + + + + + + + + + + + + +

Webware for Python +

+ +
+

Webware for Python 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.

+
+
+

News

+
+

2005-03-18 - ZPTKit 0.1

+

ZPTKit version 0.1 released. ZPTKit helps make Zope Page Templates +easy to use with Webware. For more on ZPTKit see +http://imagescape.com/software/ZPTKit/, download or view the +source.

+
+
+

2005-03-16 - Component 0.1

+

Component version 0.1 released. Component provides a subclass of +WebKit.Page that makes it easier to reuse code from multiple +sources that extend the capability of servlets.

+
+
+

2005-03-14 - Webware switches to Subversion

+

Webware has switched its source-code repository from using Sourceforge's +CVS service to using a Subversion hosted on the webwareforpython.org +(aka w4py.org) server. Here are some instructions on how to access the repository. +Newcomers to subversion may be interested in the Subversion book.

+
+
+

2004-11-17 - WSGI Webware renamed to WSGIKit

+

You can now find the WSGI-enabled Webware implementation at +svn://colorstudy.com/trunk/WSGIKit or view online. +It is now distutil-installable and includes documentation.

+
+
+

2004-10-05 - Webware WSGIKit

+

An effort is in the works to port Webware to the new +Web Server Gateway Interface (WSGI). The WSGI is an effort by +the Python Web-SIG 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 http://svn.w4py.org/WSGIKit/trunk and viewed online.

+
+
+

2004-06-19 - Website re-organization in progress

+

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 old site, +re-organizing, and updating it where necessary. Please bear with us.

+
+
+

2004-04-05 - New server and domain

+

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!

+
+
+

2003-08-01 - Webware 0.8.1 Released

+

Webware 0.8.1 was released today. It contains an important security fix, +and all users should upgrade. See the 0.8.1 release notes for details.

+
+
+

2003-02-09 - Webware 0.8.0 Released

+
    +
  • All adapters have been moved to Webware/WebKit/Adapters
  • +
  • LRWPAdapter added, for use with the Xitami web server
  • +
  • setCookie method in HTTPResponse. 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).
  • +
  • Added an experimental HTTPAdapter.py which serves directly as an +HTTP server.
  • +
  • Added an optional AutoReload setting to enable reloading the server +whenever source files, including servlets and PSP files are changed.
  • +
  • Fix so PSP works with python 2.3a1.
  • +
  • Includes an Apache 2 mod_webkit adapter, and pre-compiled DLL for windows.
  • +
  • Fixed a problem where escapes in PSP source were not being processed correctly.
  • +
  • Restored 0.7 behavior of Page.writeDocType() to output 4.01 Transitional. +See the 0.8 release notes for more info.
  • +
  • Upgraded documentation
  • +
  • Improvements to profilling.
  • +
  • Numerous bug fixes.
  • +
+
+
+
+

This page can be updated in the Webware Wiki.

+ +
+

+ +python powered +

+ +

+Hosted by tummy.com +

+ +
+ + + 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("""

+ +
Username: +
Password:
Enter "guest" and "abc123" to test drive or register to get your own user + account.

""") + + 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("""

User's List""") + self.write("""

+
+

""") + + 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(""" +

%s

+
    + """ % (todolist.name,)) + for name, item in todolist: + if item.done: + self.write("""
  • """) + self.write("""""" % item.name) + else: + self.write("""
  • """) + self.write("""""" % item.name) + self.write(""" %s
  • """ % item.name) + self.write(""" +
+
+ + +

+ Add new item: + +

+
+ + """ % (todolist.name, todolist.name,)) + + def writeContentForAllLists(self): + self.write(""" +

Select a List

+
    + """) + for listname, todolist in self.listUser: + self.write(""" +
  • %(listname)s (obliterate)
  • + """ % locals()) + self.write(""" +
+
+ +

+ Add new list: + +

+
+ """ % 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(""" +
Login incorrect""") + self.write(""" +
+ + + + + + + + + + +
Username:
Password:
+
Enter "guest" and "abc123" to test drive or register to get your own user + account.

""" % 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("""

+ + +
Enter a username and password to use.
Username: +
Password:

""" % 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) -- cgit v1.2.1