1 .. -*- coding: utf-8 -*- |
|
2 |
|
3 .. _ViewDefinition: |
|
4 |
|
5 Views definition |
|
6 ================ |
|
7 |
|
8 This chapter aims to describe the concept of a `view` used all along |
|
9 the development of a web application and how it has been implemented |
|
10 in `CubicWeb`. |
|
11 |
|
12 We'll start with a description of the interface providing you with a basic |
|
13 understanding of the classes and methods available, then detail the view |
|
14 selection principle which makes `CubicWeb` web interface very flexible. |
|
15 |
|
16 A `View` is an object applied to another object such as an entity. |
|
17 |
|
18 Basic class for views |
|
19 --------------------- |
|
20 |
|
21 Class `View` (`cubicweb.common.view`) |
|
22 ````````````````````````````````````` |
|
23 |
|
24 This class is an abstraction of a view class, used as a base class for every |
|
25 renderable object such as views, templates, graphic components, etc. |
|
26 |
|
27 A `View` is instantiated to render a result set or part of a result set. `View` |
|
28 subclasses may be parametrized using the following class attributes: |
|
29 |
|
30 * `templatable` indicates if the view may be embeded in a main |
|
31 template or if it has to be rendered standalone (i.e. XML views |
|
32 must not be embeded in the main template for HTML pages) |
|
33 * if the view is not templatable, it should set the `content_type` class |
|
34 attribute to the correct MIME type (text/xhtml by default) |
|
35 * the `category` attribute may be used in the interface to regroup related |
|
36 objects together |
|
37 |
|
38 At instantiation time, the standard `req`, `rset`, and `cursor` |
|
39 attributes are added and the `w` attribute will be set at rendering |
|
40 time. |
|
41 |
|
42 A view writes to its output stream thanks to its attribute `w` (`UStreamIO`). |
|
43 |
|
44 The basic interface for views is as follows (remember that the result set has a |
|
45 tabular structure with rows and columns, hence cells): |
|
46 |
|
47 * `dispatch(**context)`, render the view by calling `call` or |
|
48 `cell_call` depending on the given parameters |
|
49 * `call(**kwargs)`, call the view for a complete result set or null (default |
|
50 implementation calls `cell_call()` on each cell of the result set) |
|
51 * `cell_call(row, col, **kwargs)`, call the view for a given cell of a result set |
|
52 * `url()`, returns the URL enabling us to get the view with the current |
|
53 result set |
|
54 * `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier |
|
55 `__vid` on the given result set. It is possible to give a view identifier |
|
56 of fallback that will be used if the view requested is not applicable to the |
|
57 result set |
|
58 |
|
59 * `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except |
|
60 the flow is automatically passed in the parameters |
|
61 |
|
62 * `html_headers()`, returns a list of HTML headers to set by the main template |
|
63 |
|
64 * `page_title()`, returns the title to use in the HTML header `title` |
|
65 |
|
66 |
|
67 Other basic view classes |
|
68 ```````````````````````` |
|
69 Here are some of the subclasses of `View` defined in `cubicweb.common.view` |
|
70 that are more concrete as they relate to data rendering within the application: |
|
71 |
|
72 * `EntityView`, view applying to lines or cell containing an entity (e.g. an eid) |
|
73 * `StartupView`, start view that does not require a result set to apply to |
|
74 * `AnyRsetView`, view applied to any result set |
|
75 * `EmptyRsetView`, view applied to an empty result set |
|
76 |
|
77 |
|
78 The selection view principle |
|
79 ---------------------------- |
|
80 |
|
81 A view is essentially defined by: |
|
82 |
|
83 - an identifier (all objects in `CubicWeb` are entered in a registry |
|
84 and this identifier will be used as a key). This is defined in the class |
|
85 attribute ``id``. |
|
86 |
|
87 - a filter to select the result sets it can be applied to. This is defined in |
|
88 the class attribute ``__selectors__``, which expects a tuple of selectors |
|
89 as its value. |
|
90 |
|
91 |
|
92 For a given identifier, multiple views can be defined. `CubicWeb` uses |
|
93 a selector which computes scores to identify and select the |
|
94 best view to apply in the given context. The selectors library is in |
|
95 ``cubicweb.common.selector`` and a library of the methods used to |
|
96 compute scores is in ``cubicweb.vregistry.vreq``. |
|
97 |
|
98 .. include:: B1021-views-selectors.en.txt |
|
99 |
|
100 Registerer |
|
101 `````````` |
|
102 [Registerers are deprecated: they will soon disappear for explicite |
|
103 registration...] |
|
104 |
|
105 A view is also customizable through its attribute ``__registerer__``. |
|
106 This is used at the time the application is launched to manage how |
|
107 objects (views, graphic components, actions, etc.) |
|
108 are registered in the `cubicWeb` registry. |
|
109 |
|
110 A `registerer` can, for example, identify when we register an |
|
111 object that is equivalent to an already registered object, which |
|
112 could happen when we define two `primary` views for an entity type. |
|
113 |
|
114 The purpose of a `registerer` is to control object registry |
|
115 at the application startup whereas `selectors` control objects |
|
116 when they are selected for display. |
|
117 |
|
118 |
|
119 .. include:: B1022-views-stdlib.en.txt |
|
120 |
|
121 |
|
122 Examples of views class |
|
123 ----------------------- |
|
124 |
|
125 - Using the attribute `templatable` |
|
126 |
|
127 :: |
|
128 |
|
129 |
|
130 class RssView(XmlView): |
|
131 id = 'rss' |
|
132 title = _('rss') |
|
133 templatable = False |
|
134 content_type = 'text/xml' |
|
135 http_cache_manager = MaxAgeHTTPCacheManager |
|
136 cache_max_age = 60*60*2 # stay in http cache for 2 hours by default |
|
137 |
|
138 |
|
139 |
|
140 - Using the attribute `__selectors__` |
|
141 |
|
142 :: |
|
143 |
|
144 |
|
145 class SearchForAssociationView(EntityView): |
|
146 """view called by the edition view when the user asks |
|
147 to search for something to link to the edited eid |
|
148 """ |
|
149 id = 'search-associate' |
|
150 title = _('search for association') |
|
151 __selectors__ = (one_line_rset, match_search_state, accept_selector) |
|
152 accepts = ('Any',) |
|
153 search_states = ('linksearch',) |
|
154 |
|
155 |
|
156 Rendering methods and attributes for ``PrimaryView`` |
|
157 ---------------------------------------------------- |
|
158 |
|
159 By default, `CubicWeb` provides a primary view for each new entity type |
|
160 you create. The first view you might be interested in modifying. |
|
161 |
|
162 Let's have a quick look at the EntityView ``PrimaryView`` as well as |
|
163 its rendering method:: |
|
164 |
|
165 class PrimaryView(EntityView): |
|
166 """the full view of an non final entity""" |
|
167 id = 'primary' |
|
168 title = _('primary') |
|
169 show_attr_label = True |
|
170 show_rel_label = True |
|
171 skip_none = True |
|
172 skip_attrs = ('eid', 'creation_date', 'modification_date') |
|
173 skip_rels = () |
|
174 main_related_section = True |
|
175 |
|
176 ... |
|
177 |
|
178 def cell_call(self, row, col): |
|
179 self.row = row |
|
180 self.render_entity(self.complete_entity(row, col)) |
|
181 |
|
182 def render_entity(self, entity): |
|
183 """return html to display the given entity""" |
|
184 siderelations = [] |
|
185 self.render_entity_title(entity) |
|
186 self.render_entity_metadata(entity) |
|
187 # entity's attributes and relations, excluding meta data |
|
188 # if the entity isn't meta itself |
|
189 self.w(u'<div>') |
|
190 self.w(u'<div class="mainInfo">') |
|
191 self.render_entity_attributes(entity, siderelations) |
|
192 self.w(u'</div>') |
|
193 self.content_navigation_components('navcontenttop') |
|
194 if self.main_related_section: |
|
195 self.render_entity_relations(entity, siderelations) |
|
196 self.w(u'</div>') |
|
197 # side boxes |
|
198 self.w(u'<div class="primaryRight">') |
|
199 self.render_side_related(entity, siderelations) |
|
200 self.w(u'</div>') |
|
201 self.w(u'<div class="clear"></div>') |
|
202 self.content_navigation_components('navcontentbottom') |
|
203 |
|
204 ... |
|
205 |
|
206 ``cell_call`` is executed for each entity of a result set and apply ``render_entity``. |
|
207 |
|
208 The methods you want to modify while customizing a ``PrimaryView`` are: |
|
209 |
|
210 *render_entity_title(self, entity)* |
|
211 Renders the entity title based on the assumption that the method |
|
212 ``def content_title(self)`` is implemented for the given entity type. |
|
213 |
|
214 *render_entity_metadata(self, entity)* |
|
215 Renders the entity metadata based on the assumption that the method |
|
216 ``def summary(self)`` is implemented for the given entity type. |
|
217 |
|
218 *render_entity_attributes(self, entity, siderelations)* |
|
219 Renders all the attribute of an entity with the exception of attribute |
|
220 of type `Password` and `Bytes`. |
|
221 |
|
222 *content_navigation_components(self, context)* |
|
223 This method is applicable only for entity type implementing the interface |
|
224 `IPrevNext`. This interface is for entities which can be linked to a previous |
|
225 and/or next entity. This methods will render the navigation links between |
|
226 entities of this type, either at the top or at the bottom of the page |
|
227 given the context (navcontent{top|bottom}). |
|
228 |
|
229 *render_entity_relations(self, entity, siderelations)* |
|
230 Renders all the relations of the entity in the main section of the page. |
|
231 |
|
232 *render_side_related(self, entity, siderelations)* |
|
233 Renders all the relations of the entity in a side box. This is equivalent |
|
234 to *render_entity_relations* in addition to render the relations |
|
235 in a box. |
|
236 |
|
237 Also, please note that by setting the following attributes in you class, |
|
238 you can already customize some of the rendering: |
|
239 |
|
240 *show_attr_label* |
|
241 Renders the attribute label next to the attribute value if set to True. |
|
242 Otherwise, does only display the attribute value. |
|
243 |
|
244 *show_rel_label* |
|
245 Renders the relation label next to the relation value if set to True. |
|
246 Otherwise, does only display the relation value. |
|
247 |
|
248 *skip_none* |
|
249 Does not render an attribute value that is None if set to True. |
|
250 |
|
251 *skip_attrs* |
|
252 Given a list of attributes name, does not render the value of the attributes listed. |
|
253 |
|
254 *skip_rels* |
|
255 Given a list of relations name, does not render the relations listed. |
|
256 |
|
257 *main_related_section* |
|
258 Renders the relations of the entity if set to True. |
|
259 |
|
260 A good practice is for you to identify the content of your entity type for which |
|
261 the default rendering does not answer your need so that you can focus on the specific |
|
262 method (from the list above) that needs to be modified. We do not recommand you to |
|
263 overwrite ``render_entity`` as you might potentially loose the benefits of the side |
|
264 boxes handling. |
|
265 |
|
266 Example of a view customization |
|
267 ------------------------------- |
|
268 |
|
269 [FIXME] XXX Example needs to be rewritten as it shows how to modify cell_call which |
|
270 contredicts our advise of not modifying it. |
|
271 |
|
272 We'll show you now an example of a ``primary`` view and how to customize it. |
|
273 |
|
274 If you want to change the way a ``BlogEntry`` is displayed, just override |
|
275 the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py`` :: |
|
276 |
|
277 01. from cubicweb.web.views import baseviews |
|
278 02. |
|
279 03. class BlogEntryPrimaryView(baseviews.PrimaryView): |
|
280 04. |
|
281 05. accepts = ('BlogEntry',) |
|
282 06. |
|
283 07. def cell_call(self, row, col): |
|
284 08. entity = self.entity(row, col) |
|
285 09. self.w(u'<h1>%s</h1>' % entity.title) |
|
286 10. self.w(u'<p>published on %s in category %s</p>' % \ |
|
287 11. (entity.publish_date.strftime('%Y-%m-%d'), entity.category)) |
|
288 12. self.w(u'<p>%s</p>' % entity.text) |
|
289 |
|
290 The above source code defines a new primary view (`line 03`) for |
|
291 ``BlogEntry`` (`line 05`). |
|
292 |
|
293 Since views are applied to result sets which can be tables of |
|
294 data, we have to recover the entity from its (row,col)-coordinates (`line 08`). |
|
295 We will get to this in more detail later. |
|
296 |
|
297 The view method ``self.w()`` is used to output data. Here `lines |
|
298 09-12` output HTML tags and values of the entity's attributes. |
|
299 |
|
300 When displaying the same blog entry as before, you will notice that the |
|
301 page is now looking much nicer. [FIXME: it is not clear to what this refers.] |
|
302 |
|
303 .. image:: images/lax-book.09-new-view-blogentry.en.png |
|
304 :alt: blog entries now look much nicer |
|
305 |
|
306 Let us now improve the primary view of a blog :: |
|
307 |
|
308 01. class BlogPrimaryView(baseviews.PrimaryView): |
|
309 02. |
|
310 03. accepts = ('Blog',) |
|
311 04. |
|
312 05. def cell_call(self, row, col): |
|
313 06. entity = self.entity(row, col) |
|
314 07. self.w(u'<h1>%s</h1>' % entity.title) |
|
315 08. self.w(u'<p>%s</p>' % entity.description) |
|
316 09. rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid) |
|
317 10. self.wview('primary', rset) |
|
318 |
|
319 In the above source code, `lines 01-08` are similar to the previous |
|
320 view we defined. [FIXME: defined where ?] |
|
321 |
|
322 At `line 09`, a simple request is made to build a result set with all |
|
323 the entities linked to the current ``Blog`` entity by the relationship |
|
324 ``entry_of``. The part of the framework handling the request knows |
|
325 about the schema and infer that such entities have to be of the |
|
326 ``BlogEntry`` kind and retrieves them. |
|
327 |
|
328 The request returns a selection of data called a result set. At |
|
329 `line 10` the view 'primary' is applied to this result set to output |
|
330 HTML. |
|
331 |
|
332 **This is to be compared to interfaces and protocols in object-oriented |
|
333 languages. Applying a given view called 'a_view' to all the entities |
|
334 of a result set only requires to have for each entity of this result set, |
|
335 an available view called 'a_view' which accepts the entity.** |
|
336 |
|
337 Assuming we added entries to the blog titled `MyLife`, displaying it |
|
338 now allows to read its description and all its entries. |
|
339 |
|
340 .. image:: images/lax-book.10-blog-with-two-entries.en.png |
|
341 :alt: a blog and all its entries |
|
342 |
|
343 **Before we move forward, remember that the selection/view principle is |
|
344 at the core of `CubicWeb`. Everywhere in the engine, data is requested |
|
345 using the RQL language, then HTML/XML/text/PNG is output by applying a |
|
346 view to the result set returned by the query. That is where most of the |
|
347 flexibility comes from.** |
|
348 |
|
349 [WRITE ME] |
|
350 |
|
351 * implementing interfaces, calendar for blog entries |
|
352 * show that a calendar view can export data to ical |
|
353 |
|
354 We will implement the `cubicweb.interfaces.ICalendarable` interfaces on |
|
355 entities.BlogEntry and apply the OneMonthCalendar and iCalendar views |
|
356 to result sets like "Any E WHERE E is BlogEntry" |
|
357 |
|
358 * create view "blogentry table" with title, publish_date, category |
|
359 |
|
360 We will show that by default the view that displays |
|
361 "Any E,D,C WHERE E publish_date D, E category C" is the table view. |
|
362 Of course, the same can be obtained by calling |
|
363 self.wview('table',rset) |
|
364 |
|
365 * in view blog, select blogentries and apply view "blogentry table" |
|
366 * demo ajax by filtering blogentry table on category |
|
367 |
|
368 we did the same with 'primary', but with tables we can turn on filters |
|
369 and show that ajax comes for free. |
|
370 [FILLME] |
|
371 |
|
372 |
|
373 Templates |
|
374 --------- |
|
375 |
|
376 *Templates* are specific views that do not depend on a result set. The basic |
|
377 class `Template` (`cubicweb.common.view`) is derived from the class `View`. |
|
378 |
|
379 To build a HTML page, a *main template* is used. In general, the template of |
|
380 identifier `main` is the one to use (it is not used in case an error is raised or for |
|
381 the login form for example). This template uses other templates in addition |
|
382 to the views which depends on the content to generate the HTML page to return. |
|
383 |
|
384 A *template* is responsible for: |
|
385 |
|
386 1. executing RQL query of data to render if necessary |
|
387 2. identifying the view to use to render data if it is not specified |
|
388 3. composing the HTML page to return |
|
389 |
|
390 You will find out more about templates in :ref:`templates`. |
|
391 |
|
392 XML views, binaries... |
|
393 ---------------------- |
|
394 For views generating other formats than HTML (an image generated dynamically |
|
395 for example), and which can not simply be included in the HTML page generated |
|
396 by the main template (see above), you have to: |
|
397 |
|
398 * set the attribute `templatable` of the class to `False` |
|
399 * set, through the attribute `content_type` of the class, the MIME type generated |
|
400 by the view to `application/octet-stream` |
|
401 |
|
402 For views dedicated to binary content creation (like dynamically generated |
|
403 images), we have to set the attribute `binary` of the class to `True` (which |
|
404 implies that `templatable == False`, so that the attribute `w` of the view could be |
|
405 replaced by a binary flow instead of unicode). |
|
406 |
|
407 (X)HTML tricks to apply |
|
408 ----------------------- |
|
409 |
|
410 Some web browser (Firefox for example) are not happy with empty `<div>` |
|
411 (by empty we mean that there is no content in the tag, but there |
|
412 could be attributes), so we should always use `<div></div>` even if |
|
413 it is empty and not use `<div/>`. |
|
414 |
|