diff --git a/roundup/cgi_client.py b/roundup/cgi_client.py
index 5d84479c908f67a36e11748b0815cdbf2aa8b8c9..aeb5fd64e50b34e9f6c7c5197b2f0899be85bdba 100644 (file)
--- a/roundup/cgi_client.py
+++ b/roundup/cgi_client.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: cgi_client.py,v 1.98 2002-01-14 02:20:14 richard Exp $
+# $Id: cgi_client.py,v 1.111 2002-02-25 04:32:21 richard Exp $
__doc__ = """
WWW request handler (also used in the stand-alone server).
self.env = env
self.path = env['PATH_INFO']
self.split_path = self.path.split('/')
+ self.instance_path_name = env['INSTANCE_NAME']
+ url = self.env['SCRIPT_NAME'] + '/'
+ machine = self.env['SERVER_NAME']
+ port = self.env['SERVER_PORT']
+ if port != '80': machine = machine + ':' + port
+ self.base = urlparse.urlunparse(('http', env['HTTP_HOST'], url,
+ None, None, None))
if form is None:
self.form = cgi.FieldStorage(environ=env)
if self.debug:
self.headers_sent = headers
+ global_javascript = '''
+<script language="javascript">
+submitted = false;
+function submit_once() {
+ if (submitted) {
+ alert("Your request is being processed.\\nPlease be patient.");
+ return 0;
+ }
+ submitted = true;
+ return 1;
+}
+
+function help_window(helpurl, width, height) {
+ HelpWin = window.open('%(base)s%(instance_path_name)s/' + helpurl, 'HelpWindow', 'scrollbars=yes,resizable=yes,toolbar=no,height='+height+',width='+width);
+}
+
+</script>
+'''
+
def pagehead(self, title, message=None):
- url = self.env['SCRIPT_NAME'] + '/'
- machine = self.env['SERVER_NAME']
- port = self.env['SERVER_PORT']
- if port != '80': machine = machine + ':' + port
- base = urlparse.urlunparse(('http', machine, url, None, None, None))
if message is not None:
message = _('<div class="system-msg">%(message)s</div>')%locals()
else:
''')
else:
add_links = ''
+ global_javascript = self.global_javascript%self.__dict__
self.write(_('''<html><head>
<title>%(title)s</title>
<style type="text/css">%(style)s</style>
</head>
+%(global_javascript)s
<body bgcolor=#ffffff>
%(message)s
<table width=100%% border=0 cellspacing=0 cellpadding=2>
show_customization = self.customization_widget()
index = htmltemplate.IndexTemplate(self, self.instance.TEMPLATES, cn)
- index.render(filterspec, filter, columns, sort, group,
- show_customization=show_customization)
+ try:
+ index.render(filterspec, filter, columns, sort, group,
+ show_customization=show_customization)
+ except htmltemplate.MissingTemplateError:
+ self.basicClassEditPage()
self.pagefoot()
+ def basicClassEditPage(self):
+ '''Display a basic edit page that allows simple editing of the
+ nodes of the current class
+ '''
+ if self.user != 'admin':
+ raise Unauthorised
+ w = self.write
+ cn = self.classname
+ cl = self.db.classes[cn]
+ idlessprops = cl.getprops(protected=0).keys()
+ props = ['id'] + idlessprops
+
+
+ # get the CSV module
+ try:
+ import csv
+ except ImportError:
+ w(_('Sorry, you need the csv module to use this function.<br>\n'
+ 'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/'))
+ return
+
+ # do the edit
+ if self.form.has_key('rows'):
+ rows = self.form['rows'].value.splitlines()
+ p = csv.parser()
+ found = {}
+ line = 0
+ for row in rows:
+ line += 1
+ values = p.parse(row)
+ # not a complete row, keep going
+ if not values: continue
+
+ # extract the nodeid
+ nodeid, values = values[0], values[1:]
+ found[nodeid] = 1
+
+ # confirm correct weight
+ if len(idlessprops) != len(values):
+ w(_('Not enough values on line %(line)s'%{'line':line}))
+ return
+
+ # extract the new values
+ d = {}
+ for name, value in zip(idlessprops, values):
+ d[name] = value.strip()
+
+ # perform the edit
+ if cl.hasnode(nodeid):
+ # edit existing
+ cl.set(nodeid, **d)
+ else:
+ # new node
+ found[cl.create(**d)] = 1
+
+ # retire the removed entries
+ for nodeid in cl.list():
+ if not found.has_key(nodeid):
+ cl.retire(nodeid)
+
+ w(_('''<p class="form-help">You may edit the contents of the
+ "%(classname)s" class using this form. The lines are full-featured
+ Comma-Separated-Value lines, so you may include commas and even
+ newlines by enclosing the values in double-quotes ("). Double
+ quotes themselves must be quoted by doubling ("").</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>''')%{'classname':cn})
+
+ l = []
+ for name in props:
+ l.append(name)
+ w('<tt>')
+ w(', '.join(l) + '\n')
+ w('</tt>')
+
+ w('<form onSubmit="return submit_once()" method="POST">')
+ w('<textarea name="rows" cols=80 rows=15>')
+ p = csv.parser()
+ for nodeid in cl.list():
+ l = []
+ for name in props:
+ l.append(cgi.escape(str(cl.get(nodeid, name))))
+ w(p.join(l) + '\n')
+
+ w(_('</textarea><br><input type="submit" value="Save Changes"></form>'))
+
+ def classhelp(self):
+ '''Display a table of class info
+ '''
+ w = self.write
+ cn = self.form['classname'].value
+ cl = self.db.classes[cn]
+ props = self.form['properties'].value.split(',')
+
+ w('<table border=1 cellspacing=0 cellpaddin=2>')
+ w('<tr>')
+ for name in props:
+ w('<th align=left>%s</th>'%name)
+ w('</tr>')
+ for nodeid in cl.list():
+ w('<tr>')
+ for name in props:
+ value = cgi.escape(str(cl.get(nodeid, name)))
+ w('<td align="left" valign="top">%s</td>'%value)
+ w('</tr>')
+ w('</table>')
+
def shownode(self, message=None):
''' display an item
'''
', '.join(props.keys())}
elif self.form.has_key('__note') and self.form['__note'].value:
message = _('note added')
- elif self.form.has_key('__file'):
+ elif (self.form.has_key('__file') and
+ self.form['__file'].filename):
message = _('file added')
else:
message = _('nothing changed')
return
assignedto_id = props['assignedto']
if not props.has_key('nosy'):
- props['nosy'] = [assignedto_id]
- elif assignedto_id not in props['nosy']:
+ # load current nosy
+ if self.nodeid:
+ cl = self.db.classes[self.classname]
+ l = cl.get(self.nodeid, 'nosy')
+ if assignedto_id in l:
+ return
+ props['nosy'] = l
+ else:
+ props['nosy'] = []
+ if assignedto_id not in props['nosy']:
props['nosy'].append(assignedto_id)
def _changenode(self, props):
self.write('<table border=0 cellspacing=0 cellpadding=2>\n')
for cn in classnames:
cl = self.db.getclass(cn)
- self.write('<tr class="list-header"><th colspan=2 align=left>%s</th></tr>'%cn.capitalize())
+ self.write('<tr class="list-header"><th colspan=2 align=left>'
+ '<a href="%s">%s</a></th></tr>'%(cn, cn.capitalize()))
for key, value in cl.properties.items():
if value is None: value = ''
else: value = str(value)
self.write(_('''
<table>
<tr><td colspan=2 class="strong-header">Existing User Login</td></tr>
-<form action="login_action" method=POST>
+<form onSubmit="return submit_once()" action="login_action" method=POST>
<input type="hidden" name="__destination_url" value="%(action)s">
<tr><td align=right>Login name: </td>
<td><input name="__login_name"></td></tr>
return
values = {'realname': '', 'organisation': '', 'address': '',
'phone': '', 'username': '', 'password': '', 'confirm': '',
- 'action': action}
+ 'action': action, 'alternate_addresses': ''}
if newuser_form is not None:
for key in newuser_form.keys():
values[key] = newuser_form[key].value
<p>
<tr><td colspan=2 class="strong-header">New User Registration</td></tr>
<tr><td colspan=2><em>marked items</em> are optional...</td></tr>
-<form action="newuser_action" method=POST>
+<form onSubmit="return submit_once()" action="newuser_action" method=POST>
<input type="hidden" name="__destination_url" value="%(action)s">
<tr><td align=right><em>Name: </em></td>
- <td><input name="realname" value="%(realname)s"></td></tr>
+ <td><input name="realname" value="%(realname)s" size=40></td></tr>
<tr><td align=right><em>Organisation: </em></td>
- <td><input name="organisation" value="%(organisation)s"></td></tr>
+ <td><input name="organisation" value="%(organisation)s" size=40></td></tr>
<tr><td align=right>E-Mail Address: </td>
- <td><input name="address" value="%(address)s"></td></tr>
+ <td><input name="address" value="%(address)s" size=40></td></tr>
+<tr><td align=right><em>Alternate E-mail Addresses: </em></td>
+ <td><textarea name="alternate_addresses" rows=5 cols=40>%(alternate_addresses)s</textarea></td></tr>
<tr><td align=right><em>Phone: </em></td>
<td><input name="phone" value="%(phone)s"></td></tr>
<tr><td align=right>Preferred Login name: </td>
if action == 'list_classes':
self.classes()
return
+ if action == 'classhelp':
+ self.classhelp()
+ return
if action == 'login':
self.login()
return
if action == 'logout':
self.logout()
return
+
+ # see if we're to display an existing node
m = dre.match(action)
if m:
self.classname = m.group(1)
raise NotFound
func()
return
+
+ # see if we're to put up the new node page
m = nre.match(action)
if m:
self.classname = m.group(1)
raise NotFound
func()
return
+
+ # otherwise, display the named class
self.classname = action
try:
self.db.getclass(self.classname)
default_index_filterspec = {'status': ['1', '2', '3', '4', '5', '6', '7']}
def pagehead(self, title, message=None):
- url = self.env['SCRIPT_NAME'] + '/' #self.env.get('PATH_INFO', '/')
- machine = self.env['SERVER_NAME']
- port = self.env['SERVER_PORT']
- if port != '80': machine = machine + ':' + port
- base = urlparse.urlunparse(('http', machine, url, None, None, None))
if message is not None:
message = _('<div class="system-msg">%(message)s</div>')%locals()
else:
''')
else:
add_links = ''
+ global_javascript = self.global_javascript%self.__dict__
self.write(_('''<html><head>
<title>%(title)s</title>
<style type="text/css">%(style)s</style>
</head>
+%(global_javascript)s
<body bgcolor=#ffffff>
%(message)s
<table width=100%% border=0 cellspacing=0 cellpadding=2>
elif isinstance(proptype, hyperdb.Password):
value = password.Password(form[key].value.strip())
elif isinstance(proptype, hyperdb.Date):
- value = date.Date(form[key].value.strip())
+ value = form[key].value.strip()
+ if value:
+ value = date.Date(form[key].value.strip())
+ else:
+ value = None
elif isinstance(proptype, hyperdb.Interval):
- value = date.Interval(form[key].value.strip())
+ value = form[key].value.strip()
+ if value:
+ value = date.Interval(form[key].value.strip())
+ else:
+ value = None
elif isinstance(proptype, hyperdb.Link):
value = form[key].value.strip()
# see if it's the "no selection" choice
#
# $Log: not supported by cvs2svn $
+# Revision 1.110 2002/02/21 07:19:08 richard
+# ... and label, width and height control for extra flavour!
+#
+# Revision 1.109 2002/02/21 07:08:19 richard
+# oops
+#
+# Revision 1.108 2002/02/21 07:02:54 richard
+# The correct var is "HTTP_HOST"
+#
+# Revision 1.107 2002/02/21 06:57:38 richard
+# . Added popup help for classes using the classhelp html template function.
+# - add <display call="classhelp('priority', 'id,name,description')">
+# to an item page, and it generates a link to a popup window which displays
+# the id, name and description for the priority class. The description
+# field won't exist in most installations, but it will be added to the
+# default templates.
+#
+# Revision 1.106 2002/02/21 06:23:00 richard
+# *** empty log message ***
+#
+# Revision 1.105 2002/02/20 05:52:10 richard
+# better error handling
+#
+# Revision 1.104 2002/02/20 05:45:17 richard
+# Use the csv module for generating the form entry so it's correct.
+# [also noted the sf.net feature request id in the change log]
+#
+# Revision 1.103 2002/02/20 05:05:28 richard
+# . Added simple editing for classes that don't define a templated interface.
+# - access using the admin "class list" interface
+# - limited to admin-only
+# - requires the csv module from object-craft (url given if it's missing)
+#
+# Revision 1.102 2002/02/15 07:08:44 richard
+# . Alternate email addresses are now available for users. See the MIGRATION
+# file for info on how to activate the feature.
+#
+# Revision 1.101 2002/02/14 23:39:18 richard
+# . All forms now have "double-submit" protection when Javascript is enabled
+# on the client-side.
+#
+# Revision 1.100 2002/01/16 07:02:57 richard
+# . lots of date/interval related changes:
+# - more relaxed date format for input
+#
+# Revision 1.99 2002/01/16 03:02:42 richard
+# #503793 ] changing assignedto resets nosy list
+#
+# Revision 1.98 2002/01/14 02:20:14 richard
+# . changed all config accesses so they access either the instance or the
+# config attriubute on the db. This means that all config is obtained from
+# instance_config instead of the mish-mash of classes. This will make
+# switching to a ConfigParser setup easier too, I hope.
+#
+# At a minimum, this makes migration a _little_ easier (a lot easier in the
+# 0.5.0 switch, I hope!)
+#
# Revision 1.97 2002/01/11 23:22:29 richard
# . #502437 ] rogue reactor and unittest
# in short, the nosy reactor was modifying the nosy list. That code had