doc/book/en/development/devweb/views.rst
branchstable
changeset 5262 ebd90d2a5639
parent 5222 ed6905d98a5e
equal deleted inserted replaced
5261:829f575dd853 5262:ebd90d2a5639
     6 
     6 
     7 This chapter aims to describe the concept of a `view` used all along
     7 This chapter aims to describe the concept of a `view` used all along
     8 the development of a web application and how it has been implemented
     8 the development of a web application and how it has been implemented
     9 in *CubicWeb*.
     9 in *CubicWeb*.
    10 
    10 
    11 We'll start with a description of the interface providing you with a basic
    11 We'll start with a description of the interface providing you with a
    12 understanding of the available classes and methods, then detail the view
    12 basic understanding of the available classes and methods, then detail
    13 selection principle which makes *CubicWeb* web interface very flexible.
    13 the view selection principle.
    14 
    14 
    15 A `View` is an object applied to another object such as an entity.
    15 A `View` is an object responsible for the rendering of data from the
       
    16 model into an end-user consummable form. They typically churn out an
       
    17 XHTML stream, but there are views concerned with email other non-html
       
    18 outputs.
    16 
    19 
    17 Basic class for views
    20 Basic class for views
    18 ~~~~~~~~~~~~~~~~~~~~~
    21 ~~~~~~~~~~~~~~~~~~~~~
    19 
    22 
    20 Class `View` (`cubicweb.view`)
    23 Class `View` (`cubicweb.view`)
    21 `````````````````````````````````````
    24 ```````````````````````````````
    22 
    25 
    23 This class is an abstraction of a view class, used as a base class for every
    26 This class is an abstraction of a view class, used as a base class for every
    24 renderable object such as views, templates, graphic components, etc.
    27 renderable object such as views, templates, graphic components, etc.
    25 
    28 
    26 A `View` is instantiated to render a result set or part of a result set. `View`
    29 A `View` is instantiated to render a result set or part of a result
    27 subclasses may be parametrized using the following class attributes:
    30 set. `View` subclasses may be parametrized using the following class
       
    31 attributes:
    28 
    32 
    29 * `templatable` indicates if the view may be embedded in a main
    33 * `templatable` indicates if the view may be embedded in a main
    30   template or if it has to be rendered standalone (i.e. XML views must
    34   template or if it has to be rendered standalone (i.e. pure XML views
    31   not be embedded in the main template for HTML pages)
    35   must not be embedded in the main template of HTML pages)
    32 
    36 
    33 * if the view is not templatable, it should set the `content_type`
    37 * if the view is not templatable, it should set the `content_type`
    34   class attribute to the correct MIME type (text/xhtml by default)
    38   class attribute to the correct MIME type (text/xhtml being the
       
    39   default)
    35 
    40 
    36 * the `category` attribute may be used in the interface to regroup
    41 * the `category` attribute may be used in the interface to regroup
    37   related objects together
    42   related view kinds together
    38 
    43 
    39 A view writes to its output stream thanks to its attribute `w` (an
    44 A view writes to its output stream thanks to its attribute `w` (the
    40 `UStreamIO`, except for binary views).
    45 append method of an `UStreamIO`, except for binary views).
    41 
    46 
    42 At instantiation time, the standard `_cw` and `cw_rset` attributes are
    47 At instantiation time, the standard `_cw` and `cw_rset` attributes are
    43 added and the `w` attribute will be set at rendering time.
    48 added and the `w` attribute will be set at rendering time.
    44 
    49 
    45 The basic interface for views is as follows (remember that the result set has a
    50 The basic interface for views is as follows (remember that the result
    46 tabular structure with rows and columns, hence cells):
    51 set has a tabular structure with rows and columns, hence cells):
    47 
    52 
    48 * `render(**context)`, render the view by calling `call` or
    53 * `render(**context)`, render the view by calling `call` or
    49   `cell_call` depending on the given parameters
    54   `cell_call` depending on the context
    50 
    55 
    51 * `call(**kwargs)`, call the view for a complete result set or null
    56 * `call(**kwargs)`, call the view for a complete result set or null
    52   (the default implementation calls `cell_call()` on each cell of the
    57   (the default implementation calls `cell_call()` on each cell of the
    53   result set)
    58   result set)
    54 
    59 
    91         content_type = 'text/xml'
    96         content_type = 'text/xml'
    92         http_cache_manager = MaxAgeHTTPCacheManager
    97         http_cache_manager = MaxAgeHTTPCacheManager
    93         cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
    98         cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
    94 
    99 
    95 
   100 
    96 - Using custom selector
   101 - Using a custom selector
    97 
   102 
    98 .. sourcecode:: python
   103 .. sourcecode:: python
    99 
   104 
   100     class SearchForAssociationView(EntityView):
   105     class SearchForAssociationView(EntityView):
   101         """view called by the edition view when the user asks
   106         """view called by the edition view when the user asks
   105         title = _('search for association')
   110         title = _('search for association')
   106         __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any')
   111         __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any')
   107 
   112 
   108 
   113 
   109 
   114 
   110 Example of view customization and creation
       
   111 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   112 
       
   113 We'll show you now an example of a ``primary`` view and how to customize it.
       
   114 
       
   115 If you want to change the way a ``BlogEntry`` is displayed, just override
       
   116 the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py``:
       
   117 
       
   118 .. sourcecode:: python
       
   119 
       
   120   from cubicweb.selectors import implements
       
   121   from cubicweb.web.views.primary improt Primaryview
       
   122 
       
   123   class BlogEntryPrimaryView(PrimaryView):
       
   124     __select__ = PrimaryView.__select__ & implements('BlogEntry')
       
   125 
       
   126       def render_entity_attributes(self, entity):
       
   127           self.w(u'<p>published on %s</p>' %
       
   128                  entity.publish_date.strftime('%Y-%m-%d'))
       
   129           super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
       
   130 
       
   131 The above source code defines a new primary view for
       
   132 ``BlogEntry``. The `id` class attribute is not repeated there since it
       
   133 is inherited through the `primary.PrimaryView` class.
       
   134 
       
   135 The selector for this view chains the selector of the inherited class
       
   136 with its own specific criterion.
       
   137 
       
   138 The view method ``self.w()`` is used to output data. Here `lines
       
   139 08-09` output HTML for the publication date of the entry.
       
   140 
       
   141 .. image:: ../../images/lax-book.09-new-view-blogentry.en.png
       
   142    :alt: blog entries now look much nicer
       
   143 
       
   144 Let us now improve the primary view of a blog
       
   145 
       
   146 .. sourcecode:: python
       
   147 
       
   148  from logilab.mtconverter import xml_escape
       
   149  from cubicweb.selectors import implements, one_line_rset
       
   150  from cubicweb.web.views.primary import Primaryview
       
   151 
       
   152  class BlogPrimaryView(PrimaryView):
       
   153      __regid__ = 'primary'
       
   154      __select__ = PrimaryView.__select__ & implements('Blog')
       
   155      rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s'
       
   156 
       
   157      def render_entity_relations(self, entity):
       
   158          rset = self._cw.execute(self.rql, {'b' : entity.eid})
       
   159          for entry in rset.entities():
       
   160              self.w(u'<p>%s</p>' % entry.view('inblogcontext'))
       
   161 
       
   162  class BlogEntryInBlogView(EntityView):
       
   163      __regid__ = 'inblogcontext'
       
   164      __select__ = implements('BlogEntry')
       
   165 
       
   166      def cell_call(self, row, col):
       
   167          entity = self.cw_rset.get_entity(row, col)
       
   168          self.w(u'<a href="%s" title="%s">%s</a>' %
       
   169                 entity.absolute_url(),
       
   170                 xml_escape(entity.content[:50]),
       
   171                 xml_escape(entity.description))
       
   172 
       
   173 This happens in two places. First we override the
       
   174 render_entity_relations method of a Blog's primary view. Here we want
       
   175 to display our blog entries in a custom way.
       
   176 
       
   177 At `line 10`, a simple request is made to build a result set with all
       
   178 the entities linked to the current ``Blog`` entity by the relationship
       
   179 ``entry_of``. The part of the framework handling the request knows
       
   180 about the schema and infers that such entities have to be of the
       
   181 ``BlogEntry`` kind and retrieves them (in the prescribed publish_date
       
   182 order).
       
   183 
       
   184 The request returns a selection of data called a result set. Result
       
   185 set objects have an .entities() method returning a generator on
       
   186 requested entities (going transparently through the `ORM` layer).
       
   187 
       
   188 At `line 13` the view 'inblogcontext' is applied to each blog entry to
       
   189 output HTML. (Note that the 'inblogcontext' view is not defined
       
   190 whatsoever in *CubicWeb*. You are absolutely free to define whole view
       
   191 families.) We juste arrange to wrap each blogentry output in a 'p'
       
   192 html element.
       
   193 
       
   194 Next, we define the 'inblogcontext' view. This is NOT a primary view,
       
   195 with its well-defined sections (title, metadata, attribtues,
       
   196 relations/boxes). All a basic view has to define is cell_call.
       
   197 
       
   198 Since views are applied to result sets which can be tables of data, we
       
   199 have to recover the entity from its (row,col)-coordinates (`line
       
   200 20`). Then we can spit some HTML.
       
   201 
       
   202 But careful: all strings manipulated in *CubicWeb* are actually
       
   203 unicode strings. While web browsers are usually tolerant to incoherent
       
   204 encodings they are being served, we should not abuse it. Hence we have
       
   205 to properly escape our data. The xml_escape() function has to be used
       
   206 to safely fill (X)HTML elements from Python unicode strings.
       
   207 
       
   208 
       
   209 **This is to be compared to interfaces and protocols in object-oriented
       
   210 languages. Applying a given view called 'a_view' to all the entities
       
   211 of a result set only requires to have for each entity of this result set,
       
   212 an available view called 'a_view' which accepts the entity.**
       
   213 
       
   214 **Instead of merely using type based dispatch, we do predicate dispatch
       
   215 which is quite more powerful.**
       
   216 
       
   217 Assuming we added entries to the blog titled `MyLife`, displaying it
       
   218 now allows to read its description and all its entries.
       
   219 
       
   220 .. image:: ../../images/lax-book.10-blog-with-two-entries.en.png
       
   221    :alt: a blog and all its entries
       
   222 
       
   223 **Before we move forward, remember that the selection/view principle is
       
   224 at the core of *CubicWeb*. Everywhere in the engine, data is requested
       
   225 using the RQL language, then HTML/XML/text/PNG is output by applying a
       
   226 view to the result set returned by the query. That is where most of the
       
   227 flexibility comes from.**
       
   228 
       
   229 [WRITE ME]
       
   230 
       
   231 * implementing interfaces, calendar for blog entries
       
   232 * show that a calendar view can export data to ical
       
   233 
       
   234 We will implement the `cubicweb.interfaces.ICalendarable` interfaces on
       
   235 entities.BlogEntry and apply the OneMonthCalendar and iCalendar views
       
   236 to result sets like "Any E WHERE E is BlogEntry"
       
   237 
       
   238 * create view "blogentry table" with title, publish_date, category
       
   239 
       
   240 We will show that by default the view that displays
       
   241 "Any E,D,C WHERE E publish_date D, E category C" is the table view.
       
   242 Of course, the same can be obtained by calling
       
   243 self.wview('table',rset)
       
   244 
       
   245 * in view blog, select blogentries and apply view "blogentry table"
       
   246 * demo ajax by filtering blogentry table on category
       
   247 
       
   248 we did the same with 'primary', but with tables we can turn on filters
       
   249 and show that ajax comes for free.
       
   250 [FILLME]
       
   251 
       
   252 
   115 
   253 XML views, binaries views...
   116 XML views, binaries views...
   254 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   117 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   255 
   118 
   256 For views generating other formats than HTML (an image generated dynamically
   119 For views generating other formats than HTML (an image generated dynamically
   257 for example), and which can not simply be included in the HTML page generated
   120 for example), and which can not simply be included in the HTML page generated
   258 by the main template (see above), you have to:
   121 by the main template (see above), you have to:
   259 
   122 
   260 * set the attribute `templatable` of the class to `False`
   123 * set the attribute `templatable` of the class to `False`
   261 * set, through the attribute `content_type` of the class, the MIME type generated
   124 * set, through the attribute `content_type` of the class, the MIME
   262   by the view to `application/octet-stream`
   125   type generated by the view to `application/octet-stream` or any
       
   126   relevant and more specialised mime type
   263 
   127 
   264 For views dedicated to binary content creation (like dynamically generated
   128 For views dedicated to binary content creation (like dynamically generated
   265 images), we have to set the attribute `binary` of the class to `True` (which
   129 images), we have to set the attribute `binary` of the class to `True` (which
   266 implies that `templatable == False`, so that the attribute `w` of the view could be
   130 implies that `templatable == False`, so that the attribute `w` of the view could be
   267 replaced by a binary flow instead of unicode).
   131 replaced by a binary flow instead of unicode).