doc/book/en/development/devweb/views.rst
brancholdstable
changeset 5422 0865e1e90674
parent 4985 02b52bf9f5f8
parent 5421 8167de96c523
child 5424 8ecbcbff9777
equal deleted inserted replaced
4985:02b52bf9f5f8 5422:0865e1e90674
     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 classes and methods available, 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 embeded in a main
       
    30       template or if it has to be rendered standalone (i.e. XML views
       
    31       must not be embeded in the main template for HTML pages)
       
    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)
       
    34     * the `category` attribute may be used in the interface to regroup related
       
    35       objects together
       
    36 
       
    37 At instantiation time, the standard `_cw` and `cw_rset` attributes are
       
    38 added and the `w` attribute will be set at rendering time.
       
    39 
       
    40 A view writes to its output stream thanks to its attribute `w` (an
       
    41 `UStreamIO`, except for binary views).
       
    42 
       
    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):
       
    45 
       
    46 * `render(**context)`, render the view by calling `call` or
       
    47   `cell_call` depending on the given parameters
       
    48 
       
    49 * `call(**kwargs)`, call the view for a complete result set or null
       
    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 
       
    56 * `url()`, returns the URL enabling us to get the view with the current
       
    57   result set
       
    58 
       
    59 * `view(__vid, rset, __fallback_vid=None, **kwargs)`, call the view of identifier
       
    60   `__vid` on the given result set. It is possible to give a view identifier
       
    61   of fallback that will be used if the view requested is not applicable to the
       
    62   result set. This is actually defined on the AppObject class.
       
    63 
       
    64 * `wview(__vid, rset, __fallback_vid=None, **kwargs)`, similar to `view` except
       
    65   the flow is automatically passed in the parameters
       
    66 
       
    67 * `html_headers()`, returns a list of HTML headers to set by the main template
       
    68 
       
    69 * `page_title()`, returns the title to use in the HTML header `title`
       
    70 
       
    71 
       
    72 Other basic view classes
       
    73 ````````````````````````
       
    74 Here are some of the subclasses of `View` defined in `cubicweb.common.view`
       
    75 that are more concrete as they relate to data rendering within the application:
       
    76 
       
    77 * `EntityView`, view applying to lines or cell containing an entity (e.g. an eid)
       
    78 * `StartupView`, start view that does not require a result set to apply to
       
    79 * `AnyRsetView`, view applicable to any result set
       
    80 * `EmptyRsetView`, view applicable to an empty result set
       
    81 
       
    82 
       
    83 Examples of views class
       
    84 -----------------------
       
    85 
       
    86 - Using `templatable`, `content_type` and HTTP cache configuration
       
    87 
       
    88 .. sourcecode:: python
       
    89 
       
    90     class RSSView(XMLView):
       
    91         __regid__ = 'rss'
       
    92         title = _('rss')
       
    93         templatable = False
       
    94         content_type = 'text/xml'
       
    95         http_cache_manager = MaxAgeHTTPCacheManager
       
    96         cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
       
    97 
       
    98 
       
    99 - Using custom selector
       
   100 
       
   101 .. sourcecode:: python
       
   102 
       
   103     class SearchForAssociationView(EntityView):
       
   104         """view called by the edition view when the user asks
       
   105         to search for something to link to the edited eid
       
   106         """
       
   107         __regid__ = 'search-associate'
       
   108         title = _('search for association')
       
   109         __select__ = one_line_rset() & match_search_state('linksearch') & implements('Any')
       
   110 
       
   111 
       
   112 Example of view customization and creation
       
   113 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   114 
       
   115 We'll show you now an example of a ``primary`` view and how to customize it.
       
   116 
       
   117 If you want to change the way a ``BlogEntry`` is displayed, just override
       
   118 the method ``cell_call()`` of the view ``primary`` in ``BlogDemo/views.py``:
       
   119 
       
   120 .. sourcecode:: python
       
   121 
       
   122   from cubicweb.selectors import implements
       
   123   from cubicweb.web.views.primary improt Primaryview
       
   124 
       
   125   class BlogEntryPrimaryView(PrimaryView):
       
   126     __select__ = PrimaryView.__select__ & implements('BlogEntry')
       
   127 
       
   128       def render_entity_attributes(self, entity):
       
   129           self.w(u'<p>published on %s</p>' %
       
   130                  entity.publish_date.strftime('%Y-%m-%d'))
       
   131           super(BlogEntryPrimaryView, self).render_entity_attributes(entity)
       
   132 
       
   133 The above source code defines a new primary view for
       
   134 ``BlogEntry``. The `id` class attribute is not repeated there since it
       
   135 is inherited through the `primary.PrimaryView` class.
       
   136 
       
   137 The selector for this view chains the selector of the inherited class
       
   138 with its own specific criterion.
       
   139 
       
   140 The view method ``self.w()`` is used to output data. Here `lines
       
   141 08-09` output HTML for the publication date of the entry.
       
   142 
       
   143 .. image:: ../../images/lax-book.09-new-view-blogentry.en.png
       
   144    :alt: blog entries now look much nicer
       
   145 
       
   146 Let us now improve the primary view of a blog
       
   147 
       
   148 .. sourcecode:: python
       
   149 
       
   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):
       
   155      __regid__ = 'primary'
       
   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._cw.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      __regid__ = 'inblogcontext'
       
   166      __select__ = implements('BlogEntry')
       
   167 
       
   168      def cell_call(self, row, col):
       
   169          entity = self.cw_rset.get_entity(row, col)
       
   170          self.w(u'<a href="%s" title="%s">%s</a>' %
       
   171                 entity.absolute_url(),
       
   172                 xml_escape(entity.content[:50]),
       
   173                 xml_escape(entity.description))
       
   174 
       
   175 This happens in two places. First we override the
       
   176 render_entity_relations method of a Blog's primary view. Here we want
       
   177 to display our blog entries in a custom way.
       
   178 
       
   179 At `line 10`, a simple request is made to build a result set with all
       
   180 the entities linked to the current ``Blog`` entity by the relationship
       
   181 ``entry_of``. The part of the framework handling the request knows
       
   182 about the schema and infers that such entities have to be of the
       
   183 ``BlogEntry`` kind and retrieves them (in the prescribed publish_date
       
   184 order).
       
   185 
       
   186 The request returns a selection of data called a result set. Result
       
   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 
       
   210 
       
   211 **This is to be compared to interfaces and protocols in object-oriented
       
   212 languages. Applying a given view called 'a_view' to all the entities
       
   213 of a result set only requires to have for each entity of this result set,
       
   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 is quite more powerful.**
       
   218 
       
   219 Assuming we added entries to the blog titled `MyLife`, displaying it
       
   220 now allows to read its description and all its entries.
       
   221 
       
   222 .. image:: ../../images/lax-book.10-blog-with-two-entries.en.png
       
   223    :alt: a blog and all its entries
       
   224 
       
   225 **Before we move forward, remember that the selection/view principle is
       
   226 at the core of *CubicWeb*. Everywhere in the engine, data is requested
       
   227 using the RQL language, then HTML/XML/text/PNG is output by applying a
       
   228 view to the result set returned by the query. That is where most of the
       
   229 flexibility comes from.**
       
   230 
       
   231 [WRITE ME]
       
   232 
       
   233 * implementing interfaces, calendar for blog entries
       
   234 * show that a calendar view can export data to ical
       
   235 
       
   236 We will implement the `cubicweb.interfaces.ICalendarable` interfaces on
       
   237 entities.BlogEntry and apply the OneMonthCalendar and iCalendar views
       
   238 to result sets like "Any E WHERE E is BlogEntry"
       
   239 
       
   240 * create view "blogentry table" with title, publish_date, category
       
   241 
       
   242 We will show that by default the view that displays
       
   243 "Any E,D,C WHERE E publish_date D, E category C" is the table view.
       
   244 Of course, the same can be obtained by calling
       
   245 self.wview('table',rset)
       
   246 
       
   247 * in view blog, select blogentries and apply view "blogentry table"
       
   248 * demo ajax by filtering blogentry table on category
       
   249 
       
   250 we did the same with 'primary', but with tables we can turn on filters
       
   251 and show that ajax comes for free.
       
   252 [FILLME]
       
   253 
       
   254 
       
   255 XML views, binaries views...
       
   256 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
       
   257 
       
   258 For views generating other formats than HTML (an image generated dynamically
       
   259 for example), and which can not simply be included in the HTML page generated
       
   260 by the main template (see above), you have to:
       
   261 
       
   262 * set the attribute `templatable` of the class to `False`
       
   263 * set, through the attribute `content_type` of the class, the MIME type generated
       
   264   by the view to `application/octet-stream`
       
   265 
       
   266 For views dedicated to binary content creation (like dynamically generated
       
   267 images), we have to set the attribute `binary` of the class to `True` (which
       
   268 implies that `templatable == False`, so that the attribute `w` of the view could be
       
   269 replaced by a binary flow instead of unicode).