diff roundup_cgi.py @ 0:5e92642cd1f8

Initial import of code Currently version 1.0.2 but with the 1.0.3 changes as given in the CHANGES file. Is about ready for a 1.0.3 release. [[This repository is actually a lift into git made in October 2011 of code history originally kept in CVS and later in Subversion. This import marks the point at which the original private CVS moved to public CVS on SourceForge. Due to technical problems with CVS and the CVS to Subversion conversion tools, portions of the early history have somewhat garbled metadata. Here and elsewhere in this repo, comments enclosed in curly braces were added at the time of the git lift in an attempt to document the problem.]]
author Richard Jones <richard@users.sourceforge.net>
date Thu, 19 Jul 2001 02:16:19 +0000
parents
children 97559f7bae2e
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/roundup_cgi.py	Thu Jul 19 02:16:19 2001 +0000
@@ -0,0 +1,479 @@
+import os, cgi, pprint, StringIO, urlparse, re, traceback
+
+import config, roundupdb, template, date
+
+class Unauthorised(ValueError):
+    pass
+
+class Client:
+    def __init__(self, out, env, user):
+        self.out = out
+        self.headers_done = 0
+        self.env = env
+        self.path = env.get("PATH_INFO", '').strip()
+        self.user = user
+        self.form = cgi.FieldStorage(environ=env)
+        self.split_path = self.path.split('/')[1:]
+        self.db = roundupdb.openDB(config.DATABASE, self.user)
+        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('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?: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(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']
+        return self.list(columns=columns, filter=filter, group=group, sort=sort)
+
+    # XXX deviates from spec - loses the '+' (that's a reserved character
+    # in URLS
+    def list(self, sort=None, group=None, filter=None, columns=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')
+
+        # 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
+
+        template.index(self, 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, 
+                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
+                    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.append('\n%s\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.append('\n%s\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
+        template.item(self, 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:
+                        if len(changed) > 1:
+                            plural = 's were'
+                        else:
+                            plural = ' was'
+                        summary = 'This %s has been created through the web.'%cn
+                        m.append('\n%s\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 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)
+        template.newitem(self, 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()
+

Roundup Issue Tracker: http://roundup-tracker.org/