From: richard Date: Sun, 1 Sep 2002 04:32:30 +0000 (+0000) Subject: . Lots of cleanup in the classic html (stylesheet, search page, index page, ...) X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=695e0389755249df74f86013f9537a958ca7539a;p=roundup.git . Lots of cleanup in the classic html (stylesheet, search page, index page, ...) . Reinstated searching, but not query saving yet . Filtering only allows sorting and grouping by one property - all backends now implement this behaviour. . Nosy list journalling turned off by default, everything else is on. . Added some convenience methods (reverse, propchanged, [item] accesses, ...) . Did I mention the stylesheet is much cleaner now? :) git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1019 57a73879-2fb5-44c3-a270-3262357dd7e2 --- diff --git a/TODO.txt b/TODO.txt index e879330..4e48e0e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -14,7 +14,6 @@ pending hyperdb: range searching of values (dates in particular) [value, value, ...] implies "in" pending hyperdb: make creator, creation and activity available pre-commit pending hyperdb: migrate "id" property to be Number type -pending hyperdb: allow classes to define their ordering (properties, asc/desc) pending instance: including much simpler upgrade path and the use of non-Python configuration files (ConfigParser) pending instance: split instance.open() into open() and login() @@ -34,25 +33,27 @@ pending project: have the demo allow anonymous login pending security: at least an LDAP user database implementation pending security: authenticate over a secure connection pending security: use digital signatures in mailgw +pending security: submission protection pending web: I18N pending web: Better message summary display (feature request #520244) pending web: Navigating around the issues (feature request #559149) -pending web: Re-enable link backrefs from messages (feature request #568714) pending web: Quick help links next to the property labels giving a description of the property. Combine with help for the actual form element too, eg. how to use the nosy list edit box. pending web: clicking on a group header should filter for that type of entry pending web: re-enable auth by basic http auth -New templating TODO +New templating TODO: +. generic class editing . classhelp . query saving -. search page is a shambles -. view customisation is a shambles -. style is a shambles +. search "refinement" (pre-fill the search page with the current search + parameters) +. security on actions (only allows/enforces generic Edit perm on the class :() ongoing: any bugs +done web: Re-enable link backrefs from messages (feature request #568714) (RJ) done web: have the page layout (header/footer) be templatable (RJ) done web: fixing the templating so it works (RJ) done web: re-work cgi interface to abstract out the explicit "issue" diff --git a/roundup/backends/back_anydbm.py b/roundup/backends/back_anydbm.py index fee6d95..0f0faa0 100644 --- a/roundup/backends/back_anydbm.py +++ b/roundup/backends/back_anydbm.py @@ -15,7 +15,7 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -#$Id: back_anydbm.py,v 1.65 2002-08-30 08:35:45 richard Exp $ +#$Id: back_anydbm.py,v 1.66 2002-09-01 04:32:30 richard Exp $ ''' This module defines a backend that saves the hyperdatabase in a database chosen by anydbm. It is guaranteed to always be available in python @@ -1471,8 +1471,8 @@ class Class(hyperdb.Class): sort spec. "filterspec" is {propname: value(s)} - "sort" is ['+propname', '-propname', 'propname', ...] - "group is ['+propname', '-propname', 'propname', ...] + "sort" and "group" are (dir, prop) where dir is '+', '-' or None + and prop is a prop name or None "search_matches" is {nodeid: marker} ''' cn = self.classname @@ -1591,126 +1591,109 @@ class Class(hyperdb.Class): k.append(v) l = k - # optimise sort - m = [] - for entry in sort: - if entry[0] != '-': - m.append(('+', entry)) - else: - m.append((entry[0], entry[1:])) - sort = m - - # optimise group - m = [] - for entry in group: - if entry[0] != '-': - m.append(('+', entry)) - else: - m.append((entry[0], entry[1:])) - group = m # now, sort the result def sortfun(a, b, sort=sort, group=group, properties=self.getprops(), db = self.db, cl=self): a_id, an = a b_id, bn = b # sort by group and then sort - for list in group, sort: - for dir, prop in list: - # sorting is class-specific - propclass = properties[prop] + for dir, prop in group, sort: + if dir is None: continue - # handle the properties that might be "faked" - # also, handle possible missing properties - try: - if not an.has_key(prop): - an[prop] = cl.get(a_id, prop) - av = an[prop] - except KeyError: - # the node doesn't have a value for this property - if isinstance(propclass, Multilink): av = [] - else: av = '' + # sorting is class-specific + propclass = properties[prop] + + # handle the properties that might be "faked" + # also, handle possible missing properties + try: + if not an.has_key(prop): + an[prop] = cl.get(a_id, prop) + av = an[prop] + except KeyError: + # the node doesn't have a value for this property + if isinstance(propclass, Multilink): av = [] + else: av = '' + try: + if not bn.has_key(prop): + bn[prop] = cl.get(b_id, prop) + bv = bn[prop] + except KeyError: + # the node doesn't have a value for this property + if isinstance(propclass, Multilink): bv = [] + else: bv = '' + + # String and Date values are sorted in the natural way + if isinstance(propclass, String): + # clean up the strings + if av and av[0] in string.uppercase: + av = an[prop] = av.lower() + if bv and bv[0] in string.uppercase: + bv = bn[prop] = bv.lower() + if (isinstance(propclass, String) or + isinstance(propclass, Date)): + # it might be a string that's really an integer try: - if not bn.has_key(prop): - bn[prop] = cl.get(b_id, prop) - bv = bn[prop] - except KeyError: - # the node doesn't have a value for this property - if isinstance(propclass, Multilink): bv = [] - else: bv = '' - - # String and Date values are sorted in the natural way - if isinstance(propclass, String): - # clean up the strings - if av and av[0] in string.uppercase: - av = an[prop] = av.lower() - if bv and bv[0] in string.uppercase: - bv = bn[prop] = bv.lower() - if (isinstance(propclass, String) or - isinstance(propclass, Date)): - # it might be a string that's really an integer - try: - av = int(av) - bv = int(bv) - except: - pass + av = int(av) + bv = int(bv) + except: + pass + if dir == '+': + r = cmp(av, bv) + if r != 0: return r + elif dir == '-': + r = cmp(bv, av) + if r != 0: return r + + # Link properties are sorted according to the value of + # the "order" property on the linked nodes if it is + # present; or otherwise on the key string of the linked + # nodes; or finally on the node ids. + elif isinstance(propclass, Link): + link = db.classes[propclass.classname] + if av is None and bv is not None: return -1 + if av is not None and bv is None: return 1 + if av is None and bv is None: continue + if link.getprops().has_key('order'): if dir == '+': - r = cmp(av, bv) + r = cmp(link.get(av, 'order'), + link.get(bv, 'order')) if r != 0: return r elif dir == '-': - r = cmp(bv, av) + r = cmp(link.get(bv, 'order'), + link.get(av, 'order')) if r != 0: return r - - # Link properties are sorted according to the value of - # the "order" property on the linked nodes if it is - # present; or otherwise on the key string of the linked - # nodes; or finally on the node ids. - elif isinstance(propclass, Link): - link = db.classes[propclass.classname] - if av is None and bv is not None: return -1 - if av is not None and bv is None: return 1 - if av is None and bv is None: continue - if link.getprops().has_key('order'): - if dir == '+': - r = cmp(link.get(av, 'order'), - link.get(bv, 'order')) - if r != 0: return r - elif dir == '-': - r = cmp(link.get(bv, 'order'), - link.get(av, 'order')) - if r != 0: return r - elif link.getkey(): - key = link.getkey() - if dir == '+': - r = cmp(link.get(av, key), link.get(bv, key)) - if r != 0: return r - elif dir == '-': - r = cmp(link.get(bv, key), link.get(av, key)) - if r != 0: return r - else: - if dir == '+': - r = cmp(av, bv) - if r != 0: return r - elif dir == '-': - r = cmp(bv, av) - if r != 0: return r - - # Multilink properties are sorted according to how many - # links are present. - elif isinstance(propclass, Multilink): + elif link.getkey(): + key = link.getkey() if dir == '+': - r = cmp(len(av), len(bv)) + r = cmp(link.get(av, key), link.get(bv, key)) if r != 0: return r elif dir == '-': - r = cmp(len(bv), len(av)) + r = cmp(link.get(bv, key), link.get(av, key)) if r != 0: return r - elif isinstance(propclass, Number) or isinstance(propclass, Boolean): + else: if dir == '+': r = cmp(av, bv) + if r != 0: return r elif dir == '-': r = cmp(bv, av) - - # end for dir, prop in list: - # end for list in sort, group: + if r != 0: return r + + # Multilink properties are sorted according to how many + # links are present. + elif isinstance(propclass, Multilink): + if dir == '+': + r = cmp(len(av), len(bv)) + if r != 0: return r + elif dir == '-': + r = cmp(len(bv), len(av)) + if r != 0: return r + elif isinstance(propclass, Number) or isinstance(propclass, Boolean): + if dir == '+': + r = cmp(av, bv) + elif dir == '-': + r = cmp(bv, av) + + # end for dir, prop in sort, group: # if all else fails, compare the ids return cmp(a[0], b[0]) @@ -1909,13 +1892,18 @@ class IssueClass(Class, roundupdb.IssueClass): if not properties.has_key('files'): properties['files'] = hyperdb.Multilink("file") if not properties.has_key('nosy'): - properties['nosy'] = hyperdb.Multilink("user") + # note: journalling is turned off as it really just wastes + # space. this behaviour may be overridden in an instance + properties['nosy'] = hyperdb.Multilink("user", do_journal="no") if not properties.has_key('superseder'): properties['superseder'] = hyperdb.Multilink(classname) Class.__init__(self, db, classname, **properties) # #$Log: not supported by cvs2svn $ +#Revision 1.65 2002/08/30 08:35:45 richard +#minor edits +# #Revision 1.64 2002/08/22 07:57:11 richard #Consistent quoting # diff --git a/roundup/backends/back_gadfly.py b/roundup/backends/back_gadfly.py index 7ac724b..95b5bef 100644 --- a/roundup/backends/back_gadfly.py +++ b/roundup/backends/back_gadfly.py @@ -1,4 +1,4 @@ -# $Id: back_gadfly.py,v 1.6 2002-08-30 08:35:16 richard Exp $ +# $Id: back_gadfly.py,v 1.7 2002-09-01 04:32:30 richard Exp $ __doc__ = ''' About Gadfly ============ @@ -1469,8 +1469,8 @@ class Class(hyperdb.Class): sort spec "filterspec" is {propname: value(s)} - "sort" is ['+propname', '-propname', 'propname', ...] - "group is ['+propname', '-propname', 'propname', ...] + "sort" and "group" are (dir, prop) where dir is '+', '-' or None + and prop is a prop name or None "search_matches" is {nodeid: marker} ''' cn = self.classname @@ -1511,26 +1511,24 @@ class Class(hyperdb.Class): # figure the order by clause orderby = [] ordercols = [] - if sort: - for entry in sort: - if entry[0] != '-': - orderby.append('_'+entry) - ordercols.append(entry) - else: - orderby.append('_'+entry[1:]+' desc') - ordercols.append(entry) + if sort is not None: + if sort[0] != '-': + orderby.append('_'+sort[1]) + ordercols.append(sort[1]) + else: + orderby.append('_'+sort[1]+' desc') + ordercols.append(sort[1]) # figure the group by clause groupby = [] groupcols = [] - if group: - for entry in group: - if entry[0] != '-': - groupby.append('_'+entry) - groupcols.append(entry) - else: - groupby.append('_'+entry[1:]+' desc') - groupcols.append(entry[1:]) + if group is not None: + if group[0] != '-': + groupby.append('_'+group[1]) + groupcols.append(group[1]) + else: + groupby.append('_'+group[1]+' desc') + groupcols.append(group[1]) # construct the SQL frum = ','.join(frum) @@ -1743,13 +1741,18 @@ class IssueClass(Class, roundupdb.IssueClass): if not properties.has_key('files'): properties['files'] = hyperdb.Multilink("file") if not properties.has_key('nosy'): - properties['nosy'] = hyperdb.Multilink("user") + # note: journalling is turned off as it really just wastes + # space. this behaviour may be overridden in an instance + properties['nosy'] = hyperdb.Multilink("user", do_journal="no") if not properties.has_key('superseder'): properties['superseder'] = hyperdb.Multilink(classname) Class.__init__(self, db, classname, **properties) # # $Log: not supported by cvs2svn $ +# Revision 1.6 2002/08/30 08:35:16 richard +# very basic filter support +# # Revision 1.5 2002/08/23 05:33:32 richard # implemented multilink changes (and a unit test) # diff --git a/roundup/backends/back_metakit.py b/roundup/backends/back_metakit.py index d78b27f..8919382 100755 --- a/roundup/backends/back_metakit.py +++ b/roundup/backends/back_metakit.py @@ -638,7 +638,9 @@ class Class: # search_matches is None or a set (dict of {nodeid: {propname:[nodeid,...]}}) # filterspec is a dict {propname:value} # sort and group are lists of propnames - + # sort and group are (dir, prop) where dir is '+', '-' or None + # and prop is a prop name or None + where = {'_isdel':0} mlcriteria = {} regexes = {} @@ -745,10 +747,10 @@ class Class: if sort or group: sortspec = [] rev = [] - for propname in group + sort: + for dir, propname in group, sort: + if propname is None: continue isreversed = 0 - if propname[0] == '-': - propname = propname[1:] + if dir == '-': isreversed = 1 try: prop = getattr(v, propname) @@ -1013,7 +1015,9 @@ class IssueClass(Class, roundupdb.IssueClass): if not properties.has_key('files'): properties['files'] = hyperdb.Multilink("file") if not properties.has_key('nosy'): - properties['nosy'] = hyperdb.Multilink("user") + # note: journalling is turned off as it really just wastes + # space. this behaviour may be overridden in an instance + properties['nosy'] = hyperdb.Multilink("user", do_journal="no") if not properties.has_key('superseder'): properties['superseder'] = hyperdb.Multilink(classname) Class.__init__(self, db, classname, **properties) diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py index bcc0a16..8169500 100644 --- a/roundup/cgi/client.py +++ b/roundup/cgi/client.py @@ -1,4 +1,4 @@ -# $Id: client.py,v 1.1 2002-08-30 08:28:44 richard Exp $ +# $Id: client.py,v 1.2 2002-09-01 04:32:30 richard Exp $ __doc__ = """ WWW request handler (also used in the stand-alone server). @@ -296,6 +296,7 @@ class Client: 'login': 'login_action', 'logout': 'logout_action', 'register': 'register_action', + 'search': 'search_action', } def handle_action(self): ''' Determine whether there should be an _action called. @@ -308,6 +309,7 @@ class Client: "login" -> self.login_action "logout" -> self.logout_action "register" -> self.register_action + "search" -> self.search_action ''' if not self.form.has_key(':action'): @@ -512,23 +514,29 @@ class Client: "files" property. Attach the file to the message created from the __note if it's supplied. ''' - cn = self.classname - cl = self.db.classes[cn] + cl = self.db.classes[self.classname] # check permission userid = self.db.user.lookup(self.user) - if not self.db.security.hasPermission('Edit', userid, cn): + if not self.db.security.hasPermission('Edit', userid, self.classname): self.error_message.append( - _('You do not have permission to edit %s' %cn)) + _('You do not have permission to edit %(classname)s' % + self.__dict__)) + return # perform the edit - props = parsePropsFromForm(self.db, cl, self.form, self.nodeid) + try: + props = parsePropsFromForm(self.db, cl, self.form, self.nodeid) - # make changes to the node - props = self._changenode(props) + # make changes to the node + props = self._changenode(props) - # handle linked nodes - self._post_editnode(self.nodeid) + # handle linked nodes + self._post_editnode(self.nodeid) + + except (ValueError, KeyError), message: + self.error_message.append(_('Error: ') + str(message)) + return # commit now that all the tricky stuff is done self.db.commit() @@ -545,8 +553,8 @@ class Client: message = _('nothing changed') # redirect to the item's edit page - raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, cn, self.nodeid, - urllib.quote(message)) + raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, + self.nodeid, urllib.quote(message)) def newitem_action(self): ''' Add a new item to the database. @@ -554,11 +562,10 @@ class Client: This follows the same form as the edititem_action ''' # check permission - cn = self.classname userid = self.db.user.lookup(self.user) - if not self.db.security.hasPermission('Edit', userid, cn): + if not self.db.security.hasPermission('Edit', userid, self.classname): self.error_message.append( - _('You do not have permission to create %s' %cn)) + _('You do not have permission to create %s' %self.classname)) # XXX # cl = self.db.classes[cn] @@ -583,17 +590,21 @@ class Client: self.nodeid = nid # and some nice feedback for the user - message = _('%(classname)s created ok')%{'classname': cn} + message = _('%(classname)s created ok')%self.__dict__ + except (ValueError, KeyError), message: + self.error_message.append(_('Error: ') + str(message)) + return except: # oops self.db.rollback() s = StringIO.StringIO() traceback.print_exc(None, s) self.error_message.append('
%s
'%cgi.escape(s.getvalue())) + return # redirect to the new item's page - raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, cn, nid, - urllib.quote(message)) + raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname, + nid, urllib.quote(message)) def genericedit_action(self): ''' Performs an edit of all of a class' items in one go. @@ -603,12 +614,10 @@ class Client: non-existent ID) and removed lines are retired. ''' userid = self.db.user.lookup(self.user) - if not self.db.security.hasPermission('Edit', userid): + if not self.db.security.hasPermission('Edit', userid, self.classname): raise Unauthorised, _("You do not have permission to access"\ " %(action)s.")%{'action': self.classname} - w = self.write - cn = self.classname - cl = self.db.classes[cn] + cl = self.db.classes[self.classname] idlessprops = cl.getprops(protected=0).keys() props = ['id'] + idlessprops @@ -638,7 +647,7 @@ class Client: # confirm correct weight if len(idlessprops) != len(values): - w(_('Not enough values on line %(line)s'%{'line':line})) + message=(_('Not enough values on line %(line)s'%{'line':line})) return # extract the new values @@ -668,7 +677,7 @@ class Client: message = _('items edited OK') # redirect to the class' edit page - raise Redirect, '%s/%s?:ok_message=%s'%(self.base, cn, + raise Redirect, '%s/%s?:ok_message=%s'%(self.base, self.classname, urllib.quote(message)) def _changenode(self, props): @@ -797,6 +806,19 @@ class Client: link = self.db.classes[link] link.set(nodeid, **{property: nid}) + def search_action(self): + ''' Mangle some of the form variables. + + Set the form ":filter" variable based on the values of the + filter variables - if they're set to anything other than + "dontcare" then add them to :filter. + ''' + # add a faked :filter form variable for each filtering prop + props = self.db.classes[self.classname].getprops() + for key in self.form.keys(): + if not props.has_key(key): continue + if not self.form[key].value: continue + self.form.value.append(cgi.MiniFieldStorage(':filter', key)) def remove_action(self, dre=re.compile(r'([^\d]+)(\d+)')): # XXX handle this ! @@ -838,7 +860,11 @@ def parsePropsFromForm(db, cl, form, nodeid=0, num_re=re.compile('^\d+$')): if isinstance(proptype, hyperdb.String): value = form[key].value.strip() elif isinstance(proptype, hyperdb.Password): - value = password.Password(form[key].value.strip()) + value = form[key].value.strip() + if not value: + # ignore empty password values + continue + value = password.Password(value) elif isinstance(proptype, hyperdb.Date): value = form[key].value.strip() if value: diff --git a/roundup/cgi/templating.py b/roundup/cgi/templating.py index a01fa7c..2a04f5a 100644 --- a/roundup/cgi/templating.py +++ b/roundup/cgi/templating.py @@ -1,4 +1,4 @@ -import sys, cgi, urllib, os +import sys, cgi, urllib, os, re from roundup import hyperdb, date from roundup.i18n import _ @@ -136,15 +136,15 @@ class HTMLClass: def __repr__(self): return ''%(id(self), self.classname) - def __getattr__(self, attr): + def __getitem__(self, item): ''' return an HTMLItem instance''' - #print 'getattr', (self, attr) - if attr == 'creator': + #print 'getitem', (self, attr) + if item == 'creator': return HTMLUser(self.client) - if not self.props.has_key(attr): - raise AttributeError, attr - prop = self.props[attr] + if not self.props.has_key(item): + raise KeyError, item + prop = self.props[item] # look up the correct HTMLProperty class for klass, htmlklass in propclasses: @@ -153,10 +153,17 @@ class HTMLClass: else: value = None if isinstance(prop, klass): - return htmlklass(self.db, '', prop, attr, value) + return htmlklass(self.db, '', prop, item, value) # no good - raise AttributeError, attr + raise KeyError, item + + def __getattr__(self, attr): + ''' convenience access ''' + try: + return self[attr] + except KeyError: + raise AttributeError, attr def properties(self): ''' Return HTMLProperty for all props @@ -248,29 +255,33 @@ class HTMLItem: def __repr__(self): return ''%(id(self), self.classname, self.nodeid) - def __getattr__(self, attr): + def __getitem__(self, item): ''' return an HTMLItem instance''' - #print 'getattr', (self, attr) - if attr == 'id': + if item == 'id': return self.nodeid - - if not self.props.has_key(attr): - raise AttributeError, attr - prop = self.props[attr] + if not self.props.has_key(item): + raise KeyError, item + prop = self.props[item] # get the value, handling missing values - value = self.klass.get(self.nodeid, attr, None) + value = self.klass.get(self.nodeid, item, None) if value is None: - if isinstance(self.props[attr], hyperdb.Multilink): + if isinstance(self.props[item], hyperdb.Multilink): value = [] # look up the correct HTMLProperty class for klass, htmlklass in propclasses: if isinstance(prop, klass): - return htmlklass(self.db, self.nodeid, prop, attr, value) + return htmlklass(self.db, self.nodeid, prop, item, value) - # no good - raise AttributeError, attr + raise KeyErorr, item + + def __getattr__(self, attr): + ''' convenience access to properties ''' + try: + return self[attr] + except KeyError: + raise AttributeError, attr def submit(self, label="Submit Changes"): ''' Generate a submit button (and action hidden element) @@ -613,7 +624,7 @@ class LinkHTMLProperty(HTMLProperty): def plain(self, escape=0): if self.value is None: return _('[unselected]') - linkcl = self.db.classes[self.klass.classname] + linkcl = self.db.classes[self.prop.classname] k = linkcl.labelprop(1) value = str(linkcl.get(self.value, k)) if escape: @@ -688,10 +699,10 @@ class LinkHTMLProperty(HTMLProperty): s = 'selected ' l.append(_('')%s) if linkcl.getprops().has_key('order'): - sort_on = 'order' + sort_on = ('+', 'order') else: - sort_on = linkcl.labelprop() - options = linkcl.filter(None, conditions, [sort_on], []) + sort_on = ('+', linkcl.labelprop()) + options = linkcl.filter(None, conditions, sort_on, (None, None)) for optionid in options: option = linkcl.get(optionid, k) s = '' @@ -735,6 +746,12 @@ class MultilinkHTMLProperty(HTMLProperty): value = self.value[num] return HTMLItem(self.db, self.prop.classname, value) + def reverse(self): + ''' return the list in reverse order ''' + l = self.value[:] + l.reverse() + return [HTMLItem(self.db, self.prop.classname, value) for value in l] + def plain(self, escape=0): linkcl = self.db.classes[self.prop.classname] k = linkcl.labelprop(1) @@ -772,10 +789,10 @@ class MultilinkHTMLProperty(HTMLProperty): linkcl = self.db.getclass(self.prop.classname) if linkcl.getprops().has_key('order'): - sort_on = 'order' + sort_on = ('+', 'order') else: - sort_on = linkcl.labelprop() - options = linkcl.filter(None, conditions, [sort_on], []) + sort_on = ('+', linkcl.labelprop()) + options = linkcl.filter(None, conditions, sort_on, (None,None)) height = height or min(len(options), 7) l = ['' - if self.columns: + if columns and self.columns: l.append(s%(':columns', ','.join(self.columns.keys()))) - if self.sort: - l.append(s%(':sort', ','.join(self.sort))) - if self.group: - l.append(s%(':group', ','.join(self.group))) - if self.filter: + if sort and self.sort is not None: + l.append(s%(':sort', self.sort)) + if group and self.group is not None: + l.append(s%(':group', self.group)) + if filter and self.filter: l.append(s%(':filter', ','.join(self.filter))) - for k,v in self.filterspec.items(): - l.append(s%(k, ','.join(v))) + if filterspec: + for k,v in self.filterspec.items(): + l.append(s%(k, ','.join(v))) return '\n'.join(l) def indexargs_href(self, url, args): l = ['%s=%s'%(k,v) for k,v in args.items()] if self.columns: l.append(':columns=%s'%(','.join(self.columns.keys()))) - if self.sort: - l.append(':sort=%s'%(','.join(self.sort))) - if self.group: - l.append(':group=%s'%(','.join(self.group))) + if self.sort is not None: + l.append(':sort=%s'%self.sort) + if self.group is not None: + l.append(':group=%s'%self.group) if self.filter: l.append(':filter=%s'%(','.join(self.filter))) for k,v in self.filterspec.items(): @@ -954,7 +1002,12 @@ function help_window(helpurl, width, height) { # get the list of ids we're batching over klass = self.client.db.getclass(self.classname) - l = klass.filter(None, filterspec, sort, group) + if self.search_text: + matches = self.client.db.indexer.search( + re.findall(r'\b\w{2,25}\b', self.search_text), klass) + else: + matches = None + l = klass.filter(matches, filterspec, sort, group) # figure batch args if self.form.has_key(':pagesize'): @@ -973,6 +1026,8 @@ class Batch(ZTUtils.Batch): def __init__(self, client, classname, l, size, start, end=0, orphan=0, overlap=0): self.client = client self.classname = classname + self.last_index = self.last_item = None + self.current_item = None ZTUtils.Batch.__init__(self, l, size, start, end, orphan, overlap) # overwrite so we can late-instantiate the HTMLItem instance @@ -983,13 +1038,28 @@ class Batch(ZTUtils.Batch): if index >= self.length: raise IndexError, index + # move the last_item along - but only if the fetched index changes + # (for some reason, index 0 is fetched twice) + if index != self.last_index: + self.last_item = self.current_item + self.last_index = index + # wrap the return in an HTMLItem - return HTMLItem(self.client.db, self.classname, + self.current_item = HTMLItem(self.client.db, self.classname, self._sequence[index+self.first]) + return self.current_item + + def propchanged(self, property): + ''' Detect if the property marked as being the group property + changed in the last iteration fetch + ''' + if (self.last_item is None or + self.last_item[property] != self.current_item[property]): + return 1 + return 0 # override these 'cos we don't have access to acquisition def previous(self): - print self.start if self.start == 1: return None return Batch(self.client, self.classname, self._sequence, self._size, diff --git a/roundup/templates/classic/dbinit.py b/roundup/templates/classic/dbinit.py index 29242df..c5a8ccf 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.23 2002-08-30 08:30:45 richard Exp $ +# $Id: dbinit.py,v 1.24 2002-09-01 04:32:30 richard Exp $ import os @@ -111,6 +111,11 @@ def open(name=None): p = db.security.getPermission('Email Access') db.security.addPermissionToRole('User', p) + # May users view other user information? Comment these lines out + # if you don't want them to + p = db.security.getPermission('View', 'user') + db.security.addPermissionToRole('User', p) + # Assign the appropriate permissions to the anonymous user's Anonymous # Role. Choices here are: # - Allow anonymous users to register through the web @@ -185,6 +190,9 @@ def init(adminpw): # # $Log: not supported by cvs2svn $ +# Revision 1.23 2002/08/30 08:30:45 richard +# allow perms on user class +# # Revision 1.22 2002/08/01 00:56:22 richard # Added the web access and email access permissions, so people can restrict # access to users who register through the email interface (for example). diff --git a/roundup/templates/classic/html/home b/roundup/templates/classic/html/home index b8e7ab0..1d5d38c 100644 --- a/roundup/templates/classic/html/home +++ b/roundup/templates/classic/html/home @@ -5,7 +5,8 @@ whatever. It's a good idea to have the issues on the front page though --> diff --git a/roundup/templates/classic/html/issue.index b/roundup/templates/classic/html/issue.index index f726492..13b316f 100644 --- a/roundup/templates/classic/html/issue.index +++ b/roundup/templates/classic/html/issue.index @@ -1,100 +1,96 @@ - - - - - - - - - - - - - - - - - - - - - +
IDActivityPriorityTitleStatusAssigned To
- title + + + + + + + + + + + + + + + + + + + + + + + + + + - - -
PriorityIDActivityTitleStatusCreated ByAssigned To
+
+ title +
+ + + +
+ << previous +   + + next >> +   +
- - - - -
-<< previous - -next >> -
-
- Pagesize: - Starting at: -
- - Status: - - - +
- - Assigned To: - - - - -
- Show: - - - -
- Sort on: - + + + + + + + + + + + +
Sort on: + - Group on: + Descending: +
Group on: - - + Descending:
+ + diff --git a/roundup/templates/classic/html/issue.item b/roundup/templates/classic/html/issue.item index d07fbec..750f35c 100644 --- a/roundup/templates/classic/html/issue.item +++ b/roundup/templates/classic/html/issue.item @@ -1,47 +1,44 @@
- - - - +
Title
+ + - + - + - + - + - - + - - + - + @@ -50,18 +47,14 @@ - + - + @@ -73,31 +66,25 @@ submit button will go here -
Title title
CreatedCreated creation (creator)Last activityLast activity activity
PriorityPriority priorityStatusStatus status
Superseder + Superseder
View:
Nosy List + Nosy List
- Assigned To - Assigned To assignedto menu
- Change Note - Change Note
- File - File
- - - - - - - - - - - - - + +
Messages
dateauthor
content
+ + + + + + + + + + +
Messages
authordate
content
-
Files
File nameUploaded
+ + - +
Files
File nameUploaded
creation date
- - History - - - history - - + + + +
History
history
+
diff --git a/roundup/templates/classic/html/issue.search b/roundup/templates/classic/html/issue.search index accf7ae..30ed988 100644 --- a/roundup/templates/classic/html/issue.search +++ b/roundup/templates/classic/html/issue.search @@ -2,64 +2,144 @@
-Columns you may display: - - name - - -
-Sort on: -
-Group on: - - -
-Priority: - - -
-Status: - - -
-Assigned To: - - - - Filter: - - -
-Pagesize: - - -
-Start With: - - -
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Filter onDisplaySort onGroup on
All text*:   
Title: 
Created:
Creator: + +
Activity: 
Priority: + +
Status: + +
Assigned To: + +
Pagesize:
Start With:
Sort Descending: +
Group Descending: +
 
 *: The "all text" field will look in message + bodies and issue titles
diff --git a/roundup/templates/classic/html/page b/roundup/templates/classic/html/page index a1b0da8..c6d6f98 100644 --- a/roundup/templates/classic/html/page +++ b/roundup/templates/classic/html/page @@ -13,41 +13,46 @@ - + - - +
 

name

-

+ + - - - - - - - +
Your Details
Namerealname
+ + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - + - + +
Namerealname
Login Nameusername
Login Nameusername
Login Passwordpassword
Login Passwordpassword
Rolesroles
Rolesroles
Phonephone
Phonephone
Organisationorganisation
Organisationorganisation
E-mail addressaddress
E-mail addressaddress
Alternate - E-mail addresses
- One address per line
alternate_addresses
Alternate E-mail addresses
One address per line
alternate_addresses
  - submit button will go here - submit button here
+ -
Queries
query
+ + + +
Queries
query
- History + + - +
History
historyhistory
+ - - - - - +
realname
+ + - - + + - - + + - - + + - - + +
realname
Login NameusernameLogin Nameusername
PhonephonePhonephone
OrganisationorganisationOrganisationorganisation
E-mail addressaddressE-mail addressaddress