21 from cubicweb.selectors import yes |
21 from cubicweb.selectors import yes |
22 from cubicweb.utils import UStringIO, ustrftime |
22 from cubicweb.utils import UStringIO, ustrftime |
23 |
23 |
24 ONESECOND = timedelta(0, 1, 0) |
24 ONESECOND = timedelta(0, 1, 0) |
25 |
25 |
26 class Cache(dict): |
26 class Cache(dict): |
27 def __init__(self): |
27 def __init__(self): |
28 super(Cache, self).__init__() |
28 super(Cache, self).__init__() |
29 self.cache_creation_date = None |
29 self.cache_creation_date = None |
30 self.latest_cache_lookup = datetime.now() |
30 self.latest_cache_lookup = datetime.now() |
31 |
31 |
32 CACHE_REGISTRY = {} |
32 CACHE_REGISTRY = {} |
33 |
33 |
34 class AppRsetObject(VObject): |
34 class AppRsetObject(VObject): |
35 """This is the base class for CubicWeb application objects |
35 """This is the base class for CubicWeb application objects |
36 which are selected according to a request and result set. |
36 which are selected according to a request and result set. |
37 |
37 |
38 Classes are kept in the vregistry and instantiation is done at selection |
38 Classes are kept in the vregistry and instantiation is done at selection |
39 time. |
39 time. |
40 |
40 |
41 At registration time, the following attributes are set on the class: |
41 At registration time, the following attributes are set on the class: |
42 :vreg: |
42 :vreg: |
43 the application's registry |
43 the application's registry |
44 :schema: |
44 :schema: |
45 the application's schema |
45 the application's schema |
60 cls.vreg = vreg |
60 cls.vreg = vreg |
61 cls.schema = vreg.schema |
61 cls.schema = vreg.schema |
62 cls.config = vreg.config |
62 cls.config = vreg.config |
63 cls.register_properties() |
63 cls.register_properties() |
64 return cls |
64 return cls |
65 |
65 |
66 @classmethod |
66 @classmethod |
67 def vreg_initialization_completed(cls): |
67 def vreg_initialization_completed(cls): |
68 pass |
68 pass |
69 |
69 |
70 @classmethod |
70 @classmethod |
71 def selected(cls, *args, **kwargs): |
71 def selected(cls, *args, **kwargs): |
72 """by default web app objects are usually instantiated on |
72 """by default web app objects are usually instantiated on |
73 selection according to a request, a result set, and optional |
73 selection according to a request, a result set, and optional |
74 row and col |
74 row and col |
83 # possible types are those used by `logilab.common.configuration` |
83 # possible types are those used by `logilab.common.configuration` |
84 # |
84 # |
85 # notice that when it exists multiple objects with the same id (adaptation, |
85 # notice that when it exists multiple objects with the same id (adaptation, |
86 # overriding) only the first encountered definition is considered, so those |
86 # overriding) only the first encountered definition is considered, so those |
87 # objects can't try to have different default values for instance. |
87 # objects can't try to have different default values for instance. |
88 |
88 |
89 property_defs = {} |
89 property_defs = {} |
90 |
90 |
91 @classmethod |
91 @classmethod |
92 def register_properties(cls): |
92 def register_properties(cls): |
93 for propid, pdef in cls.property_defs.items(): |
93 for propid, pdef in cls.property_defs.items(): |
94 pdef = pdef.copy() # may be shared |
94 pdef = pdef.copy() # may be shared |
95 pdef['default'] = getattr(cls, propid, pdef['default']) |
95 pdef['default'] = getattr(cls, propid, pdef['default']) |
96 pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide')) |
96 pdef['sitewide'] = getattr(cls, 'site_wide', pdef.get('sitewide')) |
97 cls.vreg.register_property(cls.propkey(propid), **pdef) |
97 cls.vreg.register_property(cls.propkey(propid), **pdef) |
98 |
98 |
99 @classmethod |
99 @classmethod |
100 def propkey(cls, propid): |
100 def propkey(cls, propid): |
101 return '%s.%s.%s' % (cls.__registry__, cls.id, propid) |
101 return '%s.%s.%s' % (cls.__registry__, cls.id, propid) |
102 |
102 |
103 @classproperty |
103 @classproperty |
107 if isinstance(selector, AndSelector): |
107 if isinstance(selector, AndSelector): |
108 return tuple(selector.selectors) |
108 return tuple(selector.selectors) |
109 if not isinstance(selector, tuple): |
109 if not isinstance(selector, tuple): |
110 selector = (selector,) |
110 selector = (selector,) |
111 return selector |
111 return selector |
112 |
112 |
113 def __init__(self, req=None, rset=None, row=None, col=None, **extra): |
113 def __init__(self, req=None, rset=None, row=None, col=None, **extra): |
114 super(AppRsetObject, self).__init__() |
114 super(AppRsetObject, self).__init__() |
115 self.req = req |
115 self.req = req |
116 self.rset = rset |
116 self.rset = rset |
117 self.row = row |
117 self.row = row |
118 self.col = col |
118 self.col = col |
119 self.extra_kwargs = extra |
119 self.extra_kwargs = extra |
120 |
120 |
121 def get_cache(self, cachename): |
121 def get_cache(self, cachename): |
122 """ |
122 """ |
123 NOTE: cachename should be dotted names as in : |
123 NOTE: cachename should be dotted names as in : |
124 - cubicweb.mycache |
124 - cubicweb.mycache |
125 - cubes.blog.mycache |
125 - cubes.blog.mycache |
126 - etc. |
126 - etc. |
127 """ |
127 """ |
128 if cachename in CACHE_REGISTRY: |
128 if cachename in CACHE_REGISTRY: |
129 cache = CACHE_REGISTRY[cachename] |
129 cache = CACHE_REGISTRY[cachename] |
130 else: |
130 else: |
131 cache = Cache() |
131 cache = Cache() |
132 CACHE_REGISTRY[cachename] = cache |
132 CACHE_REGISTRY[cachename] = cache |
133 _now = datetime.now() |
133 _now = datetime.now() |
134 if _now > cache.latest_cache_lookup + ONESECOND: |
134 if _now > cache.latest_cache_lookup + ONESECOND: |
135 ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T', |
135 ecache = self.req.execute('Any C,T WHERE C is CWCache, C name %(name)s, C timestamp T', |
136 {'name':cachename}).get_entity(0,0) |
136 {'name':cachename}).get_entity(0,0) |
137 cache.latest_cache_lookup = _now |
137 cache.latest_cache_lookup = _now |
138 if not ecache.valid(cache.cache_creation_date): |
138 if not ecache.valid(cache.cache_creation_date): |
139 cache.clear() |
139 cache.clear() |
140 cache.cache_creation_date = _now |
140 cache.cache_creation_date = _now |
141 return cache |
141 return cache |
142 |
142 |
143 def propval(self, propid): |
143 def propval(self, propid): |
144 assert self.req |
144 assert self.req |
145 return self.req.property_value(self.propkey(propid)) |
145 return self.req.property_value(self.propkey(propid)) |
146 |
146 |
147 def limited_rql(self): |
147 def limited_rql(self): |
148 """return a printable rql for the result set associated to the object, |
148 """return a printable rql for the result set associated to the object, |
149 with limit/offset correctly set according to maximum page size and |
149 with limit/offset correctly set according to maximum page size and |
150 currently displayed page when necessary |
150 currently displayed page when necessary |
151 """ |
151 """ |
163 # navigation component doesn't apply and rset has not been limited, no |
163 # navigation component doesn't apply and rset has not been limited, no |
164 # need to limit query |
164 # need to limit query |
165 else: |
165 else: |
166 rql = self.rset.printable_rql() |
166 rql = self.rset.printable_rql() |
167 return rql |
167 return rql |
168 |
168 |
169 def _limit_offset_rql(self, limit, offset): |
169 def _limit_offset_rql(self, limit, offset): |
170 rqlst = self.rset.syntax_tree() |
170 rqlst = self.rset.syntax_tree() |
171 if len(rqlst.children) == 1: |
171 if len(rqlst.children) == 1: |
172 select = rqlst.children[0] |
172 select = rqlst.children[0] |
173 olimit, ooffset = select.limit, select.offset |
173 olimit, ooffset = select.limit, select.offset |
185 newunion = Union() |
185 newunion = Union() |
186 newunion.append(newselect) |
186 newunion.append(newselect) |
187 rql = rqlst.as_string(kwargs=self.rset.args) |
187 rql = rqlst.as_string(kwargs=self.rset.args) |
188 rqlst.parent = None |
188 rqlst.parent = None |
189 return rql |
189 return rql |
190 |
190 |
191 def view(self, __vid, rset=None, __fallback_vid=None, **kwargs): |
191 def view(self, __vid, rset=None, __fallback_vid=None, **kwargs): |
192 """shortcut to self.vreg.render method avoiding to pass self.req""" |
192 """shortcut to self.vreg.render method avoiding to pass self.req""" |
193 try: |
193 try: |
194 view = self.vreg.select_view(__vid, self.req, rset, **kwargs) |
194 view = self.vreg.select_view(__vid, self.req, rset, **kwargs) |
195 except NoSelectableObject: |
195 except NoSelectableObject: |
196 if __fallback_vid is None: |
196 if __fallback_vid is None: |
197 raise |
197 raise |
198 view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs) |
198 view = self.vreg.select_view(__fallback_vid, self.req, rset, **kwargs) |
199 return view.dispatch(**kwargs) |
199 return view.dispatch(**kwargs) |
200 |
200 |
201 # url generation methods ################################################## |
201 # url generation methods ################################################## |
202 |
202 |
203 controller = 'view' |
203 controller = 'view' |
204 |
204 |
205 def build_url(self, method=None, **kwargs): |
205 def build_url(self, method=None, **kwargs): |
206 """return an absolute URL using params dictionary key/values as URL |
206 """return an absolute URL using params dictionary key/values as URL |
207 parameters. Values are automatically URL quoted, and the |
207 parameters. Values are automatically URL quoted, and the |
208 publishing method to use may be specified or will be guessed. |
208 publishing method to use may be specified or will be guessed. |
209 """ |
209 """ |
215 not '_restpath' in kwargs: |
215 not '_restpath' in kwargs: |
216 method = self.req.relative_path(includeparams=False) or 'view' |
216 method = self.req.relative_path(includeparams=False) or 'view' |
217 return self.req.build_url(method, **kwargs) |
217 return self.req.build_url(method, **kwargs) |
218 |
218 |
219 # various resources accessors ############################################# |
219 # various resources accessors ############################################# |
220 |
220 |
221 def entity(self, row, col=0): |
221 def entity(self, row, col=0): |
222 """short cut to get an entity instance for a particular row/column |
222 """short cut to get an entity instance for a particular row/column |
223 (col default to 0) |
223 (col default to 0) |
224 """ |
224 """ |
225 return self.rset.get_entity(row, col) |
225 return self.rset.get_entity(row, col) |
226 |
226 |
227 def complete_entity(self, row, col=0, skip_bytes=True): |
227 def complete_entity(self, row, col=0, skip_bytes=True): |
228 """short cut to get an completed entity instance for a particular |
228 """short cut to get an completed entity instance for a particular |
229 row (all instance's attributes have been fetched) |
229 row (all instance's attributes have been fetched) |
230 """ |
230 """ |
231 entity = self.entity(row, col) |
231 entity = self.entity(row, col) |
237 to call it ready to be inserted in html |
237 to call it ready to be inserted in html |
238 """ |
238 """ |
239 def rqlexec(req, rql, args=None, key=None): |
239 def rqlexec(req, rql, args=None, key=None): |
240 req.execute(rql, args, key) |
240 req.execute(rql, args, key) |
241 return self.user_callback(rqlexec, args, msg) |
241 return self.user_callback(rqlexec, args, msg) |
242 |
242 |
243 def user_callback(self, cb, args, msg=None, nonify=False): |
243 def user_callback(self, cb, args, msg=None, nonify=False): |
244 """register the given user callback and return an url to call it ready to be |
244 """register the given user callback and return an url to call it ready to be |
245 inserted in html |
245 inserted in html |
246 """ |
246 """ |
247 self.req.add_js('cubicweb.ajax.js') |
247 self.req.add_js('cubicweb.ajax.js') |
248 cbname = self.req.register_onetime_callback(cb, *args) |
248 cbname = self.req.register_onetime_callback(cb, *args) |
249 msg = dumps(msg or '') |
249 msg = dumps(msg or '') |
250 return "javascript:userCallbackThenReloadPage('%s', %s)" % ( |
250 return "javascript:userCallbackThenReloadPage('%s', %s)" % ( |
251 cbname, msg) |
251 cbname, msg) |
252 |
252 |
253 # formating methods ####################################################### |
253 # formating methods ####################################################### |
254 |
254 |
291 configuration |
291 configuration |
292 """ |
292 """ |
293 if num: |
293 if num: |
294 return self.req.property_value('ui.float-format') % num |
294 return self.req.property_value('ui.float-format') % num |
295 return u'' |
295 return u'' |
296 |
296 |
297 # security related methods ################################################ |
297 # security related methods ################################################ |
298 |
298 |
299 def ensure_ro_rql(self, rql): |
299 def ensure_ro_rql(self, rql): |
300 """raise an exception if the given rql is not a select query""" |
300 """raise an exception if the given rql is not a select query""" |
301 first = rql.split(' ', 1)[0].lower() |
301 first = rql.split(' ', 1)[0].lower() |
302 if first in ('insert', 'set', 'delete'): |
302 if first in ('insert', 'set', 'delete'): |
303 raise Unauthorized(self.req._('only select queries are authorized')) |
303 raise Unauthorized(self.req._('only select queries are authorized')) |
304 |
304 |
305 |
305 |
306 class AppObject(AppRsetObject): |
306 class AppObject(AppRsetObject): |
307 """base class for application objects which are not selected |
307 """base class for application objects which are not selected |
308 according to a result set, only by their identifier. |
308 according to a result set, only by their identifier. |
309 |
309 |
310 Those objects may not have req, rset and cursor set. |
310 Those objects may not have req, rset and cursor set. |
311 """ |
311 """ |
312 |
312 |
313 @classmethod |
313 @classmethod |
314 def selected(cls, *args, **kwargs): |
314 def selected(cls, *args, **kwargs): |
315 """by default web app objects are usually instantiated on |
315 """by default web app objects are usually instantiated on |
316 selection |
316 selection |
317 """ |
317 """ |