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