index 062462bff6e7c1a58d7b25615c047d9405ad8cfb..68abcaa04c1c382a40f5d2b260eeb8a146f58213 100644 (file)
+"""Implements the API used in the HTML templating for the web interface.
+"""
+__docformat__ = 'restructuredtext'
+
+from __future__ import nested_scopes
+
import sys, cgi, urllib, os, re, os.path, time, errno, mimetypes
from roundup import hyperdb, date, rcsv
class NoTemplate(Exception):
pass
+class Unauthorised(Exception):
+ def __init__(self, action, klass):
+ self.action = action
+ self.klass = klass
+ def __str__(self):
+ return 'You are not allowed to %s items of class %s'%(self.action,
+ self.klass)
+
def find_template(dir, name, extension):
''' Find a template in the nominated dir
'''
raise
if self.templates.has_key(src) and \
- stime < self.templates[src].mtime:
+ stime <= self.templates[src].mtime:
# compiled template is up to date
return self.templates[src]
content_type = mimetypes.guess_type(filename)[0] or 'text/html'
pt.pt_edit(open(src).read(), content_type)
pt.id = filename
- pt.mtime = time.time()
+ pt.mtime = stime
return pt
def __getitem__(self, name):
raise KeyError, message
class RoundupPageTemplate(PageTemplate.PageTemplate):
- ''' A Roundup-specific PageTemplate.
-
- Interrogate the client to set up the various template variables to
- be available:
-
- *context*
- this is one of three things:
- 1. None - we're viewing a "home" page
- 2. The current class of item being displayed. This is an HTMLClass
- instance.
- 3. The current item from the database, if we're viewing a specific
- item, as an HTMLItem instance.
- *request*
- Includes information about the current request, including:
- - the url
- - the current index information (``filterspec``, ``filter`` args,
- ``properties``, etc) parsed out of the form.
- - methods for easy filterspec link generation
- - *user*, the current user node as an HTMLItem instance
- - *form*, the current CGI form information as a FieldStorage
- *config*
- The current tracker config.
- *db*
- The current database, used to access arbitrary database items.
- *utils*
- This is a special class that has its base in the TemplatingUtils
- class in this file. If the tracker interfaces module defines a
- TemplatingUtils class then it is mixed in, overriding the methods
- in the base class.
+ '''A Roundup-specific PageTemplate.
+
+ Interrogate the client to set up the various template variables to
+ be available:
+
+ *context*
+ this is one of three things:
+
+ 1. None - we're viewing a "home" page
+ 2. The current class of item being displayed. This is an HTMLClass
+ instance.
+ 3. The current item from the database, if we're viewing a specific
+ item, as an HTMLItem instance.
+ *request*
+ Includes information about the current request, including:
+
+ - the url
+ - the current index information (``filterspec``, ``filter`` args,
+ ``properties``, etc) parsed out of the form.
+ - methods for easy filterspec link generation
+ - *user*, the current user node as an HTMLItem instance
+ - *form*, the current CGI form information as a FieldStorage
+ *config*
+ The current tracker config.
+ *db*
+ The current database, used to access arbitrary database items.
+ *utils*
+ This is a special class that has its base in the TemplatingUtils
+ class in this file. If the tracker interfaces module defines a
+ TemplatingUtils class then it is mixed in, overriding the methods
+ in the base class.
'''
def getContext(self, client, classname, request):
# construct the TemplatingUtils class
'tracker': client.instance,
'utils': utils(client),
'templates': Templates(client.instance.config.TEMPLATES),
+ 'template': self,
}
# add in the item if there is one
if client.nodeid:
c['context'] = HTMLItem(client, classname, client.nodeid,
anonymous=1)
elif client.db.classes.has_key(classname):
- c['context'] = HTMLClass(client, classname, anonymous=1)
+ if classname == 'user':
+ c['context'] = HTMLUserClass(client, classname, anonymous=1)
+ else:
+ c['context'] = HTMLClass(client, classname, anonymous=1)
return c
def render(self, client, classname, request, **options):
getEngine().getContext(c), output, tal=1, strictinsert=0)()
return output.getvalue()
+ def __repr__(self):
+ return '<Roundup PageTemplate %r>'%self.id
+
class HTMLDatabase:
''' Return HTMLClasses for valid class fetches
'''
return HTMLItem(self._client, m.group('cl'), m.group('id'))
else:
self._client.db.getclass(item)
+ if item == 'user':
+ return HTMLUserClass(self._client, item)
return HTMLClass(self._client, item)
def __getattr__(self, attr):
def classes(self):
l = self._client.db.classes.keys()
l.sort()
- return [HTMLClass(self._client, cn) for cn in l]
-
-def lookupIds(db, prop, ids, num_re=re.compile('-?\d+')):
+ m = []
+ for item in l:
+ if item == 'user':
+ m.append(HTMLUserClass(self._client, item))
+ m.append(HTMLClass(self._client, item))
+ return m
+
+def lookupIds(db, prop, ids, fail_ok=0, num_re=re.compile('-?\d+')):
+ ''' "fail_ok" should be specified if we wish to pass through bad values
+ (most likely form values that we wish to represent back to the user)
+ '''
cl = db.getclass(prop.classname)
l = []
for entry in ids:
else:
try:
l.append(cl.lookup(entry))
- except KeyError:
- # ignore invalid keys
- pass
+ except (TypeError, KeyError):
+ if fail_ok:
+ # pass through the bad value
+ l.append(entry)
+ return l
+
+def lookupKeys(linkcl, key, ids, num_re=re.compile('-?\d+')):
+ ''' Look up the "key" values for "ids" list - though some may already
+ be key values, not ids.
+ '''
+ l = []
+ for entry in ids:
+ if num_re.match(entry):
+ l.append(linkcl.get(entry, key))
+ else:
+ l.append(entry)
return l
class HTMLPermissions:
'''
return self._db.security.hasPermission('Edit', self._client.userid,
self._classname)
+
def is_view_ok(self):
''' Is the user allowed to View the current class?
'''
return self._db.security.hasPermission('View', self._client.userid,
self._classname)
+
def is_only_view_ok(self):
''' Is the user only allowed to View (ie. not Edit) the current class?
'''
return self.is_view_ok() and not self.is_edit_ok()
-class HTMLClass(HTMLPermissions):
+ def view_check(self):
+ ''' Raise the Unauthorised exception if the user's not permitted to
+ view this class.
+ '''
+ if not self.is_view_ok():
+ raise Unauthorised("view", self._classname)
+
+ def edit_check(self):
+ ''' Raise the Unauthorised exception if the user's not permitted to
+ edit this class.
+ '''
+ if not self.is_edit_ok():
+ raise Unauthorised("edit", self._classname)
+
+def input_html4(**attrs):
+ """Generate an 'input' (html4) element with given attributes"""
+ return '<input %s>'%' '.join(['%s="%s"'%item for item in attrs.items()])
+
+def input_xhtml(**attrs):
+ """Generate an 'input' (xhtml) element with given attributes"""
+ return '<input %s/>'%' '.join(['%s="%s"'%item for item in attrs.items()])
+
+class HTMLInputMixin:
+ ''' requires a _client property '''
+ def __init__(self):
+ html_version = 'html4'
+ if hasattr(self._client.instance.config, 'HTML_VERSION'):
+ html_version = self._client.instance.config.HTML_VERSION
+ if html_version == 'xhtml':
+ self.input = input_xhtml
+ else:
+ self.input = input_html4
+
+class HTMLClass(HTMLInputMixin, HTMLPermissions):
''' Accesses through a class (either through *class* or *db.<classname>*)
'''
def __init__(self, client, classname, anonymous=0):
self._klass = self._db.getclass(self.classname)
self._props = self._klass.getprops()
+ HTMLInputMixin.__init__(self)
+
def __repr__(self):
return '<HTMLClass(0x%x) %s>'%(id(self), self.classname)
return None
# get the property
- prop = self._props[item]
+ try:
+ prop = self._props[item]
+ except KeyError:
+ raise KeyError, 'No such property "%s" on %s'%(item, self.classname)
# look up the correct HTMLProperty class
form = self._client.form
if form.has_key(item):
if isinstance(prop, hyperdb.Multilink):
value = lookupIds(self._db, prop,
- handleListCGIValue(form[item]))
+ handleListCGIValue(form[item]), fail_ok=1)
elif isinstance(prop, hyperdb.Link):
value = form[item].value.strip()
if value:
- value = lookupIds(self._db, prop, [value])[0]
+ value = lookupIds(self._db, prop, [value],
+ fail_ok=1)[0]
else:
value = None
else:
''' Get an item of this class by its item id.
'''
# make sure we're looking at an itemid
- if not num_re.match(itemid):
+ if not isinstance(itemid, type(1)) and not num_re.match(itemid):
itemid = self._klass.lookup(itemid)
if self.classname == 'user':
l.sort(lambda a,b:cmp(a._name, b._name))
return l
- def list(self):
+ def list(self, sort_on=None):
''' List all items in this class.
'''
if self.classname == 'user':
# get the list and sort it nicely
l = self._klass.list()
- sortfunc = make_sort_function(self._db, self.classname)
+ sortfunc = make_sort_function(self._db, self.classname, sort_on)
l.sort(sortfunc)
l = [klass(self._client, self.classname, x) for x in l]
idlessprops.sort()
return ['id'] + idlessprops
- def filter(self, request=None):
+ def filter(self, request=None, filterspec={}, sort=(None,None),
+ group=(None,None)):
''' Return a list of items from this class, filtered and sorted
by the current requested filterspec/filter/sort/group args
+
+ "request" takes precedence over the other three arguments.
'''
- # XXX allow direct specification of the filterspec etc.
if request is not None:
filterspec = request.filterspec
sort = request.sort
group = request.group
- else:
- filterspec = {}
- sort = (None,None)
- group = (None,None)
if self.classname == 'user':
klass = HTMLUser
else:
def submit(self, label="Submit New Entry"):
''' Generate a submit button (and action hidden element)
'''
- return ' <input type="hidden" name="@action" value="new">\n'\
- ' <input type="submit" name="submit" value="%s">'%label
+ self.view_check()
+ if self.is_edit_ok():
+ return self.input(type="hidden",name="@action",value="new") + \
+ '\n' + self.input(type="submit",name="submit",value=label)
+ return ''
def history(self):
+ self.view_check()
return 'New node - no history'
def renderWith(self, name, **kwargs):
pt = Templates(self._db.config.TEMPLATES).get(self.classname, name)
# use our fabricated request
- return pt.render(self._client, self.classname, req)
+ args = {
+ 'ok_message': self._client.ok_message,
+ 'error_message': self._client.error_message
+ }
+ return pt.render(self._client, self.classname, req, **args)
-class HTMLItem(HTMLPermissions):
+class HTMLItem(HTMLInputMixin, HTMLPermissions):
''' Accesses through an *item*
'''
def __init__(self, client, classname, nodeid, anonymous=0):
# do we prefix the form items with the item's identification?
self._anonymous = anonymous
+ HTMLInputMixin.__init__(self)
+
def __repr__(self):
return '<HTMLItem(0x%x) %s %s>'%(id(self), self._classname,
self._nodeid)
raise AttributeError, attr
def designator(self):
- ''' Return this item's designator (classname + id) '''
+ """Return this item's designator (classname + id)."""
return '%s%s'%(self._classname, self._nodeid)
+
+ def is_retired(self):
+ """Is this item retired?"""
+ return self._klass.is_retired(self._nodeid)
def submit(self, label="Submit Changes"):
- ''' Generate a submit button (and action hidden element)
- '''
- return ' <input type="hidden" name="@action" value="edit">\n'\
- ' <input type="submit" name="submit" value="%s">'%label
+ """Generate a submit button.
+
+ Also sneak in the lastactivity and action hidden elements.
+ """
+ return self.input(type="hidden", name="@lastactivity", value=date.Date('.')) + '\n' + \
+ self.input(type="hidden", name="@action", value="edit") + '\n' + \
+ self.input(type="submit", name="submit", value=label)
def journal(self, direction='descending'):
''' Return a list of HTMLJournalEntry instances.
return []
def history(self, direction='descending', dre=re.compile('\d+')):
+ self.view_check()
+
l = ['<table class="history">'
'<tr><th colspan="4" class="header">',
_('History'),
# use our fabricated request
return pt.render(self._client, req.classname, req)
-class HTMLUser(HTMLItem):
+class HTMLUserPermission:
+
+ def is_edit_ok(self):
+ ''' Is the user allowed to Edit the current class?
+ Also check whether this is the current user's info.
+ '''
+ return self._user_perm_check('Edit')
+
+ def is_view_ok(self):
+ ''' Is the user allowed to View the current class?
+ Also check whether this is the current user's info.
+ '''
+ return self._user_perm_check('View')
+
+ def _user_perm_check(self, type):
+ # some users may view / edit all users
+ s = self._db.security
+ userid = self._client.userid
+ if s.hasPermission(type, userid, self._classname):
+ return 1
+
+ # users may view their own info
+ is_anonymous = self._db.user.get(userid, 'username') == 'anonymous'
+ if getattr(self, '_nodeid', None) == userid and not is_anonymous:
+ return 1
+
+ # may anonymous users register?
+ if (is_anonymous and s.hasPermission('Web Registration', userid,
+ self._classname)):
+ return 1
+
+ # nope, no access here
+ return 0
+
+class HTMLUserClass(HTMLUserPermission, HTMLClass):
+ pass
+
+class HTMLUser(HTMLUserPermission, HTMLItem):
''' Accesses through the *user* (a special case of item)
'''
def __init__(self, client, classname, nodeid, anonymous=0):
classname = self._default_classname
return self._security.hasPermission(permission, self._nodeid, classname)
- def is_edit_ok(self):
- ''' Is the user allowed to Edit the current class?
- Also check whether this is the current user's info.
- '''
- return self._db.security.hasPermission('Edit', self._client.userid,
- self._classname) or (self._nodeid == self._client.userid and
- self._db.user.get(self._client.userid, 'username') != 'anonymous')
-
- def is_view_ok(self):
- ''' Is the user allowed to View the current class?
- Also check whether this is the current user's info.
- '''
- return self._db.security.hasPermission('Edit', self._client.userid,
- self._classname) or (self._nodeid == self._client.userid and
- self._db.user.get(self._client.userid, 'username') != 'anonymous')
-
-class HTMLProperty:
+class HTMLProperty(HTMLInputMixin, HTMLPermissions):
''' String, Number, Date, Interval HTMLProperty
Has useful attributes:
self._formname = '%s%s@%s'%(classname, nodeid, name)
else:
self._formname = name
+
+ HTMLInputMixin.__init__(self)
+
def __repr__(self):
return '<HTMLProperty(0x%x) %s %r %r>'%(id(self), self._formname,
self._prop, self._value)
return cmp(self._value, other._value)
return cmp(self._value, other)
+ def is_edit_ok(self):
+ ''' Is the user allowed to Edit the current class?
+ '''
+ thing = HTMLDatabase(self._client)[self._classname]
+ if self._nodeid:
+ # this is a special-case for the User class where permission's
+ # on a per-item basis :(
+ thing = thing.getItem(self._nodeid)
+ return thing.is_edit_ok()
+
+ def is_view_ok(self):
+ ''' Is the user allowed to View the current class?
+ '''
+ thing = HTMLDatabase(self._client)[self._classname]
+ if self._nodeid:
+ # this is a special-case for the User class where permission's
+ # on a per-item basis :(
+ thing = thing.getItem(self._nodeid)
+ return thing.is_view_ok()
+
class StringHTMLProperty(HTMLProperty):
hyper_re = re.compile(r'((?P<url>\w{3,6}://\S+)|'
r'(?P<email>[-+=%/\w\.]+@[\w\.\-]+)|'
s2 = match.group('id')
try:
# make sure s1 is a valid tracker classname
- self._db.getclass(s1)
- return '<a href="%s">%s %s</a>'%(s, s1, s2)
+ cl = self._db.getclass(s1)
+ if not cl.hasnode(s2):
+ raise KeyError, 'oops'
+ return '<a href="%s">%s%s</a>'%(s, s1, s2)
except KeyError:
return '%s%s'%(s1, s2)
return self.plain(hyperlink=1)
def plain(self, escape=0, hyperlink=0):
- ''' Render a "plain" representation of the property
+ '''Render a "plain" representation of the property
- "escape" turns on/off HTML quoting
- "hyperlink" turns on/off in-text hyperlinking of URLs, email
- addresses and designators
+ - "escape" turns on/off HTML quoting
+ - "hyperlink" turns on/off in-text hyperlinking of URLs, email
+ addresses and designators
'''
+ self.view_check()
+
if self._value is None:
return ''
if escape:
This requires the StructureText module to be installed separately.
'''
+ self.view_check()
+
s = self.plain(escape=escape)
if not StructuredText:
return s
return StructuredText(s,level=1,header=0)
def field(self, size = 30):
- ''' Render a form edit field for the property
+ ''' Render the property as a field in HTML.
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._formname, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
def multiline(self, escape=0, rows=5, cols=40):
- ''' Render a multiline form edit field for the property
+ ''' Render a multiline form edit field for the property.
+
+ If not editable, just display the plain() value in a <pre> tag.
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%(
- self._formname, rows, cols, value)
+ return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%(
+ self._formname, rows, cols, value)
+
+ return '<pre>%s</pre>'%self.plain()
def email(self, escape=1):
''' Render the value of the property as an obscured email address
'''
- if self._value is None: value = ''
- else: value = str(self._value)
+ self.view_check()
+
+ if self._value is None:
+ value = ''
+ else:
+ value = str(self._value)
if value.find('@') != -1:
name, domain = value.split('@')
domain = ' '.join(domain.split('.')[:-1])
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return _('*encrypted*')
def field(self, size = 30):
''' Render a form edit field for the property.
+
+ If not editable, just display the value via plain().
'''
- return '<input type="password" name="%s" size="%s">'%(self._formname, size)
+ self.view_check()
+
+ if self.is_edit_ok():
+ return self.input(type="password", name=self._formname, size=size)
+
+ return self.plain()
def confirm(self, size = 30):
''' Render a second form edit field for the property, used for
confirmation that the user typed the password correctly. Generates
a field with name "@confirm@name".
+
+ If not editable, display nothing.
'''
- return '<input type="password" name="@confirm@%s" size="%s">'%(
- self._formname, size)
+ self.view_check()
+
+ if self.is_edit_ok():
+ return self.input(type="password",
+ name="@confirm@%s"%self._formname, size=size)
+
+ return ''
class NumberHTMLProperty(HTMLProperty):
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
return str(self._value)
def field(self, size = 30):
- ''' Render a form edit field for the property
+ ''' Render a form edit field for the property.
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._formname, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
def __int__(self):
''' Return an int of me
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return self._value and "Yes" or "No"
def field(self):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
+
checked = self._value and "checked" or ""
- s = '<input type="radio" name="%s" value="yes" %s>Yes'%(self._formname,
- checked)
- if checked:
- checked = ""
+ if self._value:
+ s = self.input(type="radio", name=self._formname, value="yes",
+ checked="checked")
+ s += 'Yes'
+ s +=self.input(type="radio", name=self._formname, value="no")
+ s += 'No'
else:
- checked = "checked"
- s += '<input type="radio" name="%s" value="no" %s>No'%(self._formname,
- checked)
+ s = self.input(type="radio", name=self._formname, value="yes")
+ s += 'Yes'
+ s +=self.input(type="radio", name=self._formname, value="no",
+ checked="checked")
+ s += 'No'
return s
class DateHTMLProperty(HTMLProperty):
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return str(self._value.local(self._db.getUserTimezone()))
This is useful for defaulting a new value. Returns a
DateHTMLProperty.
'''
- return DateHTMLProperty(self._client, self._nodeid, self._prop,
- self._formname, date.Date('.'))
+ self.view_check()
+
+ return DateHTMLProperty(self._client, self._classname, self._nodeid,
+ self._prop, self._formname, date.Date('.'))
def field(self, size = 30):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
- value = cgi.escape(str(self._value.local(self._db.getUserTimezone())))
+ tz = self._db.getUserTimezone()
+ value = cgi.escape(str(self._value.local(tz)))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._formname, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
def reldate(self, pretty=1):
''' Render the interval between the date and now.
If the "pretty" flag is true, then make the display pretty.
'''
+ self.view_check()
+
if not self._value:
return ''
# figure the interval
- interval = date.Date('.') - self._value
+ interval = self._value - date.Date('.')
if pretty:
return interval.pretty()
return str(interval)
string, then it'll be stripped from the output. This is handy
for the situatin when a date only specifies a month and a year.
'''
+ self.view_check()
+
if format is not self._marker:
return self._value.pretty(format)
else:
def local(self, offset):
''' Return the date/time as a local (timezone offset) date/time.
'''
- return DateHTMLProperty(self._client, self._nodeid, self._prop,
- self._formname, self._value.local(offset))
+ self.view_check()
+
+ return DateHTMLProperty(self._client, self._classname, self._nodeid,
+ self._prop, self._formname, self._value.local(offset))
class IntervalHTMLProperty(HTMLProperty):
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return str(self._value)
def pretty(self):
''' Render the interval in a pretty format (eg. "yesterday")
'''
+ self.view_check()
+
return self._value.pretty()
def field(self, size = 30):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._formname, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
class LinkHTMLProperty(HTMLProperty):
''' Link HTMLProperty
def plain(self, escape=0):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
linkcl = self._db.classes[self._prop.classname]
def field(self, showid=0, size=None):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
+
+ # edit field
linkcl = self._db.getclass(self._prop.classname)
- if linkcl.getprops().has_key('order'):
- sort_on = 'order'
- else:
- sort_on = linkcl.labelprop()
- options = linkcl.filter(None, {}, ('+', sort_on), (None, None))
- # TODO: make this a field display, not a menu one!
- l = ['<select name="%s">'%self._formname]
- k = linkcl.labelprop(1)
if self._value is None:
- s = 'selected '
+ value = ''
else:
- s = ''
- l.append(_('<option %svalue="-1">- no selection -</option>')%s)
-
- # make sure we list the current value if it's retired
- if self._value and self._value not in options:
- options.insert(0, self._value)
-
- for optionid in options:
- # get the option value, and if it's None use an empty string
- option = linkcl.get(optionid, k) or ''
-
- # figure if this option is selected
- s = ''
- if optionid == self._value:
- s = 'selected '
-
- # figure the label
- if showid:
- lab = '%s%s: %s'%(self._prop.classname, optionid, option)
+ k = linkcl.getkey()
+ if k:
+ value = linkcl.get(self._value, k)
else:
- lab = option
-
- # truncate if it's too long
- if size is not None and len(lab) > size:
- lab = lab[:size-3] + '...'
-
- # and generate
- lab = cgi.escape(lab)
- l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
- l.append('</select>')
- return '\n'.join(l)
+ value = self._value
+ value = cgi.escape(str(value))
+ value = '"'.join(value.split('"'))
+ return '<input name="%s" value="%s" size="%s">'%(self._formname,
+ value, size)
def menu(self, size=None, height=None, showid=0, additional=[],
- **conditions):
+ sort_on=None, **conditions):
''' Render a form select list for this property
+
+ If not editable, just display the value via plain().
'''
- value = self._value
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
- # sort function
- sortfunc = make_sort_function(self._db, self._prop.classname)
+ value = self._value
linkcl = self._db.getclass(self._prop.classname)
l = ['<select name="%s">'%self._formname]
k = linkcl.labelprop(1)
s = ''
if value is None:
- s = 'selected '
+ s = 'selected="selected" '
l.append(_('<option %svalue="-1">- no selection -</option>')%s)
if linkcl.getprops().has_key('order'):
sort_on = ('+', 'order')
else:
- sort_on = ('+', linkcl.labelprop())
+ if sort_on is None:
+ sort_on = ('+', linkcl.labelprop())
+ else:
+ sort_on = ('+', sort_on)
options = linkcl.filter(None, conditions, sort_on, (None, None))
# make sure we list the current value if it's retired
# figure if this option is selected
s = ''
if value in [optionid, option]:
- s = 'selected '
+ s = 'selected="selected" '
# figure the label
if showid:
Also be iterable, returning a wrapper object like the Link case for
each entry in the multilink.
'''
+ def __init__(self, *args, **kwargs):
+ HTMLProperty.__init__(self, *args, **kwargs)
+ if self._value:
+ sortfun = make_sort_function(self._db, self._prop.classname)
+ self._value.sort(sortfun)
+
def __len__(self):
''' length of the multilink '''
return len(self._value)
def __contains__(self, value):
''' Support the "in" operator. We have to make sure the passed-in
- value is a string first, not a *HTMLProperty.
+ value is a string first, not a HTMLProperty.
'''
return str(value) in self._value
def plain(self, escape=0):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
linkcl = self._db.classes[self._prop.classname]
k = linkcl.labelprop(1)
labels = []
def field(self, size=30, showid=0):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
- sortfunc = make_sort_function(self._db, self._prop.classname)
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
+
linkcl = self._db.getclass(self._prop.classname)
value = self._value[:]
- if value:
- value.sort(sortfunc)
# map the id to the label property
if not linkcl.getkey():
showid=1
if not showid:
k = linkcl.labelprop(1)
- value = [linkcl.get(v, k) for v in value]
+ value = lookupKeys(linkcl, k, value)
value = cgi.escape(','.join(value))
- return '<input name="%s" size="%s" value="%s">'%(self._formname, size, value)
+ return self.input(name=self._formname,size=size,value=value)
def menu(self, size=None, height=None, showid=0, additional=[],
- **conditions):
+ sort_on=None, **conditions):
''' Render a form select list for this property
+
+ If not editable, just display the value via plain().
'''
- value = self._value
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
- # sort function
- sortfunc = make_sort_function(self._db, self._prop.classname)
+ value = self._value
linkcl = self._db.getclass(self._prop.classname)
- if linkcl.getprops().has_key('order'):
- sort_on = ('+', 'order')
- else:
- sort_on = ('+', linkcl.labelprop())
- options = linkcl.filter(None, conditions, sort_on, (None,None))
+ if sort_on is None:
+ sort_on = ('+', find_sort_key(linkcl))
+ else:
+ sort_on = ('+', sort_on)
+ options = linkcl.filter(None, conditions, sort_on)
height = height or min(len(options), 7)
l = ['<select multiple name="%s" size="%s">'%(self._formname, height)]
k = linkcl.labelprop(1)
# figure if this option is selected
s = ''
if optionid in value or option in value:
- s = 'selected '
+ s = 'selected="selected" '
# figure the label
if showid:
(hyperdb.Multilink, MultilinkHTMLProperty),
)
-def make_sort_function(db, classname):
+def make_sort_function(db, classname, sort_on=None):
'''Make a sort function for a given class
'''
linkcl = db.getclass(classname)
- if linkcl.getprops().has_key('order'):
- sort_on = 'order'
- else:
- sort_on = linkcl.labelprop()
- def sortfunc(a, b, linkcl=linkcl, sort_on=sort_on):
+ if sort_on is None:
+ sort_on = find_sort_key(linkcl)
+ def sortfunc(a, b):
return cmp(linkcl.get(a, sort_on), linkcl.get(b, sort_on))
return sortfunc
+def find_sort_key(linkcl):
+ if linkcl.getprops().has_key('order'):
+ return 'order'
+ else:
+ return linkcl.labelprop()
+
def handleListCGIValue(value):
''' Value is either a single item or a list of items. Each item has a
.value that we're actually interested in.
def __getitem__(self, name):
return self.columns.has_key(name)
-class HTMLRequest:
- ''' The *request*, holding the CGI form and environment.
-
- "form" the CGI form as a cgi.FieldStorage
- "env" the CGI environment variables
- "base" the base URL for this instance
- "user" a HTMLUser instance for this user
- "classname" the current classname (possibly None)
- "template" the current template (suffix, also possibly None)
-
- 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
- "filterspec" values to filter the index on
- "search_text" text to perform a full-text search on for an index
-
+class HTMLRequest(HTMLInputMixin):
+ '''The *request*, holding the CGI form and environment.
+
+ - "form" the CGI form as a cgi.FieldStorage
+ - "env" the CGI environment variables
+ - "base" the base URL for this instance
+ - "user" a HTMLUser instance for this user
+ - "classname" the current classname (possibly None)
+ - "template" the current template (suffix, also possibly None)
+
+ 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
+ - "filterspec" values to filter the index on
+ - "search_text" text to perform a full-text search on for an index
'''
def __init__(self, client):
- self.client = client
+ # _client is needed by HTMLInputMixin
+ self._client = self.client = client
# easier access vars
self.form = client.form
# the special char to use for special vars
self.special_char = '@'
+ HTMLInputMixin.__init__(self)
+
self._post_init()
def _post_init(self):
''' return the current index args as form elements '''
l = []
sc = self.special_char
- s = '<input type="hidden" name="%s" value="%s">'
+ s = self.input(type="hidden",name="%s",value="%s")
if columns and self.columns:
l.append(s%(sc+'columns', ','.join(self.columns)))
if sort and self.sort[1] is not None:
function submit_once() {
if (submitted) {
alert("Your request is being processed.\\nPlease be patient.");
+ event.returnValue = 0; // work-around for IE
return 0;
}
submitted = true;