Code

- added a favicon
[roundup.git] / roundup / cgi / templating.py
index 0d644dd22d033dc9ba18662530dbf742e1ea7a54..324e75f47ac5114833fbdeeb51b05fb6139f2706 100644 (file)
@@ -1,5 +1,15 @@
 """Implements the API used in the HTML templating for the web interface.
 """
+
+todo = '''
+- Most methods should have a "default" arg to supply a value 
+  when none appears in the hyperdb or request. 
+- Multilink property additions: change_note and new_upload
+- Add class.find() too
+- NumberHTMLProperty should support numeric operations
+- HTMLProperty should have an isset() method
+'''
+
 __docformat__ = 'restructuredtext'
 
 from __future__ import nested_scopes
@@ -124,7 +134,7 @@ class Templates:
                 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]
 
@@ -134,7 +144,7 @@ class Templates:
         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):
@@ -195,6 +205,7 @@ class RoundupPageTemplate(PageTemplate.PageTemplate):
              '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:
@@ -252,8 +263,13 @@ class HTMLDatabase:
         # check to see if we're actually accessing an item
         m = desre.match(item)
         if m:
-            self._client.db.getclass(m.group('cl'))
-            return HTMLItem(self._client, m.group('cl'), m.group('id'))
+            cl = m.group('cl')
+            self._client.db.getclass(cl)
+            if cl == 'user':
+                klass = HTMLUser
+            else:
+                klass = HTMLItem
+            return klass(self._client, cl, m.group('id'))
         else:
             self._client.db.getclass(item)
             if item == 'user':
@@ -269,14 +285,14 @@ class HTMLDatabase:
     def classes(self):
         l = self._client.db.classes.keys()
         l.sort()
-        r = []
+        m = []
         for item in l:
             if item == 'user':
                 m.append(HTMLUserClass(self._client, item))
             m.append(HTMLClass(self._client, item))
-        return r
+        return m
 
