From 64860b7d36f9fa7af75bf98aa1257263671a5a07 Mon Sep 17 00:00:00 2001 From: gmcm Date: Wed, 17 Jul 2002 12:39:11 +0000 Subject: [PATCH] Saving, running & editing queries. git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@884 57a73879-2fb5-44c3-a270-3262357dd7e2 --- roundup/backends/back_metakit.py | 26 ++-- roundup/cgi_client.py | 130 ++++++++++++++-- roundup/htmltemplate.py | 166 ++++++++++++++------- roundup/templates/classic/dbinit.py | 14 +- roundup/templates/classic/html/query.index | 13 ++ roundup/templates/classic/html/query.item | 25 ++++ roundup/templates/classic/html/user.item | 9 +- 7 files changed, 304 insertions(+), 79 deletions(-) create mode 100755 roundup/templates/classic/html/query.index create mode 100755 roundup/templates/classic/html/query.item diff --git a/roundup/backends/back_metakit.py b/roundup/backends/back_metakit.py index 19a380f..be58256 100755 --- a/roundup/backends/back_metakit.py +++ b/roundup/backends/back_metakit.py @@ -166,11 +166,11 @@ class _Database(hyperdb.Database): return self.__RW == 0 def getWriteAccess(self): if self.journaltag is not None and self.__RW == 0: - now = time.time - start = now() + #now = time.time + #start = now() self._db = None #print "closing the file took %2.2f secs" % (now()-start) - start = now() + #start = now() self._db = metakit.storage(self.dbnm, 1) self.__RW = 1 self.hist = self._db.view('history') @@ -504,15 +504,18 @@ class Class: # no, I'm not going to subclass the existing! # first setkey for this run self.keyname = propname iv = self.db._db.view('_%s' % self.classname) - if self.db.fastopen or iv.structure(): + if self.db.fastopen and iv.structure(): return # very first setkey ever + self.db.getWriteAccess() + self.db.dirty = 1 iv = self.db._db.getas('_%s[k:S,i:I]' % self.classname) iv = iv.ordered(1) #XXX # print "setkey building index" for row in self.getview(): iv.append(k=getattr(row, propname), i=row.id) + self.db.commit() def getkey(self): return self.keyname def lookup(self, keyvalue): @@ -601,7 +604,10 @@ class Class: # no, I'm not going to subclass the existing! if self.ruprops.has_key(key): raise ValueError, "%s is already a property of %s" % (key, self.classname) self.ruprops.update(properties) + self.db.getWriteAccess() + self.db.fastopen = 0 view = self.__getview() + self.db.commit() # ---- end of ping's spec def filter(self, search_matches, filterspec, sort, group): # search_matches is None or a set (dict of {nodeid: {propname:[nodeid,...]}}) @@ -821,10 +827,10 @@ class Class: # no, I'm not going to subclass the existing! def __getview(self): db = self.db._db view = db.view(self.classname) - if self.db.fastopen: + mkprops = view.structure() + if mkprops and self.db.fastopen: return view.ordered(1) # is the definition the same? - mkprops = view.structure() for nm, rutyp in self.ruprops.items(): for mkprop in mkprops: if mkprop.name == nm: @@ -832,15 +838,16 @@ class Class: # no, I'm not going to subclass the existing! else: mkprop = None if mkprop is None: - #print "%s missing prop %s (%s)" % (self.classname, nm, rutyp.__class__.__name__) + print "%s missing prop %s (%s)" % (self.classname, nm, rutyp.__class__.__name__) break if _typmap[rutyp.__class__] != mkprop.type: - #print "%s - prop %s (%s) has wrong mktyp (%s)" % (self.classname, nm, rutyp.__class__.__name__, mkprop.type) + print "%s - prop %s (%s) has wrong mktyp (%s)" % (self.classname, nm, rutyp.__class__.__name__, mkprop.type) break else: return view.ordered(1) # need to create or restructure the mk view # id comes first, so MK will order it for us + self.db.getWriteAccess() self.db.dirty = 1 s = ["%s[id:I" % self.classname] for nm, rutyp in self.ruprops.items(): @@ -849,7 +856,8 @@ class Class: # no, I'm not going to subclass the existing! if mktyp == 'V': s[-1] += ('[fid:I]') s.append('_isdel:I,activity:I,creation:I,creator:I]') - v = db.getas(','.join(s)) + v = self.db._db.getas(','.join(s)) + self.db.commit() return v.ordered(1) def getview(self, RW=0): if RW and self.db.isReadOnly(): diff --git a/roundup/cgi_client.py b/roundup/cgi_client.py index 3d769eb..2bc7922 100644 --- a/roundup/cgi_client.py +++ b/roundup/cgi_client.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: cgi_client.py,v 1.140 2002-07-14 23:17:15 richard Exp $ +# $Id: cgi_client.py,v 1.141 2002-07-17 12:39:10 gmcm Exp $ __doc__ = """ WWW request handler (also used in the stand-alone server). @@ -193,6 +193,29 @@ function help_window(helpurl, width, height) { _('Unassigned Issues') ] + if userid: + # add any personal queries to the menu + try: + queries = self.db.getclass('query') + except KeyError: + # no query class + queries = self.instance.dbinit.Class(self.db, + "query", + klass=hyperdb.String(), + name=hyperdb.String(), + url=hyperdb.String()) + queries.setkey('name') +#queries.disableJournalling() + try: + qids = self.db.getclass('user').get(userid, 'queries') + except KeyError, e: + #self.db.getclass('user').addprop(queries=hyperdb.Multilink('query')) + qids = [] + for qid in qids: + links.append('%s' % (queries.get(qid, 'klass'), + queries.get(qid, 'url'), + queries.get(qid, 'name'))) + # if they're logged in, include links to their information, and the # ability to add an issue if user_name not in ('', 'anonymous'): @@ -222,7 +245,7 @@ function help_window(helpurl, width, height) { admin_links = '' if user_name == 'admin': links.append(_('Class List')) - links.append(_('User List')) + links.append(_('User List')) links.append(_('Add User')) # add the search links @@ -251,11 +274,14 @@ function help_window(helpurl, width, height) { %(message)s - - - - - + + + + + + + +
%(title)s%(user_name)s
%(links)s%(user_info)s
%(title)s%(user_name)s
%(links)s%(user_info)s

