13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
13 # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
14 # details. |
14 # details. |
15 # |
15 # |
16 # You should have received a copy of the GNU Lesser General Public License along |
16 # You should have received a copy of the GNU Lesser General Public License along |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
17 # with CubicWeb. If not, see <http://www.gnu.org/licenses/>. |
18 """The default primary view""" |
18 """ |
|
19 The *primary* view is supposed to render a maximum of informations about the |
|
20 entity. |
|
21 |
|
22 .. _primary_view_layout: |
|
23 |
|
24 Layout |
|
25 `````` |
|
26 |
|
27 The primary view has the following layout. |
|
28 |
|
29 .. image:: ../../images/primaryview_template.png |
|
30 |
|
31 .. _primary_view_configuration: |
|
32 |
|
33 Primary view configuration |
|
34 `````````````````````````` |
|
35 |
|
36 If you want to customize the primary view of an entity, overriding the primary |
|
37 view class may not be necessary. For simple adjustments (attributes or relations |
|
38 display locations and styles), a much simpler way is to use uicfg. |
|
39 |
|
40 Attributes/relations display location |
|
41 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
|
42 |
|
43 In the primary view, there are three sections where attributes and |
|
44 relations can be displayed (represented in pink in the image above): |
|
45 |
|
46 * 'attributes' |
|
47 * 'relations' |
|
48 * 'sideboxes' |
|
49 |
|
50 **Attributes** can only be displayed in the attributes section (default |
|
51 behavior). They can also be hidden. By default, attributes of type `Password` |
|
52 and `Bytes` are hidden. |
|
53 |
|
54 For instance, to hide the ``title`` attribute of the ``Blog`` entity: |
|
55 |
|
56 .. sourcecode:: python |
|
57 |
|
58 from cubicweb.web import uicfg |
|
59 uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden') |
|
60 |
|
61 **Relations** can be either displayed in one of the three sections or hidden. |
|
62 |
|
63 For relations, there are two methods: |
|
64 |
|
65 * ``tag_object_of`` for modifying the primary view of the object |
|
66 * ``tag_subject_of`` for modifying the primary view of the subject |
|
67 |
|
68 These two methods take two arguments: |
|
69 |
|
70 * a triplet ``(subject, relation_name, object)``, where subject or object can be replaced with ``'*'`` |
|
71 * the section name or ``hidden`` |
|
72 |
|
73 .. sourcecode:: python |
|
74 |
|
75 pv_section = uicfg.primaryview_section |
|
76 # hide every relation `entry_of` in the `Blog` primary view |
|
77 pv_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden') |
|
78 |
|
79 # display `entry_of` relations in the `relations` |
|
80 # section in the `BlogEntry` primary view |
|
81 pv_section.tag_subject_of(('BlogEntry', 'entry_of', '*'), 'relations') |
|
82 |
|
83 |
|
84 Display content |
|
85 ^^^^^^^^^^^^^^^ |
|
86 |
|
87 You can use ``primaryview_display_ctrl`` to customize the display of attributes |
|
88 or relations. Values of ``primaryview_display_ctrl`` are dictionaries. |
|
89 |
|
90 |
|
91 Common keys for attributes and relations are: |
|
92 |
|
93 * ``vid``: specifies the regid of the view for displaying the attribute or the relation. |
|
94 |
|
95 If ``vid`` is not specified, the default value depends on the section: |
|
96 * ``attributes`` section: 'reledit' view |
|
97 * ``relations`` section: 'autolimited' view |
|
98 * ``sideboxes`` section: 'sidebox' view |
|
99 |
|
100 * ``order``: int used to control order within a section. When not specified, |
|
101 automatically set according to order in which tags are added. |
|
102 |
|
103 * ``label``: label for the relations section or side box |
|
104 |
|
105 * ``showlabel``: boolean telling whether the label is displayed |
|
106 |
|
107 .. sourcecode:: python |
|
108 |
|
109 # let us remind the schema of a blog entry |
|
110 class BlogEntry(EntityType): |
|
111 title = String(required=True, fulltextindexed=True, maxsize=256) |
|
112 publish_date = Date(default='TODAY') |
|
113 content = String(required=True, fulltextindexed=True) |
|
114 entry_of = SubjectRelation('Blog', cardinality='?*') |
|
115 |
|
116 # now, we want to show attributes |
|
117 # with an order different from that in the schema definition |
|
118 view_ctrl = uicfg.primaryview_display_ctrl |
|
119 for index, attr in enumerate('title', 'content', 'publish_date'): |
|
120 view_ctrl.tag_attribute(('BlogEntry', attr), {'order': index}) |
|
121 |
|
122 By default, relations displayed in the 'relations' section are being displayed by |
|
123 the 'autolimited' view. This view will use comma separated values, or list view |
|
124 and/or limit your rset if there is too much items in it (and generate the "view |
|
125 all" link in this case). |
|
126 |
|
127 You can control this view by setting the following values in the |
|
128 `primaryview_display_ctrl` relation tag: |
|
129 |
|
130 * `limit`, maximum number of entities to display. The value of the |
|
131 'navigation.related-limit' cwproperty is used by default (which is 8 by default). |
|
132 If None, no limit. |
|
133 |
|
134 * `use_list_limit`, number of entities until which they should be display as a list |
|
135 (eg using the 'list' view). Below that limit, the 'csv' view is used. If None, |
|
136 display using 'csv' anyway. |
|
137 |
|
138 * `subvid`, the subview identifier (eg view that should be used of each item in the |
|
139 list) |
|
140 |
|
141 Notice you can also use the `filter` key to set up a callback taking the related |
|
142 result set as argument and returning it filtered, to do some arbitrary filtering |
|
143 that can't be done using rql for instance. |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 .. sourcecode:: python |
|
149 |
|
150 pv_section = uicfg.primaryview_section |
|
151 # in `CWUser` primary view, display `created_by` |
|
152 # relations in relations section |
|
153 pv_section.tag_object_of(('*', 'created_by', 'CWUser'), 'relations') |
|
154 |
|
155 # display this relation as a list, sets the label, |
|
156 # limit the number of results and filters on comments |
|
157 def filter_comment(rset): |
|
158 return rset.filtered_rset(lambda x: x.e_schema == 'Comment') |
|
159 pv_ctrl = uicfg.primaryview_display_ctrl |
|
160 pv_ctrl.tag_object_of(('*', 'created_by', 'CWUser'), |
|
161 {'vid': 'list', 'label': _('latest comment(s):'), |
|
162 'limit': True, |
|
163 'filter': filter_comment}) |
|
164 |
|
165 .. warning:: with the ``primaryview_display_ctrl`` rtag, the subject or the |
|
166 object of the relation is ignored for respectively ``tag_object_of`` or |
|
167 ``tag_subject_of``. To avoid warnings during execution, they should be set to |
|
168 ``'*'``. |
|
169 |
|
170 Rendering methods and attributes |
|
171 ```````````````````````````````` |
|
172 |
|
173 The basic layout of a primary view is as in the |
|
174 :ref:`primary_view_layout` section. This layout is actually drawn by |
|
175 the `render_entity` method. |
|
176 |
|
177 The methods you may want to modify while customizing a ``PrimaryView`` |
|
178 are: |
|
179 |
|
180 *render_entity_title(self, entity)* |
|
181 Renders the entity title, by default using entity's :meth:`dc_title()` method. |
|
182 |
|
183 *render_entity_attributes(self, entity)* |
|
184 Renders all attributes and relations in the 'attributes' section . The |
|
185 :attr:`skip_none` attribute controls the display of `None` valued attributes. |
|
186 |
|
187 *render_entity_relations(self, entity)* |
|
188 Renders all relations in the 'relations' section. |
|
189 |
|
190 *render_side_boxes(self, entity, boxes)* |
|
191 Renders side boxes on the right side of the content. This will generate a box |
|
192 for each relation in the 'sidebox' section, as well as explicit box |
|
193 appobjects selectable in this context. |
|
194 |
|
195 The placement of relations in the relations section or in side boxes |
|
196 can be controlled through the :ref:`primary_view_configuration` mechanism. |
|
197 |
|
198 *content_navigation_components(self, context)* |
|
199 This method is applicable only for entity type implementing the interface |
|
200 `IPrevNext`. This interface is for entities which can be linked to a previous |
|
201 and/or next entity. This method will render the navigation links between |
|
202 entities of this type, either at the top or at the bottom of the page |
|
203 given the context (navcontent{top|bottom}). |
|
204 |
|
205 Also, please note that by setting the following attributes in your |
|
206 subclass, you can already customize some of the rendering: |
|
207 |
|
208 *show_attr_label* |
|
209 Renders the attribute label next to the attribute value if set to `True`. |
|
210 Otherwise, does only display the attribute value. |
|
211 |
|
212 *show_rel_label* |
|
213 Renders the relation label next to the relation value if set to `True`. |
|
214 Otherwise, does only display the relation value. |
|
215 |
|
216 *skip_none* |
|
217 Does not render an attribute value that is None if set to `True`. |
|
218 |
|
219 *main_related_section* |
|
220 Renders the relations of the entity if set to `True`. |
|
221 |
|
222 A good practice is for you to identify the content of your entity type for which |
|
223 the default rendering does not answer your need so that you can focus on the specific |
|
224 method (from the list above) that needs to be modified. We do not advise you to |
|
225 overwrite ``render_entity`` unless you want a completely different layout. |
|
226 |
|
227 |
|
228 Example of customization and creation |
|
229 ````````````````````````````````````` |
|
230 |
|
231 We'll show you now an example of a ``primary`` view and how to customize it. |
|
232 |
|
233 If you want to change the way a ``BlogEntry`` is displayed, just |
|
234 override the method ``cell_call()`` of the view ``primary`` in |
|
235 ``BlogDemo/views.py``. |
|
236 |
|
237 .. sourcecode:: python |
|
238 |
|
239 from cubicweb.selectors import is_instance |
|
240 from cubicweb.web.views.primary import Primaryview |
|
241 |
|
242 class BlogEntryPrimaryView(PrimaryView): |
|
243 __select__ = PrimaryView.__select__ & is_instance('BlogEntry') |
|
244 |
|
245 def render_entity_attributes(self, entity): |
|
246 self.w(u'<p>published on %s</p>' % |
|
247 entity.publish_date.strftime('%Y-%m-%d')) |
|
248 super(BlogEntryPrimaryView, self).render_entity_attributes(entity) |
|
249 |
|
250 |
|
251 The above source code defines a new primary view for |
|
252 ``BlogEntry``. The `__reid__` class attribute is not repeated there since it |
|
253 is inherited through the `primary.PrimaryView` class. |
|
254 |
|
255 The selector for this view chains the selector of the inherited class |
|
256 with its own specific criterion. |
|
257 |
|
258 The view method ``self.w()`` is used to output data. Here `lines |
|
259 08-09` output HTML for the publication date of the entry. |
|
260 |
|
261 .. image:: ../../images/lax-book_09-new-view-blogentry_en.png |
|
262 :alt: blog entries now look much nicer |
|
263 |
|
264 Let us now improve the primary view of a blog |
|
265 |
|
266 .. sourcecode:: python |
|
267 |
|
268 from logilab.mtconverter import xml_escape |
|
269 from cubicweb.selectors import is_instance, one_line_rset |
|
270 from cubicweb.web.views.primary import Primaryview |
|
271 |
|
272 class BlogPrimaryView(PrimaryView): |
|
273 __regid__ = 'primary' |
|
274 __select__ = PrimaryView.__select__ & is_instance('Blog') |
|
275 rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s' |
|
276 |
|
277 def render_entity_relations(self, entity): |
|
278 rset = self._cw.execute(self.rql, {'b' : entity.eid}) |
|
279 for entry in rset.entities(): |
|
280 self.w(u'<p>%s</p>' % entry.view('inblogcontext')) |
|
281 |
|
282 class BlogEntryInBlogView(EntityView): |
|
283 __regid__ = 'inblogcontext' |
|
284 __select__ = is_instance('BlogEntry') |
|
285 |
|
286 def cell_call(self, row, col): |
|
287 entity = self.cw_rset.get_entity(row, col) |
|
288 self.w(u'<a href="%s" title="%s">%s</a>' % |
|
289 entity.absolute_url(), |
|
290 xml_escape(entity.content[:50]), |
|
291 xml_escape(entity.description)) |
|
292 |
|
293 This happens in two places. First we override the |
|
294 render_entity_relations method of a Blog's primary view. Here we want |
|
295 to display our blog entries in a custom way. |
|
296 |
|
297 At `line 10`, a simple request is made to build a result set with all |
|
298 the entities linked to the current ``Blog`` entity by the relationship |
|
299 ``entry_of``. The part of the framework handling the request knows |
|
300 about the schema and infers that such entities have to be of the |
|
301 ``BlogEntry`` kind and retrieves them (in the prescribed publish_date |
|
302 order). |
|
303 |
|
304 The request returns a selection of data called a result set. Result |
|
305 set objects have an .entities() method returning a generator on |
|
306 requested entities (going transparently through the `ORM` layer). |
|
307 |
|
308 At `line 13` the view 'inblogcontext' is applied to each blog entry to |
|
309 output HTML. (Note that the 'inblogcontext' view is not defined |
|
310 whatsoever in *CubicWeb*. You are absolutely free to define whole view |
|
311 families.) We juste arrange to wrap each blogentry output in a 'p' |
|
312 html element. |
|
313 |
|
314 Next, we define the 'inblogcontext' view. This is NOT a primary view, |
|
315 with its well-defined sections (title, metadata, attribtues, |
|
316 relations/boxes). All a basic view has to define is cell_call. |
|
317 |
|
318 Since views are applied to result sets which can be tables of data, we |
|
319 have to recover the entity from its (row,col)-coordinates (`line |
|
320 20`). Then we can spit some HTML. |
|
321 |
|
322 .. warning:: |
|
323 |
|
324 Be careful: all strings manipulated in *CubicWeb* are actually |
|
325 unicode strings. While web browsers are usually tolerant to |
|
326 incoherent encodings they are being served, we should not abuse |
|
327 it. Hence we have to properly escape our data. The xml_escape() |
|
328 function has to be used to safely fill (X)HTML elements from Python |
|
329 unicode strings. |
|
330 |
|
331 Assuming we added entries to the blog titled `MyLife`, displaying it |
|
332 now allows to read its description and all its entries. |
|
333 |
|
334 .. image:: ../../images/lax-book_10-blog-with-two-entries_en.png |
|
335 :alt: a blog and all its entries |
|
336 |
|
337 Views that may be used to display an entity's attribute or relation |
|
338 ``````````````````````````````````````````````````````````````````` |
|
339 |
|
340 Yoy may easily the display of an attribute or relation by simply configuring the |
|
341 view using one of `primaryview_display_ctrl` or `reledit_ctrl` to use one of the |
|
342 views describled below. For instance: |
|
343 |
|
344 .. sourcecode:: python |
|
345 |
|
346 primaryview_display_ctrl.tag_attribute(('Foo', 'bar'), {'vid': 'attribute'}) |
|
347 |
|
348 |
|
349 .. autoclass:: AttributeView |
|
350 .. autoclass:: URLAttributeView |
|
351 |
|
352 """ |
19 |
353 |
20 __docformat__ = "restructuredtext en" |
354 __docformat__ = "restructuredtext en" |
21 _ = unicode |
355 _ = unicode |
22 |
356 |
23 from warnings import warn |
357 from warnings import warn |