Mercurial > p > roundup > code
diff roundup/cgi_client.py @ 25:4cf1daf2f2eb
More Grande Splite
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Sun, 22 Jul 2001 12:01:27 +0000 |
| parents | |
| children | c7c14960f413 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roundup/cgi_client.py Sun Jul 22 12:01:27 2001 +0000 @@ -0,0 +1,513 @@ +# $Id: cgi_client.py,v 1.1 2001-07-22 11:58:35 richard Exp $ + +import os, cgi, pprint, StringIO, urlparse, re, traceback + +import config, roundupdb, htmltemplate, date + +class Unauthorised(ValueError): + pass + +class Client: + def __init__(self, out, db, env, user): + self.out = out + self.db = db + self.env = env + self.user = user + self.path = env['PATH_INFO'] + self.split_path = self.path.split('/') + + self.headers_done = 0 + self.form = cgi.FieldStorage(environ=env) + self.headers_done = 0 + self.debug = 0 + + def header(self, headers={'Content-Type':'text/html'}): + if not headers.has_key('Content-Type'): + headers['Content-Type'] = 'text/html' + for entry in headers.items(): + self.out.write('%s: %s\n'%entry) + self.out.write('\n') + self.headers_done = 1 + + def pagehead(self, title, message=None): + url = self.env['SCRIPT_NAME'] + '/' #self.env.get('PATH_INFO', '/') + machine = self.env['SERVER_NAME'] + port = self.env['SERVER_PORT'] + if port != '80': machine = machine + ':' + port + base = urlparse.urlunparse(('http', machine, url, None, None, None)) + if message is not None: + message = '<div class="system-msg">%s</div>'%message + else: + message = '' + style = open(os.path.join(self.TEMPLATES, 'style.css')).read() + userid = self.db.user.lookup(self.user) + if self.user == 'admin': + extras = ' | <a href="list_classes">Class List</a>' + else: + extras = '' + self.write('''<html><head> +<title>%s</title> +<style type="text/css">%s</style> +</head> +<body bgcolor=#ffffff> +%s +<table width=100%% border=0 cellspacing=0 cellpadding=2> +<tr class="location-bar"><td><big><strong>%s</strong></big></td> +<td align=right valign=bottom>%s</td></tr> +<tr class="location-bar"> +<td align=left><a href="issue?status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=activity,status,title&:group=priority">All issues</a> | +<a href="issue?priority=fatal-bug,bug">Bugs</a> | +<a href="issue?priority=usability">Support</a> | +<a href="issue?priority=feature">Wishlist</a> | +<a href="newissue">New Issue</a> +%s</td> +<td align=right><a href="user%s">Your Details</a></td> +</table> +'''%(title, style, message, title, self.user, extras, userid)) + + def pagefoot(self): + if self.debug: + self.write('<hr><small><dl>') + self.write('<dt><b>Path</b></dt>') + self.write('<dd>%s</dd>'%(', '.join(map(repr, self.split_path)))) + keys = self.form.keys() + keys.sort() + if keys: + self.write('<dt><b>Form entries</b></dt>') + for k in self.form.keys(): + v = str(self.form[k].value) + self.write('<dd><em>%s</em>:%s</dd>'%(k, cgi.escape(v))) + keys = self.env.keys() + keys.sort() + self.write('<dt><b>CGI environment</b></dt>') + for k in keys: + v = self.env[k] + self.write('<dd><em>%s</em>:%s</dd>'%(k, cgi.escape(v))) + self.write('</dl></small>') + self.write('</body></html>') + + def write(self, content): + if not self.headers_done: + self.header() + self.out.write(content) + + def index_arg(self, arg): + ''' handle the args to index - they might be a list from the form + (ie. submitted from a form) or they might be a command-separated + single string (ie. manually constructed GET args) + ''' + if self.form.has_key(arg): + arg = self.form[arg] + if type(arg) == type([]): + return [arg.value for arg in arg] + return arg.value.split(',') + return [] + + def index_filterspec(self): + ''' pull the index filter spec from the form + ''' + # all the other form args are filters + filterspec = {} + for key in self.form.keys(): + if key[0] == ':': continue + value = self.form[key] + if type(value) == type([]): + value = [arg.value for arg in value] + else: + value = value.value.split(',') + l = filterspec.get(key, []) + l = l + value + filterspec[key] = l + return filterspec + + def index(self): + ''' put up an index + ''' + self.classname = 'issue' + if self.form.has_key(':sort'): sort = self.index_arg(':sort') + else: sort=['-activity'] + if self.form.has_key(':group'): group = self.index_arg(':group') + else: group=['priority'] + if self.form.has_key(':filter'): filter = self.index_arg(':filter') + else: filter = [] + if self.form.has_key(':columns'): columns = self.index_arg(':columns') + else: columns=['activity','status','title'] + filterspec = self.index_filterspec() + if not filterspec: + filterspec['status'] = ['1', '2', '3', '4', '5', '6', '7'] + return self.list(columns=columns, filter=filter, group=group, + sort=sort, filterspec=filterspec) + + # XXX deviates from spec - loses the '+' (that's a reserved character + # in URLS + def list(self, sort=None, group=None, filter=None, columns=None, + filterspec=None): + ''' call the template index with the args + + :sort - sort by prop name, optionally preceeded with '-' + to give descending or nothing for ascending sorting. + :group - group by prop name, optionally preceeded with '-' or + to sort in descending or nothing for ascending order. + :filter - selects which props should be displayed in the filter + section. Default is all. + :columns - selects the columns that should be displayed. + Default is all. + + ''' + cn = self.classname + self.pagehead('Index: %s'%cn) + if sort is None: sort = self.index_arg(':sort') + if group is None: group = self.index_arg(':group') + if filter is None: filter = self.index_arg(':filter') + if columns is None: columns = self.index_arg(':columns') + if filterspec is None: filterspec = self.index_filterspec() + + htmltemplate.index(self, self.TEMPLATES, self.db, cn, filterspec, + filter, columns, sort, group) + self.pagefoot() + + def showitem(self, message=None): + ''' display an item + ''' + cn = self.classname + cl = self.db.classes[cn] + + # possibly perform an edit + keys = self.form.keys() + num_re = re.compile('^\d+$') + if keys: + changed = [] + props = {} + try: + keys = self.form.keys() + for key in keys: + if not cl.properties.has_key(key): + continue + proptype = cl.properties[key] + if proptype.isStringType: + value = str(self.form[key].value).strip() + elif proptype.isDateType: + value = date.Date(str(self.form[key].value)) + elif proptype.isIntervalType: + value = date.Interval(str(self.form[key].value)) + elif proptype.isLinkType: + value = str(self.form[key].value).strip() + # handle key values + link = cl.properties[key].classname + if not num_re.match(value): + try: + value = self.db.classes[link].lookup(value) + except: + raise ValueError, 'property "%s": %s not a %s'%( + key, value, link) + elif proptype.isMultilinkType: + value = self.form[key] + if type(value) != type([]): + value = [i.strip() for i in str(value.value).split(',')] + else: + value = [str(i.value).strip() for i in value] + link = cl.properties[key].classname + l = [] + for entry in map(str, value): + if not num_re.match(entry): + try: + entry = self.db.classes[link].lookup(entry) + except: + raise ValueError, \ + 'property "%s": %s not a %s'%(key, + entry, link) + l.append(entry) + l.sort() + value = l + # if changed, set it + if value != cl.get(self.nodeid, key): + changed.append(key) + props[key] = value + cl.set(self.nodeid, **props) + + # if this item has messages, generate an edit message + # TODO: don't send the edit message to the person who + # performed the edit + if (cl.getprops().has_key('messages') and + cl.getprops()['messages'].isMultilinkType and + cl.getprops()['messages'].classname == 'msg'): + nid = self.nodeid + m = [] + for name, prop in cl.getprops().items(): + value = cl.get(nid, name) + if prop.isLinkType: + link = self.db.classes[prop.classname] + key = link.getkey() + if value is not None and key: + value = link.get(value, key) + else: + value = '-' + elif prop.isMultilinkType: + l = [] + link = self.db.classes[prop.classname] + for entry in value: + key = link.getkey() + if key: + l.append(link.get(entry, link.getkey())) + else: + l.append(entry) + value = ', '.join(l) + if name in changed: + chg = '*' + else: + chg = ' ' + m.append('%s %s: %s'%(chg, name, value)) + + # handle the note + if self.form.has_key('__note'): + note = self.form['__note'].value + if '\n' in note: + summary = re.split(r'\n\r?', note)[0] + else: + summary = note + m.insert(0, '%s\n\n'%note) + else: + if len(changed) > 1: + plural = 's were' + else: + plural = ' was' + summary = 'This %s has been edited through the web '\ + 'and the %s value%s changed.'%(cn, + ', '.join(changed), plural) + m.insert(0, '%s\n\n'%summary) + + # now create the message + content = '\n'.join(m) + message_id = self.db.msg.create(author=1, recipients=[], + date=date.Date('.'), summary=summary, content=content) + messages = cl.get(nid, 'messages') + messages.append(message_id) + props = {'messages': messages} + cl.set(nid, **props) + + # and some nice feedback for the user + message = '%s edited ok'%', '.join(changed) + except: + s = StringIO.StringIO() + traceback.print_exc(None, s) + message = '<pre>%s</pre>'%cgi.escape(s.getvalue()) + + # now the display + id = self.nodeid + if cl.getkey(): + id = cl.get(id, cl.getkey()) + self.pagehead('%s: %s'%(self.classname.capitalize(), id), message) + + nodeid = self.nodeid + + # use the template to display the item + htmltemplate.item(self, self.TEMPLATES, self.db, self.classname, nodeid) + self.pagefoot() + showissue = showitem + showmsg = showitem + + def newissue(self, message=None): + ''' add an issue + ''' + cn = self.classname + cl = self.db.classes[cn] + + # possibly perform a create + keys = self.form.keys() + num_re = re.compile('^\d+$') + if keys: + props = {} + try: + keys = self.form.keys() + for key in keys: + if not cl.properties.has_key(key): + continue + proptype = cl.properties[key] + if proptype.isStringType: + value = str(self.form[key].value).strip() + elif proptype.isDateType: + value = date.Date(str(self.form[key].value)) + elif proptype.isIntervalType: + value = date.Interval(str(self.form[key].value)) + elif proptype.isLinkType: + value = str(self.form[key].value).strip() + # handle key values + link = cl.properties[key].classname + if not num_re.match(value): + try: + value = self.db.classes[link].lookup(value) + except: + raise ValueError, 'property "%s": %s not a %s'%( + key, value, link) + elif proptype.isMultilinkType: + value = self.form[key] + if type(value) != type([]): + value = [i.strip() for i in str(value.value).split(',')] + else: + value = [str(i.value).strip() for i in value] + link = cl.properties[key].classname + l = [] + for entry in map(str, value): + if not num_re.match(entry): + try: + entry = self.db.classes[link].lookup(entry) + except: + raise ValueError, \ + 'property "%s": %s not a %s'%(key, + entry, link) + l.append(entry) + l.sort() + value = l + props[key] = value + nid = cl.create(**props) + + # if this item has messages, + if (cl.getprops().has_key('messages') and + cl.getprops()['messages'].isMultilinkType and + cl.getprops()['messages'].classname == 'msg'): + # generate an edit message - nosyreactor will send it + m = [] + for name, prop in cl.getprops().items(): + value = cl.get(nid, name) + if prop.isLinkType: + link = self.db.classes[prop.classname] + key = link.getkey() + if value is not None and key: + value = link.get(value, key) + else: + value = '-' + elif prop.isMultilinkType: + l = [] + link = self.db.classes[prop.classname] + for entry in value: + key = link.getkey() + if key: + l.append(link.get(entry, link.getkey())) + else: + l.append(entry) + value = ', '.join(l) + m.append('%s: %s'%(name, value)) + + # handle the note + if self.form.has_key('__note'): + note = self.form['__note'].value + if '\n' in note: + summary = re.split(r'\n\r?', note)[0] + else: + summary = note + m.append('\n%s\n'%note) + else: + m.append('\nThis %s has been created through ' + 'the web.\n'%cn) + + # now create the message + content = '\n'.join(m) + message_id = self.db.msg.create(author=1, recipients=[], + date=date.Date('.'), summary=summary, content=content) + messages = cl.get(nid, 'messages') + messages.append(message_id) + props = {'messages': messages} + cl.set(nid, **props) + + # and some nice feedback for the user + message = '%s created ok'%cn + except: + s = StringIO.StringIO() + traceback.print_exc(None, s) + message = '<pre>%s</pre>'%cgi.escape(s.getvalue()) + self.pagehead('New %s'%self.classname.capitalize(), message) + htmltemplate.newitem(self, self.TEMPLATES, self.db, self.classname, + self.form) + self.pagefoot() + + def showuser(self, message=None): + ''' display an item + ''' + if self.user in ('admin', self.db.user.get(self.nodeid, 'username')): + self.showitem(message) + else: + raise Unauthorised + + def showfile(self): + ''' display a file + ''' + nodeid = self.nodeid + cl = self.db.file + type = cl.get(nodeid, 'type') + if type == 'message/rfc822': + type = 'text/plain' + self.header(headers={'Content-Type': type}) + self.write(cl.get(nodeid, 'content')) + + def classes(self, message=None): + ''' display a list of all the classes in the database + ''' + if self.user == 'admin': + self.pagehead('Table of classes', message) + classnames = self.db.classes.keys() + classnames.sort() + self.write('<table border=0 cellspacing=0 cellpadding=2>\n') + for cn in classnames: + cl = self.db.getclass(cn) + self.write('<tr class="list-header"><th colspan=2 align=left>%s</th></tr>'%cn.capitalize()) + for key, value in cl.properties.items(): + if value is None: value = '' + else: value = str(value) + self.write('<tr><th align=left>%s</th><td>%s</td></tr>'%( + key, cgi.escape(value))) + self.write('</table>') + self.pagefoot() + else: + raise Unauthorised + + def main(self, dre=re.compile(r'([^\d]+)(\d+)'), nre=re.compile(r'new(\w+)')): + path = self.split_path + if not path or path[0] in ('', 'index'): + self.index() + elif len(path) == 1: + if path[0] == 'list_classes': + self.classes() + return + m = dre.match(path[0]) + if m: + self.classname = m.group(1) + self.nodeid = m.group(2) + getattr(self, 'show%s'%self.classname)() + return + m = nre.match(path[0]) + if m: + self.classname = m.group(1) + getattr(self, 'new%s'%self.classname)() + return + self.classname = path[0] + self.list() + else: + raise 'ValueError', 'Path not understood' + + def __del__(self): + self.db.close() + +# +# $Log: not supported by cvs2svn $ +# Revision 1.7 2001/07/20 07:35:55 richard +# largish changes as a start of splitting off bits and pieces to allow more +# flexible installation / database back-ends +# +# Revision 1.6 2001/07/20 00:53:20 richard +# Default index now filters out the resolved issues ;) +# +# Revision 1.5 2001/07/20 00:17:16 richard +# Fixed adding a new issue when there is no __note +# +# Revision 1.4 2001/07/19 06:27:07 anthonybaxter +# fixing (manually) the (dollarsign)Log(dollarsign) entries caused by +# my using the magic (dollarsign)Id(dollarsign) and (dollarsign)Log(dollarsign) +# strings in a commit message. I'm a twonk. +# +# Also broke the help string in two. +# +# Revision 1.3 2001/07/19 05:52:22 anthonybaxter +# Added CVS keywords Id and Log to all python files. +# +# +