''')%locals()) @@ -330,14 +356,17 @@ function help_window(helpurl, width, height) { x.append('%s%s' % (desc, colnm)) return x - def index_filterspec(self, filter): + def index_filterspec(self, filter, classname=None): ''' pull the index filter spec from the form Links and multilinks want to be lists - the rest are straight strings. ''' + if classname is None: + classname = self.classname + klass = self.db.getclass(classname) filterspec = {} - props = self.db.classes[self.classname].getprops() + props = klass.getprops() for colnm in filter: widget = ':%s_fs' % colnm try: @@ -486,6 +515,34 @@ function help_window(helpurl, width, height) { else: startwith = 0 + if self.form.has_key('Query') and self.form['Query'].value == 'Save': + # format a query string + qd = {} + qd[':sort'] = ','.join(map(urllib.quote, sort)) + qd[':group'] = ','.join(map(urllib.quote, group)) + qd[':filter'] = ','.join(map(urllib.quote, filter)) + qd[':columns'] = ','.join(map(urllib.quote, columns)) + for k, l in filterspec.items(): + qd[urllib.quote(k)] = ','.join(map(urllib.quote, l)) + url = '&'.join([k+'='+v for k,v in qd.items()]) + url += '&:pagesize=%s' % pagesize + if search_text: + url += '&search_text=%s' % search_text + + # create a query + d = {} + d['name'] = self.form[':name'].value + d['klass'] = self.form[':classname'].value + d['url'] = url + qid = self.db.getclass('query').create(**d) + + # and add it to the user's query multilink + uid = self.getuid() + usercl = self.db.getclass('user') + queries = usercl.get(uid, 'queries') + queries.append(qid) + usercl.set(uid, queries=queries) + index = htmltemplate.IndexTemplate(self, self.instance.TEMPLATES, cn) try: index.render(filterspec, search_text, filter, columns, sort, @@ -665,6 +722,56 @@ function help_window(helpurl, width, height) { showmsg = shownode searchissue = searchnode + def showquery(self): + queries = self.db.getclass(self.classname) + if self.form.keys(): + sort = self.index_sort() + group = self.index_arg(':group') + filter = self.index_arg(':filter') + columns = self.index_arg(':columns') + filterspec = self.index_filterspec(filter, queries.get(self.nodeid, 'klass')) + if self.form.has_key('search_text'): + search_text = self.form['search_text'].value + else: + search_text = '' + if self.form.has_key(':pagesize'): + pagesize = int(self.form[':pagesize'].value) + else: + pagesize = 50 + # format a query string + qd = {} + qd[':sort'] = ','.join(map(urllib.quote, sort)) + qd[':group'] = ','.join(map(urllib.quote, group)) + qd[':filter'] = ','.join(map(urllib.quote, filter)) + qd[':columns'] = ','.join(map(urllib.quote, columns)) + for k, l in filterspec.items(): + qd[urllib.quote(k)] = ','.join(map(urllib.quote, l)) + url = '&'.join([k+'='+v for k,v in qd.items()]) + url += '&:pagesize=%s' % pagesize + if search_text: + url += '&search_text=%s' % search_text + qname = self.form['name'].value + chgd = [] + if qname != queries.get(self.nodeid, 'name'): + chgd.append('name') + if url != queries.get(self.nodeid, 'url'): + chgd.append('url') + if chgd: + queries.set(self.nodeid, name=qname, url=url) + message = _('%(changes)s edited ok')%{'changes': ', '.join(chgd)} + else: + message = _('nothing changed') + else: + message = None + nm = queries.get(self.nodeid, 'name') + self.pagehead('%s: %s'%(self.classname.capitalize(), nm), message) + + # use the template to display the item + item = htmltemplate.ItemTemplate(self, self.instance.TEMPLATES, + self.classname) + item.render(self.nodeid) + self.pagefoot() + def _changenode(self, props): ''' change the node based on the contents of the form ''' @@ -954,7 +1061,7 @@ function help_window(helpurl, width, height) { if self.user not in ('admin', node_user): raise Unauthorised - + # # perform any editing # @@ -1497,6 +1604,9 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')): # # $Log: not supported by cvs2svn $ +# Revision 1.140 2002/07/14 23:17:15 richard +# cleaned up structure +# # Revision 1.139 2002/07/14 06:14:40 richard # Some more TODOs # diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py index b6bbb48..f7648c1 100644 --- a/roundup/htmltemplate.py +++ b/roundup/htmltemplate.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: htmltemplate.py,v 1.98 2002-07-10 00:17:46 richard Exp $ +# $Id: htmltemplate.py,v 1.99 2002-07-17 12:39:10 gmcm Exp $ __doc__ = """ Template engine. @@ -182,7 +182,6 @@ class TemplateFunctions: ''' if not self.nodeid and self.form is None and self.filterspec is None: return _('[Field: not called from item]') - if size is None: size = 30 @@ -190,7 +189,6 @@ class TemplateFunctions: # get the value value = self.determine_value(property) - # now display if (isinstance(propclass, hyperdb.String) or isinstance(propclass, hyperdb.Date) or @@ -796,6 +794,32 @@ class TemplateFunctions: value = cgi.escape(value) return value + def do_filterspec(self, classprop, urlprop): + cl = self.db.getclass(self.classname) + qs = cl.get(self.nodeid, urlprop) + classname = cl.get(self.nodeid, classprop) + all_columns = self.db.getclass(classname).getprops().keys() + filterspec = {} + query = cgi.parse_qs(qs) + for k,v in query.items(): + query[k] = v[0].split(',') + pagesize = query.get(':pagesize',['25'])[0] + for k,v in query.items(): + if k[0] != ':': + filterspec[k] = v + ixtmplt = IndexTemplate(self.client, self.templates, classname) + qform = '
\n'%(self.classname,self.nodeid) + qform += ixtmplt.filter_form(query.get('search_text', ''), + query.get(':filter', []), + query.get(':columns', []), + query.get(':group', []), + all_columns, + query.get(':sort',[]), + filterspec, + pagesize) + ixtmplt.clear() + return qform + '\n' + # # INDEX TEMPLATES @@ -965,7 +989,7 @@ class IndexTemplate(TemplateFunctions): l.append(value) w('' '' - '%s'%( + '%s\n'%( len(columns), ', '.join(l))) old_group = this_group @@ -977,7 +1001,7 @@ class IndexTemplate(TemplateFunctions): self.node_matches(matches[nodeid], len(columns)) self.nodeid = None - w('') + w('\n') # the previous and next links if nodeids: baseurl = self.buildurl(filterspec, search_text, filter, columns, sort, group, pagesize) @@ -991,7 +1015,7 @@ class IndexTemplate(TemplateFunctions): else: nexturl = "" if prevurl or nexturl: - w('
%s%s
' % (prevurl, nexturl)) + w('
%s%s
\n' % (prevurl, nexturl)) # display the filter section if (show_display_form and hasattr(self.instance, 'FILTER_POSITION') and @@ -1018,7 +1042,7 @@ class IndexTemplate(TemplateFunctions): message_links.append('%(lab)s' %locals()) w(_('' - '  Matched messages: %s')%( + '  Matched messages: %s\n')%( colspan, ', '.join(message_links))) if match.has_key('files'): @@ -1028,12 +1052,11 @@ class IndexTemplate(TemplateFunctions): file_links.append('%(filename)s' %locals()) w(_('' - '  Matched files: %s')%( + '  Matched files: %s\n')%( colspan, ', '.join(file_links))) - - def filter_section(self, search_text, filter, columns, group, all_columns, sort, filterspec, - pagesize, startwith): + def filter_form(self, search_text, filter, columns, group, all_columns, sort, filterspec, + pagesize): sortspec = {} for i in range(len(sort)): @@ -1045,57 +1068,57 @@ class IndexTemplate(TemplateFunctions): sortspec[colnm] = '%d%s' % (i+1, mod) startwith = 0 - w = self.client.write + rslt = [] + w = rslt.append # display the filter section - w( '
\n') - w( '\n') - w( '\n') - w(_(' \n')) - w( '\n') + w( '
') + w( '
Filter specification...
') + w( '') + w(_(' ')) + w( '') # see if we have any indexed properties if self.classname in self.db.config.HEADER_SEARCH_LINKS: #if self.properties.has_key('messages') or self.properties.has_key('files'): - w( '\n') - w( ' \n') - w( ' \n') - w( ' \n' % search_text) - w( '\n') - w( '\n') - w( ' \n') - w(_(' \n')) - w(_(' \n')) - w(_(' \n')) - w(_(' \n')) - w( '\n') + w( '') + w( ' ') + w( ' ' % search_text) + w( '') + w( '') + w( ' ') + w(_(' ')) + w(_(' ')) + w(_(' ')) + w(_(' ')) + w( '') for nm in all_columns: propdescr = self.properties.get(nm, None) if not propdescr: print "hey sysadmin - %s is not a property of %r" % (nm, self.classname) continue - w( '\n') - w(_(' \n' % nm.capitalize())) + w( '') + w(_(' ' % nm.capitalize())) # show column - can't show multilinks if isinstance(propdescr, hyperdb.Multilink): - w(' \n') + w(' ') else: checked = columns and nm in columns or 0 checked = ('', 'checked')[checked] - w(' \n' % (nm, checked) ) + w(' ' % (nm, checked) ) # can only group on Link if isinstance(propdescr, hyperdb.Link): checked = group and nm in group or 0 checked = ('', 'checked')[checked] - w(' \n' % (nm, checked) ) + w(' ' % (nm, checked) ) else: - w(' \n') + w(' ') # sort - no sort on Multilinks if isinstance(propdescr, hyperdb.Multilink): - w('\n') + w('') else: val = sortspec.get(nm, '') - w('\n' % (nm,val)) + w('' % (nm,val)) # condition val = '' if isinstance(propdescr, hyperdb.Link): @@ -1113,27 +1136,55 @@ class IndexTemplate(TemplateFunctions): xtra = "" val = filterspec.get(nm, '') else: - w('\n') + w('') continue checked = filter and nm in filter or 0 checked = ('', 'checked')[checked] - w( ' \n' % (nm, checked)) - w(_(' \n' % (op, nm, val, xtra))) - w( '\n') - w('\n') - w(' \n') - w('\n') - w('\n') - w(_(' \n')) - w(' \n' % pagesize) - w(' \n') - w('\n') - w('\n') - w(_(' \n')) - w(' \n' % startwith) - w(' \n') - w(' \n') - w('\n') + w( ' ' % (nm, checked)) + w(_(' ' % (op, nm, val, xtra))) + w( '') + w('') + w(' ') + w('') + w('') + w(_(' ')) + w(' ' % pagesize) + w(' ') + w('') + w('') + w(_(' ')) + w(' ' % startwith) + w(' ') + w(' ') + w('') + + return '\n'.join(rslt) + + def filter_section(self, search_text, filter, columns, group, all_columns, sort, filterspec, + pagesize, startwith): + + w = self.client.write + w(self.filter_form(search_text, filter, columns, group, all_columns, + sort, filterspec, pagesize)) + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n') + if self.db.getclass('user').getprops().has_key('queries'): + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n') + w(' \n' % self.classname) + w(' \n') + w(' \n') w('
Filter specification...
Search Terms 
 ShowGroupSortCondition
