From fb1c4f5da7d5606485d684528a2eaf74331d8a15 Mon Sep 17 00:00:00 2001 From: richard Date: Mon, 30 Jul 2001 08:12:17 +0000 Subject: [PATCH] Added time logging and file uploading to the templates. git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@173 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 12 + roundup-admin | 24 +- roundup/cgi_client.py | 385 +++++++++----------- roundup/htmltemplate.py | 13 +- roundup/templatebuilder.py | 7 +- roundup/templates/classic/html/file.newitem | 18 + roundup/templates/classic/html/issue.item | 15 +- roundup/templates/classic/htmlbase.py | 49 ++- roundup/templates/extended/htmlbase.py | 139 ++++++- roundup/templates/extended/interfaces.py | 14 +- 10 files changed, 422 insertions(+), 254 deletions(-) create mode 100644 roundup/templates/classic/html/file.newitem diff --git a/CHANGES.txt b/CHANGES.txt index 36b5ad8..3fd36f7 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,6 +1,18 @@ This file contains the changes to the Roundup system over time. The entries are given with the most recent entry first. +2001-08-?? - 0.2.4 +Features: + . Added ability for cgi newblah forms to indicate that the new node + should be linked somewhere. + . Added time logging and file uploading to the templates. + +Fixed: + . Fixed the agument handling for the roundup-admin find command. + . Fixed handling of summary when no note supplied for newblah. Again. + . Fixed detection of no form in htmltemplate Field display. + + 2001-07-30 - 0.2.3 Big change: . I've split off the support class from the issue class in "extended". diff --git a/roundup-admin b/roundup-admin index 1ec3994..9a39bc6 100755 --- a/roundup-admin +++ b/roundup-admin @@ -1,12 +1,12 @@ #! /usr/bin/python -# $Id: roundup-admin,v 1.9 2001-07-30 03:52:55 richard Exp $ +# $Id: roundup-admin,v 1.10 2001-07-30 08:12:17 richard Exp $ import sys if int(sys.version[0]) < 2: print 'Roundup requires python 2.0 or later.' sys.exit(1) -import string, os, getpass, getopt +import string, os, getpass, getopt, re from roundup import date, roundupdb, init def usage(message=''): @@ -173,19 +173,25 @@ def do_find(db, args): '''Usage: find classname propname=value ... Find the nodes of the given class with a given property value. - Find the nodes of the given class with a given property value. + Find the nodes of the given class with a given property value. The + value may be either the nodeid of the linked node, or its key value. ''' classname = args[0] cl = db.getclass(classname) # look up the linked-to class and get the nodeid that has the value - propname, value = args[1:].split('=') - propcl = cl[propname].classname - nodeid = propcl.lookup(value) + propname, value = args[1].split('=') + num_re = re.compile('^\d+$') + if num_re.match(value): + nodeid = value + else: + propcl = cl.properties[propname].classname + propcl = db.getclass(propcl) + nodeid = propcl.lookup(value) # now do the find # TODO: handle the -c option - print cl.find(propname, nodeid) + print cl.find(**{propname: nodeid}) return 0 def do_spec(db, args): @@ -236,7 +242,6 @@ def do_list(db, args): in order: the key, "name", "title" and then the first property, alphabetically. ''' - db = instance.open() classname = args[0] cl = db.getclass(classname) if len(args) > 1: @@ -404,6 +409,9 @@ if __name__ == '__main__': # # $Log: not supported by cvs2svn $ +# Revision 1.9 2001/07/30 03:52:55 richard +# init help now lists templates and backends +# # Revision 1.8 2001/07/30 02:37:07 richard # Freshen is really broken. Commented out. # diff --git a/roundup/cgi_client.py b/roundup/cgi_client.py index ff515de..3e2361e 100644 --- a/roundup/cgi_client.py +++ b/roundup/cgi_client.py @@ -1,6 +1,6 @@ -# $Id: cgi_client.py,v 1.12 2001-07-30 06:26:31 richard Exp $ +# $Id: cgi_client.py,v 1.13 2001-07-30 08:12:17 richard Exp $ -import os, cgi, pprint, StringIO, urlparse, re, traceback +import os, cgi, pprint, StringIO, urlparse, re, traceback, mimetypes import roundupdb, htmltemplate, date @@ -226,71 +226,7 @@ class Client: 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(): - # TODO: the None default is only here because we - # don't have schema migration :( - if prop.isMultilinkType: - value = cl.get(nid, name, []) - else: - value = cl.get(nid, name, None) - 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) - + self._post_editnode(self.nodeid) # and some nice feedback for the user message = '%s edited ok'%', '.join(changed) except: @@ -312,6 +248,153 @@ class Client: showissue = shownode showmsg = shownode + def showuser(self, message=None): + ''' display an item + ''' + if self.user in ('admin', self.db.user.get(self.nodeid, 'username')): + self.shownode(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 _createnode(self): + ''' create a node based on the contents of the form + ''' + cn = self.classname + cl = self.db.classes[cn] + props = {} + keys = self.form.keys() + num_re = re.compile('^\d+$') + for key in keys: + if not cl.properties.has_key(key): + continue + proptype = cl.properties[key] + if proptype.isStringType: + value = self.form[key].value.strip() + elif proptype.isDateType: + value = date.Date(self.form[key].value.strip()) + elif proptype.isIntervalType: + value = date.Interval(self.form[key].value.strip()) + elif proptype.isLinkType: + value = 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 value.value.split(',')] + else: + value = [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 + return cl.create(**props) + + def _post_editnode(self, nid): + ''' do the linking and message sending part of the node creation + ''' + cn = self.classname + cl = self.db.classes[cn] + # link if necessary + keys = self.form.keys() + for key in keys: + if key == ':multilink': + value = self.form[key].value + if type(value) != type([]): value = [value] + for value in value: + designator, property = value.split(':') + link, nodeid = roundupdb.splitDesignator(designator) + link = self.db.classes[link] + value = link.get(nodeid, property) + value.append(nid) + link.set(nodeid, **{property: value}) + elif key == ':link': + value = self.form[key].value + if type(value) != type([]): value = [value] + for value in value: + designator, property = value.split(':') + link, nodeid = roundupdb.splitDesignator(designator) + link = self.db.classes[link] + link.set(nodeid, **{property: nid}) + + # 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 + note = None + if self.form.has_key('__note'): + note = self.form['__note'] + if note is not None and note.value: + note = note.value + if '\n' in note: + summary = re.split(r'\n\r?', note)[0] + else: + summary = note + m.append('\n%s\n'%note) + else: + summary = 'This %s has been created through the web.'%cn + m.append('\n%s\s'%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) + def newnode(self, message=None): ''' Add a new node to the database. @@ -339,130 +422,11 @@ class Client: # possibly perform a create keys = self.form.keys() - num_re = re.compile('^\d+$') if [i for i in keys if i[0] != ':']: 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 = self.form[key].value.strip() - elif proptype.isDateType: - value = date.Date(self.form[key].value.strip()) - elif proptype.isIntervalType: - value = date.Interval(self.form[key].value.strip()) - elif proptype.isLinkType: - value = 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 value.value.split(',')] - else: - value = [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) - - # link if necessary - for key in keys: - print key, - if key == ':multilink': - value = self.form[key].value - if type(value) != type([]): value = [value] - for value in value: - designator, property = value.split(':') - print 'miltilinking to ', designator, property - link, nodeid = roundupdb.splitDesignator(designator) - link = self.db.classes[link] - value = link.get(nodeid, property) - value.append(nid) - link.set(nodeid, **{property: value}) - elif key == ':link': - value = self.form[key].value - if type(value) != type([]): value = [value] - for value in value: - designator, property = value.split(':') - print 'linking to ', designator, property - link, nodeid = roundupdb.splitDesignator(designator) - link = self.db.classes[link] - link.set(nodeid, **{property: nid}) - else: - print 'ignoring' - - # 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 - note = None - if self.form.has_key('__note'): - note = self.form['__note'] - if note and note.value: - note = note.value - if '\n' in note: - summary = re.split(r'\n\r?', note)[0] - else: - summary = note - m.append('\n%s\n'%note) - else: - summary = 'This %s has been created through the web.'%cn - m.append('\n%s\s'%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) - + nid = self._createnode() + self._post_editnode(nid) # and some nice feedback for the user message = '%s created ok'%cn except: @@ -476,24 +440,34 @@ class Client: newissue = newnode newuser = newnode - def showuser(self, message=None): - ''' display an item + def newfile(self, message=None): + ''' Add a new file to the database. + + This form works very much the same way as newnode - it just has a + file upload. ''' - if self.user in ('admin', self.db.user.get(self.nodeid, 'username')): - self.shownode(message) - else: - raise Unauthorised + cn = self.classname + cl = self.db.classes[cn] - 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')) + # possibly perform a create + keys = self.form.keys() + if [i for i in keys if i[0] != ':']: + try: + file = self.form['content'] + self._post_editnode(cl.create(content=file.file.read(), + type=mimetypes.guess_type(file.filename)[0], + name=file.filename)) + # and some nice feedback for the user + message = '%s created ok'%cn + except: + s = StringIO.StringIO() + traceback.print_exc(None, s) + message = '
%s
'%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 classes(self, message=None): ''' display a list of all the classes in the database @@ -545,6 +519,9 @@ class Client: # # $Log: not supported by cvs2svn $ +# Revision 1.12 2001/07/30 06:26:31 richard +# Added some documentation on how the newblah works. +# # Revision 1.11 2001/07/30 06:17:45 richard # Features: # . Added ability for cgi newblah forms to indicate that the new node diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py index 1820431..1ce86ca 100644 --- a/roundup/htmltemplate.py +++ b/roundup/htmltemplate.py @@ -1,4 +1,4 @@ -# $Id: htmltemplate.py,v 1.14 2001-07-30 06:17:45 richard Exp $ +# $Id: htmltemplate.py,v 1.15 2001-07-30 08:12:17 richard Exp $ import os, re, StringIO, urllib, cgi, errno @@ -710,7 +710,7 @@ def newitem(client, templates, db, classname, form, replace=re.compile( s = open(os.path.join(templates, classname+'.newitem')).read() except: s = open(os.path.join(templates, classname+'.item')).read() - w('
'%classname) + w(''%classname) for key in form.keys(): if key[0] == ':': value = form[key].value @@ -723,6 +723,15 @@ def newitem(client, templates, db, classname, form, replace=re.compile( # # $Log: not supported by cvs2svn $ +# Revision 1.14 2001/07/30 06:17:45 richard +# Features: +# . Added ability for cgi newblah forms to indicate that the new node +# should be linked somewhere. +# Fixed: +# . Fixed the agument handling for the roundup-admin find command. +# . Fixed handling of summary when no note supplied for newblah. Again. +# . Fixed detection of no form in htmltemplate Field display. +# # Revision 1.13 2001/07/30 02:37:53 richard # Temporary measure until we have decent schema migration. # diff --git a/roundup/templatebuilder.py b/roundup/templatebuilder.py index 90c8f49..bd47d0c 100644 --- a/roundup/templatebuilder.py +++ b/roundup/templatebuilder.py @@ -1,4 +1,4 @@ -# $Id: templatebuilder.py,v 1.7 2001-07-30 00:06:52 richard Exp $ +# $Id: templatebuilder.py,v 1.8 2001-07-30 08:12:17 richard Exp $ import errno preamble = """ @@ -18,6 +18,8 @@ def makeHtmlBase(templateDir): fd = open(os.path.join(templateDir, 'htmlbase.py'), 'w') fd.write(preamble) for file in filelist: + # skip the backup files created by richard's vim + if file[-1] == '~': continue mangled_name = os.path.basename(re.sub(r'\.', 'DOT', file)) fd.write('%s = """'%mangled_name) fd.write(open(file).read()) @@ -65,6 +67,9 @@ if __name__ == "__main__": # # $Log: not supported by cvs2svn $ +# Revision 1.7 2001/07/30 00:06:52 richard +# Hrm - had IOError instead of OSError. Not sure why there's two. Ho hum. +# # Revision 1.6 2001/07/29 07:01:39 richard # Added vim command to all source so that we don't get no steenkin' tabs :) # diff --git a/roundup/templates/classic/html/file.newitem b/roundup/templates/classic/html/file.newitem new file mode 100644 index 0000000..be795cf --- /dev/null +++ b/roundup/templates/classic/html/file.newitem @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +
File upload details
File:
 
