[ZPT] CVS: Packages/ZTUtils - CHANGES.txt:1.1 Zope.py:1.2 __init__.py:1.4

Evan Simpson evan@zope.com
Mon, 13 Aug 2001 14:15:35 -0400


Update of /cvs-repository/Packages/ZTUtils
In directory cvs.zope.org:/tmp/cvs-serv18444

Modified Files:
	Zope.py __init__.py 
Added Files:
	CHANGES.txt 
Log Message:
Added skip_unauthorized functionality, utility functions.


=== Added File Packages/ZTUtils/CHANGES.txt ===
ZTUtils changes

  This file contains change information for the current release. 
  Change information for previous versions can be found in the
  file HISTORY.txt.

    Version 1.1.0

      Features Added

        - TreeMakers have a setChildAccess() method that you can use
          to control tree construction.  Child nodes can be accessed
          through either an attribute name or callback function.
          Children fetched by attribute name can be filtered through a
          callback function.

        - A new LazyFilter class allows you to filter a sequence using
          Zope security and an optional filter callback function.  The
          security and filter tests are lazy, meaning they are
          performed as late as possible.  

          The optional 'skip' argument determines the reaction when
          access to a sequence element is refused by the Zope security
          policy.  The default (None) is to raise the 'Unauthorized'
          exception.  If a string is passed, such elements are
          skipped.  If the string is non-empty, it is treated as a
          permission name, and the element is skipped if the user
          doesn't have that permission on the element.

        - The Zope versions of TreeMaker, SimpleTreeMaker, and Batch
          now use LazyFilter. The TreeMakers have a setSkip() method
          that can be used to set the 'skip' value. Batch has an
          optional 'skip_unauthorized' argument that is passed to
          LazyFilter as 'skip'.

        - Utility functions make_query(), url_query(), and
          make_hidden_input() have been added.

      Bugs Fixed



=== Packages/ZTUtils/Zope.py 1.1 => 1.2 ===
 __version__='$Revision$'[11:-2]
 
-from Tree import encodeExpansion, decodeExpansion
+import sys, cgi, urllib, cgi
+from Tree import encodeExpansion, decodeExpansion, TreeMaker
 from SimpleTree import SimpleTreeMaker
 from Batch import Batch
+from Products.ZCatalog.Lazy import Lazy
+from AccessControl.ZopeGuards import guarded_getitem
+from AccessControl import getSecurityManager, Unauthorized
 from string import split, join
+from types import StringType, ListType, IntType, FloatType
+from DateTime import DateTime
 
-class SimpleTreeMaker(SimpleTreeMaker):
+class LazyFilter(Lazy):
+    # A LazyFilter that checks with the security policy
+
+    def __init__(self, seq, test=None, skip=None):
+        self._seq=seq
+        self._data=[]
+        self._eindex=-1
+        self._test=test
+        if not (skip is None or str(skip) == skip): 
+            raise TypeError, 'Skip must be None or a string'
+        self._skip = skip 
+
+    def __getitem__(self,index):
+
+        data=self._data
+        try: s=self._seq
+        except AttributeError: return data[index]
+
+        i=index
+        if i < 0: i=len(self)+i
+        if i < 0: raise IndexError, index
+
+        ind=len(data)
+        if i < ind: return data[i]
+        ind=ind-1
+
+        test=self._test
+        e=self._eindex
+        skip = self._skip
+        while i > ind:
+            try:
+                e=e+1
+                try: v = guarded_getitem(s, e)
+                except Unauthorized, vv:
+                    if skip is None:
+                        msg = '(item %s): %s' % (index, vv)
+                        raise Unauthorized, msg, sys.exc_info()[2]
+                    continue
+                if skip and not getSecurityManager().checkPermission(skip, v):
+                    continue
+                if test is None or test(v):
+                    data.append(v)
+                    ind=ind+1
+            except IndexError:
+                del self._test
+                del self._seq
+                del self._eindex
+                raise IndexError, index
+        self._eindex=e
+        return data[i]
+
+class TreeSkipMixin:
+    '''Mixin class to make trees test security, and allow
+    skipping of unauthorized objects. '''
+    skip = None
+    def setSkip(self, skip):
+        self.skip = skip
+        return self
+    def getChildren(self, object):
+        return LazyFilter(self._getChildren(object), skip=self.skip)
+
+class TreeMaker(TreeSkipMixin, TreeMaker):
+    _getChildren = TreeMaker.getChildren
+
+class SimpleTreeMaker(TreeSkipMixin, SimpleTreeMaker):
+    _getChildren = SimpleTreeMaker.getChildren
     def cookieTree(self, root_object):
         '''Make a tree with state stored in a cookie.'''
         tree_pre = self.tree_pre
@@ -121,3 +192,166 @@
         rows = tree.flat()
         req.RESPONSE.setCookie(state_name, encodeExpansion(rows))
         return tree, rows
