aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAaron Bentley <abentley@panoramicfeedback.com>2005-12-15 16:02:37 -0500
committerAaron Bentley <abentley@panoramicfeedback.com>2005-12-15 16:02:37 -0500
commite0e29d35bb901fd3a6b0be1289c3b5afac130be4 (patch)
treeb762202dcf7865502ee50c4c942882b0321314eb
parent00db1231de6d38609289287be2c00f924a116e15 (diff)
downloadbugseverywhere-e0e29d35bb901fd3a6b0be1289c3b5afac130be4.tar.gz
Switched to restresource for resolving URLs
-rw-r--r--beweb/beweb/controllers.py90
-rw-r--r--beweb/beweb/restresource.py195
-rw-r--r--beweb/beweb/templates/bugs.kid9
-rw-r--r--beweb/beweb/templates/projects.kid2
4 files changed, 289 insertions, 7 deletions
diff --git a/beweb/beweb/controllers.py b/beweb/beweb/controllers.py
index 2a8d0aa..8ba1595 100644
--- a/beweb/beweb/controllers.py
+++ b/beweb/beweb/controllers.py
@@ -4,6 +4,7 @@ import cherrypy
from libbe.bugdir import tree_root, cmp_severity
from libbe import names
from config import projects
+from restresource import RESTResource
def project_tree(project):
try:
@@ -11,10 +12,95 @@ def project_tree(project):
except KeyError:
raise Exception("Unknown project %s" % project)
+def expose_resource(html=None):
+ def exposer(func):
+ func = turbogears.expose(html=html)(func)
+ func.expose_resource = True
+ return func
+ return exposer
+
+class Bug(RESTResource):
+ @expose_resource(html="beweb.templates.edit_bug")
+ def index(self, bug):
+ return {"bug": bug, "project_id": self.parent}
+
+ @turbogears.expose(html="beweb.templates.bugs")
+ def list(self, sort_by=None, show_closed=False, action=None):
+ if action == "New bug":
+ self.new_bug()
+ if show_closed == "False":
+ show_closed = False
+ bug_tree = project_tree(self.parent)
+ bugs = list(bug_tree.list())
+ if sort_by is None:
+ def cmp_date(bug1, bug2):
+ return -cmp(bug1.time, bug2.time)
+ bugs.sort(cmp_date)
+ bugs.sort(cmp_severity)
+ return {"project_id" : self.parent,
+ "project_name" : projects[self.parent][0],
+ "bugs" : bugs,
+ "show_closed" : show_closed,
+ }
+
+ def new_bug(self):
+ bug = self.bug_tree().new_bug()
+ bug.creator = names.creator()
+ bug.severity = "minor"
+ bug.status = "open"
+ bug.save()
+ raise cherrypy.HTTPRedirect(bug_url(self.parent, bug.uuid))
+
+ @expose_resource()
+ def update(self, bug, status, severity, summary, action):
+ bug.status = status
+ bug.severity = severity
+ bug.summary = summary
+ bug.save()
+ raise cherrypy.HTTPRedirect(bug_list_url(self.parent))
+
+ def REST_instantiate(self, bug_uuid):
+ return self.bug_tree().get_bug(bug_uuid)
+
+ def bug_tree(self):
+ return project_tree(self.parent)
+
+def project_url(project_id=None):
+ project_url = "/project/"
+ if project_id is not None:
+ project_url += "%s/" % project_id
+ return turbogears.url(project_url)
+
+def bug_url(project_id, bug_uuid=None):
+ bug_url = "/project/%s/bug/" % project_id
+ if bug_uuid is not None:
+ bug_url += "%s/" % bug_uuid
+ return turbogears.url(bug_url)
+
+def bug_list_url(project_id, show_closed=False):
+ bug_url = "/project/%s/bug/?show_closed=%s" % (project_id,
+ str(show_closed))
+ return turbogears.url(bug_url)
+
+
+class Project(RESTResource):
+ REST_children = {"bug": Bug()}
+ @expose_resource(html="beweb.templates.projects")
+ def index(self, project_id=None):
+ if project_id is not None:
+ raise cherrypy.HTTPRedirect(bug_url(project_id))
+ else:
+ return {"projects": projects}
+
+ def REST_instantiate(self, project_id):
+ return project_id
+
+
class Root(controllers.Root):
- @turbogears.expose(html="beweb.templates.projects")
+ project = Project()
+ @turbogears.expose()
def index(self):
- return {"projects" : projects}
+ raise cherrypy.HTTPRedirect(project_url())
@turbogears.expose()
def default(self, *args, **kwargs):
diff --git a/beweb/beweb/restresource.py b/beweb/beweb/restresource.py
new file mode 100644
index 0000000..47db637
--- /dev/null
+++ b/beweb/beweb/restresource.py
@@ -0,0 +1,195 @@
+"""
+REST Resource
+
+cherrypy controller mixin to make it easy to build REST applications.
+
+handles nested resources and method-based dispatching.
+
+here's a rough sample of what a controller would look like using this:
+
+cherrypy.root = MainController()
+cherrypy.root.user = UserController()
+
+class PostController(RESTResource):
+ def index(self,post):
+ return post.as_html()
+ index.expose_resource = True
+
+ def delete(self,post):
+ post.destroySelf()
+ return "ok"
+ delete.expose_resource = True
+
+ def update(self,post,title="",body=""):
+ post.title = title
+ post.body = body
+ return "ok"
+ update.expose_resource = True
+
+ def add(self, post, title="", body="")
+ post.title = title
+ post.body = body
+ return "ok"
+ update.expose_resource = True
+
+ def REST_instantiate(self, slug):
+ try:
+ return Post.select(Post.q.slug == slug, Post.q.userID = self.parent.id)[0]
+ except:
+ return None
+
+ def REST_create(self, slug):
+ return Post(slug=slug,user=self.parent)
+
+class UserController(RESTResource):
+ REST_children = {'posts' : PostController()}
+
+ def index(self,user):
+ return user.as_html()
+ index.expose_resource = True
+
+ def delete(self,user):
+ user.destroySelf()
+ return "ok"
+ delete.expose_resource = True
+
+ def update(self,user,fullname="",email=""):
+ user.fullname = fullname
+ user.email = email
+ return "ok"
+ update.expose_resource = True
+
+ def add(self, user, fullname="", email=""):
+ user.fullname = fullname
+ user.email = email
+ return "ok"
+ add.expose_resource = True
+
+ def extra_action(self,user):
+ # do something else
+ extra_action.expose_resource = True
+
+ def REST_instantiate(self, username):
+ try:
+ return User.byUsername(username)
+ except:
+ return None
+
+ def REST_create(self, username):
+ return User(username=username)
+
+then, the site would have urls like:
+
+ /user/bob
+ /user/bob/posts/my-first-post
+ /user/bob/posts/my-second-post
+
+which represent REST resources. calling 'GET /usr/bob' would call the index() method on UserController
+for the user bob. 'PUT /usr/joe' would create a new user with username 'joe'. 'DELETE /usr/joe'
+would delete that user. 'GET /usr/bob/posts/my-first-post' would call index() on the Post Controller
+with the post with the slug 'my-first-post' that is owned by bob.
+
+
+"""
+
+
+import cherrypy
+class RESTResource:
+ # default method mapping. ie, if a GET request is made for
+ # the resource's url, it will try to call an index() method (if it exists);
+ # if a PUT request is made, it will try to call an add() method.
+ # if you prefer other method names, just override these values in your
+ # controller with REST_map
+ REST_defaults = {'DELETE' : 'delete',
+ 'GET' : 'index',
+ 'POST' : 'update',
+ 'PUT' : 'add'}
+ REST_map = {}
+ # if the resource has children resources, list them here. format is
+ # a dictionary of name -> resource mappings. ie,
+ #
+ # REST_children = {'posts' : PostController()}
+
+ REST_children = {}
+
+ def REST_dispatch(self, resource, **params):
+ # if this gets called, we assume that default has already
+ # traversed down the tree to the right location and this is
+ # being called for a raw resource
+ method = cherrypy.request.method
+ if self.REST_map.has_key(method):
+ m = getattr(self,self.REST_map[method])
+ if m and getattr(m, "expose_resource"):
+ return m(resource,**params)
+ else:
+ if self.REST_defaults.has_key(method):
+ m = getattr(self,self.REST_defaults[method])
+ try:
+ if m and getattr(m, "expose_resource"):
+ return m(resource,**params)
+ except:
+ raise
+ raise Exception("can't find expose_resource on %r", m)
+
+ raise cherrypy.NotFound
+
+ @cherrypy.expose
+ def default(self, *vpath, **params):
+ if not vpath:
+ return self.list(**params)
+ # Make a copy of vpath in a list
+ vpath = list(vpath)
+ atom = vpath.pop(0)
+
+ # Coerce the ID to the correct db type
+ resource = self.REST_instantiate(atom)
+ if resource is None:
+ if cherrypy.request.method == "PUT":
+ # PUT is special since it can be used to create
+ # a resource
+ resource = self.REST_create(atom)
+ else:
+ raise cherrypy.NotFound
+
+ # There may be further virtual path components.
+ # Try to map them to methods in children or this class.
+ if vpath:
+ a = vpath.pop(0)
+ if self.REST_children.has_key(a):
+ c = self.REST_children[a]
+ c.parent = resource
+ return c.default(*vpath, **params)
+ method = getattr(self, a, None)
+ if method and getattr(method, "expose_resource"):
+ return method(resource, *vpath, **params)
+ else:
+ # path component was specified but doesn't
+ # map to anything exposed and callable
+ raise cherrypy.NotFound
+
+ # No further known vpath components. Call a default handler
+ # based on the method
+ return self.REST_dispatch(resource,**params)
+
+ def REST_instantiate(self,id):
+ """ instantiate a REST resource based on the id
+
+ this method MUST be overridden in your class. it will be passed
+ the id (from the url fragment) and should return a model object
+ corresponding to the resource.
+
+ if the object doesn't exist, it should return None rather than throwing
+ an error. if this method returns None and it is a PUT request,
+ REST_create() will be called so you can actually create the resource.
+ """
+ raise cherrypy.NotFound
+
+ def REST_create(self,id):
+ """ create a REST resource with the specified id
+
+ this method should be overridden in your class.
+ this method will be called when a PUT request is made for a resource
+ that doesn't already exist. you should create the resource in this method
+ and return it.
+ """
+ raise cherrypy.NotFound
diff --git a/beweb/beweb/templates/bugs.kid b/beweb/beweb/templates/bugs.kid
index c5014c8..b8b2ff7 100644
--- a/beweb/beweb/templates/bugs.kid
+++ b/beweb/beweb/templates/bugs.kid
@@ -1,6 +1,7 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<?python
from libbe.cmdutil import unique_name
+from beweb.controllers import bug_url, project_url, bug_list_url
def row_class(bug):
if bug.status == "closed":
return "closed"
@@ -19,12 +20,12 @@ def row_class(bug):
<h1>Bug list for ${project_name}</h1>
<table>
<tr><td>ID</td><td>Status</td><td>Severity</td><td>Assigned To</td><td>Summary</td></tr>
-<div py:for="bug in bugs" py:strip="True"><tr class="${row_class(bug)}" py:if="bug.status != 'closed' or show_closed"><td><a href="${'/%s/%s/' % (project_id, bug.uuid)}">${unique_name(bug, bugs[:])}</a></td><td>${bug.status}</td><td>${bug.severity}</td><td>${bug.assigned}</td><td>${bug.summary}</td></tr>
+<div py:for="bug in bugs" py:strip="True"><tr class="${row_class(bug)}" py:if="bug.status != 'closed' or show_closed"><td><a href="${bug_url(project_id, bug.uuid)}">${unique_name(bug, bugs[:])}</a></td><td>${bug.status}</td><td>${bug.severity}</td><td>${bug.assigned}</td><td>${bug.summary}</td></tr>
</div>
</table>
-<a href="/">Project list</a>
-<a href="${'/%s/?show_closed=%s' % (project_id, str(not show_closed))}">Toggle closed</a>
-<form action="/$project_id/new/" method="post">
+<a href="${project_url()}">Project list</a>
+<a href="${bug_list_url(project_id, not show_closed)}">Toggle closed</a>
+<form action="${bug_list_url(project_id)}" method="post">
<input type="submit" name="action" value="New bug"/>
</form>
</body>
diff --git a/beweb/beweb/templates/projects.kid b/beweb/beweb/templates/projects.kid
index 21b2777..09bde77 100644
--- a/beweb/beweb/templates/projects.kid
+++ b/beweb/beweb/templates/projects.kid
@@ -26,7 +26,7 @@ project_triples.sort()
<body>
<h1>Project List</h1>
<table>
-<tr py:for="project_name, project_id, project_loc in project_triples"><td><a href="/${project_id}/">${project_name}</a></td></tr>
+<tr py:for="project_name, project_id, project_loc in project_triples"><td><a href="/project/${project_id}/">${project_name}</a></td></tr>
</table>
</body>
</html>