X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fcgi%2Ftemplating.py;h=a403481864364642d7a03072f332ee334593f6d9;hb=a26e8ef75040dc5f5203680378b56dc0e92ede98;hp=ef6fb41ccdc4eb2bfbdfd02699a0ef7ef0293068;hpb=e695dea634bd982b563cf6129490b38cf6459c20;p=roundup.git diff --git a/roundup/cgi/templating.py b/roundup/cgi/templating.py index ef6fb41..a403481 100644 --- a/roundup/cgi/templating.py +++ b/roundup/cgi/templating.py @@ -1,3 +1,9 @@ +"""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 @@ -25,6 +31,14 @@ from roundup.cgi import ZTUtils 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 ''' @@ -133,35 +147,37 @@ class Templates: 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 @@ -189,7 +205,10 @@ class RoundupPageTemplate(PageTemplate.PageTemplate): 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): @@ -216,6 +235,9 @@ class RoundupPageTemplate(PageTemplate.PageTemplate): getEngine().getContext(c), output, tal=1, strictinsert=0)() return output.getvalue() + def __repr__(self): + return ''%self.id + class HTMLDatabase: ''' Return HTMLClasses for valid class fetches ''' @@ -234,6 +256,8 @@ class HTMLDatabase: 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): @@ -245,9 +269,17 @@ class HTMLDatabase: 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+')): + r = [] + for item in l: + if item == 'user': + m.append(HTMLUserClass(self._client, item)) + m.append(HTMLClass(self._client, item)) + return r + +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: @@ -256,9 +288,22 @@ def lookupIds(db, prop, ids, num_re=re.compile('-?\d+')): 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: @@ -269,17 +314,52 @@ 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 ''%' '.join(['%s="%s"'%item for item in attrs.items()]) + +def input_xhtml(**attrs): + """Generate an 'input' (xhtml) element with given attributes""" + return ''%' '.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.*) ''' def __init__(self, client, classname, anonymous=0): @@ -293,6 +373,8 @@ class HTMLClass(HTMLPermissions): self._klass = self._db.getclass(self.classname) self._props = self._klass.getprops() + HTMLInputMixin.__init__(self) + def __repr__(self): return ''%(id(self), self.classname) @@ -306,7 +388,10 @@ class HTMLClass(HTMLPermissions): 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 @@ -316,11 +401,12 @@ class HTMLClass(HTMLPermissions): 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: @@ -347,11 +433,11 @@ class HTMLClass(HTMLPermissions): ''' Return this class' designator (classname) ''' return self._classname - def getItem(self, itemid, num_re=re.compile('\d+')): + def getItem(self, itemid, num_re=re.compile('-?\d+')): ''' 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': @@ -378,7 +464,7 @@ class HTMLClass(HTMLPermissions): 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': @@ -388,7 +474,7 @@ class HTMLClass(HTMLPermissions): # 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] @@ -424,19 +510,17 @@ class HTMLClass(HTMLPermissions): 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: @@ -467,19 +551,23 @@ class HTMLClass(HTMLPermissions): properties.sort() properties = ','.join(properties) if property: - property = '&property=%s'%property + property = '&property=%s'%property return '%s'%(self.classname, properties, property, width, height, label) def submit(self, label="Submit New Entry"): ''' Generate a submit button (and action hidden element) ''' - return ' \n'\ - ' '%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): @@ -494,9 +582,13 @@ class HTMLClass(HTMLPermissions): 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): @@ -510,6 +602,8 @@ class HTMLItem(HTMLPermissions): # do we prefix the form items with the item's identification? self._anonymous = anonymous + HTMLInputMixin.__init__(self) + def __repr__(self): return ''%(id(self), self._classname, self._nodeid) @@ -548,14 +642,17 @@ class HTMLItem(HTMLPermissions): 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 submit(self, label="Submit Changes"): - ''' Generate a submit button (and action hidden element) - ''' - return ' \n'\ - ' '%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. @@ -564,6 +661,8 @@ class HTMLItem(HTMLPermissions): return [] def history(self, direction='descending', dre=re.compile('\d+')): + self.view_check() + l = ['' '
', _('History'), @@ -773,7 +872,7 @@ class HTMLItem(HTMLPermissions): req.classname = self._klass.get(self._nodeid, 'klass') name = self._klass.get(self._nodeid, 'name') req.updateFromURL(self._klass.get(self._nodeid, 'url') + - '&:queryname=%s'%urllib.quote(name)) + '&@queryname=%s'%urllib.quote(name)) # new template, using the specified classname and request pt = Templates(self._db.config.TEMPLATES).get(req.classname, 'search') @@ -781,7 +880,44 @@ class HTMLItem(HTMLPermissions): # 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): @@ -802,21 +938,7 @@ class HTMLUser(HTMLItem): 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 - - 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 - -class HTMLProperty: +class HTMLProperty(HTMLInputMixin, HTMLPermissions): ''' String, Number, Date, Interval HTMLProperty Has useful attributes: @@ -840,6 +962,9 @@ class HTMLProperty: self._formname = '%s%s@%s'%(classname, nodeid, name) else: self._formname = name + + HTMLInputMixin.__init__(self) + def __repr__(self): return ''%(id(self), self._formname, self._prop, self._value) @@ -850,6 +975,26 @@ class HTMLProperty: 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\w{3,6}://\S+)|' r'(?P[-+=%/\w\.]+@[\w\.\-]+)|' @@ -867,8 +1012,10 @@ class StringHTMLProperty(HTMLProperty): s2 = match.group('id') try: # make sure s1 is a valid tracker classname - self._db.getclass(s1) - return '%s %s'%(s, s1, s2) + cl = self._db.getclass(s1) + if not cl.hasnode(s2): + raise KeyError, 'oops' + return '%s%s'%(s, s1, s2) except KeyError: return '%s%s'%(s1, s2) @@ -877,12 +1024,14 @@ class StringHTMLProperty(HTMLProperty): 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: @@ -901,37 +1050,59 @@ class StringHTMLProperty(HTMLProperty): 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 ''%(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
 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 ''%(
-            self._formname, rows, cols, value)
+            return ''%(
+                self._formname, rows, cols, value)
+
+        return '
%s
'%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]) @@ -947,38 +1118,64 @@ class PasswordHTMLProperty(HTMLProperty): 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 ''%(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". + a field with name "@confirm@name". + + If not editable, display nothing. ''' - return ''%( - 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 ''%(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 @@ -995,28 +1192,43 @@ class BooleanHTMLProperty(HTMLProperty): 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 is_edit_ok(): + return self.plain() + checked = self._value and "checked" or "" - 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 += '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())) @@ -1027,29 +1239,42 @@ class DateHTMLProperty(HTMLProperty): 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 is_edit_ok(): value = '"'.join(value.split('"')) - return ''%(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) @@ -1063,6 +1288,8 @@ class DateHTMLProperty(HTMLProperty): 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: @@ -1071,13 +1298,17 @@ class DateHTMLProperty(HTMLProperty): 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) @@ -1085,17 +1316,27 @@ class IntervalHTMLProperty(HTMLProperty): 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 ''%(self._formname, value, size) + return self.input(name=self._formname,value=value,size=size) + + return self.plain() class LinkHTMLProperty(HTMLProperty): ''' Link HTMLProperty @@ -1129,6 +1370,8 @@ class LinkHTMLProperty(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] @@ -1140,71 +1383,56 @@ class LinkHTMLProperty(HTMLProperty): 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 = ['') - return '\n'.join(l) + label = self._value + value = cgi.escape(str(self._value)) + value = '"'.join(value.split('"')) + return ''%(self._formname, + label, 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 = [''%(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 = ['' + 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: @@ -1685,6 +1936,7 @@ submitted = false; 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;