+
+# Make the Batch class test security, and let it skip unauthorized.
+_Batch = Batch
+class Batch(Batch):
+    def __init__(self, sequence, size, start=0, end=0,
+                 orphan=3, overlap=0, skip_unauthorized=None):
+        sequence = LazyFilter(sequence, skip=skip_unauthorized)
+        _Batch.__init__(self, sequence, size, start, end,
+                        orphan, overlap)
+
+# These functions are meant to be used together in templates that use
+# trees or batches.  For example, given a batch with a 'bstart' query
+# argument, you would use "url_query(request, omit='bstart')" to get
+# the base for the batching links, then append 
+# "make_query(bstart=batch.previous.first)" to one and
+# "make_query(bstart=batch.end)" to the other.
+
+def make_query(*args, **kwargs):
+    '''Construct a URL query string, with marshalling markup.
+
+    If there are positional arguments, they must be dictionaries.
+    They are combined with the dictionary of keyword arguments to form
+    a dictionary of query names and values.
+
+    Query names (the keys) must be strings.  Values may be strings,
+    integers, floats, or DateTimes, and they may also be lists or
+    namespaces containing these types.  Names and string values
+    should not be URL-quoted.  All arguments are marshalled with
+    complex_marshal().
+    '''
+
+    d = {}
+    for arg in args:
+        d.update(arg)
+    d.update(kwargs)
+
+    uq = urllib.quote
+    qlist = complex_marshal(d.items())
+    for i in range(len(qlist)):
+        k, m, v = qlist[i]
+        qlist[i] = '%s%s=%s' % (uq(k), m, uq(str(v)))
+
+    return join(qlist, '&')
+                
+def make_hidden_input(*args, **kwargs):
+    '''Construct a set of hidden input elements, with marshalling markup.
+
+    If there are positional arguments, they must be dictionaries.
+    They are combined with the dictionary of keyword arguments to form
+    a dictionary of query names and values.
+
+    Query names (the keys) must be strings.  Values may be strings,
+    integers, floats, or DateTimes, and they may also be lists or
+    namespaces containing these types.  All arguments are marshalled with
+    complex_marshal().
+    '''
+
+    d = {}
+    for arg in args:
+        d.update(arg)
+    d.update(kwargs)
+
+    hq = cgi.escape
+    qlist = complex_marshal(d.items())
+    for i in range(len(qlist)):
+        k, m, v = qlist[i]
+        qlist[i] = ('<input type="hidden" name="%s%s" value="%s">'
+                    % (hq(k), m, hq(str(v))))
+
+    return join(qlist, '\n')
+                
+def complex_marshal(pairs):
+    '''Add request marshalling information to a list of name-value pairs.
+
+    Names must be strings.  Values may be strings,
+    integers, floats, or DateTimes, and they may also be lists or
+    namespaces containing these types.
+
+    The list is edited in place so that each (name, value) pair
+    becomes a (name, marshal, value) triple.  The middle value is the
+    request marshalling string.  Integer, float, and DateTime values
+    will have ":int", ":float", or ":date" as their marshal string.
+    Lists will be flattened, and the elements given ":list" in
+    addition to their simple marshal string.  Dictionaries will be
+    flattened and marshalled using ":record".
+    '''
+
+    for i in range(len(pairs)):
+        k, v = pairs[i]
+        m = ''
+        sublist = None
+        if isinstance(v, StringType):
+            pass
+        elif hasattr(v, 'items'):
+            sublist = []
+            for sk, sv in v.items():
+                sm = simple_marshal(sv)
+                sublist.append(('%s.%s' % (k, sk), '%s:record' % sm,  sv))
+        elif isinstance(v, ListType):
+            sublist = []
+            for sv in v:
+                sm = simple_marshal(sv)
+                sublist.append((k, '%s:list' % sm, sv))
+        else:
+            m = simple_marshal(v)
+        if sublist is None:
+            pairs[i] = (k, m, v)
+        else:
+            pairs[i:i + 1] = sublist
+
+    return pairs
+
+def simple_marshal(v):
+    if isinstance(v, StringType):
+        return ''
+    if isinstance(v, IntType):
+        return ':int'
+    if isinstance(v, FloatType):
+        return ':float'
+    if isinstance(v, DateTime):
+        return ':date'
+    return ''
+
+def url_query(request, req_name="URL", omit=None):
+    '''Construct a URL with a query string, using the current request.
+
+    request: the request object
+    req_name: the name, such as "URL1" or "BASEPATH1", to get from request
+    omit: sequence of name of query arguments to omit.  If a name
+    contains a colon, it is treated literally.  Otherwise, it will
+    match each argument name that starts with the name and a period or colon. 
+    '''
+
+    base = request[req_name]
+    qs = request.get('QUERY_STRING', '')
+    
+    if qs and omit:
+        qsparts = split(qs, '&')
+
+        if isinstance(omit, StringType):
+            omits = {omit: None}
+        else:
+            omits = {}
+            for name in omit:
+                omits[name] = None
+        omitted = omits.has_key
+
+        unq = urllib.unquote
+        for i in range(len(qsparts)):
+            name = unq(split(qsparts[i], '=', 1)[0])
+            if omitted(name):
+                qsparts[i] = ''
+            name = split(name, ':', 1)[0]
+            if omitted(name):
+                qsparts[i] = ''
+            name = split(name, '.', 1)[0]
+            if omitted(name):
+                qsparts[i] = ''
+            
+        qs = join(filter(None, qsparts), '&')
+
+    # We alway append '?' since arguments will be appended to the URL
+    return '%s?%s' % (base, qs)


=== Packages/ZTUtils/__init__.py 1.3 => 1.4 ===
     __roles__ = None
 
-    from Zope import Batch, SimpleTreeMaker
+    from Zope import Batch, TreeMaker, SimpleTreeMaker, LazyFilter
+    from Zope import url_query, make_query, make_hidden_input
+