Search Terms   
 ShowGroupSortCondition
%s
%s
%s%s

Pagesize
Start With
%s%s

Pagesize
Start With

 

Name
 
\n') def sortby(self, sort_name, filterspec, columns, filter, group, sort, pagesize, startwith): @@ -1229,7 +1280,7 @@ class ItemTemplate(TemplateFunctions): def render(self, nodeid): self.nodeid = nodeid - + if (self.properties.has_key('type') and self.properties.has_key('content')): pass @@ -1290,6 +1341,9 @@ class NewItemTemplate(TemplateFunctions): # # $Log: not supported by cvs2svn $ +# Revision 1.98 2002/07/10 00:17:46 richard +# . added sorting of checklist HTML display +# # Revision 1.97 2002/07/09 05:20:09 richard # . added email display function - mangles email addrs so they're not so easily # scraped from the web diff --git a/roundup/templates/classic/dbinit.py b/roundup/templates/classic/dbinit.py index 03fbeac..1edc2dd 100644 --- a/roundup/templates/classic/dbinit.py +++ b/roundup/templates/classic/dbinit.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: dbinit.py,v 1.19 2002-07-14 02:05:54 richard Exp $ +# $Id: dbinit.py,v 1.20 2002-07-17 12:39:10 gmcm Exp $ import os @@ -50,12 +50,17 @@ def open(name=None): keyword = Class(db, "keyword", name=String()) keyword.setkey("name") - + + query = Class(db, "query", + klass=String(), name=String(), + url=String()) + query.setkey("name") + user = Class(db, "user", username=String(), password=Password(), address=String(), realname=String(), phone=String(), organisation=String(), - alternate_addresses=String()) + alternate_addresses=String(), queries=Multilink("query")) user.setkey("username") # FileClass automatically gets these properties: @@ -126,6 +131,9 @@ def init(adminpw): # # $Log: not supported by cvs2svn $ +# Revision 1.19 2002/07/14 02:05:54 richard +# . all storage-specific code (ie. backend) is now implemented by the backends +# # Revision 1.18 2002/07/09 03:02:53 richard # More indexer work: # - all String properties may now be indexed too. Currently there's a bit of diff --git a/roundup/templates/classic/html/query.index b/roundup/templates/classic/html/query.index new file mode 100755 index 0000000..3de3d05 --- /dev/null +++ b/roundup/templates/classic/html/query.index @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/roundup/templates/classic/html/query.item b/roundup/templates/classic/html/query.item new file mode 100755 index 0000000..ba3c8c2 --- /dev/null +++ b/roundup/templates/classic/html/query.item @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + +
Query
Class
Name
+ diff --git a/roundup/templates/classic/html/user.item b/roundup/templates/classic/html/user.item index 3443ca2..e4a4eac 100644 --- a/roundup/templates/classic/html/user.item +++ b/roundup/templates/classic/html/user.item @@ -1,4 +1,4 @@ - + @@ -41,6 +41,13 @@ + + + + + + + -- 2.30.2
Queries
History