diff --git a/roundup/templates/classic/html/issue.item b/roundup/templates/classic/html/issue.item index c1f1ea9..22dc0ec 100644 --- a/roundup/templates/classic/html/issue.item +++ b/roundup/templates/classic/html/issue.item @@ -1,4 +1,4 @@ - + @@ -42,19 +42,24 @@ - + + + + + + + - - - diff --git a/roundup/templates/classic/htmlbase.py b/roundup/templates/classic/htmlbase.py index 99f32f8..cccb573 100644 --- a/roundup/templates/classic/htmlbase.py +++ b/roundup/templates/classic/htmlbase.py @@ -2,7 +2,7 @@ # Do Not Edit (Unless You Want To) # This file automagically generated by roundup.htmldata.makeHtmlBase # -fileDOTindex = """ +fileDOTindex = """ @@ -13,7 +13,27 @@ fileDOTindex = """ +fileDOTnewitem = """ +
Messages
Files
+ :files">Attach a file to this issue +
Files
+ + + + + + + + + + + + + + + +
File upload details
File:
 
+""" + +issueDOTfilter = """ Title @@ -28,7 +48,7 @@ issueDOTfilter = """ +issueDOTindex = """ "> @@ -51,7 +71,7 @@ issueDOTindex = """ +issueDOTitem = """ @@ -95,19 +115,24 @@ issueDOTitem = """ +msgDOTindex = """ @@ -131,7 +156,7 @@ msgDOTindex = """ +msgDOTitem = """
@@ -378,7 +403,7 @@ th { } """ -userDOTindex = """ +userDOTindex = """ @@ -398,7 +423,7 @@ userDOTindex = """ +userDOTitem = """
diff --git a/roundup/templates/extended/htmlbase.py b/roundup/templates/extended/htmlbase.py index 4072251..91a3952 100644 --- a/roundup/templates/extended/htmlbase.py +++ b/roundup/templates/extended/htmlbase.py @@ -2,7 +2,7 @@ # Do Not Edit (Unless You Want To) # This file automagically generated by roundup.htmldata.makeHtmlBase # -fileDOTindex = """ +fileDOTindex = """ @@ -13,7 +13,27 @@ fileDOTindex = """ +fileDOTnewitem = """ +
+ + + + + + + + + + + + + + + +
File upload details
File:
 