-def lookupIds(db, prop, ids, fail_ok=False, num_re=re.compile('-?\d+')):
+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)
     '''
@@ -401,12 +417,12 @@ class HTMLClass(HTMLInputMixin, HTMLPermissions):
             if form.has_key(item):
                 if isinstance(prop, hyperdb.Multilink):
                     value = lookupIds(self._db, prop,
-                        handleListCGIValue(form[item]), fail_ok=True)
+                        handleListCGIValue(form[item]), fail_ok=1)
                 elif isinstance(prop, hyperdb.Link):
                     value = form[item].value.strip()
                     if value:
                         value = lookupIds(self._db, prop, [value],
-                            fail_ok=True)[0]
+                            fail_ok=1)[0]
                     else:
                         value = None
                 else:
@@ -644,6 +660,10 @@ class HTMLItem(HTMLInputMixin, HTMLPermissions):
     def designator(self):
         """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.
@@ -679,25 +699,27 @@ class HTMLItem(HTMLInputMixin, HTMLPermissions):
         timezone = self._db.getUserTimezone()
         if direction == 'descending':
             history.reverse()
+            # pre-load the history with the current state
             for prop_n in self._props.keys():
                 prop = self[prop_n]
-                if isinstance(prop, HTMLProperty):
-                    current[prop_n] = prop.plain()
-                    # make link if hrefable
-                    if (self._props.has_key(prop_n) and
-                            isinstance(self._props[prop_n], hyperdb.Link)):
-                        classname = self._props[prop_n].classname
-                        try:
-                            template = find_template(self._db.config.TEMPLATES,
-                                classname, 'item')
-                            if template[1].startswith('_generic'):
-                                raise NoTemplate, 'not really...'
-                        except NoTemplate:
-                            pass
-                        else:
-                            id = self._klass.get(self._nodeid, prop_n, None)
-                            current[prop_n] = '<a href="%s%s">%s</a>'%(
-                                classname, id, current[prop_n])
+                if not isinstance(prop, HTMLProperty):
+                    continue
+                current[prop_n] = prop.plain()
+                # make link if hrefable
+                if (self._props.has_key(prop_n) and
+                        isinstance(self._props[prop_n], hyperdb.Link)):
+                    classname = self._props[prop_n].classname
+                    try:
+                        template = find_template(self._db.config.TEMPLATES,
+                            classname, 'item')
+                        if template[1].startswith('_generic'):
+                            raise NoTemplate, 'not really...'
+                    except NoTemplate:
+                        pass
+                    else:
+                        id = self._klass.get(self._nodeid, prop_n, None)
+                        current[prop_n] = '<a href="%s%s">%s</a>'%(
+                            classname, id, current[prop_n])
  
         for id, evt_date, user, action, args in history:
             date_s = str(evt_date.local(timezone)).replace("."," ")
@@ -819,17 +841,25 @@ class HTMLItem(HTMLInputMixin, HTMLPermissions):
                             current[k] = str(d)
 
                     elif isinstance(prop, hyperdb.Interval) and args[k]:
-                        d = date.Interval(args[k])
-                        cell.append('%s: %s'%(k, str(d)))
+                        val = str(date.Interval(args[k]))
+                        cell.append('%s: %s'%(k, val))
                         if current.has_key(k):
                             cell[-1] += ' -> %s'%current[k]
-                            current[k] = str(d)
+                            current[k] = val
 
                     elif isinstance(prop, hyperdb.String) and args[k]:
-                        cell.append('%s: %s'%(k, cgi.escape(args[k])))
+                        val = cgi.escape(args[k])
+                        cell.append('%s: %s'%(k, val))
                         if current.has_key(k):
                             cell[-1] += ' -> %s'%current[k]
-                            current[k] = cgi.escape(args[k])
+                            current[k] = val
+
+                    elif isinstance(prop, hyperdb.Boolean) and args[k] is not None:
+                        val = args[k] and 'Yes' or 'No'
+                        cell.append('%s: %s'%(k, val))
+                        if current.has_key(k):
+                            cell[-1] += ' -> %s'%current[k]
+                            current[k] = val
 
                     elif not args[k]:
                         if current.has_key(k):
@@ -880,6 +910,15 @@ class HTMLItem(HTMLInputMixin, HTMLPermissions):
         # use our fabricated request
         return pt.render(self._client, req.classname, req)
 
+    def download_url(self):
+        ''' Assume that this item is a FileClass and that it has a name
+        and content. Construct a URL for the download of the content.
+        '''
+        name = self._klass.get(self._nodeid, 'name')
+        url = '%s%s/%s'%(self._classname, self._nodeid, name)
+        return urllib.quote(url)
+
+
 class HTMLUserPermission:
 
     def is_edit_ok(self):
@@ -975,6 +1014,10 @@ class HTMLProperty(HTMLInputMixin, HTMLPermissions):
             return cmp(self._value, other._value)
         return cmp(self._value, other)
 
+    def isset(self):
+        '''Is my _value None?'''
+        return self._value is None
+
     def is_edit_ok(self):
         ''' Is the user allowed to Edit the current class?
         '''
@@ -1205,7 +1248,7 @@ class BooleanHTMLProperty(HTMLProperty):
         '''
         self.view_check()
 
-        if not is_edit_ok():
+        if not self.is_edit_ok():
             return self.plain()
 
         checked = self._value and "checked" or ""
@@ -1257,7 +1300,7 @@ class DateHTMLProperty(HTMLProperty):
             tz = self._db.getUserTimezone()
             value = cgi.escape(str(self._value.local(tz)))
 
-        if is_edit_ok():
+        if self.is_edit_ok():
             value = '&quot;'.join(value.split('"'))
             return self.input(name=self._formname,value=value,size=size)
         
@@ -1398,13 +1441,13 @@ class LinkHTMLProperty(HTMLProperty):
         else:
             k = linkcl.getkey()
             if k:
-                label = linkcl.get(self._value, k)
+                value = linkcl.get(self._value, k)
             else:
-                label = self._value
-            value = cgi.escape(str(self._value))
+                value = self._value
+            value = cgi.escape(str(value))
             value = '&quot;'.join(value.split('"'))
         return '<input name="%s" value="%s" size="%s">'%(self._formname,
-            label, size)
+            value, size)
 
     def menu(self, size=None, height=None, showid=0, additional=[],
             sort_on=None, **conditions):
@@ -1507,6 +1550,10 @@ class MultilinkHTMLProperty(HTMLProperty):
         '''
         return str(value) in self._value
 
+    def isset(self):
+        '''Is my _value []?'''
+        return self._value == []
+
     def reverse(self):
         ''' return the list in reverse order
         '''
@@ -2064,3 +2111,11 @@ class TemplatingUtils:
         return Batch(self.client, sequence, size, start, end, orphan,
             overlap)
 
+    def url_quote(self, url):
+        '''URL-quote the supplied text.'''
+        return urllib.quote(url)
+
+    def html_quote(self, html):
+        '''HTML-quote the supplied text.'''
+        return cgi.escape(url)
+