summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 98d4be2)
raw | patch | inline | side by side (parent: 98d4be2)
author | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Wed, 4 Sep 2002 04:31:51 +0000 (04:31 +0000) | ||
committer | richard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2> | |
Wed, 4 Sep 2002 04:31:51 +0000 (04:31 +0000) |
git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1060 57a73879-2fb5-44c3-a270-3262357dd7e2
TODO.txt | patch | blob | history | |
roundup/cgi/client.py | patch | blob | history | |
roundup/cgi/templating.py | patch | blob | history | |
roundup/templates/classic/html/_generic.help | [new file with mode: 0644] | patch | blob |
roundup/templates/classic/html/_generic.index | [new file with mode: 0644] | patch | blob |
roundup/templates/classic/html/home.classlist | patch | blob | history |
diff --git a/TODO.txt b/TODO.txt
index 1a8feb9674ce64bfc7ca3dd03e5a82ab1608435f..027a6a8fe6fc709fbf0d4c3d9b61bf58e2eedba4 100644 (file)
--- a/TODO.txt
+++ b/TODO.txt
pending web: have roundup.cgi pick up instance config from the environment
New templating TODO:
-. generic class editing
-. classhelp
. 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 b3f862842f650825eda6a6ebd94b4dd68e6ffd2d..a9443765c1c6bfc7479336cd1b97021457bb20f4 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
-# $Id: client.py,v 1.10 2002-09-03 07:42:38 richard Exp $
+# $Id: client.py,v 1.11 2002-09-04 04:31:51 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
self.determine_user()
# figure out the context and desired content template
self.determine_context()
- # possibly handle a form submit action (may change self.message
- # and self.template_name)
+ # possibly handle a form submit action (may change self.message,
+ # self.classname and self.template)
self.handle_action()
# now render the page
- self.write(self.template('page', ok_message=self.ok_message,
+ self.write(self.renderTemplate('page', '', ok_message=self.ok_message,
error_message=self.error_message))
except Redirect, url:
# let's redirect - if the url isn't None, then we need to do
except SendStaticFile, file:
self.serve_static_file(str(file))
except Unauthorised, message:
- self.write(self.template('page.unauthorised',
- error_message=message))
+ self.write(self.renderTemplate('page', '', error_message=message))
except:
# everything else
self.write(cgitb.html())
full item designator supplied: "item"
We set:
- self.classname
- self.nodeid
- self.template_name
+ self.classname - the class to display, can be None
+ self.template - the template to render the current context with
+ self.nodeid - the nodeid of the class we're displaying
'''
# default the optional variables
self.classname = None
path = self.split_path
if not path or path[0] in ('', 'home', 'index'):
if self.form.has_key(':template'):
- self.template_type = self.form[':template'].value
- self.template_name = 'home' + '.' + self.template_type
+ self.template = self.form[':template'].value
else:
- self.template_type = ''
- self.template_name = 'home'
+ self.template = ''
return
elif path[0] == '_file':
raise SendStaticFile, path[1]
self.classname = m.group(1)
self.nodeid = m.group(2)
# with a designator, we default to item view
- self.template_type = 'item'
+ self.template = 'item'
else:
# with only a class, we default to index view
- self.template_type = 'index'
+ self.template = 'index'
# see if we have a template override
if self.form.has_key(':template'):
- self.template_type = self.form[':template'].value
+ self.template = self.form[':template'].value
# see if we were passed in a message
if self.form.has_key(':error_message'):
self.error_message.append(self.form[':error_message'].value)
- # we have the template name now
- self.template_name = self.classname + '.' + self.template_type
-
def serve_file(self, designator, dre=re.compile(r'([^\d]+)(\d+)')):
''' Serve the file from the content property of the designated item.
'''
self.header({'Content-Type': mt})
self.write(open(os.path.join(self.instance.TEMPLATES, file)).read())
- def template(self, name, **kwargs):
+ def renderTemplate(self, name, extension, **kwargs):
''' Return a PageTemplate for the named page
'''
- pt = getTemplate(self.instance.TEMPLATES, name)
+ pt = getTemplate(self.instance.TEMPLATES, name, extension)
# XXX handle PT rendering errors here more nicely
try:
# let the template render figure stuff out
def content(self):
''' Callback used by the page template to render the content of
the page.
+
+ If we don't have a specific class to display, that is none was
+ determined in determine_context(), then we display a "home"
+ template.
'''
# now render the page content using the template we determined in
# determine_context
- return self.template(self.template_name)
+ if self.classname is None:
+ name = 'home'
+ else:
+ name = self.classname
+ return self.renderTemplate(self.classname, self.template)
# these are the actions that are available
actions = {
'edit': 'editItemAction',
+ 'editCSV': 'editCSVAction',
'new': 'newItemAction',
'register': 'registerAction',
'login': 'login_action',
self.error_message.append(
_('You do not have permission to create %s' %self.classname))
- # XXX
-# cl = self.db.classes[cn]
-# if self.form.has_key(':multilink'):
-# link = self.form[':multilink'].value
-# designator, linkprop = link.split(':')
-# xtra = ' for <a href="%s">%s</a>' % (designator, designator)
-# else:
-# xtra = ''
+ # create a little extra message for anticipated :link / :multilink
+ if self.form.has_key(':multilink'):
+ link = self.form[':multilink'].value
+ elif self.form.has_key(':link'):
+ link = self.form[':multilink'].value
+ else:
+ link = None
+ xtra = ''
+ if link:
+ designator, linkprop = link.split(':')
+ xtra = ' for <a href="%s">%s</a>'%(designator, designator)
try:
# do the create
self.nodeid = nid
# and some nice feedback for the user
- message = _('%(classname)s created ok')%self.__dict__
+ message = _('%(classname)s created ok')%self.__dict__ + xtra
except (ValueError, KeyError), message:
self.error_message.append(_('Error: ') + str(message))
return
return 1
return 0
- def genericEditAction(self):
+ def editCSVAction(self):
''' Performs an edit of all of a class' items in one go.
The "rows" CGI var defines the CSV-formatted entries for the
class. New nodes are identified by the ID 'X' (or any other
non-existent ID) and removed lines are retired.
'''
- # generic edit is per-class only
- if not self.genericEditPermission():
+ # this is per-class only
+ if not self.editCSVPermission():
self.error_message.append(
_('You do not have permission to edit %s' %self.classname))
cl = self.db.classes[self.classname]
idlessprops = cl.getprops(protected=0).keys()
+ idlessprops.sort()
props = ['id'] + idlessprops
# do the edit
p = csv.parser()
found = {}
line = 0
- for row in rows:
+ for row in rows[1:]:
line += 1
values = p.parse(row)
# not a complete row, keep going
if not values: continue
+ # skip property names header
+ if values == props:
+ continue
+
# extract the nodeid
nodeid, values = values[0], values[1:]
found[nodeid] = 1
# confirm correct weight
if len(idlessprops) != len(values):
- message=(_('Not enough values on line %(line)s'%{'line':line}))
+ self.error_message.append(
+ _('Not enough values on line %(line)s')%{'line':line})
return
# extract the new values
if not found.has_key(nodeid):
cl.retire(nodeid)
- message = _('items edited OK')
+ # all OK
+ self.db.commit()
- # redirect to the class' edit page
- raise Redirect, '%s/%s?:ok_message=%s'%(self.base, self.classname,
- urllib.quote(message))
+ self.ok_message.append(_('Items edited OK'))
- def genericEditPermission(self):
+ def editCSVPermission(self):
''' Determine whether the user has permission to edit this class.
Base behaviour is to check the user can edit this class.
index dfc662b2dcb7df7526077cd2ad24aa415265ecd5..aea865120c74e74ecc7e398f55453e46c2ceac06 100644 (file)
-import sys, cgi, urllib, os, re, os.path, time
+import sys, cgi, urllib, os, re, os.path, time, errno
from roundup import hyperdb, date
from roundup.i18n import _
templates = {}
-def getTemplate(dir, name, classname=None, request=None):
+def getTemplate(dir, name, extension, classname=None, request=None):
''' Interface to get a template, possibly loading a compiled template.
+
+ "name" and "extension" indicate the template we're after, which in
+ most cases will be "name.extension". If "extension" is None, then
+ we look for a template just called "name" with no extension.
+
+ If the file "name.extension" doesn't exist, we look for
+ "_generic.extension" as a fallback.
'''
- # 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]
+ # default the name to "home"
+ if name is None:
+ name = 'home'
- key = (dir, name)
+ # find the source, figure the time it was last modified
+ if extension:
+ filename = '%s.%s'%(name, extension)
+ else:
+ filename = name
+ src = os.path.join(dir, filename)
+ try:
+ stime = os.stat(src)[os.path.stat.ST_MTIME]
+ except os.error, error:
+ if error.errno != errno.ENOENT or not extension:
+ raise
+ # try for a generic template
+ filename = '_generic.%s'%extension
+ src = os.path.join(dir, filename)
+ stime = os.stat(src)[os.path.stat.ST_MTIME]
+
+ key = (dir, filename)
if templates.has_key(key) and stime < templates[key].mtime:
# compiled template is up to date
return templates[key]
l = [HTMLItem(self.db, self.classname, x) for x in self.klass.list()]
return l
+ def csv(self):
+ ''' Return the items of this class as a chunk of CSV text.
+ '''
+ # get the CSV module
+ try:
+ import csv
+ except ImportError:
+ return 'Sorry, you need the csv module to use this function.\n'\
+ 'Get it from: http://www.object-craft.com.au/projects/csv/'
+
+ props = self.propnames()
+ p = csv.parser()
+ s = StringIO.StringIO()
+ s.write(p.join(props) + '\n')
+ for nodeid in self.klass.list():
+ l = []
+ for name in props:
+ value = self.klass.get(nodeid, name)
+ if value is None:
+ l.append('')
+ elif isinstance(value, type([])):
+ l.append(':'.join(map(str, value)))
+ else:
+ l.append(str(self.klass.get(nodeid, name)))
+ s.write(p.join(l) + '\n')
+ return s.getvalue()
+
+ def propnames(self):
+ ''' Return the list of the names of the properties of this class.
+ '''
+ idlessprops = self.klass.getprops(protected=0).keys()
+ idlessprops.sort()
+ return ['id'] + idlessprops
+
def filter(self, request=None):
''' Return a list of items from this class, filtered and sorted
by the current requested filterspec/filter/sort/group args
You may optionally override the label displayed, the width and
height. The popup window will be resizable and scrollable.
'''
- return '<a href="javascript:help_window(\'classhelp?classname=%s&' \
+ return '<a href="javascript:help_window(\'%s?:template=help&' \
'properties=%s\', \'%s\', \'%s\')"><b>(%s)</b></a>'%(self.classname,
properties, width, height, label)
req.update(kwargs)
# new template, using the specified classname and request
- name = self.classname + '.' + name
- pt = getTemplate(self.db.config.TEMPLATES, name)
+ pt = getTemplate(self.db.config.TEMPLATES, self.classname, name)
# XXX handle PT rendering errors here nicely
try:
"base" the base URL for this instance
"user" a HTMLUser instance for this user
"classname" the current classname (possibly None)
- "template_type" the current template type (suffix, also possibly None)
+ "template" the current template (suffix, also possibly None)
Index args:
"columns" dictionary of the columns to display in an index page
# store the current class name and action
self.classname = client.classname
- self.template_type = client.template_type
+ self.template = client.template
# extract the index display information from the form
self.columns = []
url: %(url)r
base: %(base)r
classname: %(classname)r
-template_type: %(template_type)r
+template: %(template)r
columns: %(columns)r
sort: %(sort)r
group: %(group)r
diff --git a/roundup/templates/classic/html/_generic.help b/roundup/templates/classic/html/_generic.help
--- /dev/null
@@ -0,0 +1,11 @@
+<table tal:define="props python:request.form['properties'].value.split(',')"
+ border=1 cellspacing=0 cellpadding=2>
+<tr>
+ <th align=left tal:repeat="prop props" tal:content="prop"></th>
+</tr>
+<tr tal:repeat="item klass/list">
+ <td align="left" valign="top" tal:repeat="prop props"
+ tal:content="python:item[prop]"></td>
+</tr>
+</table>
+
diff --git a/roundup/templates/classic/html/_generic.index b/roundup/templates/classic/html/_generic.index
--- /dev/null
@@ -0,0 +1,27 @@
+<!-- dollarId: issue.index,v 1.2 2001/07/29 04:07:37 richard Exp dollar-->
+
+<p class="form-help">
+ You may edit the contents of the <span tal:replace="request/classname" />
+ class using this form. Commas, newlines and double quotes (") must be
+ handled delicately. You may include commas and newlines by enclosing the
+ values in double-quotes ("). Double quotes themselves must be quoted by
+ doubling ("").
+</p>
+
+<p class="form-help">
+ Multilink properties have their multiple values colon (":") separated
+ (... ,"one:two:three", ...)
+</p>
+
+<p class="form-help">
+ Remove entries by deleting their line. Add new entries by appending
+ them to the table - put an X in the id column.
+</p>
+
+<form onSubmit="return submit_once()" method="POST">
+<textarea rows="15" cols="60" name="rows" tal:content="klass/csv"></textarea>
+<br>
+<input type="hidden" name=":action" value="editCSV">
+<input type="submit" value="Edit Items">
+</form>
+
diff --git a/roundup/templates/classic/html/home.classlist b/roundup/templates/classic/html/home.classlist
index bcabfea114dff31b24b301fca1227ab437d4fedc..f79ce5480152f0e490819e78f72bb180eedc14b7 100644 (file)
<tal:block tal:repeat="cl db/classes">
<tr class="list-header">
<th colspan="2" align="left">
- <a tal:attributes="href string:${cl/classname}?:template=genericedit"
+ <a tal:attributes="href string:${cl/classname}"
tal:content="python:cl.classname.capitalize()">classname</a>
</th>
</tr>