doc/book/en/development/devweb/views.rst
changeset 5274 16461f675734
parent 5272 f7d2df59231a
parent 5273 c4caef6f09c9
child 5297 cc747dcef851
equal deleted inserted replaced
5272:f7d2df59231a 5274:16461f675734
     1 
       
     2 .. _Views:
       
     3 
       
     4 Views
       
     5 -----
       
     6 
       
     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
       
     9 in *CubicWeb*.
       
    10 
       
    11 We'll start with a description of the interface providing you with a basic
       
    12 understanding of the available classes and methods, then detail the view
       
    13 selection principle which makes *CubicWeb* web interface very flexible.
       
    14 
       
    15 A `View` is an object applied to another object such as an entity.
       
    16 
       
    17 Basic class for views
       
    18 ~~~~~~~~~~~~~~~~~~~~~
       
    19 
       
    20 Class `View` (`cubicweb.view`)
       
    21 `````````````````````````````````````
       
    22 
       
    23 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.
       
    25 
       
    26 A `View` is instantiated to render a result set or part of a result set. `View`
       
    27 subclasses may be parametrized using the following class attributes:
       
    28 
       
    29 * `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
       
    31   not be embedded in the main template for HTML pages)
       
    32 
       
    33 * if the view is not templatable, it should set the `content_type`
       
    34   class attribute to the correct MIME type (text/xhtml by default)
       
    35 
       
    36 * the `category` attribute may be used in the interface to regroup
       
    37   related objects together
       
    38 
       
    39 A view writes to its output stream thanks to its attribute `w` (an
       
    40 `UStreamIO`, except for binary views).
       
    41 
       
    42 At instantiation time, the standard `_cw` and `cw_rset` attributes are
       
    43 added and the `w` attribute will be set at rendering time.
       
    44 
       
    45 The basic interface for views is as follows (remember that the result set has a
       
    46 tabular structure with rows and columns, hence cells):
       
    47 
       
    48 * `render(**context)`, render the view by calling `call` or
       
    49   `cell_call` depending on the given parameters
       
    50 
       
    51 * `call(**kwargs)`, call the view for a complete result set or null
       
    52   (the default implementation calls `cell_call()` on each cell of the
       
    53   result set)
       
    54 
       
    55 * `cell_call(row, col, **kwargs)`, call the view for a given cell of a
       
    56   result set (`row` and `col` being integers used to access the cell)
       
    57 
       
    58 * `url()`, returns the URL enabling us to get the view with the current
       
    59   result set
       
    60 
       
    61 * `wview(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of
       
    62   identifier `__vid` on the given result set. It is possible to give a
       
    63   fallback view identifier that will be used if the requested view is
       
    64   not applicable to the result set.
       
    65 
       
    66 * `html_headers()`, returns a list of HTML headers to be set by the
       
    67   main template
       
    68 
       
    69 * `page_title()`, returns the title to use in the HTML header `title`
       
    70 
       
    71 Other basic view classes
       
    72 ````````````````````````
       
    73 Here are some of the subclasses of `View` defined in `cubicweb.common.view`
       
    74 that are more concrete as they relate to data rendering within the application:
       
    75 
       
    76 * `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
       
    77 * `StartupView`, start view that does not require a result set to apply to
       
    78 * `AnyRsetView`, view applicable to any result set
       
    79 
       
    80 Examples of views class
       
    81 -----------------------
       
    82 
       
    83 - Using `templatable`, `content_type` and HTTP cache configuration
       
    84 
       
    85 .. sourcecode:: python
       
    86 
       
    87     class RSSView(XMLView):
       
    88         __regid__ = 'rss'
       
    89         title = _('rss')
       
    90         templatable = False
       
    91         content_type = 'text/xml'
       
    92         http_cache_manager = MaxAgeHTTPCacheManager
       
    93         cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
       
    94 
       
    95 
       
    96 - Using custom selector
       
    97 
       
    98 .. sourcecode:: python
       
    99 
       
   100     class SearchForAssociationView(EntityView):
       
   101         """view called by the edition view when the user asks
       
   102         to search for something to link to the edited eid
       
   103         """
       
   104         __regid__ = 'search-associate'
       
   105         title = _('search for association')
       
   106         __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any')
       
   107 
       
   108 
       
   109 
       
   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 
       
   253 XML views, binaries views...
       
   254 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   255 
       
   256 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
       
   258 by the main template (see above), you have to:
       
   259 
       
   260 * set the attribute `templatable` of the class to `False`
       
   261 * set, through the attribute `content_type` of the class, the MIME type generated
       
   262   by the view to `application/octet-stream`
       
   263 
       
   264 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
       
   266 implies that `templatable == False`, so that the attribute `w` of the view could be
       
   267 replaced by a binary flow instead of unicode).