doc/book/en/development/devweb/views.rst
branchstable
changeset 3258 6536ee4f37f7
parent 2544 282261b26774
child 3293 69c0ba095536
equal deleted inserted replaced
3257:0d953f0b41c4 3258:6536ee4f37f7
    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
    68 Here are some of the subclasses of `View` defined in `cubicweb.common.view`
    74 Here are some of the subclasses of `View` defined in `cubicweb.common.view`
    69 that are more concrete as they relate to data rendering within the application:
    75 that are more concrete as they relate to data rendering within the application:
    70 
    76 
    71 * `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
    77 * `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
    72 * `StartupView`, start view that does not require a result set to apply to
    78 * `StartupView`, start view that does not require a result set to apply to
    73 * `AnyRsetView`, view applied to any result set
    79 * `AnyRsetView`, view applicable to any result set
    74 * `EmptyRsetView`, view applied to an empty result set
    80 * `EmptyRsetView`, view applicable to an empty result set
    75 
    81 
    76 
    82 
    77 Examples of views class
    83 Examples of views class
    78 -----------------------
    84 -----------------------
    79 
    85 
   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