X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fcgi%2Ftemplating.py;h=3cb9780c909f94bb833018cce663673f09ec2971;hb=e26c0e6a8f9dca8e604bae291509feec1e492354;hp=f536774b4215b0a7e0e04ff540bc2117310c8226;hpb=3eab8d9d3569a12b19c5a859ca21060e9c67ab09;p=roundup.git
diff --git a/roundup/cgi/templating.py b/roundup/cgi/templating.py
index f536774..3cb9780 100644
--- a/roundup/cgi/templating.py
+++ b/roundup/cgi/templating.py
@@ -27,6 +27,8 @@ from roundup import hyperdb, date, support
from roundup import i18n
from roundup.i18n import _
+from KeywordsExpr import render_keywords_expression_editor
+
try:
import cPickle as pickle
except ImportError:
@@ -115,9 +117,9 @@ def find_template(dir, name, view):
if os.path.exists(src):
return (src, generic)
- raise NoTemplate, 'No template file exists for templating "%s" '\
+ raise NoTemplate('No template file exists for templating "%s" '
'with template "%s" (neither "%s" nor "%s")'%(name, view,
- filename, generic)
+ filename, generic))
class Templates:
templates = {}
@@ -183,12 +185,28 @@ class Templates:
return self.templates[src]
# compile the template
- self.templates[src] = pt = RoundupPageTemplate()
+ pt = RoundupPageTemplate()
# use pt_edit so we can pass the content_type guess too
content_type = mimetypes.guess_type(filename)[0] or 'text/html'
pt.pt_edit(open(src).read(), content_type)
pt.id = filename
pt.mtime = stime
+ # Add it to the cache. We cannot do this until the template
+ # is fully initialized, as we could otherwise have a race
+ # condition when running with multiple threads:
+ #
+ # 1. Thread A notices the template is not in the cache,
+ # adds it, but has not yet set "mtime".
+ #
+ # 2. Thread B notices the template is in the cache, checks
+ # "mtime" (above) and crashes.
+ #
+ # Since Python dictionary access is atomic, as long as we
+ # insert "pt" only after it is fully initialized, we avoid
+ # this race condition. It's possible that two separate
+ # threads will both do the work of initializing the template,
+ # but the risk of wasted work is offset by avoiding a lock.
+ self.templates[src] = pt
return pt
def __getitem__(self, name):
@@ -420,17 +438,19 @@ def _set_input_default_args(dic):
except KeyError:
pass
+def cgi_escape_attrs(**attrs):
+ return ' '.join(['%s="%s"'%(k,cgi.escape(str(v), True))
+ for k,v in attrs.items()])
+
def input_html4(**attrs):
"""Generate an 'input' (html4) element with given attributes"""
_set_input_default_args(attrs)
- return ''%' '.join(['%s="%s"'%(k,cgi.escape(str(v), True))
- for k,v in attrs.items()])
+ return ''%cgi_escape_attrs(**attrs)
def input_xhtml(**attrs):
"""Generate an 'input' (xhtml) element with given attributes"""
_set_input_default_args(attrs)
- return ''%' '.join(['%s="%s"'%(k,cgi.escape(str(v), True))
- for k,v in attrs.items()])
+ return ''%cgi_escape_attrs(**attrs)
class HTMLInputMixin:
""" requires a _client property """
@@ -502,20 +522,23 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions):
def is_edit_ok(self):
""" Is the user allowed to Create the current class?
"""
- return self._db.security.hasPermission('Create', self._client.userid,
- self._classname)
+ perm = self._db.security.hasPermission
+ return perm('Web Access', self._client.userid) and perm('Create',
+ self._client.userid, self._classname)
def is_retire_ok(self):
""" Is the user allowed to retire items of the current class?
"""
- return self._db.security.hasPermission('Retire', self._client.userid,
- self._classname)
+ perm = self._db.security.hasPermission
+ return perm('Web Access', self._client.userid) and perm('Retire',
+ 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)
+ perm = self._db.security.hasPermission
+ return perm('Web Access', self._client.userid) and perm('View',
+ self._client.userid, self._classname)
def is_only_view_ok(self):
""" Is the user only allowed to View (ie. not Create) the current class?
@@ -544,10 +567,7 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions):
for klass, htmlklass in propclasses:
if not isinstance(prop, klass):
continue
- if isinstance(prop, hyperdb.Multilink):
- value = []
- else:
- value = None
+ value = prop.get_default_value()
return htmlklass(self._client, self._classname, None, prop, item,
value, self._anonymous)
@@ -580,13 +600,10 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions):
l = []
for name, prop in self._props.items():
for klass, htmlklass in propclasses:
- if isinstance(prop, hyperdb.Multilink):
- value = []
- else:
- value = None
if isinstance(prop, klass):
+ value = prop.get_default_value()
l.append(htmlklass(self._client, self._classname, '',
- prop, name, value, self._anonymous))
+ prop, name, value, self._anonymous))
if sort:
l.sort(lambda a,b:cmp(a._name, b._name))
return l
@@ -602,6 +619,8 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions):
# check perms
check = self._client.db.security.hasPermission
userid = self._client.userid
+ if not check('Web Access', userid):
+ return []
l = [HTMLItem(self._client, self._classname, id) for id in l
if check('View', userid, self._classname, itemid=id)]
@@ -616,11 +635,14 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions):
writer = csv.writer(s)
writer.writerow(props)
check = self._client.db.security.hasPermission
+ userid = self._client.userid
+ if not check('Web Access', userid):
+ return ''
for nodeid in self._klass.list():
l = []
for name in props:
# check permission to view this property on this item
- if not check('View', self._client.userid, itemid=nodeid,
+ if not check('View', userid, itemid=nodeid,
classname=self._klass.classname, property=name):
raise Unauthorised('view', self._klass.classname,
translator=self._client.translator)
@@ -647,13 +669,23 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions):
"request" takes precedence over the other three arguments.
"""
+ security = self._db.security
+ userid = self._client.userid
if request is not None:
+ # for a request we asume it has already been
+ # security-filtered
filterspec = request.filterspec
sort = request.sort
group = request.group
+ else:
+ cn = self.classname
+ filterspec = security.filterFilterspec(userid, cn, filterspec)
+ sort = security.filterSortspec(userid, cn, sort)
+ group = security.filterSortspec(userid, cn, group)
- check = self._db.security.hasPermission
- userid = self._client.userid
+ check = security.hasPermission
+ if not check('Web Access', userid):
+ return []
l = [HTMLItem(self._client, self.classname, id)
for id in self._klass.filter(None, filterspec, sort, group)
@@ -783,20 +815,23 @@ class _HTMLItem(HTMLInputMixin, HTMLPermissions):
def is_edit_ok(self):
""" Is the user allowed to Edit this item?
"""
- return self._db.security.hasPermission('Edit', self._client.userid,
- self._classname, itemid=self._nodeid)
+ perm = self._db.security.hasPermission
+ return perm('Web Access', self._client.userid) and perm('Edit',
+ self._client.userid, self._classname, itemid=self._nodeid)
def is_retire_ok(self):
""" Is the user allowed to Reture this item?
"""
- return self._db.security.hasPermission('Retire', self._client.userid,
- self._classname, itemid=self._nodeid)
+ perm = self._db.security.hasPermission
+ return perm('Web Access', self._client.userid) and perm('Retire',
+ self._client.userid, self._classname, itemid=self._nodeid)
def is_view_ok(self):
""" Is the user allowed to View this item?
"""
- if self._db.security.hasPermission('View', self._client.userid,
- self._classname, itemid=self._nodeid):
+ perm = self._db.security.hasPermission
+ if perm('Web Access', self._client.userid) and perm('View',
+ self._client.userid, self._classname, itemid=self._nodeid):
return 1
return self.is_edit_ok()
@@ -880,7 +915,8 @@ class _HTMLItem(HTMLInputMixin, HTMLPermissions):
# XXX do this
return []
- def history(self, direction='descending', dre=re.compile('^\d+$')):
+ def history(self, direction='descending', dre=re.compile('^\d+$'),
+ limit=None):
if not self.is_view_ok():
return self._('[hidden]')
@@ -912,6 +948,10 @@ class _HTMLItem(HTMLInputMixin, HTMLPermissions):
history.sort()
history.reverse()
+ # restrict the volume
+ if limit:
+ history = history[:limit]
+
timezone = self._db.getUserTimezone()
l = []
comments = {}
@@ -1068,6 +1108,13 @@ class _HTMLItem(HTMLInputMixin, HTMLPermissions):
cell[-1] += ' -> %s'%current[k]
current[k] = val
+ elif isinstance(prop, hyperdb.Password) and args[k] is not None:
+ val = args[k].dummystr()
+ cell.append('%s: %s'%(self._(k), val))
+ if current.has_key(k):
+ cell[-1] += ' -> %s'%current[k]
+ current[k] = val
+
elif not args[k]:
if current.has_key(k):
cell.append('%s: %s'%(self._(k), current[k]))
@@ -1182,12 +1229,9 @@ class _HTMLUser(_HTMLItem):
return self._db.security.hasPermission(permission,
self._nodeid, classname, property, itemid)
- def hasRole(self, rolename):
- """Determine whether the user has the Role."""
- roles = self._db.user.get(self._nodeid, 'roles').split(',')
- for role in roles:
- if role.strip() == rolename: return True
- return False
+ def hasRole(self, *rolenames):
+ """Determine whether the user has any role in rolenames."""
+ return self._db.user.has_role(self._nodeid, *rolenames)
def HTMLItem(client, classname, nodeid, anonymous=0):
if classname == 'user':
@@ -1217,7 +1261,12 @@ class HTMLProperty(HTMLInputMixin, HTMLPermissions):
self._anonymous = anonymous
self._name = name
if not anonymous:
- self._formname = '%s%s@%s'%(classname, nodeid, name)
+ if nodeid:
+ self._formname = '%s%s@%s'%(classname, nodeid, name)
+ else:
+ # This case occurs when creating a property for a
+ # non-anonymous class.
+ self._formname = '%s@%s'%(classname, name)
else:
self._formname = name
@@ -1243,8 +1292,9 @@ class HTMLProperty(HTMLInputMixin, HTMLPermissions):
HTMLInputMixin.__init__(self)
def __repr__(self):
- return ''%(id(self), self._formname,
- self._prop, self._value)
+ classname = self.__class__.__name__
+ return '<%s(0x%x) %s %r %r>'%(classname, id(self), self._formname,
+ self._prop, self._value)
def __str__(self):
return self.plain()
def __cmp__(self, other):
@@ -1264,17 +1314,22 @@ class HTMLProperty(HTMLInputMixin, HTMLPermissions):
property. Check "Create" for new items, or "Edit" for existing
ones.
"""
+ perm = self._db.security.hasPermission
+ userid = self._client.userid
if self._nodeid:
- return self._db.security.hasPermission('Edit', self._client.userid,
- self._classname, self._name, self._nodeid)
- return self._db.security.hasPermission('Create', self._client.userid,
- self._classname, self._name)
+ if not perm('Web Access', userid):
+ return False
+ return perm('Edit', userid, self._classname, self._name,
+ self._nodeid)
+ return perm('Create', userid, self._classname, self._name) or \
+ perm('Register', userid, self._classname, self._name)
def is_view_ok(self):
""" Is the user allowed to View the current class?
"""
- if self._db.security.hasPermission('View', self._client.userid,
- self._classname, self._name, self._nodeid):
+ perm = self._db.security.hasPermission
+ if perm('Web Access', self._client.userid) and perm('View',
+ self._client.userid, self._classname, self._name, self._nodeid):
return 1
return self.is_edit_ok()
@@ -1300,7 +1355,42 @@ class StringHTMLProperty(HTMLProperty):
)''', re.X | re.I)
protocol_re = re.compile('^(ht|f)tp(s?)://', re.I)
- def _hyper_repl_item(self,match,replacement):
+
+
+ def _hyper_repl(self, match):
+ if match.group('url'):
+ return self._hyper_repl_url(match, '%s%s')
+ elif match.group('email'):
+ return self._hyper_repl_email(match, '%s')
+ elif len(match.group('id')) < 10:
+ return self._hyper_repl_item(match,
+ '%(item)s')
+ else:
+ # just return the matched text
+ return match.group(0)
+
+ def _hyper_repl_url(self, match, replacement):
+ u = s = match.group('url')
+ if not self.protocol_re.search(s):
+ u = 'http://' + s
+ end = ''
+ if '>' in s:
+ # catch an escaped ">" in the URL
+ pos = s.find('>')
+ end = s[pos:]
+ u = s = s[:pos]
+ if ')' in s and s.count('(') != s.count(')'):
+ # don't include extraneous ')' in the link
+ pos = s.rfind(')')
+ end = s[pos:] + end
+ u = s = s[:pos]
+ return replacement % (u, s, end)
+
+ def _hyper_repl_email(self, match, replacement):
+ s = match.group('email')
+ return replacement % (s, s)
+
+ def _hyper_repl_item(self, match, replacement):
item = match.group('item')
cls = match.group('class').lower()
id = match.group('id')
@@ -1313,24 +1403,6 @@ class StringHTMLProperty(HTMLProperty):
except KeyError:
return item
- def _hyper_repl(self, match):
- if match.group('url'):
- u = s = match.group('url')
- if not self.protocol_re.search(s):
- u = 'http://' + s
- # catch an escaped ">" at the end of the URL
- if s.endswith('>'):
- u = s = s[:-4]
- e = '>'
- else:
- e = ''
- return '%s%s'%(u, s, e)
- elif match.group('email'):
- s = match.group('email')
- return '%s'%(s, s)
- else:
- return self._hyper_repl_item(match,
- '%(item)s')
def _hyper_repl_rst(self, match):
if match.group('url'):
@@ -1339,8 +1411,11 @@ class StringHTMLProperty(HTMLProperty):
elif match.group('email'):
s = match.group('email')
return '`%s `_'%(s, s)
- else:
+ elif len(match.group('id')) < 10:
return self._hyper_repl_item(match,'`%(item)s <%(cls)s%(id)s>`_')
+ else:
+ # just return the matched text
+ return match.group(0)
def hyperlinked(self):
""" Render a "hyperlinked" version of the text """
@@ -1457,8 +1532,7 @@ class StringHTMLProperty(HTMLProperty):
value = '"'.join(value.split('"'))
name = self._formname
- passthrough_args = ' '.join(['%s="%s"' % (k, cgi.escape(str(v), True))
- for k,v in kwargs.items()])
+ passthrough_args = cgi_escape_attrs(**kwargs)
return ('') % locals()
@@ -1494,9 +1568,12 @@ class PasswordHTMLProperty(HTMLProperty):
if self._value is None:
return ''
- return self._('*encrypted*')
+ value = self._value.dummystr()
+ if escape:
+ value = cgi.escape(value)
+ return value
- def field(self, size=30):
+ def field(self, size=30, **kwargs):
""" Render a form edit field for the property.
If not editable, just display the value via plain().
@@ -1504,7 +1581,8 @@ class PasswordHTMLProperty(HTMLProperty):
if not self.is_edit_ok():
return self.plain(escape=1)
- return self.input(type="password", name=self._formname, size=size)
+ return self.input(type="password", name=self._formname, size=size,
+ **kwargs)
def confirm(self, size=30):
""" Render a second form edit field for the property, used for
@@ -1533,7 +1611,7 @@ class NumberHTMLProperty(HTMLProperty):
return str(self._value)
- def field(self, size=30):
+ def field(self, size=30, **kwargs):
""" Render a form edit field for the property.
If not editable, just display the value via plain().
@@ -1545,7 +1623,8 @@ class NumberHTMLProperty(HTMLProperty):
if value is None:
value = ''
- return self.input(name=self._formname, value=value, size=size)
+ return self.input(name=self._formname, value=value, size=size,
+ **kwargs)
def __int__(self):
""" Return an int of me
@@ -1569,7 +1648,7 @@ class BooleanHTMLProperty(HTMLProperty):
return ''
return self._value and self._("Yes") or self._("No")
- def field(self):
+ def field(self, **kwargs):
""" Render a form edit field for the property
If not editable, just display the value via plain().
@@ -1585,15 +1664,17 @@ class BooleanHTMLProperty(HTMLProperty):
checked = value and "checked" or ""
if value:
s = self.input(type="radio", name=self._formname, value="yes",
- checked="checked")
+ checked="checked", **kwargs)
s += self._('Yes')
- s +=self.input(type="radio", name=self._formname, value="no")
+ s +=self.input(type="radio", name=self._formname, value="no",
+ **kwargs)
s += self._('No')
else:
- s = self.input(type="radio", name=self._formname, value="yes")
+ s = self.input(type="radio", name=self._formname, value="yes",
+ **kwargs)
s += self._('Yes')
s +=self.input(type="radio", name=self._formname, value="no",
- checked="checked")
+ checked="checked", **kwargs)
s += self._('No')
return s
@@ -1651,7 +1732,8 @@ class DateHTMLProperty(HTMLProperty):
return DateHTMLProperty(self._client, self._classname, self._nodeid,
self._prop, self._formname, ret)
- def field(self, size=30, default=None, format=_marker, popcal=True):
+ def field(self, size=30, default=None, format=_marker, popcal=True,
+ **kwargs):
"""Render a form edit field for the property
If not editable, just display the value via plain().
@@ -1686,7 +1768,8 @@ class DateHTMLProperty(HTMLProperty):
elif isinstance(value, str) or isinstance(value, unicode):
# most likely erroneous input to be passed back to user
if isinstance(value, unicode): value = value.encode('utf8')
- return self.input(name=self._formname, value=value, size=size)
+ return self.input(name=self._formname, value=value, size=size,
+ **kwargs)
else:
raw_value = value
@@ -1706,7 +1789,8 @@ class DateHTMLProperty(HTMLProperty):
if format is not self._marker:
value = value.pretty(format)
- s = self.input(name=self._formname, value=value, size=size)
+ s = self.input(name=self._formname, value=value, size=size,
+ **kwargs)
if popcal:
s += self.popcal()
return s
@@ -1801,7 +1885,7 @@ class IntervalHTMLProperty(HTMLProperty):
return self._value.pretty()
- def field(self, size=30):
+ def field(self, size=30, **kwargs):
""" Render a form edit field for the property
If not editable, just display the value via plain().
@@ -1813,7 +1897,8 @@ class IntervalHTMLProperty(HTMLProperty):
if value is None:
value = ''
- return self.input(name=self._formname, value=value, size=size)
+ return self.input(name=self._formname, value=value, size=size,
+ **kwargs)
class LinkHTMLProperty(HTMLProperty):
""" Link HTMLProperty
@@ -1866,7 +1951,7 @@ class LinkHTMLProperty(HTMLProperty):
value = cgi.escape(value)
return value
- def field(self, showid=0, size=None):
+ def field(self, showid=0, size=None, **kwargs):
""" Render a form edit field for the property
If not editable, just display the value via plain().
@@ -1884,10 +1969,11 @@ class LinkHTMLProperty(HTMLProperty):
value = linkcl.get(self._value, k)
else:
value = self._value
- return self.input(name=self._formname, value=value, size=size)
+ return self.input(name=self._formname, value=value, size=size,
+ **kwargs)
def menu(self, size=None, height=None, showid=0, additional=[], value=None,
- sort_on=None, **conditions):
+ sort_on=None, html_kwargs={}, translate=True, **conditions):
""" Render a form select list for this property
"size" is used to limit the length of the list labels
@@ -1900,6 +1986,11 @@ class LinkHTMLProperty(HTMLProperty):
(direction, property) where direction is '+' or '-'. A
single string with the direction prepended may be used.
For example: ('-', 'order'), '+name'.
+ "html_kwargs" specified additional html args for the
+ generated html