+""" + +issueDOTfilter = """ Title @@ -44,7 +64,7 @@ issueDOTfilter = """ +issueDOTindex = """ @@ -76,7 +96,7 @@ issueDOTindex = """ +issueDOTitem = """ @@ -135,19 +155,24 @@ issueDOTitem = """ +msgDOTindex = """ @@ -171,7 +196,7 @@ msgDOTindex = """ +msgDOTitem = """
@@ -375,7 +400,7 @@ th { } """ -supportDOTfilter = """ +supportDOTfilter = """ @@ -410,7 +435,7 @@ supportDOTfilter = """ +supportDOTindex = """ @@ -445,7 +470,7 @@ supportDOTindex = """ +supportDOTitem = """
Title
@@ -511,19 +536,38 @@ supportDOTitem = """ +timelogDOTindex = """ + + + + + + + + + + + + + + +""" + +timelogDOTitem = """ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Time log details
Time spent
Description
Date
Performed by
 
History
+ +""" + +userDOTindex = """ @@ -553,7 +654,7 @@ userDOTindex = """ +userDOTitem = """ diff --git a/roundup/templates/extended/interfaces.py b/roundup/templates/extended/interfaces.py index 0362497..071e4ab 100644 --- a/roundup/templates/extended/interfaces.py +++ b/roundup/templates/extended/interfaces.py @@ -1,4 +1,4 @@ -# $Id: interfaces.py,v 1.3 2001-07-30 01:26:59 richard Exp $ +# $Id: interfaces.py,v 1.4 2001-07-30 08:12:17 richard Exp $ import instance_config, urlparse, os from roundup import cgi_client, mailgw @@ -8,8 +8,10 @@ class Client(cgi_client.Client): with any specific extensions ''' TEMPLATES = instance_config.TEMPLATES - showsupport = cgi_client.Client.showitem - newsupport = cgi_client.Client.newissue + showsupport = cgi_client.Client.shownode + showtimelog = cgi_client.Client.shownode + newsupport = cgi_client.Client.newnode + newtimelog = cgi_client.Client.newnode default_index_sort = ['-activity'] default_index_group = ['priority'] @@ -65,6 +67,12 @@ class MailGW(mailgw.MailGW): # # $Log: not supported by cvs2svn $ +# Revision 1.3 2001/07/30 01:26:59 richard +# Big changes: +# . split off the support priority into its own class +# . added "new support, new user" to the page head +# . fixed the display options for the heading links +# # Revision 1.2 2001/07/29 07:01:39 richard # Added vim command to all source so that we don't get no steenkin' tabs :) # -- 2.30.2