index 49242ef84b0729b2d2dac3ac5bd6683b0de33cc4..3fa5b0154ba9280c3adb11efbbfbecea8855e7c6 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
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
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):
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]
+ 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, num_re=re.compile('-?\d+')):
cl = db.getclass(prop.classname)
''' 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':
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 self.input(type="hidden",name="@action",value="edit") + '\n' + \
- self.input(type="submit",name="submit",value=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.
# 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(HTMLInputMixin, HTMLPermissions):
''' String, Number, Date, Interval 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<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()
'''
self.view_check()
- return DateHTMLProperty(self._client, self._nodeid, self._prop,
- self._formname, self._value.local(offset))
+ return DateHTMLProperty(self._client, self._classname, self._nodeid,
+ self._prop, self._formname, self._value.local(offset))
class IntervalHTMLProperty(HTMLProperty):
def plain(self):
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
return self.columns.has_key(name)
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
-
+ '''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):
# _client is needed by HTMLInputMixin