view roundup/cgi_client.py @ 54:b68bcb176d1a

disabled the reloading until it can be done properly
author Richard Jones <richard@users.sourceforge.net>
date Mon, 23 Jul 2001 10:31:45 +0000
parents 6eebdf2bf262
children 381016730332
line wrap: on
line source

# $Id: cgi_client.py,v 1.3 2001-07-23 03:56:30 richard Exp $

import os, cgi, pprint, StringIO, urlparse, re, traceback

import 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.2  2001/07/22 12:09:32  richard
# Final commit of Grande Splite
#
# Revision 1.1  2001/07/22 11:58:35  richard
# More Grande Splite
#

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