summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 28dcfd5)
raw | patch | inline | side by side (parent: 28dcfd5)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Tue, 3 Sep 2002 02:58:11 +0000 (02:58 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Tue, 3 Sep 2002 02:58:11 +0000 (02:58 +0000) |
Reinstated query saving.
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1033 57a73879-2fb5-44c3-a270-3262357dd7e2
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1033 57a73879-2fb5-44c3-a270-3262357dd7e2
diff --git a/TODO.txt b/TODO.txt
index 2ef63fd68dabce508ffe02f6901a29022609dc46..be2860c2adf5878dd3401debd48134eb7d726c60 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
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
+pending web: search "refinement"
+ - pre-fill the search page with the current search parameters)
+ - add a drop-down with all queries which fills form with selected
+ query values
New templating TODO:
. generic class editing
. classhelp
-. query saving
- - add ":queryname" to search form submission, and handle it in search action
- - ?add a drop-down on search page with all queries that fills form with
- each query's values?
-. search "refinement" (pre-fill the search page with the current search
- parameters)
+. rewritten documentation (can come after the beta though so stuff is settled)
ongoing: any bugs
diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index 9683e6724c589bd8f91c2f9a65309ec3acaf9790..f50b06da105fae60ba950277c17fbc9d8e54dfee 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
-# $Id: client.py,v 1.6 2002-09-02 07:46:55 richard Exp $
+# $Id: client.py,v 1.7 2002-09-03 02:58:11 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
from roundup import roundupdb, date, hyperdb, password
from roundup.i18n import _
-from roundup.cgi.templating import RoundupPageTemplate
+from roundup.cgi.templating import getTemplate, HTMLRequest
from roundup.cgi import cgitb
+
from PageTemplates import PageTemplate
class Unauthorised(ValueError):
def template(self, name, **kwargs):
''' Return a PageTemplate for the named page
'''
- pt = RoundupPageTemplate(self)
- # make errors nicer
- pt.id = name
- pt.write(open(os.path.join(self.instance.TEMPLATES, name)).read())
- # XXX handle PT rendering errors here nicely
+ pt = getTemplate(self.instance.TEMPLATES, name)
+ # XXX handle PT rendering errors here more nicely
try:
- return pt.render(**kwargs)
+ # let the template render figure stuff out
+ return pt.render(self, None, None, **kwargs)
except PageTemplate.PTRuntimeError, message:
return '<strong>%s</strong><ol>%s</ol>'%(message,
'<li>'.join(pt._v_errors))
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.
+
+ Also handle the ":queryname" variable and save off the query to
+ the user's query list.
'''
# generic edit is per-class only
if not self.searchPermission():
if not self.form[key].value: continue
self.form.value.append(cgi.MiniFieldStorage(':filter', key))
+ # handle saving the query params
+ if self.form.has_key(':queryname'):
+ queryname = self.form[':queryname'].value.strip()
+ if queryname:
+ # parse the environment and figure what the query _is_
+ req = HTMLRequest(self)
+ url = req.indexargs_href('', {})
+
+ # handle editing an existing query
+ try:
+ qid = self.db.query.lookup(queryname)
+ self.db.query.set(qid, klass=self.classname, url=url)
+ except KeyError:
+ # create a query
+ qid = self.db.query.create(name=queryname,
+ klass=self.classname, url=url)
+
+ # and add it to the user's query multilink
+ queries = self.db.user.get(self.userid, 'queries')
+ queries.append(qid)
+ self.db.user.set(self.userid, queries=queries)
+
+ # commit the query change to the database
+ self.db.commit()
+
+
def searchPermission(self):
''' Determine whether the user has permission to search this class.
'value': value, 'classname': link}
elif isinstance(proptype, hyperdb.Multilink):
value = form[key]
- if hasattr(value, 'value'):
- # Quite likely to be a FormItem instance
- value = value.value
if not isinstance(value, type([])):
value = [i.strip() for i in value.split(',')]
else:
- value = [i.strip() for i in value]
+ value = [i.value.strip() for i in value]
link = cl.properties[key].classname
l = []
for entry in map(str, value):
index 8f4bf32892c44dfd2d4b77e95f89791ec3bde3cc..27ab4769096ae4c372b96353db48d0655d42a419 100644 (file)
-import sys, cgi, urllib, os, re, os.path
+import sys, cgi, urllib, os, re, os.path, time
from roundup import hyperdb, date
from roundup.i18n import _
-
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
+try:
+ import cStringIO as StringIO
+except ImportError:
+ import StringIO
try:
import StructuredText
except ImportError:
import Acquisition
sys.modules['Acquisition'] = Acquisition
-# now it's safe to import PageTemplates and ZTUtils
+# now it's safe to import PageTemplates, TAL and ZTUtils
from PageTemplates import PageTemplate
+from PageTemplates.Expressions import getEngine
+from TAL.TALInterpreter import TALInterpreter
import ZTUtils
+# XXX WAH pagetemplates aren't pickleable :(
+#def getTemplate(dir, name, classname=None, request=None):
+# ''' Interface to get a template, possibly loading a compiled template.
+# '''
+# # source
+# src = os.path.join(dir, name)
+#
+# # see if we can get a compile from the template"c" directory (most
+# # likely is "htmlc"
+# split = list(os.path.split(dir))
+# split[-1] = split[-1] + 'c'
+# cdir = os.path.join(*split)
+# split.append(name)
+# cpl = os.path.join(*split)
+#
+# # ok, now see if the source is newer than the compiled (or if the
+# # compiled even exists)
+# MTIME = os.path.stat.ST_MTIME
+# if (not os.path.exists(cpl) or os.stat(cpl)[MTIME] < os.stat(src)[MTIME]):
+# # nope, we need to compile
+# pt = RoundupPageTemplate()
+# pt.write(open(src).read())
+# pt.id = name
+#
+# # save off the compiled template
+# if not os.path.exists(cdir):
+# os.makedirs(cdir)
+# f = open(cpl, 'wb')
+# pickle.dump(pt, f)
+# f.close()
+# else:
+# # yay, use the compiled template
+# f = open(cpl, 'rb')
+# pt = pickle.load(f)
+# return pt
+
+templates = {}
+
+def getTemplate(dir, name, classname=None, request=None):
+ ''' Interface to get a template, possibly loading a compiled template.
+ '''
+ # find the source, figure the time it was last modified
+ src = os.path.join(dir, name)
+ stime = os.stat(src)[os.path.stat.ST_MTIME]
+
+ key = (dir, name)
+ if templates.has_key(key) and stime < templates[key].mtime:
+ # compiled template is up to date
+ return templates[key]
+
+ # compile the template
+ templates[key] = pt = RoundupPageTemplate()
+ pt.write(open(src).read())
+ pt.id = name
+ pt.mtime = time.time()
+ return pt
+
class RoundupPageTemplate(PageTemplate.PageTemplate):
''' A Roundup-specific PageTemplate.
python modules made available (XXX: not sure what's actually in
there tho)
'''
- def __init__(self, client, classname=None, request=None):
- ''' Extract the vars from the client and install in the context.
- '''
- self.client = client
- self.classname = classname or self.client.classname
- self.request = request or HTMLRequest(self.client)
-
- def pt_getContext(self):
+ def getContext(self, client, classname, request):
c = {
- 'klass': HTMLClass(self.client, self.classname),
+ 'klass': HTMLClass(client, classname),
'options': {},
'nothing': None,
- 'request': self.request,
- 'content': self.client.content,
- 'db': HTMLDatabase(self.client),
- 'instance': self.client.instance
+ 'request': request,
+ 'content': client.content,
+ 'db': HTMLDatabase(client),
+ 'instance': client.instance
}
# add in the item if there is one
- if self.client.nodeid:
- c['item'] = HTMLItem(self.client.db, self.classname,
- self.client.nodeid)
- c[self.classname] = c['item']
+ if client.nodeid:
+ c['item'] = HTMLItem(client.db, classname, client.nodeid)
+ c[classname] = c['item']
else:
- c[self.classname] = c['klass']
+ c[classname] = c['klass']
return c
-
- def render(self, *args, **kwargs):
- if not kwargs.has_key('args'):
- kwargs['args'] = args
- return self.pt_render(extra_context={'options': kwargs})
+
+ def render(self, client, classname, request, **options):
+ """Render this Page Template"""
+
+ if not self._v_cooked:
+ self._cook()
+
+ __traceback_supplement__ = (PageTemplate.PageTemplateTracebackSupplement, self)
+
+ if self._v_errors:
+ raise PTRuntimeError, 'Page Template %s has errors.' % self.id
+
+ # figure the context
+ classname = classname or client.classname
+ request = request or HTMLRequest(client)
+ c = self.getContext(client, classname, request)
+ c.update({'options': options})
+
+ # and go
+ output = StringIO.StringIO()
+ TALInterpreter(self._v_program, self._v_macros,
+ getEngine().getContext(c), output, tal=1, strictinsert=0)()
+ return output.getvalue()
class HTMLDatabase:
''' Return HTMLClasses for valid class fetches
# create a new request and override the specified args
req = HTMLRequest(self.client)
req.classname = self.classname
- req.__dict__.update(kwargs)
+ req.update(kwargs)
# new template, using the specified classname and request
- pt = RoundupPageTemplate(self.client, self.classname, req)
-
- # use the specified template
name = self.classname + '.' + name
- pt.write(open(os.path.join(self.db.config.TEMPLATES, name)).read())
- pt.id = name
+ pt = getTemplate(self.db.config.TEMPLATES, name)
# XXX handle PT rendering errors here nicely
try:
- return pt.render()
+ # use our fabricated request
+ return pt.render(self.client, self.classname, req)
except PageTemplate.PTRuntimeError, message:
return '<strong>%s</strong><ol>%s</ol>'%(message,
cgi.escape('<li>'.join(pt._v_errors)))
else:
return value.value.split(',')
+class ShowDict:
+ ''' A convenience access to the :columns index parameters
+ '''
+ def __init__(self, columns):
+ self.columns = {}
+ for col in columns:
+ self.columns[col] = 1
+ def __getitem__(self, name):
+ return self.columns.has_key(name)
+
class HTMLRequest:
''' The *request*, holding the CGI form and environment.
Index args:
"columns" dictionary of the columns to display in an index page
+ "show" a convenience access to columns - request/show/colname will
+ be true if the columns should be displayed, false otherwise
"sort" index sort column (direction, column name)
"group" index grouping property (direction, column name)
"filter" properties to filter the index on
self.template_type = client.template_type
# extract the index display information from the form
- self.columns = {}
+ self.columns = []
if self.form.has_key(':columns'):
- for entry in handleListCGIValue(self.form[':columns']):
- self.columns[entry] = 1
+ self.columns = handleListCGIValue(self.form[':columns'])
+ self.show = ShowDict(self.columns)
# sorting
self.sort = (None, None)
if self.form.has_key(':search_text'):
self.search_text = self.form[':search_text'].value
+ # pagination - size and start index
+ # figure batch args
+ if self.form.has_key(':pagesize'):
+ self.pagesize = int(self.form[':pagesize'].value)
+ else:
+ self.pagesize = 50
+ if self.form.has_key(':startwith'):
+ self.startwith = int(self.form[':startwith'].value)
+ else:
+ self.startwith = 0
+
+ def update(self, kwargs):
+ self.__dict__.update(kwargs)
+ if kwargs.has_key('columns'):
+ self.show = ShowDict(self.columns)
+
def __str__(self):
d = {}
d.update(self.__dict__)
sort: %(sort)r
group: %(group)r
filter: %(filter)r
-filterspec: %(filterspec)r
+search_text: %(search_text)r
+pagesize: %(pagesize)r
+startwith: %(startwith)r
env: %(env)s
'''%d
l = []
s = '<input type="hidden" name="%s" value="%s">'
if columns and self.columns:
- l.append(s%(':columns', ','.join(self.columns.keys())))
- if sort and self.sort is not None:
+ l.append(s%(':columns', ','.join(self.columns)))
+ if sort and self.sort[1] is not None:
if self.sort[0] == '-':
val = '-'+self.sort[1]
else:
val = self.sort[1]
l.append(s%(':sort', val))
- if group and self.group is not None:
+ if group and self.group[1] is not None:
if self.group[0] == '-':
val = '-'+self.group[1]
else:
if filterspec:
for k,v in self.filterspec.items():
l.append(s%(k, ','.join(v)))
+ if self.search_text:
+ l.append(s%(':search_text', self.search_text))
+ l.append(s%(':pagesize', self.pagesize))
+ l.append(s%(':startwith', self.startwith))
return '\n'.join(l)
def indexargs_href(self, url, args):
''' embed the current index args in a URL '''
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 is not None:
+ l.append(':columns=%s'%(','.join(self.columns)))
+ if self.sort[1] is not None:
if self.sort[0] == '-':
val = '-'+self.sort[1]
else:
val = self.sort[1]
l.append(':sort=%s'%val)
- if self.group is not None:
+ if self.group[1] is not None:
if self.group[0] == '-':
val = '-'+self.group[1]
else:
l.append(':filter=%s'%(','.join(self.filter)))
for k,v in self.filterspec.items():
l.append('%s=%s'%(k, ','.join(v)))
+ if self.search_text:
+ l.append(':search_text=%s'%self.search_text)
+ l.append(':pagesize=%s'%self.pagesize)
+ l.append(':startwith=%s'%self.startwith)
return '%s?%s'%(url, '&'.join(l))
def base_javascript(self):
matches = None
l = klass.filter(matches, filterspec, sort, group)
- # figure batch args
- if self.form.has_key(':pagesize'):
- size = int(self.form[':pagesize'].value)
- else:
- size = 50
- if self.form.has_key(':startwith'):
- start = int(self.form[':startwith'].value)
- else:
- start = 0
-
# return the batch object
- return Batch(self.client, self.classname, l, size, start)
+ return Batch(self.client, self.classname, l, self.pagesize,
+ self.startwith)
# extend the standard ZTUtils Batch object to remove dependency on
index 8dc99f76ac03a1bfe3cde08843f988bc5716021f..03fe21bbaf55ffadab473eb2a04d541a8cddf08f 100644 (file)
-->
<span tal:replace="structure python:db.issue.renderWith('index',
sort=('-', 'activity'), group=('+', 'priority'), filter=['status'],
- columns={'id':1,'activity':1,'title':1,'creator':1,'assignedto':1,
- 'status':1},
+ columns=['id','activity','title','creator','assignedto', 'status'],
filterspec={'status':['-1','1','2','3','4','5','6','7']})" />
diff --git a/roundup/templates/classic/html/issue.index b/roundup/templates/classic/html/issue.index
index fc47be7ef458aa6728cec87bf018f38e7e90cfa2..420d0e6a3b57494749126ae491c6dc7d634e136a 100644 (file)
<tal:block tal:define="batch request/batch">
<table class="list">
<tr>
- <th tal:condition="exists:request/columns/priority">Priority</th>
- <th tal:condition="exists:request/columns/id">ID</th>
- <th tal:condition="exists:request/columns/activity">Activity</th>
- <th tal:condition="exists:request/columns/title">Title</th>
- <th tal:condition="exists:request/columns/status">Status</th>
- <th tal:condition="exists:request/columns/creator">Created By</th>
- <th tal:condition="exists:request/columns/assignedto">Assigned To</th>
+ <th tal:condition="request/show/priority">Priority</th>
+ <th tal:condition="request/show/id">ID</th>
+ <th tal:condition="request/show/activity">Activity</th>
+ <th tal:condition="request/show/title">Title</th>
+ <th tal:condition="request/show/status">Status</th>
+ <th tal:condition="request/show/creator">Created By</th>
+ <th tal:condition="request/show/assignedto">Assigned To</th>
</tr>
<tal:block tal:repeat="i batch">
<tr tal:condition="python:request.group[1] and
</th>
</tr>
<tr tal:attributes="class python:['normal', 'alt'][repeat['i'].even()]">
- <td tal:condition="exists:request/columns/priority"
- tal:content="i/priority"></td>
- <td tal:condition="exists:request/columns/id"
- tal:content="i/id"></td>
- <td tal:condition="exists:request/columns/activity"
- tal:content="i/activity/reldate"></td>
- <td tal:condition="exists:request/columns/title">
+ <td tal:condition="request/show/priority" tal:content="i/priority"></td>
+ <td tal:condition="request/show/id" tal:content="i/id"></td>
+ <td tal:condition="request/show/activity"
+ tal:content="i/activity/reldate"></td>
+ <td tal:condition="request/show/title">
<a tal:attributes="href string:issue${i/id}"
tal:content="python:i.title.value or '[no title]'">title</a>
</td>
- <td tal:condition="exists:request/columns/status"
- tal:content="i/status"></td>
- <td tal:condition="exists:request/columns/creator"
- tal:content="i/creator"></td>
- <td tal:condition="exists:request/columns/assignedto"
- tal:content="i/assignedto"></td>
+ <td tal:condition="request/show/status" tal:content="i/status"></td>
+ <td tal:condition="request/show/creator" tal:content="i/creator"></td>
+ <td tal:condition="request/show/assignedto" tal:content="i/assignedto"></td>
</tr>
</tal:block>
<tr>
<td>
<select name=":sort">
<option value="">- nothing -</option>
- <option tal:repeat="col python:request.columns.keys()"
+ <option tal:repeat="col request/columns"
tal:attributes="value col; selected python:col == request.sort[1]"
tal:content="col">column</option>
- </select>
+ </select>
</td>
<th>Descending:</th>
<td><input type="checkbox" name=":sortdir"
<tr>
<th>Group on:</th>
<td>
- <select name=":group">
- <option value="">- nothing -</option>
- <option tal:repeat="col python:request.columns.keys()"
+ <select name=":group">
+ <option value="">- nothing -</option>
+ <option tal:repeat="col request/columns"
tal:attributes="value col; selected python:col == request.group[1]"
tal:content="col">column</option>
- </select>
+ </select>
</td>
<th>Descending:</th>
<td><input type="checkbox" name=":groupdir"
diff --git a/roundup/templates/classic/html/issue.search b/roundup/templates/classic/html/issue.search
index 30ed9884978fd2d8314a4a3343251a843ec66a63..3bd1ca140bf65046b9fad10ca5aeba550de970e8 100644 (file)
<th>Activity:</th>
<td><input name="activity"></td>
<td><input type="checkbox" name=":columns" value="activity" checked></td>
- <td><input type="radio" name=":sort" value="activity" checked></td>
+ <td><input type="radio" name=":sort" value="activity"></td>
<td> </td>
</tr>
</td>
<td><input type="checkbox" name=":columns" value="priority"></td>
<td><input type="radio" name=":sort" value="priority"></td>
- <td><input type="radio" name=":group" value="priority" checked></td>
+ <td><input type="radio" name=":group" value="priority"></td>
</tr>
<tr>
</td>
</tr>
+<tr>
+<th>Query name**:</th>
+<td><input name=":queryname">
+</td>
+</tr>
+
<tr><td> </td>
<td><input type="submit" value="Search"></td>
</tr>
<tr><td> </td>
- <td colspan="4" class="help">*: The "all text" field will look in message
- bodies and issue titles</td>
+ <td colspan="4" class="help">
+ *: The "all text" field will look in message bodies and issue titles<br>
+ **: If you supply a name, the query will be saved off and available as a
+ link in the sidebar
+ </td>
</tr>
</table>
index c69172b6ad1548e19d940328065f1e42cf395d22..4c0de68aeeb475d23313e7037880830a9006edbb 100644 (file)
<tr>
<td rowspan="2" valign="top" nowrap class="sidebar">
+ <p class="classblock" tal:condition="request/user/queries">
+ <b>Your Queries</b><br>
+ <a tal:repeat="qs request/user/queries"
+ tal:attributes="href python:'%s%s'%(qs['klass'], qs['url'])"
+ tal:content="qs/name">link</a>
+ </p>
+
<p class="classblock"
tal:condition="python:request.user.hasPermission('View', 'issue')">
<b>Issues</b><br>