32 * if the view is not templatable, it should set the `content_type` class |
32 * if the view is not templatable, it should set the `content_type` class |
33 attribute to the correct MIME type (text/xhtml by default) |
33 attribute to the correct MIME type (text/xhtml by default) |
34 * the `category` attribute may be used in the interface to regroup related |
34 * the `category` attribute may be used in the interface to regroup related |
35 objects together |
35 objects together |
36 |
36 |
37 At instantiation time, the standard `req`, `rset`, and `cursor` |
37 At instantiation time, the standard `req` and `rset` attributes are |
38 attributes are added and the `w` attribute will be set at rendering |
38 added and the `w` attribute will be set at rendering time. |
39 time. |
39 |
40 |
40 A view writes to its output stream thanks to its attribute `w` (an |
41 A view writes to its output stream thanks to its attribute `w` (`UStreamIO`). |
41 `UStreamIO`). |
42 |
42 |
43 The basic interface for views is as follows (remember that the result set has a |
43 The basic interface for views is as follows (remember that the result set has a |
44 tabular structure with rows and columns, hence cells): |
44 tabular structure with rows and columns, hence cells): |
45 |
45 |
46 * `dispatch(**context)`, render the view by calling `call` or |
46 * `render(**context)`, render the view by calling `call` or |
47 `cell_call` depending on the given parameters |
47 `cell_call` depending on the given parameters |
48 * `call(**kwargs)`, call the view for a complete result set or null (default |
48 |
49 implementation calls `cell_call()` on each cell of the result set) |
49 * `call(**kwargs)`, call the view for a complete result set or null |
50 * `cell_call(row, col, **kwargs)`, call the view for a given cell of a result set |
50 (the default implementation calls `cell_call()` on each cell of the |
|
51 result set) |
|
52 |
|
53 * `cell_call(row, col, **kwargs)`, call the view for a given cell of a |
|
54 result set |
|
55 |
51 * `url()`, returns the URL enabling us to get the view with the current |
56 * `url()`, returns the URL enabling us to get the view with the current |
52 result set |
57 result set |
|
58 |
53 * `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier |
59 * `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier |
54 `__vid` on the given result set. It is possible to give a view identifier |
60 `__vid` on the given result set. It is possible to give a view identifier |
55 of fallback that will be used if the view requested is not applicable to the |
61 of fallback that will be used if the view requested is not applicable to the |
56 result set |
62 result set. This is actually defined on the AppObject class. |
57 |
63 |
58 * `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except |
64 * `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except |
59 the flow is automatically passed in the parameters |
65 the flow is automatically passed in the parameters |
60 |
66 |
61 * `html_headers()`, returns a list of HTML headers to set by the main template |
67 * `html_headers()`, returns a list of HTML headers to set by the main template |
101 id = 'search-associate' |
107 id = 'search-associate' |
102 title = _('search for association') |
108 title = _('search for association') |
103 __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any') |
109 __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any') |
104 |
110 |
105 |
111 |
106 Example of a view customization |
112 Example of view customization and creation |
107 ------------------------------- |
113 ------------------------------------------ |
108 |
|
109 [FIXME] XXX Example needs to be rewritten as it shows how to modify cell_call which |
|
110 contredicts our advise of not modifying it. |
|
111 |
114 |
112 We'll show you now an example of a ``primary`` view and how to customize it. |
115 We'll show you now an example of a ``primary`` view and how to customize it. |
113 |
116 |
114 If you want to change the way a ``BlogEntry`` is displayed, just override |
117 If you want to change the way a ``BlogEntry`` is displayed, just override |
115 the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py``: |
118 the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py``: |
116 |
119 |
117 .. sourcecode:: python |
120 .. sourcecode:: python |
118 |
121 |
119 from cubicweb.view import EntityView |
122 from cubicweb.selectors import implements |
120 from cubicweb.selectors import implements |
123 from cubicweb.web.views.primary improt Primaryview |
121 |
124 |
122 class BlogEntryPrimaryView(EntityView): |
125 class BlogEntryPrimaryView(PrimaryView): |
123 id = 'primary' |
126 __select__ = PrimaryView.__select__ & implements('BlogEntry') |
124 __select__ =implements('Blog') |
127 |
125 |
128 def render_entity_attributes(self, entity): |
126 def cell_call(self, row, col): |
129 self.w(u'<p>published on %s</p>' % |
127 entity = self.entity(row, col) |
130 entity.publish_date.strftime('%Y-%m-%d')) |
128 self.w(u'<h1>%s</h1>' % entity.title) |
131 super(BlogEntryPrimaryView, self).render_entity_attributes(entity) |
129 self.w(u'<p>published on %s in category %s</p>' % \ |
132 |
130 (entity.publish_date.strftime('%Y-%m-%d'), entity.category)) |
133 The above source code defines a new primary view for |
131 self.w(u'<p>%s</p>' % entity.text) |
134 ``BlogEntry``. The `id` class attribute is not repeated there since it |
132 |
135 is inherited through the `primary.PrimaryView` class. |
133 The above source code defines a new primary view (`line 03`) for |
136 |
134 ``BlogEntry`` (`line 05`). |
137 The selector for this view chains the selector of the inherited class |
135 |
138 with its own specific criterion. |
136 Since views are applied to result sets which can be tables of |
|
137 data, we have to recover the entity from its (row,col)-coordinates (`line 08`). |
|
138 We will get to this in more detail later. |
|
139 |
139 |
140 The view method ``self.w()`` is used to output data. Here `lines |
140 The view method ``self.w()`` is used to output data. Here `lines |
141 09-12` output HTML tags and values of the entity's attributes. |
141 08-09` output HTML for the publication date of the entry. |
142 |
|
143 When displaying the same blog entry as before, you will notice that the |
|
144 page is now looking much nicer. [FIXME: it is not clear to what this refers.] |
|
145 |
142 |
146 .. image:: ../../images/lax-book.09-new-view-blogentry.en.png |
143 .. image:: ../../images/lax-book.09-new-view-blogentry.en.png |
147 :alt: blog entries now look much nicer |
144 :alt: blog entries now look much nicer |
148 |
145 |
149 Let us now improve the primary view of a blog |
146 Let us now improve the primary view of a blog |
150 |
147 |
151 .. sourcecode:: python |
148 .. sourcecode:: python |
152 |
149 |
153 class BlogPrimaryView(EntityView): |
150 from logilab.mtconverter import xml_escape |
|
151 from cubicweb.selectors import implements, one_line_rset |
|
152 from cubicweb.web.views.primary import Primaryview |
|
153 |
|
154 class BlogPrimaryView(PrimaryView): |
154 id = 'primary' |
155 id = 'primary' |
155 __select__ =implements('Blog') |
156 __select__ = PrimaryView.__select__ & implements('Blog') |
|
157 rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s' |
|
158 |
|
159 def render_entity_relations(self, entity): |
|
160 rset = self.req.execute(self.rql, {'b' : entity.eid}) |
|
161 for entry in rset.entities(): |
|
162 self.w(u'<p>%s</p>' % entry.view('inblogcontext')) |
|
163 |
|
164 class BlogEntryInBlogView(EntityView): |
|
165 'inblogcontext' |
|
166 __select__ = implements('BlogEntry') |
156 |
167 |
157 def cell_call(self, row, col): |
168 def cell_call(self, row, col): |
158 entity = self.entity(row, col) |
169 entity = self.rset.get_entity(row, col) |
159 self.w(u'<h1>%s</h1>' % entity.title) |
170 self.w(u'<a href="%s" title="%s">%s</a>' % |
160 self.w(u'<p>%s</p>' % entity.description) |
171 entity.absolute_url(), |
161 rset = self.req.execute('Any E WHERE E entry_of B, B eid "%s"' % entity.eid) |
172 xml_escape(entity.content[:50]), |
162 self.wview('primary', rset) |
173 xml_escape(entity.description)) |
163 |
174 |
164 In the above source code, `lines 01-08` are similar to the previous |
175 This happens in two places. First we override the |
165 view we defined. [FIXME: defined where ?] |
176 render_entity_relations method of a Blog's primary view. Here we want |
166 |
177 to display our blog entries in a custom way. |
167 At `line 09`, a simple request is made to build a result set with all |
178 |
|
179 At `line 10`, a simple request is made to build a result set with all |
168 the entities linked to the current ``Blog`` entity by the relationship |
180 the entities linked to the current ``Blog`` entity by the relationship |
169 ``entry_of``. The part of the framework handling the request knows |
181 ``entry_of``. The part of the framework handling the request knows |
170 about the schema and infer that such entities have to be of the |
182 about the schema and infers that such entities have to be of the |
171 ``BlogEntry`` kind and retrieves them. |
183 ``BlogEntry`` kind and retrieves them (in the prescribed publish_date |
172 |
184 order). |
173 The request returns a selection of data called a result set. At |
185 |
174 `line 10` the view 'primary' is applied to this result set to output |
186 The request returns a selection of data called a result set. Result |
175 HTML. |
187 set objects have an .entities() method returning a generator on |
|
188 requested entities (going transparently through the `ORM` layer). |
|
189 |
|
190 At `line 13` the view 'inblogcontext' is applied to each blog entry to |
|
191 output HTML. (Note that the 'inblogcontext' view is not defined |
|
192 whatsoever in *CubicWeb*. You are absolutely free to define whole view |
|
193 families.) We juste arrange to wrap each blogentry output in a 'p' |
|
194 html element. |
|
195 |
|
196 Next, we define the 'inblogcontext' view. This is NOT a primary view, |
|
197 with its well-defined sections (title, metadata, attribtues, |
|
198 relations/boxes). All a basic view has to define is cell_call. |
|
199 |
|
200 Since views are applied to result sets which can be tables of data, we |
|
201 have to recover the entity from its (row,col)-coordinates (`line |
|
202 20`). Then we can spit some HTML. |
|
203 |
|
204 But careful: all strings manipulated in *CubicWeb* are actually |
|
205 unicode strings. While web browsers are usually tolerant to incoherent |
|
206 encodings they are being served, we should not abuse it. Hence we have |
|
207 to properly escape our data. The xml_escape() function has to be used |
|
208 to safely fill (X)HTML elements from Python unicode strings. |
|
209 |
176 |
210 |
177 **This is to be compared to interfaces and protocols in object-oriented |
211 **This is to be compared to interfaces and protocols in object-oriented |
178 languages. Applying a given view called 'a_view' to all the entities |
212 languages. Applying a given view called 'a_view' to all the entities |
179 of a result set only requires to have for each entity of this result set, |
213 of a result set only requires to have for each entity of this result set, |
180 an available view called 'a_view' which accepts the entity.** |
214 an available view called 'a_view' which accepts the entity. |
|
215 |
|
216 Instead of merely using type based dispatch, we do predicate dispatch |
|
217 which quite more powerful** |
181 |
218 |
182 Assuming we added entries to the blog titled `MyLife`, displaying it |
219 Assuming we added entries to the blog titled `MyLife`, displaying it |
183 now allows to read its description and all its entries. |
220 now allows to read its description and all its entries. |
184 |
221 |
185 .. image:: ../../images/lax-book.10-blog-with-two-entries.en.png |
222 .. image:: ../../images/lax-book.10-blog-with-two-entries.en.png |