Code

better error message
[roundup.git] / ZTUtils / Zope.py
1 ##############################################################################
2 #
3 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
4
5 # This software is subject to the provisions of the Zope Public License,
6 # Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
7 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
10 # FOR A PARTICULAR PURPOSE
11
12 ##############################################################################
13 __doc__='''Zope-specific versions of ZTUTils classes
15 $Id: Zope.py,v 1.1 2002-08-30 08:25:34 richard Exp $'''
16 __version__='$Revision: 1.1 $'[11:-2]
18 import sys, cgi, urllib, cgi
19 from Tree import encodeExpansion, decodeExpansion, TreeMaker
20 from SimpleTree import SimpleTreeMaker
21 from Batch import Batch
22 from Products.ZCatalog.Lazy import Lazy
23 from AccessControl import getSecurityManager
24 from string import split, join
25 from types import StringType, ListType, IntType, FloatType
26 from DateTime import DateTime
28 try:
29     from AccessControl.ZopeGuards import guarded_getitem
30 except ImportError:
31     Unauthorized = 'Unauthorized'
32     def guarded_getitem(object, index):
33         v = object[index]
34         if getSecurityManager().validate(object, object, index, v):
35             return v
36         raise Unauthorized, 'unauthorized access to element %s' % `i`
37 else:
38     from AccessControl import Unauthorized
40 class LazyFilter(Lazy):
41     # A LazyFilter that checks with the security policy
43     def __init__(self, seq, test=None, skip=None):
44         self._seq=seq
45         self._data=[]
46         self._eindex=-1
47         self._test=test
48         if not (skip is None or str(skip) == skip): 
49             raise TypeError, 'Skip must be None or a string'
50         self._skip = skip 
52     def __getitem__(self,index):
53         data=self._data
54         try: s=self._seq
55         except AttributeError: return data[index]
57         i=index
58         if i < 0: i=len(self)+i
59         if i < 0: raise IndexError, index
61         ind=len(data)
62         if i < ind: return data[i]
63         ind=ind-1
65         test=self._test
66         e=self._eindex
67         skip = self._skip
68         while i > ind:
69             e = e + 1
70             try:
71                 try: v = guarded_getitem(s, e)
72                 except Unauthorized, vv:
73                     if skip is None:
74                         self._eindex = e
75                         msg = '(item %s): %s' % (index, vv)
76                         raise Unauthorized, msg, sys.exc_info()[2]
77                     skip_this = 1
78                 else:
79                     skip_this = 0
80             except IndexError:
81                 del self._test
82                 del self._seq
83                 del self._eindex
84                 raise IndexError, index
85             if skip_this: continue
86             if skip and not getSecurityManager().checkPermission(skip, v):
87                 continue
88             if test is None or test(v):
89                 data.append(v)
90                 ind=ind+1
91         self._eindex=e
92         return data[i]
94 class TreeSkipMixin:
95     '''Mixin class to make trees test security, and allow
96     skipping of unauthorized objects. '''
97     skip = None
98     def setSkip(self, skip):
99         self.skip = skip
100         return self
101     def getChildren(self, object):
102         return LazyFilter(self._getChildren(object), skip=self.skip)
104 class TreeMaker(TreeSkipMixin, TreeMaker):
105     _getChildren = TreeMaker.getChildren
107 class SimpleTreeMaker(TreeSkipMixin, SimpleTreeMaker):
108     _getChildren = SimpleTreeMaker.getChildren
109     def cookieTree(self, root_object, default_state=None):
110         '''Make a tree with state stored in a cookie.'''
111         tree_pre = self.tree_pre
112         state_name = '%s-state' % tree_pre
113         set_name = '%s-setstate' % tree_pre
115         req = root_object.REQUEST
116         state = req.get(state_name)
117         if state:
118             setst = req.form.get(set_name)
119             if setst:
120                 st, pn, expid = split(setst, ',')
121                 state, (m, obid) = decodeExpansion(state, int(pn))
122                 if m is None:
123                     pass
124                 elif st == 'e':
125                     if m[obid] is None:
126                         m[obid] = {expid: None}
127                     else:
128                         m[obid][expid] = None
129                 elif st == 'c' and m is not state and obid==expid:
130                     del m[obid]
131             else:
132                 state = decodeExpansion(state)
133         else:
134             state = default_state
135         tree = self.tree(root_object, state)
136         rows = tree.flat()
137         req.RESPONSE.setCookie(state_name, encodeExpansion(rows))
138         return tree, rows
140 # Make the Batch class test security, and let it skip unauthorized.
141 _Batch = Batch
142 class Batch(Batch):
143     def __init__(self, sequence, size, start=0, end=0,
144                  orphan=0, overlap=0, skip_unauthorized=None):
145         sequence = LazyFilter(sequence, skip=skip_unauthorized)
146         _Batch.__init__(self, sequence, size, start, end,
147                         orphan, overlap)
149 # These functions are meant to be used together in templates that use
150 # trees or batches.  For example, given a batch with a 'bstart' query
151 # argument, you would use "url_query(request, omit='bstart')" to get
152 # the base for the batching links, then append 
153 # "make_query(bstart=batch.previous.first)" to one and
154 # "make_query(bstart=batch.end)" to the other.
156 def make_query(*args, **kwargs):
157     '''Construct a URL query string, with marshalling markup.
159     If there are positional arguments, they must be dictionaries.
160     They are combined with the dictionary of keyword arguments to form
161     a dictionary of query names and values.
163     Query names (the keys) must be strings.  Values may be strings,
164     integers, floats, or DateTimes, and they may also be lists or
165     namespaces containing these types.  Names and string values
166     should not be URL-quoted.  All arguments are marshalled with
167     complex_marshal().
168     '''
170     d = {}
171     for arg in args:
172         d.update(arg)
173     d.update(kwargs)
175     uq = urllib.quote
176     qlist = complex_marshal(d.items())
177     for i in range(len(qlist)):
178         k, m, v = qlist[i]
179         qlist[i] = '%s%s=%s' % (uq(k), m, uq(str(v)))
181     return join(qlist, '&')
182                 
183 def make_hidden_input(*args, **kwargs):
184     '''Construct a set of hidden input elements, with marshalling markup.
186     If there are positional arguments, they must be dictionaries.
187     They are combined with the dictionary of keyword arguments to form
188     a dictionary of query names and values.
190     Query names (the keys) must be strings.  Values may be strings,
191     integers, floats, or DateTimes, and they may also be lists or
192     namespaces containing these types.  All arguments are marshalled with
193     complex_marshal().
194     '''
196     d = {}
197     for arg in args:
198         d.update(arg)
199     d.update(kwargs)
201     hq = cgi.escape
202     qlist = complex_marshal(d.items())
203     for i in range(len(qlist)):
204         k, m, v = qlist[i]
205         qlist[i] = ('<input type="hidden" name="%s%s" value="%s">'
206                     % (hq(k), m, hq(str(v))))
208     return join(qlist, '\n')
209                 
210 def complex_marshal(pairs):
211     '''Add request marshalling information to a list of name-value pairs.
213     Names must be strings.  Values may be strings,
214     integers, floats, or DateTimes, and they may also be lists or
215     namespaces containing these types.
217     The list is edited in place so that each (name, value) pair
218     becomes a (name, marshal, value) triple.  The middle value is the
219     request marshalling string.  Integer, float, and DateTime values
220     will have ":int", ":float", or ":date" as their marshal string.
221     Lists will be flattened, and the elements given ":list" in
222     addition to their simple marshal string.  Dictionaries will be
223     flattened and marshalled using ":record".
224     '''
225     i = len(pairs)
226     while i > 0:
227         i = i - 1
228         k, v = pairs[i]
229         m = ''
230         sublist = None
231         if isinstance(v, StringType):
232             pass
233         elif hasattr(v, 'items'):
234             sublist = []
235             for sk, sv in v.items():
236                 sm = simple_marshal(sv)
237                 sublist.append(('%s.%s' % (k, sk), '%s:record' % sm,  sv))
238         elif isinstance(v, ListType):
239             sublist = []
240             for sv in v:
241                 sm = simple_marshal(sv)
242                 sublist.append((k, '%s:list' % sm, sv))
243         else:
244             m = simple_marshal(v)
245         if sublist is None:
246             pairs[i] = (k, m, v)
247         else:
248             pairs[i:i + 1] = sublist
250     return pairs
252 def simple_marshal(v):
253     if isinstance(v, StringType):
254         return ''
255     if isinstance(v, IntType):
256         return ':int'
257     if isinstance(v, FloatType):
258         return ':float'
259     if isinstance(v, DateTime):
260         return ':date'
261     return ''
263 def url_query(request, req_name="URL", omit=None):
264     '''Construct a URL with a query string, using the current request.
266     request: the request object
267     req_name: the name, such as "URL1" or "BASEPATH1", to get from request
268     omit: sequence of name of query arguments to omit.  If a name
269     contains a colon, it is treated literally.  Otherwise, it will
270     match each argument name that starts with the name and a period or colon. 
271     '''
273     base = request[req_name]
274     qs = request.get('QUERY_STRING', '')
275     
276     if qs and omit:
277         qsparts = split(qs, '&')
279         if isinstance(omit, StringType):
280             omits = {omit: None}
281         else:
282             omits = {}
283             for name in omit:
284                 omits[name] = None
285         omitted = omits.has_key
287         unq = urllib.unquote
288         for i in range(len(qsparts)):
289             name = unq(split(qsparts[i], '=', 1)[0])
290             if omitted(name):
291                 qsparts[i] = ''
292             name = split(name, ':', 1)[0]
293             if omitted(name):
294                 qsparts[i] = ''
295             name = split(name, '.', 1)[0]
296             if omitted(name):
297                 qsparts[i] = ''
298             
299         qs = join(filter(None, qsparts), '&')
301     # We alway append '?' since arguments will be appended to the URL
302     return '%s?%s' % (base, qs)