diff --git a/roundup/cgi/client.py b/roundup/cgi/client.py
index d265bd404d395d6f8e0fe35ee8b155c25e10cf40..a2e1e1ee03e7c49937ddadc23ae04a047dca56bf 100644 (file)
--- a/roundup/cgi/client.py
+++ b/roundup/cgi/client.py
-# $Id: client.py,v 1.30 2002-09-12 06:05:34 richard Exp $
+# $Id: client.py,v 1.42 2002-09-25 02:10:25 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 getTemplate, HTMLRequest, NoTemplate
+from roundup.cgi.templating import Templates, HTMLRequest, NoTemplate
from roundup.cgi import cgitb
from roundup.cgi.PageTemplates import PageTemplate
keeps the nodeid of the session as the "session" attribute.
Client attributes:
- "url" is the current url path
- "path" is the PATH_INFO inside the instance
+ "path" is the PATH_INFO inside the instance (with no leading '/')
"base" is the base URL for the instance
'''
self.request = request
self.env = env
+ # save off the path
self.path = env['PATH_INFO']
- self.split_path = self.path.split('/')
- self.instance_path_name = env['TRACKER_NAME']
# this is the base URL for this instance
- url = self.env['SCRIPT_NAME'] + '/' + self.instance_path_name
- self.base = urlparse.urlunparse(('http', env['HTTP_HOST'], url,
- None, None, None))
-
- # request.path is the full request path
- x, x, path, x, x, x = urlparse.urlparse(request.path)
- self.url = urlparse.urlunparse(('http', env['HTTP_HOST'], path,
- None, None, None))
+ self.base = self.instance.config.TRACKER_WEB
+ # see if we need to re-parse the environment for the form (eg Zope)
if form is None:
self.form = cgi.FieldStorage(environ=env)
else:
self.form = form
- self.headers_done = 0
+
+ # turn debugging on/off
try:
self.debug = int(env.get("ROUNDUP_DEBUG", 0))
except ValueError:
# someone gave us a non-int debug level, turn it off
self.debug = 0
+ # flag to indicate that the HTTP headers have been sent
+ self.headers_done = 0
+
# additional headers to send with the request - must be registered
# before the first write
self.additional_headers = {}
self.response_code = 200
def main(self):
+ ''' Wrap the real main in a try/finally so we always close off the db.
+ '''
+ try:
+ self.inner_main()
+ finally:
+ if hasattr(self, 'db'):
+ self.db.close()
+
+ def inner_main(self):
''' Process a request.
The most common requests are handled like so:
# and self.template, and may also append error/ok_messages)
self.handle_action()
# now render the page
- if self.form.has_key(':contentonly'):
- # just the content
- self.write(self.content())
- else:
- # render the content inside the page template
- self.write(self.renderTemplate('page', '',
- ok_message=self.ok_message,
- error_message=self.error_message))
+
+ # we don't want clients caching our dynamic pages
+ self.additional_headers['Cache-Control'] = 'no-cache'
+ self.additional_headers['Pragma'] = 'no-cache'
+ self.additional_headers['Expires'] = 'Thu, 1 Jan 1970 00:00:00 GMT'
+
+ # render the content
+ self.write(self.renderContext())
except Redirect, url:
# let's redirect - if the url isn't None, then we need to do
# the headers, otherwise the headers have been set before the
self.nodeid = None
# determine the classname and possibly nodeid
- path = self.split_path
+ path = self.path.split('/')
if not path or path[0] in ('', 'home', 'index'):
if self.form.has_key(':template'):
self.template = self.form[':template'].value
if m:
self.classname = m.group(1)
self.nodeid = m.group(2)
+ if not self.db.getclass(self.classname).hasnode(self.nodeid):
+ raise NotFound, '%s/%s'%(self.classname, self.nodeid)
# with a designator, we default to item view
self.template = 'item'
else:
self.write(open(os.path.join(self.instance.config.TEMPLATES,
file)).read())
- def renderTemplate(self, name, extension, **kwargs):
+ def renderContext(self):
''' Return a PageTemplate for the named page
'''
- pt = getTemplate(self.instance.config.TEMPLATES, name, extension)
+ name = self.classname
+ extension = self.template
+ pt = Templates(self.instance.config.TEMPLATES).get(name, extension)
+
# catch errors so we can handle PT rendering errors more nicely
+ args = {
+ 'ok_message': self.ok_message,
+ 'error_message': self.error_message
+ }
try:
# 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))
+ return pt.render(self, None, None, **args)
except NoTemplate, message:
return '<strong>%s</strong>'%message
except:
# everything else
return cgitb.pt_html()
- 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
- 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': 'loginAction',
- 'logout': 'logout_action',
- 'search': 'searchAction',
- }
+ actions = (
+ ('edit', 'editItemAction'),
+ ('editCSV', 'editCSVAction'),
+ ('new', 'newItemAction'),
+ ('register', 'registerAction'),
+ ('login', 'loginAction'),
+ ('logout', 'logout_action'),
+ ('search', 'searchAction'),
+ )
def handle_action(self):
''' Determine whether there should be an _action called.
The action is defined by the form variable :action which
identifies the method on this object to call. The four basic
- actions are defined in the "actions" dictionary on this class:
+ actions are defined in the "actions" sequence on this class:
"edit" -> self.editItemAction
"new" -> self.newItemAction
"register" -> self.registerAction
try:
# get the action, validate it
action = self.form[':action'].value
- if not self.actions.has_key(action):
+ for name, method in self.actions:
+ if name == action:
+ break
+ else:
raise ValueError, 'No such action "%s"'%action
# call the mapped action
- getattr(self, self.actions[action])()
+ getattr(self, method)()
except Redirect:
raise
except Unauthorised:
headers = {'Content-Type':'text/html'}
if response is None:
response = self.response_code
+
+ # update with additional info
headers.update(self.additional_headers)
+
if not headers.has_key('Content-Type'):
headers['Content-Type'] = 'text/html'
self.request.send_response(response)
self.userid = self.db.user.lookup('anonymous')
self.user = 'anonymous'
- def logout(self):
- ''' Make us really anonymous - nuke the cookie too
- '''
- self.make_user_anonymous()
-
- # construct the logout cookie
- now = Cookie._getdate()
- path = '/'.join((self.env['SCRIPT_NAME'], self.env['TRACKER_NAME'],
- ''))
- self.additional_headers['Set-Cookie'] = \
- 'roundup_user_2=deleted; Max-Age=0; expires=%s; Path=%s;'%(now, path)
- self.login()
-
def opendb(self, user):
''' Open the database.
'''
# open the db if the user has changed
if not hasattr(self, 'db') or user != self.db.journaltag:
+ if hasattr(self, 'db'):
+ self.db.close()
self.db = self.instance.open(user)
#
self.db.commit()
except ValueError, message:
self.error_message.append(message)
+ return
# log the new user in
self.user = cl.get(self.userid, 'username')
message = _('You are now registered, welcome!')
# redirect to the item's edit page
- raise Redirect, '%s/%s%s?:ok_message=%s'%(
+ raise Redirect, '%s%s%s?:ok_message=%s'%(
self.base, self.classname, self.userid, urllib.quote(message))
def registerPermission(self, props):
message = _('nothing changed')
# redirect to the item's edit page
- raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname,
+ raise Redirect, '%s%s%s?:ok_message=%s'%(self.base, self.classname,
self.nodeid, urllib.quote(message))
def editItemPermission(self, props):
return
# redirect to the new item's page
- raise Redirect, '%s/%s%s?:ok_message=%s'%(self.base, self.classname,
+ raise Redirect, '%s%s%s?:ok_message=%s'%(self.base, self.classname,
nid, urllib.quote(message))
def newItemPermission(self, props):
props = {}
keys = form.keys()
+ properties = cl.getprops()
for key in keys:
- if not cl.properties.has_key(key):
+ if not properties.has_key(key):
continue
- proptype = cl.properties[key]
+ proptype = properties[key]
# Get the form value. This value may be a MiniFieldStorage or a list
# of MiniFieldStorages.
if not value:
# ignore empty password values
continue
+ if not form.has_key('%s:confirm'%key):
+ raise ValueError, 'Password and confirmation text do not match'
+ confirm = form['%s:confirm'%key]
+ if isinstance(confirm, type([])):
+ raise ValueError, 'You have submitted more than one value'\
+ ' for the %s property'%key
+ if value != confirm.value:
+ raise ValueError, 'Password and confirmation text do not match'
value = password.Password(value)
elif isinstance(proptype, hyperdb.Date):
if value:
value = date.Date(form[key].value.strip())
else:
- value = None
+ continue
elif isinstance(proptype, hyperdb.Interval):
if value:
value = date.Interval(form[key].value.strip())
else:
- value = None
+ continue
elif isinstance(proptype, hyperdb.Link):
# see if it's the "no selection" choice
if value == '-1':
- value = None
- else:
- # handle key values
- link = cl.properties[key].classname
- if not num_re.match(value):
- try:
- value = db.classes[link].lookup(value)
- except KeyError:
- raise ValueError, _('property "%(propname)s": '
- '%(value)s not a %(classname)s')%{'propname':key,
- 'value': value, 'classname': link}
- except TypeError, message:
- raise ValueError, _('you may only enter ID values '
- 'for property "%(propname)s": %(message)s')%{
- 'propname':key, 'message': message}
+ continue
+ # handle key values
+ link = proptype.classname
+ if not num_re.match(value):
+ try:
+ value = db.classes[link].lookup(value)
+ except KeyError:
+ raise ValueError, _('property "%(propname)s": '
+ '%(value)s not a %(classname)s')%{'propname':key,
+ 'value': value, 'classname': link}
+ except TypeError, message:
+ raise ValueError, _('you may only enter ID values '
+ 'for property "%(propname)s": %(message)s')%{
+ 'propname':key, 'message': message}
elif isinstance(proptype, hyperdb.Multilink):
if isinstance(value, type([])):
# it's a list of MiniFieldStorages
# it's a MiniFieldStorage, but may be a comma-separated list
# of values
value = [i.strip() for i in value.value.split(',')]
- link = cl.properties[key].classname
+ link = proptype.classname
l = []
for entry in map(str, value):
if entry == '': continue
except KeyError:
# this might be a new property for which there is no existing
# value
- if not cl.properties.has_key(key): raise
+ if not properties.has_key(key): raise
# if changed, set it
if value != existing: