Code

. added generic item editing
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Fri, 13 Sep 2002 03:31:19 +0000 (03:31 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Fri, 13 Sep 2002 03:31:19 +0000 (03:31 +0000)
 . much nicer layout of template rendering errors
 . added context/is_edit_ok and context/is_view_ok convenience methods and
   implemented use of them in the classic template

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1158 57a73879-2fb5-44c3-a270-3262357dd7e2

14 files changed:
CHANGES.txt
doc/customizing.txt
roundup/cgi/PageTemplates/Expressions.py
roundup/cgi/PageTemplates/MultiMapping.py
roundup/cgi/cgitb.py
roundup/cgi/templating.py
roundup/templates/classic/dbinit.py
roundup/templates/classic/html/_generic.index
roundup/templates/classic/html/_generic.item
roundup/templates/classic/html/issue.index
roundup/templates/classic/html/issue.item
roundup/templates/classic/html/query.item
roundup/templates/classic/html/user.index
roundup/templates/classic/html/user.item

index 22da228994ffae80a2328ed727a49d7d9232974e..b6217a3f432db26eabfe32b773ad30de90ceecbe 100644 (file)
@@ -8,6 +8,12 @@ Fixed:
  . switched the default issue item display to only show issue summary
    (add instructions to doc to make it display entire content)
 
+Feature:
+ . added generic item editing
+ . much nicer layout of template rendering errors
+ . added context/is_edit_ok and context/is_view_ok convenience methods and
+   implemented use of them in the classic template
+
 2002-09-11 0.5.0 beta1
 Fixed:
  . #576086 ] dumb copying mistake (frontends/ZRoundup.py)
index 76a890164c40e66cc9874b35ca0622c8d31a58e1..614e8f64445e5e39924a6fe9d915e5c4332a2e81 100644 (file)
@@ -2,7 +2,7 @@
 Customising Roundup
 ===================
 
-:Version: $Revision: 1.32 $
+:Version: $Revision: 1.33 $
 
 .. This document borrows from the ZopeBook section on ZPT. The original is at:
    http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -877,6 +877,8 @@ classhelp   display a link to a javascript popup containing this class'
 submit      generate a submit button (and action hidden element)
 renderWith  render this class with the given template.
 history     returns 'New node - no history' :)
+is_edit_ok  is the user allowed to Edit the current class?
+is_view_ok  is the user allowed to View the current class?
 =========== =============================================================
 
 Note that if you have a property of the same name as one of the above methods,
@@ -910,6 +912,8 @@ renderQueryForm specific to the "query" class - render the search form for
                 the query
 hasPermission   specific to the "user" class - determine whether the user
                 has a Permission
+is_edit_ok      is the user allowed to Edit the current item?
+is_view_ok      is the user allowed to View the current item?
 =============== =============================================================
 
 
index 1d253bd82a04fa5c5319d149d7b2124333433b9e..054e29344b2793c1c1b79997ed48aee1eb816282 100644 (file)
@@ -25,7 +25,7 @@ Modified for Roundup 0.5 release:
 
 """
 
-__version__='$Revision: 1.5 $'[11:-2]
+__version__='$Revision: 1.6 $'[11:-2]
 
 import re, sys
 from TALES import Engine, CompilerError, _valid_name, NAME_RE, \
@@ -121,7 +121,7 @@ class SubPathExpr:
                     # of path names.
                     path[i:i+1] = list(val)
         base = self._base
-        __traceback_info__ = 'sub path expression "%s"'%base
+        __traceback_info__ = 'path expression "%s"'%('/'.join(self._path))
         if base == 'CONTEXTS':
             ob = econtext.contexts
         else:
@@ -264,6 +264,10 @@ class DeferExpr:
     def __repr__(self):
         return 'defer:%s' % `self._s`
 
+class TraversalError:
+    def __init__(self, path, name):
+        self.path = path
+        self.name = name
 
 def restrictedTraverse(self, path, securityManager,
                        get=getattr, has=hasattr, N=None, M=[],
@@ -281,8 +285,8 @@ def restrictedTraverse(self, path, securityManager,
     #print 'TRAVERSE', (object, path)
     done = []
     while path:
-        __traceback_info__ = 'Traversed %r\n ... looking for %r'%(done, path)
         name = path.pop()
+        __traceback_info__ = TraversalError(done, name)
 
         if isinstance(name, TupleType):
             object = apply(object, name)
@@ -320,7 +324,7 @@ def restrictedTraverse(self, path, securityManager,
                 raise
         #print '... object is now', `o`
         object = o
-        done.append(o)
+        done.append((name, o))
 
     return object
 
index b528288766c99c9d6016e1f1357bdd7efa180563..d4bb542509738ab7edd0df46ab94626841a91a15 100644 (file)
@@ -22,4 +22,8 @@ class MultiMapping:
         self.stores.append(store)
     def pop(self):
         return self.stores.pop()
-
+    def items(self):
+        l = []
+        for store in self.stores:
+            l = l + store.items()
+        return l
index 1b13ab32bfb6fd457c33268b82a5f22401e22b71..630121b7d65c5518677782feba285c90da780503 100644 (file)
@@ -1,13 +1,13 @@
 #
 # This module was written by Ka-Ping Yee, <ping@lfw.org>.
 # 
-# $Id: cgitb.py,v 1.5 2002-09-10 01:07:05 richard Exp $
+# $Id: cgitb.py,v 1.6 2002-09-13 03:31:18 richard Exp $
 
 __doc__ = """
 Extended CGI traceback handler by Ka-Ping Yee, <ping@lfw.org>.
 """
 
-import sys, os, types, string, keyword, linecache, tokenize, inspect
+import sys, os, types, string, keyword, linecache, tokenize, inspect, cgi
 import pydoc, traceback
 
 from roundup.i18n import _
@@ -20,43 +20,55 @@ def breaker():
 def niceDict(indent, dict):
     l = []
     for k,v in dict.items():
-        l.append('%s%s: %r'%(indent,k,v))
+        l.append('<tr><td><strong>%s</strong></td><td>%s</td></tr>'%(k,
+            cgi.escape(repr(v))))
     return '\n'.join(l)
 
 def pt_html(context=5):
-    import cgi
-    etype, evalue = sys.exc_type, sys.exc_value
-    if type(etype) is types.ClassType:
-        etype = etype.__name__
-    pyver = 'Python ' + string.split(sys.version)[0] + '<br>' + sys.executable
-    head = pydoc.html.heading(
-        '<font size=+1><strong>%s</strong>: %s</font>'%(etype, evalue),
-        '#ffffff', '#777777', pyver)
-
-    head = head + _('<p>A problem occurred in your template</p><pre>')
-
-    l = []
+    l = ['<h1>Templating Error</h1>'
+         '<p class="help">Debugging information follows</p>'
+         '<ol>']
+    from roundup.cgi.PageTemplates.Expressions import TraversalError
     for frame, file, lnum, func, lines, index in inspect.trace(context):
         args, varargs, varkw, locals = inspect.getargvalues(frame)
         if locals.has_key('__traceback_info__'):
             ti = locals['__traceback_info__']
-            l.append(str(ti))
+            if isinstance(ti, TraversalError):
+                s = []
+                for name, info in ti.path:
+                    s.append('<li>"%s" (%s)</li>'%(name,cgi.escape(repr(info))))
+                s = '\n'.join(s)
+                l.append('<li>Looking for "%s", current path:<ol>%s</ol></li>'%(
+                    ti.name, s))
+            else:
+                l.append('<li>In %s</li>'%cgi.escape(str(ti)))
         if locals.has_key('__traceback_supplement__'):
             ts = locals['__traceback_supplement__']
             if len(ts) == 2:
                 supp, context = ts
-                l.append('in template %r'%context.id)
+                l.append('<li>A problem occurred in your template "%s"</li>'%
+                    str(context.id))
             elif len(ts) == 3:
                 supp, context, info = ts
-                l.append('in expression %r\n current variables:\n%s\n%s\n'%(info,
-                    niceDict('    ', context.global_vars),
-                    niceDict('    ', context.local_vars)))
-                # context._scope_stack))
-
-    l.append('\n')
-    l.append(''.join(traceback.format_exception(etype, evalue,
-        sys.exc_traceback)))
-    return head + cgi.escape('\n'.join(l)) + '</pre><p>&nbsp;</p>'
+                l.append('''
+<li>While evaluating the %r expression on line %d
+<table class="otherinfo" style="font-size: 90%%">
+ <tr><th colspan="2" class="header">Current variables:</th></tr>
+ %s
+ %s
+</table></li>
+'''%(info, context.position[0], niceDict('    ', context.global_vars),
+     niceDict('    ', context.local_vars)))
+
+    l.append('''
+</ol>
+<table style="font-size: 80%%; color: gray">
+ <tr><th class="header" align="left">Full traceback:</th></tr>
+ <tr><td><pre>%s</pre></td></tr>
+</table>'''%cgi.escape(''.join(traceback.format_exception(sys.exc_type,
+        sys.exc_value, sys.exc_traceback))))
+    l.append('<p>&nbsp;</p>')
+    return '\n'.join(l)
 
 def html(context=5):
     etype, evalue = sys.exc_type, sys.exc_value
index 8f63d3a8576c24897cb600d89e3cd194f82144f8..2425f6810a2125fdf526c21b482d24c33915927f 100644 (file)
@@ -155,7 +155,10 @@ class RoundupPageTemplate(PageTemplate.PageTemplate):
         }
         # add in the item if there is one
         if client.nodeid:
-            c['context'] = HTMLItem(client, classname, client.nodeid)
+            if classname == 'user':
+                c['context'] = HTMLUser(client, classname, client.nodeid)
+            else:
+                c['context'] = HTMLItem(client, classname, client.nodeid)
         else:
             c['context'] = HTMLClass(client, classname)
         return c
@@ -218,15 +221,34 @@ def lookupIds(db, prop, ids, num_re=re.compile('-?\d+')):
             l.append(cl.lookup(entry))
     return l
 
-class HTMLClass:
+class HTMLPermissions:
+    ''' Helpers that provide answers to commonly asked Permission questions.
+    '''
+    def is_edit_ok(self):
+        ''' Is the user allowed to Edit the current class?
+        '''
+        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):
     ''' Accesses through a class (either through *class* or *db.<classname>*)
     '''
     def __init__(self, client, classname):
         self._client = client
         self._db = client.db
 
-        # we want classname to be exposed
-        self.classname = classname
+        # we want classname to be exposed, but _classname gives a
+        # consistent API for extending Class/Item
+        self._classname = self.classname = classname
         if classname is not None:
             self._klass = self._db.getclass(self.classname)
             self._props = self._klass.getprops()
@@ -399,7 +421,7 @@ class HTMLClass:
         # use our fabricated request
         return pt.render(self._client, self.classname, req)
 
-class HTMLItem:
+class HTMLItem(HTMLPermissions):
     ''' Accesses through an *item*
     '''
     def __init__(self, client, classname, nodeid):
@@ -627,6 +649,7 @@ class HTMLUser(HTMLItem):
 
         # used for security checks
         self._security = client.db.security
+
     _marker = []
     def hasPermission(self, role, classname=_marker):
         ''' Determine if the user has the Role.
@@ -638,6 +661,20 @@ class HTMLUser(HTMLItem):
             classname = self._default_classname
         return self._security.hasPermission(role, 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:
     ''' String, Number, Date, Interval HTMLProperty
 
index 9ed49bdcaf7e9214703e695ca11adcb8e2b49517..778766f9933f06e08ceb3b40db181176c3fb1126 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: dbinit.py,v 1.28 2002-09-11 02:49:56 richard Exp $
+# $Id: dbinit.py,v 1.29 2002-09-13 03:31:18 richard Exp $
 
 import os
 
@@ -93,7 +93,7 @@ def open(name=None):
     # SECURITY SETTINGS
     #
     # new permissions for this schema
-    for cl in 'issue', 'file', 'msg', 'user', 'keyword':
+    for cl in 'issue', 'file', 'msg', 'user', 'query', 'keyword':
         db.security.addPermission(name="Edit", klass=cl,
             description="User is allowed to edit "+cl)
         db.security.addPermission(name="View", klass=cl,
@@ -101,7 +101,7 @@ def open(name=None):
 
     # Assign the access and edit permissions for issue, file and message
     # to regular users now
-    for cl in 'issue', 'file', 'msg', 'keyword':
+    for cl in 'issue', 'file', 'msg', 'query', 'keyword':
         p = db.security.getPermission('View', cl)
         db.security.addPermissionToRole('User', p)
         p = db.security.getPermission('Edit', cl)
index 69d3d9926f080d7a6e0380542370397816d58236..16ca747c01510a487bb3b2f8bda390a33d63e4cf 100644 (file)
@@ -1,14 +1,9 @@
 <!-- dollarId: issue.index,v 1.2 2001/07/29 04:07:37 richard Exp dollar-->
-<tal:block tal:define="
-    editok python:request.user.hasPermission('Edit') or
-           context.id == request.user.id;
-    viewok python:request.user.hasPermission('View')">
-
-<span tal:condition="python:not (viewok or editok)">
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())">
 You are not allowed to view this page.
 </span>
 
-<tal:block tal:condition="editok">
+<tal:block tal:condition="context/is_edit_ok">
 <p class="form-help">
  You may edit the contents of the <span tal:replace="request/classname" />
  class using this form. Commas, newlines and double quotes (") must be
@@ -35,8 +30,7 @@ You are not allowed to view this page.
 </form>
 </tal:block>
 
-<tal:block tal:condition="python:viewok and not editok">
+<tal:block tal:condition="context/is_only_view_ok">
 view ok
 </tal:block>
 
-</tal:block>
index d0750f5c240e52c6d9052b3695ef4e0cff354a24..8fcf6721978f31c409725afd01cb1fb611d30051 100644 (file)
@@ -1,5 +1,9 @@
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())">
+You are not allowed to view this page.
+</span>
+
 <form method="POST" onSubmit="return submit_once()"
-      enctype="multipart/form-data">
+      enctype="multipart/form-data" tal:condition="context/is_edit_ok">
 
 <input type="hidden" name=":template" value="item">
 <input type="hidden" name=":required" value="title">
 </table>
 
 
-<tal:block tal:condition="context/id">
+<table class="form" tal:condition="context/is_only_view_ok">
+
+<tr tal:repeat="prop python:db[context._classname].properties()">
+ <tal:block tal:condition="python:prop._name not in ('id', 'creator',
+                                  'creation', 'activity')">
+  <th tal:content="prop/_name"></th>
+  <td tal:content="structure python:context[prop._name].field()"></td>
+ </tal:block>
+</tr>
+<tr>
+ <td>&nbsp;</td>
+ <td colspan=3 tal:content="structure context/submit">
+  submit button will go here
+ </td>
+</tr>
+</table>
+
+
+<tal:block tal:condition="python:context.id and context.is_view_ok()">
  <tal:block tal:replace="structure context/history" />
 </tal:block>
-
-</form>
index 4f69ec6d80e004edd81ae161d48de90fb627d677..d6cce5335b39410f7ea68ab68fb3abca7e09007f 100644 (file)
@@ -1,5 +1,9 @@
 <!-- dollarId: issue.index,v 1.2 2001/07/29 04:07:37 richard Exp dollar-->
-<tal:block tal:define="batch request/batch">
+<tal:block tal:condition="not:context/is_view_ok">
+You are not allowed to view this page.
+</tal:block>
+
+<tal:block tal:define="batch request/batch" tal:condition="context/is_view_ok">
  <table class="list">
   <tr>
    <th tal:condition="request/show/priority">Priority</th>
index 12744a61bf21c56ae676223f84a2437681a2daee..ddf1fb917e1115581df2e192988902fd9dee723a 100644 (file)
@@ -1,6 +1,10 @@
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())">
+You are not allowed to view this page.
+</span>
+
 <!-- dollarId: issue.item,v 1.4 2001/08/03 01:19:43 richard Exp dollar-->
 <form method="POST" onSubmit="return submit_once()"
-      enctype="multipart/form-data">
+      enctype="multipart/form-data" tal:condition="context/is_edit_ok">
 
 <input type="hidden" name=":template" value="item">
 <input type="hidden" name=":required" value="title">
 </tr>
 </table>
 
-<span tal:condition="context/id" tal:content="structure string:Created on
-<b>${context/creation}</b> by <b>${context/creator}</b>, last
-changed <b>${context/activity}</b>.">activity info</span>
+</form>
+
+<table class="form" tal:condition="context/is_only_view_ok">
+<tr>
+ <th nowrap>Title</th><td colspan=3 tal:content="context/title">title</td>
+</tr>
+
+<tr>
+ <th nowrap>Priority</th><td tal:content="context/priority">priority</td>
+ <th nowrap>Status</th><td tal:content="context/status">status</td>
+</tr>
+
+<tr>
+ <th nowrap>Superseder</th>
+ <td>
+  <span tal:condition="context/superseder" tal:repeat="sup context/superseder">
+   <br>View: <a tal:attributes="href string:issue${sup/id}"
+                tal:content="sup/id"></a>
+  </span>
+ </td>
+ <th nowrap>Nosy List</th><td><span tal:replace="context/nosy" /></td>
+</tr>
+
+<tr>
+ <th nowrap>Assigned To</th><td tal:content="context/assignedto"></td>
+ <th nowrap>Topics</th><td tal:content="structure context/topic"></td>
+</tr>
+</table>
+
+<tal:block tal:condition="python:context.id and context.is_view_ok()">
 
-<tal:block tal:condition="context/id">
+ <p tal:content="structure string:Created on
+  <b>${context/creation}</b> by <b>${context/creator}</b>, last
+  changed <b>${context/activity}</b>.">activity info
+ </p>
 
  <table class="messages" tal:condition="context/messages">
   <tr><th colspan=4 class="header">Messages</th></tr>
@@ -102,4 +136,3 @@ changed <b>${context/activity}</b>.">activity info</span>
 
 </tal:block>
 
-</form>
index 2b5a64842b70cbee9a514f25d1a4817ca6d4ae9e..9e4f2fed83ffd1f7de04857878d4216f74000e4d 100755 (executable)
@@ -1,2 +1,7 @@
-<span tal:content="structure context/renderQueryForm" />
+<span tal:condition="not:context/is_edit_ok">
+You are not allowed to view this page.
+</span>
+
+<span tal:condition="context/is_edit_ok"
+      tal:content="structure context/renderQueryForm" />
 
index d553f39d0d6a761dbe6e1c39bb85bd2c66ad0d14..0bb7d13498e38aba8f4877113c3339f5cb767d79 100644 (file)
@@ -1,6 +1,11 @@
 <!-- dollarId: user.index,v 1.3 2002/07/09 05:29:51 richard Exp dollar-->
-<table width="100%" border=0 cellspacing=0 cellpadding=2 class="list">
-<tr class="list-header">
+
+<span tal:condition="not:context/is_view_ok">
+You are not allowed to view this page.
+</span>
+
+<table width="100%" tal:condition="context/is_view_ok" class="list">
+<tr>
  <th>Username</th>
  <th>Real name</th>
  <th>Organisation</th>
 </tr>
 <tr tal:repeat="user context/list"
     tal:attributes="class python:['row-normal', 'row-alt'][repeat['user'].even()]">
- <td valign="top">
+ <td>
   <a tal:attributes="href string:user${user/id}"
      tal:content="user/username">username</a>
  </td>
- <td valign="top" tal:content="user/realname">realname</td>
- <td valign="top" tal:content="user/organisation">organisation</td>
- <td valign="top" tal:content="python:user.address.email()">address</td>
- <td valign="top" tal:content="user/phone">phone</td>
+ <td tal:content="user/realname">realname</td>
+ <td tal:content="user/organisation">organisation</td>
+ <td tal:content="python:user.address.email()">address</td>
+ <td tal:content="user/phone">phone</td>
 </tr>
 </table>
index 724c6aa3f3dc3a079011db0401fda13041493654..84e52a0022307f8b3735da6e560341a6336cbc8f 100644 (file)
@@ -1,15 +1,10 @@
 <!-- dollarId: user.item,v 1.7 2002/08/16 04:29:04 richard Exp dollar-->
-<tal:block tal:define="
-    editok python:request.user.hasPermission('Edit') or
-           context.id == request.user.id;
-    viewok python:request.user.hasPermission('View')">
-
-<span tal:condition="python:not (viewok or editok)">
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())">
 You are not allowed to view this page.
 </span>
 
-<tal:block tal:condition="editok">
-<form method="POST" onSubmit="return submit_once()" enctype="multipart/form-data">
+<form method="POST" onSubmit="return submit_once()"
+      enctype="multipart/form-data" tal:condition="context/is_edit_ok">
 
 <input type="hidden" name=":required" value="username,address">
 
@@ -36,7 +31,7 @@ You are not allowed to view this page.
  </tr>
  <tr>
   <th>Phone</th>
-  <td tal:content="structure context/phone/field">phone</td>
+  <td tal:content="structure context/phone/field/asdfasdf">phone</td>
  </tr>
  <tr>
   <th>Organisation</th>
@@ -58,26 +53,19 @@ You are not allowed to view this page.
 </table>
 </form>
 
-<tal:block tal:condition="context/id">
- <table class="otherinfo" tal:condition="context/queries">
-  <tr><th colspan="2" class="header">Queries</th></tr>
-  <tr><th>Name</th><th>Display</th></tr>
-  <tr tal:repeat="query context/queries">
-   <td><a tal:attributes="href string:query${query/id}"
-          tal:content="query/name"></a></td>
-   <td>
-    <a tal:attributes="href python:'%s%s'%(query['klass'], query['url'])">display</a>
-   </td>
-  </tr>
- </table>
-
- <tal:block tal:replace="structure context/history" />
-
-</tal:block>
-
-</tal:block>
+<table class="otherinfo" tal:condition="context/queries">
+ <tr><th colspan="2" class="header">Queries</th></tr>
+ <tr><th>Name</th><th>Display</th></tr>
+ <tr tal:repeat="query context/queries">
+  <td><a tal:attributes="href string:query${query/id}"
+         tal:content="query/name"></a></td>
+  <td>
+   <a tal:attributes="href python:'%s%s'%(query['klass'], query['url'])">display</a>
+  </td>
+ </tr>
+</table>
 
-<table class="form" tal:condition="python:viewok and not editok">
+<table class="form" tal:condition="context/is_only_view_ok">
  <tr>
   <th colspan=2 class="header" tal:content="context/realname">realname</th>
  </tr>
@@ -99,5 +87,7 @@ You are not allowed to view this page.
  </tr>
 </table>
 
+<tal:block tal:condition="python:context.id and context.is_view_ok()">
+ <tal:block tal:replace="structure context/history" />
 </tal:block>