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