From: richard Date: Fri, 30 Aug 2002 08:25:34 +0000 (+0000) Subject: Adding ZTUtils to the dist X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=39d5aeb17571366211a7d25fa972fa9ade7182cd;p=roundup.git Adding ZTUtils to the dist git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1000 57a73879-2fb5-44c3-a270-3262357dd7e2 --- diff --git a/ZTUtils/Batch.py b/ZTUtils/Batch.py new file mode 100644 index 0000000..88494f8 --- /dev/null +++ b/ZTUtils/Batch.py @@ -0,0 +1,121 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +__doc__='''Batch class, for iterating over a sequence in batches + +$Id: Batch.py,v 1.1 2002-08-30 08:25:33 richard Exp $''' +__version__='$Revision: 1.1 $'[11:-2] + +from ExtensionClass import Base + +class LazyPrevBatch(Base): + def __of__(self, parent): + return Batch(parent._sequence, parent._size, + parent.first - parent._size + parent.overlap, 0, + parent.orphan, parent.overlap) + +class LazyNextBatch(Base): + def __of__(self, parent): + try: parent._sequence[parent.end] + except IndexError: return None + return Batch(parent._sequence, parent._size, + parent.end - parent.overlap, 0, + parent.orphan, parent.overlap) + +class LazySequenceLength(Base): + def __of__(self, parent): + parent.sequence_length = l = len(parent._sequence) + return l + +class Batch(Base): + """Create a sequence batch""" + __allow_access_to_unprotected_subobjects__ = 1 + + previous = LazyPrevBatch() + next = LazyNextBatch() + sequence_length = LazySequenceLength() + + def __init__(self, sequence, size, start=0, end=0, + orphan=0, overlap=0): + '''Encapsulate "sequence" in batches of "size". + + Arguments: "start" and "end" are 0-based indexes into the + sequence. If the next batch would contain no more than + "orphan" elements, it is combined with the current batch. + "overlap" is the number of elements shared by adjacent + batches. If "size" is not specified, it is computed from + "start" and "end". Failing that, it is 7. + + Attributes: Note that the "start" attribute, unlike the + argument, is a 1-based index (I know, lame). "first" is the + 0-based index. "length" is the actual number of elements in + the batch. + + "sequence_length" is the length of the original, unbatched, sequence + ''' + + start = start + 1 + + start,end,sz = opt(start,end,size,orphan,sequence) + + self._sequence = sequence + self.size = sz + self._size = size + self.start = start + self.end = end + self.orphan = orphan + self.overlap = overlap + self.first = max(start - 1, 0) + self.length = self.end - self.first + if self.first == 0: + self.previous = None + + + def __getitem__(self, index): + if index < 0: + if index + self.end < self.first: raise IndexError, index + return self._sequence[index + self.end] + + if index >= self.length: raise IndexError, index + return self._sequence[index+self.first] + + def __len__(self): + return self.length + +def opt(start,end,size,orphan,sequence): + if size < 1: + if start > 0 and end > 0 and end >= start: + size=end+1-start + else: size=7 + + if start > 0: + + try: sequence[start-1] + except IndexError: start=len(sequence) + + if end > 0: + if end < start: end=start + else: + end=start+size-1 + try: sequence[end+orphan-1] + except IndexError: end=len(sequence) + elif end > 0: + try: sequence[end-1] + except IndexError: end=len(sequence) + start=end+1-size + if start - 1 < orphan: start=1 + else: + start=1 + end=start+size-1 + try: sequence[end+orphan-1] + except IndexError: end=len(sequence) + return start,end,size diff --git a/ZTUtils/CHANGES.txt b/ZTUtils/CHANGES.txt new file mode 100644 index 0000000..e2ad284 --- /dev/null +++ b/ZTUtils/CHANGES.txt @@ -0,0 +1,28 @@ +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.5 + + Features Added + + - Added 'sequence_length' attribute to batches. + + - Under Python 2.2, Iterator both accepts and produces Python + iterator objects. + + - first() and last() methods allow you to tell whether the + current element is different from the next or previous + element. This is most useful when the sequence is sorted. + + Bugs Fixed + + - Handle both string and class Unauthorized exceptions. + + - Batch construction masked sequence errors, such as + Unauthorized. + + - Orphan defaulted to different values in different places. + diff --git a/ZTUtils/HISTORY.txt b/ZTUtils/HISTORY.txt new file mode 100644 index 0000000..ee37155 --- /dev/null +++ b/ZTUtils/HISTORY.txt @@ -0,0 +1,57 @@ +ZTUtils history + + This file contains change information for previous versions of + ZTUtils. Change information for the current release can be found + in the file CHANGES.txt. + + + Version 1.1.3 + + Brown-bag bugfix release. + + Version 1.1.2 + + Bugs Fixed + + - Orphans defaulted to 3, which was confusing and out of sync + with DTML-In. + + - Orphan batches were broken. + + Version 1.1.1 + + Bugs Fixed + + - Python 1.5.2-incompatible changes crept in. + + 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. diff --git a/ZTUtils/Iterator.py b/ZTUtils/Iterator.py new file mode 100644 index 0000000..cf3d51c --- /dev/null +++ b/ZTUtils/Iterator.py @@ -0,0 +1,198 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +__doc__='''Iterator class + +Unlike the builtin iterators of Python 2.2+, these classes are +designed to maintain information about the state of an iteration. +The Iterator() function accepts either a sequence or a Python +iterator. The next() method fetches the next item, and returns +true if it succeeds. + +$Id: Iterator.py,v 1.1 2002-08-30 08:25:34 richard Exp $''' +__version__='$Revision: 1.1 $'[11:-2] + +import string + +class Iterator: + '''Simple Iterator class''' + + __allow_access_to_unprotected_subobjects__ = 1 + + nextIndex = 0 + def __init__(self, seq): + self.seq = seq + for inner in seqInner, iterInner: + if inner._supports(seq): + self._inner = inner + self._prep_next = inner.prep_next + return + raise TypeError, "Iterator does not support %s" % `seq` + + def __getattr__(self, name): + try: + inner = getattr(self._inner, 'it_' + name) + except AttributeError: + raise AttributeError, name + return inner(self) + + def next(self): + if not (hasattr(self, '_next') or self._prep_next(self)): + return 0 + self.index = i = self.nextIndex + self.nextIndex = i+1 + self._advance(self) + return 1 + + def _advance(self, it): + self.item = self._next + del self._next + del self.end + self._advance = self._inner.advance + self.start = 1 + + def number(self): return self.nextIndex + + def even(self): return not self.index % 2 + + def odd(self): return self.index % 2 + + def letter(self, base=ord('a'), radix=26): + index = self.index + s = '' + while 1: + index, off = divmod(index, radix) + s = chr(base + off) + s + if not index: return s + + def Letter(self): + return self.letter(base=ord('A')) + + def Roman(self, rnvalues=( + (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'), + (100,'C'),(90,'XC'),(50,'L'),(40,'XL'), + (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ): + n = self.index + 1 + s = '' + for v, r in rnvalues: + rct, n = divmod(n, v) + s = s + r * rct + return s + + def roman(self, lower=string.lower): + return lower(self.Roman()) + + def first(self, name=None): + if self.start: return 1 + return not self.same_part(name, self._last, self.item) + + def last(self, name=None): + if self.end: return 1 + return not self.same_part(name, self.item, self._next) + + def same_part(self, name, ob1, ob2): + if name is None: + return ob1 == ob2 + no = [] + return getattr(ob1, name, no) == getattr(ob2, name, no) is not no + + def __iter__(self): + return IterIter(self) + +class InnerBase: + '''Base Inner class for Iterators''' + # Prep sets up ._next and .end + def prep_next(self, it): + it.next = self.no_next + it.end = 1 + return 0 + + # Advance knocks them down + def advance(self, it): + it._last = it.item + it.item = it._next + del it._next + del it.end + it.start = 0 + + def no_next(self, it): + return 0 + + def it_end(self, it): + if hasattr(it, '_next'): + return 0 + return not self.prep_next(it) + +class SeqInner(InnerBase): + '''Inner class for sequence Iterators''' + + def _supports(self, ob): + try: ob[0] + except TypeError: return 0 + except: pass + return 1 + + def prep_next(self, it): + i = it.nextIndex + try: + it._next = it.seq[i] + except IndexError: + it._prep_next = self.no_next + it.end = 1 + return 0 + it.end = 0 + return 1 + + def it_length(self, it): + it.length = l = len(it.seq) + return l + +try: + StopIteration=StopIteration +except NameError: + StopIteration="StopIteration" + +class IterInner(InnerBase): + '''Iterator inner class for Python iterators''' + + def _supports(self, ob): + try: + if hasattr(ob, 'next') and (ob is iter(ob)): + return 1 + except: + return 0 + + def prep_next(self, it): + try: + it._next = it.seq.next() + except StopIteration: + it._prep_next = self.no_next + it.end = 1 + return 0 + it.end = 0 + return 1 + +class IterIter: + def __init__(self, it): + self.it = it + self.skip = it.nextIndex > 0 and not it.end + def next(self): + it = self.it + if self.skip: + self.skip = 0 + return it.item + if it.next(): + return it.item + raise StopIteration + +seqInner = SeqInner() +iterInner = IterInner() diff --git a/ZTUtils/SimpleTree.py b/ZTUtils/SimpleTree.py new file mode 100644 index 0000000..ca32073 --- /dev/null +++ b/ZTUtils/SimpleTree.py @@ -0,0 +1,57 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +__doc__='''Simple Tree classes + +$Id: SimpleTree.py,v 1.1 2002-08-30 08:25:34 richard Exp $''' +__version__='$Revision: 1.1 $'[11:-2] + +from Tree import TreeMaker, TreeNode, b2a + +class SimpleTreeNode(TreeNode): + def branch(self): + if self.state == 0: + return {'link': None, 'img': '  '} + + if self.state < 0: + setst = 'expand' + exnum = self.aq_parent.expansion_number + img = 'pl' + else: + setst = 'collapse' + exnum = self.expansion_number + img = 'mi' + + base = self.aq_acquire('baseURL') + obid = self.id + pre = self.aq_acquire('tree_pre') + + return {'link': '?%s-setstate=%s,%s,%s#%s' % (pre, setst[0], + exnum, obid, obid), + 'img': '%s' % (base, img, setst)} + + +class SimpleTreeMaker(TreeMaker): + '''Generate Simple Trees''' + + def __init__(self, tree_pre="tree"): + self.tree_pre = tree_pre + + def node(self, object): + node = SimpleTreeNode() + node.object = object + node.id = b2a(self.getId(object)) + return node + + def markRoot(self, node): + node.tree_pre = self.tree_pre + node.baseURL = node.object.REQUEST['BASEPATH1'] diff --git a/ZTUtils/Tree.py b/ZTUtils/Tree.py new file mode 100644 index 0000000..ea0b562 --- /dev/null +++ b/ZTUtils/Tree.py @@ -0,0 +1,240 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +__doc__='''Tree manipulation classes + +$Id: Tree.py,v 1.1 2002-08-30 08:25:34 richard Exp $''' +__version__='$Revision: 1.1 $'[11:-2] + +from Acquisition import Explicit +from ComputedAttribute import ComputedAttribute + +class TreeNode(Explicit): + __allow_access_to_unprotected_subobjects__ = 1 + state = 0 # leaf + height = 1 + size = 1 + def __init__(self): + self._child_list = [] + def _add_child(self, child): + 'Add a child which already has all of its children.' + self._child_list.append(child) + self.height = max(self.height, child.height + 1) + self.size = self.size + child.size + def flat(self): + 'Return a flattened preorder list of tree nodes' + items = [] + self.walk(items.append) + return items + def walk(self, f, data=None): + 'Preorder walk this tree, passing each node to a function' + if data is None: + f(self) + else: + f(self, data) + for child in self._child_list: + child.__of__(self).walk(f, data) + def _depth(self): + return self.aq_parent.depth + 1 + depth = ComputedAttribute(_depth, 1) + def __getitem__(self, index): + return self._child_list[index].__of__(self) + def __len__(self): + return len(self._child_list) + +_marker = [] + +class TreeMaker: + '''Class for mapping a hierachy of objects into a tree of nodes.''' + + __allow_access_to_unprotected_subobjects__ = 1 + + _id = 'tpId' + _values = 'tpValues' + _assume_children = 0 + _values_filter = None + _values_function = None + _expand_root = 1 + + def setChildAccess(self, attrname=_marker, filter=_marker, + function=_marker): + '''Set the criteria for fetching child nodes. + + 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. + ''' + if function is _marker: + self._values_function = None + if attrname is not _marker: + self._values = str(attrname) + if filter is not _marker: + self._values_filter = filter + else: + self._values_function = function + + def tree(self, root, expanded=None, subtree=0): + '''Create a tree from root, with specified nodes expanded. + + "expanded" must be false, true, or a mapping. + Each key of the mapping is the id of a top-level expanded + node, and each value is the "expanded" value for the + children of that node. + ''' + node = self.node(root) + child_exp = expanded + if not simple_type(expanded): + # Assume a mapping + expanded = expanded.has_key(node.id) + child_exp = child_exp.get(node.id) + if expanded or (not subtree and self._expand_root): + children = self.getChildren(root) + if children: + node.state = 1 # expanded + for child in children: + node._add_child(self.tree(child, child_exp, 1)) + elif self.hasChildren(root): + node.state = -1 # collapsed + if not subtree: + node.depth = 0 + if hasattr(self, 'markRoot'): + self.markRoot(node) + return node + + def node(self, object): + node = TreeNode() + node.object = object + node.id = b2a(self.getId(object)) + return node + + def getId(self, object): + id_attr = self._id + if hasattr(object, id_attr): + obid = getattr(object, id_attr) + if not simple_type(obid): obid = obid() + return obid + if hasattr(object, '_p_oid'): return str(object._p_oid) + return id(object) + + def hasChildren(self, object): + if self._assume_children: + return 1 + return self.getChildren(object) + + def getChildren(self, object): + if self._values_function is not None: + return self._values_function(object) + if self._values_filter and hasattr(object, 'aq_acquire'): + return object.aq_acquire(self._values, aqcallback, + self._values_filter)() + return getattr(object, self._values)() + +def simple_type(ob, + is_simple={type(''):1, type(0):1, type(0.0):1, + type(0L):1, type(None):1 }.has_key): + return is_simple(type(ob)) + +def aqcallback(self, inst, parent, name, value, filter): + return filter(self, inst, parent, name, value) + +from binascii import b2a_base64, a2b_base64 +import string +from string import split, join, translate + +a2u_map = string.maketrans('+/=', '-._') +u2a_map = string.maketrans('-._', '+/=') + +def b2a(s): + '''Encode a value as a cookie- and url-safe string. + + Encoded string use only alpahnumeric characters, and "._-". + ''' + s = str(s) + if len(s) <= 57: + return translate(b2a_base64(s)[:-1], a2u_map) + frags = [] + for i in range(0, len(s), 57): + frags.append(b2a_base64(s[i:i + 57])[:-1]) + return translate(join(frags, ''), a2u_map) + +def a2b(s): + '''Decode a b2a-encoded string.''' + s = translate(s, u2a_map) + if len(s) <= 76: + return a2b_base64(s) + frags = [] + for i in range(0, len(s), 76): + frags.append(a2b_base64(s[i:i + 76])) + return join(frags, '') + +def encodeExpansion(nodes): + '''Encode the expanded node ids of a tree into a string. + + Accepts a list of nodes, such as that produced by root.flat(). + Marks each expanded node with an expansion_number attribute. + Since node ids are encoded, the resulting string is safe for + use in cookies and URLs. + ''' + steps = [] + last_depth = -1 + n = 0 + for node in nodes: + if node.state <=0: continue + dd = last_depth - node.depth + 1 + last_depth = node.depth + if dd > 0: + steps.append('.' * dd) + steps.append(node.id) + node.expansion_number = n + n = n + 1 + return join(steps, ':') + +def decodeExpansion(s, nth=None): + '''Decode an expanded node map from a string. + + If nth is an integer, also return the (map, key) pair for the nth entry. + ''' + map = m = {} + mstack = [] + pop = 0 + nth_pair = None + if nth is not None: + nth_pair = (None, None) + for step in split(s, ':'): + if step[:1] == '.': + pop = len(step) - 1 + continue + if pop < 0: + mstack.append(m) + m[obid] = {} + m = m[obid] + elif map: + m[obid] = None + if len(step) == 0: + return map + obid = step + if pop > 0: + m = mstack[-pop] + del mstack[-pop:] + pop = -1 + if nth == 0: + nth_pair = (m, obid) + nth = None + elif nth is not None: + nth = nth - 1 + m[obid] = None + if nth == 0: + return map, (m, obid) + if nth_pair is not None: + return map, nth_pair + return map + diff --git a/ZTUtils/Zope.py b/ZTUtils/Zope.py new file mode 100644 index 0000000..1f84078 --- /dev/null +++ b/ZTUtils/Zope.py @@ -0,0 +1,302 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +__doc__='''Zope-specific versions of ZTUTils classes + +$Id: Zope.py,v 1.1 2002-08-30 08:25:34 richard Exp $''' +__version__='$Revision: 1.1 $'[11:-2] + +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 import getSecurityManager +from string import split, join +from types import StringType, ListType, IntType, FloatType +from DateTime import DateTime + +try: + from AccessControl.ZopeGuards import guarded_getitem +except ImportError: + Unauthorized = 'Unauthorized' + def guarded_getitem(object, index): + v = object[index] + if getSecurityManager().validate(object, object, index, v): + return v + raise Unauthorized, 'unauthorized access to element %s' % `i` +else: + from AccessControl import Unauthorized + +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: + e = e + 1 + try: + try: v = guarded_getitem(s, e) + except Unauthorized, vv: + if skip is None: + self._eindex = e + msg = '(item %s): %s' % (index, vv) + raise Unauthorized, msg, sys.exc_info()[2] + skip_this = 1 + else: + skip_this = 0 + except IndexError: + del self._test + del self._seq + del self._eindex + raise IndexError, index + if skip_this: continue + if skip and not getSecurityManager().checkPermission(skip, v): + continue + if test is None or test(v): + data.append(v) + ind=ind+1 + 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, default_state=None): + '''Make a tree with state stored in a cookie.''' + tree_pre = self.tree_pre + state_name = '%s-state' % tree_pre + set_name = '%s-setstate' % tree_pre + + req = root_object.REQUEST + state = req.get(state_name) + if state: + setst = req.form.get(set_name) + if setst: + st, pn, expid = split(setst, ',') + state, (m, obid) = decodeExpansion(state, int(pn)) + if m is None: + pass + elif st == 'e': + if m[obid] is None: + m[obid] = {expid: None} + else: + m[obid][expid] = None + elif st == 'c' and m is not state and obid==expid: + del m[obid] + else: + state = decodeExpansion(state) + else: + state = default_state + tree = self.tree(root_object, state) + 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=0, 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] = ('' + % (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". + ''' + i = len(pairs) + while i > 0: + i = i - 1 + 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) diff --git a/ZTUtils/__init__.py b/ZTUtils/__init__.py new file mode 100644 index 0000000..133f817 --- /dev/null +++ b/ZTUtils/__init__.py @@ -0,0 +1,31 @@ +############################################################################## +# +# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## +__doc__='''Package of template utility classes and functions. + +$Id: __init__.py,v 1.1 2002-08-30 08:25:34 richard Exp $''' +__version__='$Revision: 1.1 $'[11:-2] + +from Batch import Batch +from Iterator import Iterator +from Tree import TreeMaker, encodeExpansion, decodeExpansion, a2b, b2a +from SimpleTree import SimpleTreeMaker + +import sys +if sys.modules.has_key('Zope'): + del sys + __allow_access_to_unprotected_subobjects__ = 1 + __roles__ = None + + from Zope import Batch, TreeMaker, SimpleTreeMaker, LazyFilter + from Zope import url_query, make_query, make_hidden_input +