1 """Base class for dynamically loaded objects manipulated in the web interface |
1 """Base class for dynamically loaded objects accessible through the vregistry. |
|
2 |
|
3 You'll also find some convenience classes to build selectors. |
2 |
4 |
3 :organization: Logilab |
5 :organization: Logilab |
4 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
6 :copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2. |
5 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
7 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr |
6 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
8 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses |
7 """ |
9 """ |
8 __docformat__ = "restructuredtext en" |
10 __docformat__ = "restructuredtext en" |
9 |
11 |
|
12 import types |
|
13 from logging import getLogger |
10 from datetime import datetime, timedelta, time |
14 from datetime import datetime, timedelta, time |
11 |
15 |
12 from logilab.common.decorators import classproperty |
16 from logilab.common.decorators import classproperty |
13 from logilab.common.deprecation import deprecated |
17 from logilab.common.deprecation import deprecated |
|
18 from logilab.common.logging_ext import set_log_methods |
14 |
19 |
15 from rql.nodes import VariableRef, SubQuery |
20 from rql.nodes import VariableRef, SubQuery |
16 from rql.stmts import Union, Select |
21 from rql.stmts import Union, Select |
17 |
22 |
18 from cubicweb import Unauthorized, NoSelectableObject |
23 from cubicweb import Unauthorized, NoSelectableObject |
19 from cubicweb.vregistry import VObject, AndSelector |
|
20 from cubicweb.selectors import yes |
|
21 from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime |
24 from cubicweb.utils import UStringIO, ustrftime, strptime, todate, todatetime |
22 |
25 |
23 ONESECOND = timedelta(0, 1, 0) |
26 ONESECOND = timedelta(0, 1, 0) |
|
27 CACHE_REGISTRY = {} |
|
28 |
24 |
29 |
25 class Cache(dict): |
30 class Cache(dict): |
26 def __init__(self): |
31 def __init__(self): |
27 super(Cache, self).__init__() |
32 super(Cache, self).__init__() |
28 _now = datetime.now() |
33 _now = datetime.now() |
29 self.cache_creation_date = _now |
34 self.cache_creation_date = _now |
30 self.latest_cache_lookup = _now |
35 self.latest_cache_lookup = _now |
31 |
36 |
32 CACHE_REGISTRY = {} |
37 |
33 |
38 # selector base classes and operations ######################################## |
34 class AppRsetObject(VObject): |
39 |
35 """This is the base class for CubicWeb application objects |
40 def objectify_selector(selector_func): |
36 which are selected according to a request and result set. |
41 """convenience decorator for simple selectors where a class definition |
37 |
42 would be overkill:: |
38 Classes are kept in the vregistry and instantiation is done at selection |
43 |
39 time. |
44 @objectify_selector |
|
45 def yes(cls, *args, **kwargs): |
|
46 return 1 |
|
47 |
|
48 """ |
|
49 return type(selector_func.__name__, (Selector,), |
|
50 {'__call__': lambda self, *args, **kwargs: selector_func(*args, **kwargs)}) |
|
51 |
|
52 |
|
53 def _instantiate_selector(selector): |
|
54 """ensures `selector` is a `Selector` instance |
|
55 |
|
56 NOTE: This should only be used locally in build___select__() |
|
57 XXX: then, why not do it ?? |
|
58 """ |
|
59 if isinstance(selector, types.FunctionType): |
|
60 return objectify_selector(selector)() |
|
61 if isinstance(selector, type) and issubclass(selector, Selector): |
|
62 return selector() |
|
63 return selector |
|
64 |
|
65 |
|
66 class Selector(object): |
|
67 """base class for selector classes providing implementation |
|
68 for operators ``&`` and ``|`` |
|
69 |
|
70 This class is only here to give access to binary operators, the |
|
71 selector logic itself should be implemented in the __call__ method |
|
72 |
|
73 |
|
74 a selector is called to help choosing the correct object for a |
|
75 particular context by returning a score (`int`) telling how well |
|
76 the class given as first argument apply to the given context. |
|
77 |
|
78 0 score means that the class doesn't apply. |
|
79 """ |
|
80 |
|
81 @property |
|
82 def func_name(self): |
|
83 # backward compatibility |
|
84 return self.__class__.__name__ |
|
85 |
|
86 def search_selector(self, selector): |
|
87 """search for the given selector or selector instance in the selectors |
|
88 tree. Return it of None if not found |
|
89 """ |
|
90 if self is selector: |
|
91 return self |
|
92 if isinstance(selector, type) and isinstance(self, selector): |
|
93 return self |
|
94 return None |
|
95 |
|
96 def __str__(self): |
|
97 return self.__class__.__name__ |
|
98 |
|
99 def __and__(self, other): |
|
100 return AndSelector(self, other) |
|
101 def __rand__(self, other): |
|
102 return AndSelector(other, self) |
|
103 |
|
104 def __or__(self, other): |
|
105 return OrSelector(self, other) |
|
106 def __ror__(self, other): |
|
107 return OrSelector(other, self) |
|
108 |
|
109 def __invert__(self): |
|
110 return NotSelector(self) |
|
111 |
|
112 # XXX (function | function) or (function & function) not managed yet |
|
113 |
|
114 def __call__(self, cls, *args, **kwargs): |
|
115 return NotImplementedError("selector %s must implement its logic " |
|
116 "in its __call__ method" % self.__class__) |
|
117 |
|
118 |
|
119 class MultiSelector(Selector): |
|
120 """base class for compound selector classes""" |
|
121 |
|
122 def __init__(self, *selectors): |
|
123 self.selectors = self.merge_selectors(selectors) |
|
124 |
|
125 def __str__(self): |
|
126 return '%s(%s)' % (self.__class__.__name__, |
|
127 ','.join(str(s) for s in self.selectors)) |
|
128 |
|
129 @classmethod |
|
130 def merge_selectors(cls, selectors): |
|
131 """deal with selector instanciation when necessary and merge |
|
132 multi-selectors if possible: |
|
133 |
|
134 AndSelector(AndSelector(sel1, sel2), AndSelector(sel3, sel4)) |
|
135 ==> AndSelector(sel1, sel2, sel3, sel4) |
|
136 """ |
|
137 merged_selectors = [] |
|
138 for selector in selectors: |
|
139 try: |
|
140 selector = _instantiate_selector(selector) |
|
141 except: |
|
142 pass |
|
143 #assert isinstance(selector, Selector), selector |
|
144 if isinstance(selector, cls): |
|
145 merged_selectors += selector.selectors |
|
146 else: |
|
147 merged_selectors.append(selector) |
|
148 return merged_selectors |
|
149 |
|
150 def search_selector(self, selector): |
|
151 """search for the given selector or selector instance in the selectors |
|
152 tree. Return it of None if not found |
|
153 """ |
|
154 for childselector in self.selectors: |
|
155 if childselector is selector: |
|
156 return childselector |
|
157 found = childselector.search_selector(selector) |
|
158 if found is not None: |
|
159 return found |
|
160 return None |
|
161 |
|
162 |
|
163 class AndSelector(MultiSelector): |
|
164 """and-chained selectors (formerly known as chainall)""" |
|
165 def __call__(self, cls, *args, **kwargs): |
|
166 score = 0 |
|
167 for selector in self.selectors: |
|
168 partscore = selector(cls, *args, **kwargs) |
|
169 if not partscore: |
|
170 return 0 |
|
171 score += partscore |
|
172 return score |
|
173 |
|
174 |
|
175 class OrSelector(MultiSelector): |
|
176 """or-chained selectors (formerly known as chainfirst)""" |
|
177 def __call__(self, cls, *args, **kwargs): |
|
178 for selector in self.selectors: |
|
179 partscore = selector(cls, *args, **kwargs) |
|
180 if partscore: |
|
181 return partscore |
|
182 return 0 |
|
183 |
|
184 class NotSelector(Selector): |
|
185 """negation selector""" |
|
186 def __init__(self, selector): |
|
187 self.selector = selector |
|
188 |
|
189 def __call__(self, cls, *args, **kwargs): |
|
190 score = self.selector(cls, *args, **kwargs) |
|
191 return int(not score) |
|
192 |
|
193 def __str__(self): |
|
194 return 'NOT(%s)' % super(NotSelector, self).__str__() |
|
195 |
|
196 |
|
197 class yes(Selector): |
|
198 """return arbitrary score |
|
199 |
|
200 default score of 0.5 so any other selector take precedence |
|
201 """ |
|
202 def __init__(self, score=0.5): |
|
203 self.score = score |
|
204 |
|
205 def __call__(self, *args, **kwargs): |
|
206 return self.score |
|
207 |
|
208 |
|
209 # the base class for all appobjects ############################################ |
|
210 |
|
211 class AppObject(object): |
|
212 """This is the base class for CubicWeb application objects which are |
|
213 selected according to a context (usually at least a request and a result |
|
214 set). |
|
215 |
|
216 Concrete application objects classes are designed to be loaded by the |
|
217 vregistry and should be accessed through it, not by direct instantiation. |
|
218 |
|
219 The following attributes should be set on concret appobject classes: |
|
220 :__registry__: |
|
221 name of the registry for this object (string like 'views', |
|
222 'templates'...) |
|
223 :id: |
|
224 object's identifier in the registry (string like 'main', |
|
225 'primary', 'folder_box') |
|
226 :__select__: |
|
227 class'selector |
|
228 |
|
229 Moreover, the `__abstract__` attribute may be set to True to indicate |
|
230 that a appobject is abstract and should not be registered. |
40 |
231 |
41 At registration time, the following attributes are set on the class: |
232 At registration time, the following attributes are set on the class: |
42 :vreg: |
233 :vreg: |
43 the instance's registry |
234 the instance's registry |
44 :schema: |
235 :schema: |
45 the instance's schema |
236 the instance's schema |
46 :config: |
237 :config: |
47 the instance's configuration |
238 the instance's configuration |
48 |
239 |
49 At instantiation time, the following attributes are set on the instance: |
240 At selection time, the following attributes are set on the instance: |
50 :req: |
241 :req: |
51 current request |
242 current request |
52 :rset: |
243 :rset: |
53 result set on which the object is applied |
244 context result set or None |
|
245 :row: |
|
246 if a result set is set and the context is about a particular cell in the |
|
247 result set, and not the result set as a whole, specify the row number we |
|
248 are interested in, else None |
|
249 :col: |
|
250 if a result set is set and the context is about a particular cell in the |
|
251 result set, and not the result set as a whole, specify the col number we |
|
252 are interested in, else None |
54 """ |
253 """ |
|
254 __registry__ = None |
|
255 id = None |
55 __select__ = yes() |
256 __select__ = yes() |
56 |
257 |
57 @classmethod |
258 @classmethod |
58 def registered(cls, vreg): |
259 def classid(cls): |
59 super(AppRsetObject, cls).registered(vreg) |
260 """returns a unique identifier for the appobject""" |
60 cls.vreg = vreg |
261 return '%s.%s' % (cls.__module__, cls.__name__) |
61 cls.schema = vreg.schema |
262 |
62 cls.config = vreg.config |
263 # XXX bw compat code |
|
264 @classmethod |
|
265 def build___select__(cls): |
|
266 for klass in cls.mro(): |
|
267 if klass.__name__ == 'AppObject': |
|
268 continue # the bw compat __selector__ is there |
|
269 klassdict = klass.__dict__ |
|
270 if ('__select__' in klassdict and '__selectors__' in klassdict |
|
271 and '__selgenerated__' not in klassdict): |
|
272 raise TypeError("__select__ and __selectors__ can't be used together on class %s" % cls) |
|
273 if '__selectors__' in klassdict and '__selgenerated__' not in klassdict: |
|
274 cls.__selgenerated__ = True |
|
275 # case where __selectors__ is defined locally (but __select__ |
|
276 # is in a parent class) |
|
277 selectors = klassdict['__selectors__'] |
|
278 if len(selectors) == 1: |
|
279 # micro optimization: don't bother with AndSelector if there's |
|
280 # only one selector |
|
281 select = _instantiate_selector(selectors[0]) |
|
282 else: |
|
283 select = AndSelector(*selectors) |
|
284 cls.__select__ = select |
|
285 |
|
286 @classmethod |
|
287 def registered(cls, registry): |
|
288 """called by the registry when the appobject has been registered. |
|
289 |
|
290 It must return the object that will be actually registered (this may be |
|
291 the right hook to create an instance for example). By default the |
|
292 appobject is returned without any transformation. |
|
293 """ |
|
294 cls.build___select__() |
|
295 cls.vreg = registry.vreg |
|
296 cls.schema = registry.schema |
|
297 cls.config = registry.config |
63 cls.register_properties() |
298 cls.register_properties() |
64 return cls |
299 return cls |
65 |
300 |
66 @classmethod |
301 @classmethod |
67 def vreg_initialization_completed(cls): |
302 def vreg_initialization_completed(cls): |
68 pass |
303 pass |
69 |
|
70 @classmethod |
|
71 def selected(cls, *args, **kwargs): |
|
72 """by default web app objects are usually instantiated on |
|
73 selection according to a request, a result set, and optional |
|
74 row and col |
|
75 """ |
|
76 assert len(args) <= 2 |
|
77 return cls(*args, **kwargs) |
|
78 |
304 |
79 # Eproperties definition: |
305 # Eproperties definition: |
80 # key: id of the property (the actual CWProperty key is build using |
306 # key: id of the property (the actual CWProperty key is build using |
81 # <registry name>.<obj id>.<property id> |
307 # <registry name>.<obj id>.<property id> |
82 # value: tuple (property type, vocabfunc, default value, property description) |
308 # value: tuple (property type, vocabfunc, default value, property description) |