90 * if the view is not templatable, it should set the `content_type` class |
89 * if the view is not templatable, it should set the `content_type` class |
91 attribute to the correct MIME type (text/xhtml by default) |
90 attribute to the correct MIME type (text/xhtml by default) |
92 * the `category` attribute may be used in the interface to regroup related |
91 * the `category` attribute may be used in the interface to regroup related |
93 objects together |
92 objects together |
94 |
93 |
95 At instantiation time, the standard `req`, `rset`, and `cursor` |
94 At instantiation time, the standard `_cw`, and `cw_rset` attributes are |
96 attributes are added and the `w` attribute will be set at rendering |
95 added and the `w` attribute will be set at rendering time to a write |
97 time to a write function to use. |
96 function to use. |
98 """ |
97 """ |
99 __registry__ = 'views' |
98 __registry__ = 'views' |
100 registered = require_group_compat(AppObject.registered) |
|
101 |
99 |
102 templatable = True |
100 templatable = True |
103 # content_type = 'application/xhtml+xml' # text/xhtml' |
101 # content_type = 'application/xhtml+xml' # text/xhtml' |
104 binary = False |
102 binary = False |
105 add_to_breadcrumbs = True |
103 add_to_breadcrumbs = True |
117 % self.__class__, DeprecationWarning) |
115 % self.__class__, DeprecationWarning) |
118 return self.need_navigation |
116 return self.need_navigation |
119 return True |
117 return True |
120 |
118 |
121 def __init__(self, req=None, rset=None, **kwargs): |
119 def __init__(self, req=None, rset=None, **kwargs): |
122 super(View, self).__init__(req, rset, **kwargs) |
120 super(View, self).__init__(req, rset=rset, **kwargs) |
123 self.w = None |
121 self.w = None |
124 |
122 |
125 @property |
123 @property |
126 def content_type(self): |
124 def content_type(self): |
127 return self.req.html_content_type() |
125 return self._cw.html_content_type() |
128 |
126 |
129 def set_stream(self, w=None): |
127 def set_stream(self, w=None): |
130 if self.w is not None: |
128 if self.w is not None: |
131 return |
129 return |
132 if w is None: |
130 if w is None: |
165 view_func(**context) |
163 view_func(**context) |
166 # return stream content if we have created it |
164 # return stream content if we have created it |
167 if stream is not None: |
165 if stream is not None: |
168 return self._stream.getvalue() |
166 return self._stream.getvalue() |
169 |
167 |
170 dispatch = deprecated('.dispatch is deprecated, use .render')(render) |
168 def tal_render(self, template, variables): |
|
169 """render a precompiled page template with variables in the given |
|
170 dictionary as context |
|
171 """ |
|
172 from cubicweb.ext.tal import CubicWebContext |
|
173 context = CubicWebContext() |
|
174 context.update({'self': self, 'rset': self.cw_rset, '_' : self._cw._, |
|
175 'req': self._cw, 'user': self._cw.user}) |
|
176 context.update(variables) |
|
177 output = UStringIO() |
|
178 template.expand(context, output) |
|
179 return output.getvalue() |
|
180 |
|
181 dispatch = deprecated('[3.4] .dispatch is deprecated, use .render')(render) |
171 |
182 |
172 # should default .call() method add a <div classs="section"> around each |
183 # should default .call() method add a <div classs="section"> around each |
173 # rset item |
184 # rset item |
174 add_div_section = True |
185 add_div_section = True |
175 |
186 |
178 other rows of the result set and call the same view on the |
189 other rows of the result set and call the same view on the |
179 particular row |
190 particular row |
180 |
191 |
181 Views applicable on None result sets have to override this method |
192 Views applicable on None result sets have to override this method |
182 """ |
193 """ |
183 rset = self.rset |
194 rset = self.cw_rset |
184 if rset is None: |
195 if rset is None: |
185 raise NotImplementedError, self |
196 raise NotImplementedError, (self, "an rset is required") |
186 wrap = self.templatable and len(rset) > 1 and self.add_div_section |
197 wrap = self.templatable and len(rset) > 1 and self.add_div_section |
187 # XXX propagate self.extra_kwars? |
198 # XXX propagate self.extra_kwars? |
188 for i in xrange(len(rset)): |
199 for i in xrange(len(rset)): |
189 if wrap: |
200 if wrap: |
190 self.w(u'<div class="section">') |
201 self.w(u'<div class="section">') |
191 self.wview(self.id, rset, row=i, **kwargs) |
202 self.wview(self.__regid__, rset, row=i, **kwargs) |
192 if wrap: |
203 if wrap: |
193 self.w(u"</div>") |
204 self.w(u"</div>") |
194 |
205 |
195 def cell_call(self, row, col, **kwargs): |
206 def cell_call(self, row, col, **kwargs): |
196 """the view is called for a particular result set cell""" |
207 """the view is called for a particular result set cell""" |
204 if not getattr(self, 'title', None): |
215 if not getattr(self, 'title', None): |
205 return False |
216 return False |
206 return True |
217 return True |
207 |
218 |
208 def is_primary(self): |
219 def is_primary(self): |
209 return self.extra_kwargs.get('is_primary', self.id == 'primary') |
220 return self.cw_extra_kwargs.get('is_primary', self.__regid__ == 'primary') |
210 |
221 |
211 def url(self): |
222 def url(self): |
212 """return the url associated with this view. Should not be |
223 """return the url associated with this view. Should not be |
213 necessary for non linkable views, but a default implementation |
224 necessary for non linkable views, but a default implementation |
214 is provided anyway. |
225 is provided anyway. |
215 """ |
226 """ |
216 rset = self.rset |
227 rset = self.cw_rset |
217 if rset is None: |
228 if rset is None: |
218 return self.build_url('view', vid=self.id) |
229 return self._cw.build_url('view', vid=self.__regid__) |
219 coltypes = rset.column_types(0) |
230 coltypes = rset.column_types(0) |
220 if len(coltypes) == 1: |
231 if len(coltypes) == 1: |
221 etype = iter(coltypes).next() |
232 etype = iter(coltypes).next() |
222 if not self.schema.eschema(etype).final: |
233 if not self._cw.vreg.schema.eschema(etype).final: |
223 if len(rset) == 1: |
234 if len(rset) == 1: |
224 entity = rset.get_entity(0, 0) |
235 entity = rset.get_entity(0, 0) |
225 return entity.absolute_url(vid=self.id) |
236 return entity.absolute_url(vid=self.__regid__) |
226 # don't want to generate /<etype> url if there is some restriction |
237 # don't want to generate /<etype> url if there is some restriction |
227 # on something else than the entity type |
238 # on something else than the entity type |
228 restr = rset.syntax_tree().children[0].where |
239 restr = rset.syntax_tree().children[0].where |
229 # XXX norestriction is not correct here. For instance, in cases like |
240 # XXX norestriction is not correct here. For instance, in cases like |
230 # "Any P,N WHERE P is Project, P name N" norestriction should equal |
241 # "Any P,N WHERE P is Project, P name N" norestriction should equal |
231 # True |
242 # True |
232 norestriction = (isinstance(restr, nodes.Relation) and |
243 norestriction = (isinstance(restr, nodes.Relation) and |
233 restr.is_types_restriction()) |
244 restr.is_types_restriction()) |
234 if norestriction: |
245 if norestriction: |
235 return self.build_url(etype.lower(), vid=self.id) |
246 return self._cw.build_url(etype.lower(), vid=self.__regid__) |
236 return self.build_url('view', rql=rset.printable_rql(), vid=self.id) |
247 return self._cw.build_url('view', rql=rset.printable_rql(), vid=self.__regid__) |
237 |
248 |
238 def set_request_content_type(self): |
249 def set_request_content_type(self): |
239 """set the content type returned by this view""" |
250 """set the content type returned by this view""" |
240 self.req.set_content_type(self.content_type) |
251 self._cw.set_content_type(self.content_type) |
241 |
252 |
242 # view utilities ########################################################## |
253 # view utilities ########################################################## |
243 |
254 |
244 def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs): |
255 def wview(self, __vid, rset=None, __fallback_vid=None, **kwargs): |
245 """shortcut to self.view method automatically passing self.w as argument |
256 """shortcut to self.view method automatically passing self.w as argument |
246 """ |
257 """ |
247 self.view(__vid, rset, __fallback_vid, w=self.w, **kwargs) |
258 self._cw.view(__vid, rset, __fallback_vid, w=self.w, **kwargs) |
248 |
259 |
249 # XXX Template bw compat |
260 # XXX Template bw compat |
250 template = deprecated('.template is deprecated, use .view')(wview) |
261 template = deprecated('[3.4] .template is deprecated, use .view')(wview) |
251 |
262 |
252 def whead(self, data): |
263 def whead(self, data): |
253 self.req.html_headers.write(data) |
264 self._cw.html_headers.write(data) |
254 |
265 |
255 def wdata(self, data): |
266 def wdata(self, data): |
256 """simple helper that escapes `data` and writes into `self.w`""" |
267 """simple helper that escapes `data` and writes into `self.w`""" |
257 self.w(xml_escape(data)) |
268 self.w(xml_escape(data)) |
258 |
269 |
266 |
277 |
267 def page_title(self): |
278 def page_title(self): |
268 """returns a title according to the result set - used for the |
279 """returns a title according to the result set - used for the |
269 title in the HTML header |
280 title in the HTML header |
270 """ |
281 """ |
271 vtitle = self.req.form.get('vtitle') |
282 vtitle = self._cw.form.get('vtitle') |
272 if vtitle: |
283 if vtitle: |
273 return self.req._(vtitle) |
284 return self._cw._(vtitle) |
274 # class defined title will only be used if the resulting title doesn't |
285 # class defined title will only be used if the resulting title doesn't |
275 # seem clear enough |
286 # seem clear enough |
276 vtitle = getattr(self, 'title', None) or u'' |
287 vtitle = getattr(self, 'title', None) or u'' |
277 if vtitle: |
288 if vtitle: |
278 vtitle = self.req._(vtitle) |
289 vtitle = self._cw._(vtitle) |
279 rset = self.rset |
290 rset = self.cw_rset |
280 if rset and rset.rowcount: |
291 if rset and rset.rowcount: |
281 if rset.rowcount == 1: |
292 if rset.rowcount == 1: |
282 try: |
293 try: |
283 entity = self.complete_entity(0) |
294 entity = rset.complete_entity(0, 0) |
284 # use long_title to get context information if any |
295 # use long_title to get context information if any |
285 clabel = entity.dc_long_title() |
296 clabel = entity.dc_long_title() |
286 except NotAnEntity: |
297 except NotAnEntity: |
287 clabel = display_name(self.req, rset.description[0][0]) |
298 clabel = display_name(self._cw, rset.description[0][0]) |
288 clabel = u'%s (%s)' % (clabel, vtitle) |
299 clabel = u'%s (%s)' % (clabel, vtitle) |
289 else : |
300 else : |
290 etypes = rset.column_types(0) |
301 etypes = rset.column_types(0) |
291 if len(etypes) == 1: |
302 if len(etypes) == 1: |
292 etype = iter(etypes).next() |
303 etype = iter(etypes).next() |
293 clabel = display_name(self.req, etype, 'plural') |
304 clabel = display_name(self._cw, etype, 'plural') |
294 else : |
305 else : |
295 clabel = u'#[*] (%s)' % vtitle |
306 clabel = u'#[*] (%s)' % vtitle |
296 else: |
307 else: |
297 clabel = vtitle |
308 clabel = vtitle |
298 return u'%s (%s)' % (clabel, self.req.property_value('ui.site-title')) |
309 return u'%s (%s)' % (clabel, self._cw.property_value('ui.site-title')) |
299 |
310 |
300 def output_url_builder( self, name, url, args ): |
311 def output_url_builder( self, name, url, args ): |
301 self.w(u'<script language="JavaScript"><!--\n' \ |
312 self.w(u'<script language="JavaScript"><!--\n' \ |
302 u'function %s( %s ) {\n' % (name, ','.join(args) ) ) |
313 u'function %s( %s ) {\n' % (name, ','.join(args) ) ) |
303 url_parts = url.split("%s") |
314 url_parts = url.split("%s") |
308 self.w(u'+"%s"' % part) |
319 self.w(u'+"%s"' % part) |
309 self.w('\n document.window.href=url;\n') |
320 self.w('\n document.window.href=url;\n') |
310 self.w('}\n-->\n</script>\n') |
321 self.w('}\n-->\n</script>\n') |
311 |
322 |
312 def create_url(self, etype, **kwargs): |
323 def create_url(self, etype, **kwargs): |
313 """return the url of the entity creation form for a given entity type""" |
324 """ return the url of the entity creation form for a given entity type""" |
314 return self.req.build_url('add/%s' % etype, **kwargs) |
325 return self._cw.build_url('add/%s' % etype, **kwargs) |
315 |
326 |
316 def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False): |
327 def field(self, label, value, row=True, show_label=True, w=None, tr=True, table=False): |
317 """read-only field""" |
328 """read-only field""" |
318 if w is None: |
329 if w is None: |
319 w = self.w |
330 w = self.w |
341 # concrete views base classes ################################################# |
352 # concrete views base classes ################################################# |
342 |
353 |
343 class EntityView(View): |
354 class EntityView(View): |
344 """base class for views applying on an entity (i.e. uniform result set)""" |
355 """base class for views applying on an entity (i.e. uniform result set)""" |
345 __select__ = non_final_entity() |
356 __select__ = non_final_entity() |
346 registered = accepts_compat(View.registered) |
|
347 |
|
348 category = 'entityview' |
357 category = 'entityview' |
349 |
358 |
350 |
359 |
351 class StartupView(View): |
360 class StartupView(View): |
352 """base class for views which doesn't need a particular result set to be |
361 """base class for views which doesn't need a particular result set to be |
353 displayed (so they can always be displayed !) |
362 displayed (so they can always be displayed !) |
354 """ |
363 """ |
355 __select__ = none_rset() |
364 __select__ = none_rset() |
356 registered = require_group_compat(View.registered) |
|
357 |
365 |
358 category = 'startupview' |
366 category = 'startupview' |
359 |
367 |
360 def html_headers(self): |
368 def html_headers(self): |
361 """return a list of html headers (eg something to be inserted between |
369 """return a list of html headers (eg something to be inserted between |
386 |
394 |
387 def call(self, **kwargs): |
395 def call(self, **kwargs): |
388 """override call to execute rql returned by the .startup_rql method if |
396 """override call to execute rql returned by the .startup_rql method if |
389 necessary |
397 necessary |
390 """ |
398 """ |
391 if self.rset is None: |
399 rset = self.cw_rset |
392 self.rset = self.req.execute(self.startup_rql()) |
400 if rset is None: |
393 rset = self.rset |
401 rset = self.cw_rset = self._cw.execute(self.startup_rql()) |
394 for i in xrange(len(rset)): |
402 for i in xrange(len(rset)): |
395 self.wview(self.id, rset, row=i, **kwargs) |
403 self.wview(self.__regid__, rset, row=i, **kwargs) |
396 |
404 |
397 |
405 |
398 class AnyRsetView(View): |
406 class AnyRsetView(View): |
399 """base class for views applying on any non empty result sets""" |
407 """base class for views applying on any non empty result sets""" |
400 __select__ = nonempty_rset() |
408 __select__ = nonempty_rset() |
401 |
409 |
402 category = 'anyrsetview' |
410 category = 'anyrsetview' |
403 |
411 |
404 def columns_labels(self, mainindex=0, tr=True): |
412 def columns_labels(self, mainindex=0, tr=True): |
405 if tr: |
413 if tr: |
406 translate = lambda val, req=self.req: display_name(req, val) |
414 translate = lambda val, req=self._cw: display_name(req, val) |
407 else: |
415 else: |
408 translate = lambda val: val |
416 translate = lambda val: val |
409 # XXX [0] because of missing Union support |
417 # XXX [0] because of missing Union support |
410 rqlstdescr = self.rset.syntax_tree().get_description(mainindex, |
418 rqlstdescr = self.cw_rset.syntax_tree().get_description(mainindex, |
411 translate)[0] |
419 translate)[0] |
412 labels = [] |
420 labels = [] |
413 for colindex, label in enumerate(rqlstdescr): |
421 for colindex, label in enumerate(rqlstdescr): |
414 # compute column header |
422 # compute column header |
415 if label == 'Any': # find a better label |
423 if label == 'Any': # find a better label |
416 label = ','.join(translate(et) |
424 label = ','.join(translate(et) |
417 for et in self.rset.column_types(colindex)) |
425 for et in self.cw_rset.column_types(colindex)) |
418 labels.append(label) |
426 labels.append(label) |
419 return labels |
427 return labels |
420 |
428 |
421 |
429 |
422 # concrete template base classes ############################################## |
430 # concrete template base classes ############################################## |
424 class MainTemplate(View): |
432 class MainTemplate(View): |
425 """main template are primary access point to render a full HTML page. |
433 """main template are primary access point to render a full HTML page. |
426 There is usually at least a regular main template and a simple fallback |
434 There is usually at least a regular main template and a simple fallback |
427 one to display error if the first one failed |
435 one to display error if the first one failed |
428 """ |
436 """ |
429 registered = require_group_compat(View.registered) |
|
430 |
437 |
431 @property |
438 @property |
432 def doctype(self): |
439 def doctype(self): |
433 if self.req.xhtml_browser(): |
440 if self._cw.xhtml_browser(): |
434 return STRICT_DOCTYPE |
441 return STRICT_DOCTYPE |
435 return STRICT_DOCTYPE_NOEXT |
442 return STRICT_DOCTYPE_NOEXT |
436 |
443 |
437 def set_stream(self, w=None): |
444 def set_stream(self, w=None): |
438 if self.w is not None: |
445 if self.w is not None: |
439 return |
446 return |
440 if w is None: |
447 if w is None: |
441 if self.binary: |
448 if self.binary: |
442 self._stream = stream = StringIO() |
449 self._stream = stream = StringIO() |
443 else: |
450 else: |
444 self._stream = stream = HTMLStream(self.req) |
451 self._stream = stream = HTMLStream(self._cw) |
445 w = stream.write |
452 w = stream.write |
446 else: |
453 else: |
447 stream = None |
454 stream = None |
448 self.w = w |
455 self.w = w |
449 return stream |
456 return stream |
464 |
471 |
465 def user_callback(self, cb, args, msg=None, nonify=False): |
472 def user_callback(self, cb, args, msg=None, nonify=False): |
466 """register the given user callback and return an url to call it ready to be |
473 """register the given user callback and return an url to call it ready to be |
467 inserted in html |
474 inserted in html |
468 """ |
475 """ |
469 self.req.add_js('cubicweb.ajax.js') |
476 self._cw.add_js('cubicweb.ajax.js') |
470 if nonify: |
477 if nonify: |
471 _cb = cb |
478 _cb = cb |
472 def cb(*args): |
479 def cb(*args): |
473 _cb(*args) |
480 _cb(*args) |
474 cbname = self.req.register_onetime_callback(cb, *args) |
481 cbname = self._cw.register_onetime_callback(cb, *args) |
475 return self.build_js(cbname, xml_escape(msg or '')) |
482 return self.build_js(cbname, xml_escape(msg or '')) |
476 |
483 |
477 def build_update_js_call(self, cbname, msg): |
484 def build_update_js_call(self, cbname, msg): |
478 rql = self.rset.printable_rql() |
485 rql = self.cw_rset.printable_rql() |
479 return "javascript:userCallbackThenUpdateUI('%s', '%s', %s, %s, '%s', '%s')" % ( |
486 return "javascript:userCallbackThenUpdateUI('%s', '%s', %s, %s, '%s', '%s')" % ( |
480 cbname, self.id, dumps(rql), dumps(msg), |
487 cbname, self.id, dumps(rql), dumps(msg), |
481 self.__registry__, self.div_id()) |
488 self.__registry__, self.div_id()) |
482 |
489 |
483 def build_reload_js_call(self, cbname, msg): |
490 def build_reload_js_call(self, cbname, msg): |
491 |
498 |
492 class Component(ReloadableMixIn, View): |
499 class Component(ReloadableMixIn, View): |
493 """base class for components""" |
500 """base class for components""" |
494 __registry__ = 'components' |
501 __registry__ = 'components' |
495 __select__ = yes() |
502 __select__ = yes() |
496 property_defs = {} |
|
497 |
503 |
498 # XXX huummm, much probably useless |
504 # XXX huummm, much probably useless |
499 htmlclass = 'mainRelated' |
505 htmlclass = 'mainRelated' |
500 def div_class(self): |
506 def div_class(self): |
501 return '%s %s' % (self.htmlclass, self.id) |
507 return '%s %s' % (self.htmlclass, self.__regid__) |
502 # XXX a generic '%s%s' % (self.id, self.__registry__.capitalize()) would probably be nicer |
508 |
|
509 # XXX a generic '%s%s' % (self.__regid__, self.__registry__.capitalize()) would probably be nicer |
503 def div_id(self): |
510 def div_id(self): |
504 return '%sComponent' % self.id |
511 return '%sComponent' % self.__regid__ |