# HG changeset patch # User Sylvain Thénault # Date 1316711638 -7200 # Node ID ea13609380332db969becc113b3952594800b56a # Parent 6f32f142e2da27d3a3649a51720652eff8cb0232 [doc] backport primary view documentation into the code, also document [URL]AttributeView diff -r 6f32f142e2da -r ea1360938033 doc/book/en/devweb/views/primary.rst --- a/doc/book/en/devweb/views/primary.rst Thu Sep 22 19:02:39 2011 +0200 +++ b/doc/book/en/devweb/views/primary.rst Thu Sep 22 19:13:58 2011 +0200 @@ -10,322 +10,7 @@ It is automatically selected on a one line result set containing an entity. -This view is supposed to render a maximum of informations about the -entity. - It lives in the :mod:`cubicweb.web.views.primary` module. -.. _primary_view_layout: - -Layout -`````` - -The primary view has the following layout. - -.. image:: ../../images/primaryview_template.png - -.. _primary_view_configuration: - -Primary view configuration -`````````````````````````` - -If you want to customize the primary view of an entity, overriding the primary -view class may not be necessary. For simple adjustments (attributes or relations -display locations and styles), a much simpler way is to use uicfg. - -Attributes/relations display location -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In the primary view, there are three sections where attributes and -relations can be displayed (represented in pink in the image above): - -* 'attributes' -* 'relations' -* 'sideboxes' - -**Attributes** can only be displayed in the attributes section (default - behavior). They can also be hidden. By default, attributes of type `Password` - and `Bytes` are hidden. - -For instance, to hide the ``title`` attribute of the ``Blog`` entity: - -.. sourcecode:: python - - from cubicweb.web import uicfg - uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden') - -**Relations** can be either displayed in one of the three sections or hidden. - -For relations, there are two methods: - -* ``tag_object_of`` for modifying the primary view of the object -* ``tag_subject_of`` for modifying the primary view of the subject - -These two methods take two arguments: - -* a triplet ``(subject, relation_name, object)``, where subject or object can be replaced with ``'*'`` -* the section name or ``hidden`` - -.. sourcecode:: python - - pv_section = uicfg.primaryview_section - # hide every relation `entry_of` in the `Blog` primary view - pv_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden') - - # display `entry_of` relations in the `relations` - # section in the `BlogEntry` primary view - pv_section.tag_subject_of(('BlogEntry', 'entry_of', '*'), 'relations') - - -Display content -^^^^^^^^^^^^^^^ - -You can use ``primaryview_display_ctrl`` to customize the display of attributes -or relations. Values of ``primaryview_display_ctrl`` are dictionaries. - - -Common keys for attributes and relations are: - -* ``vid``: specifies the regid of the view for displaying the attribute or the relation. - - If ``vid`` is not specified, the default value depends on the section: - * ``attributes`` section: 'reledit' view - * ``relations`` section: 'autolimited' view - * ``sideboxes`` section: 'sidebox' view - -* ``order``: int used to control order within a section. When not specified, - automatically set according to order in which tags are added. - -* ``label``: label for the relations section or side box - -* ``showlabel``: boolean telling whether the label is displayed - -.. sourcecode:: python - - # let us remind the schema of a blog entry - class BlogEntry(EntityType): - title = String(required=True, fulltextindexed=True, maxsize=256) - publish_date = Date(default='TODAY') - content = String(required=True, fulltextindexed=True) - entry_of = SubjectRelation('Blog', cardinality='?*') - - # now, we want to show attributes - # with an order different from that in the schema definition - view_ctrl = uicfg.primaryview_display_ctrl - for index, attr in enumerate('title', 'content', 'publish_date'): - view_ctrl.tag_attribute(('BlogEntry', attr), {'order': index}) - -By default, relations displayed in the 'relations' section are being displayed by -the 'autolimited' view. This view will use comma separated values, or list view -and/or limit your rset if there is too much items in it (and generate the "view -all" link in this case). - -You can control this view by setting the following values in the -`primaryview_display_ctrl` relation tag: - -* `limit`, maximum number of entities to display. The value of the - 'navigation.related-limit' cwproperty is used by default (which is 8 by default). - If None, no limit. - -* `use_list_limit`, number of entities until which they should be display as a list - (eg using the 'list' view). Below that limit, the 'csv' view is used. If None, - display using 'csv' anyway. - -* `subvid`, the subview identifier (eg view that should be used of each item in the - list) - -Notice you can also use the `filter` key to set up a callback taking the related -result set as argument and returning it filtered, to do some arbitrary filtering -that can't be done using rql for instance. - - - -.. sourcecode:: python - - pv_section = uicfg.primaryview_section - # in `CWUser` primary view, display `created_by` - # relations in relations section - pv_section.tag_object_of(('*', 'created_by', 'CWUser'), 'relations') - - # display this relation as a list, sets the label, - # limit the number of results and filters on comments - def filter_comment(rset): - return rset.filtered_rset(lambda x: x.e_schema == 'Comment') - pv_ctrl = uicfg.primaryview_display_ctrl - pv_ctrl.tag_object_of(('*', 'created_by', 'CWUser'), - {'vid': 'list', 'label': _('latest comment(s):'), - 'limit': True, - 'filter': filter_comment}) - -.. warning:: with the ``primaryview_display_ctrl`` rtag, the subject or the - object of the relation is ignored for respectively ``tag_object_of`` or - ``tag_subject_of``. To avoid warnings during execution, they should be set to - ``'*'``. - -Rendering methods and attributes -```````````````````````````````` - -The basic layout of a primary view is as in the -:ref:`primary_view_layout` section. This layout is actually drawn by -the `render_entity` method. - -The methods you may want to modify while customizing a ``PrimaryView`` -are: - -*render_entity_title(self, entity)* - Renders the entity title, by default using entity's :meth:`dc_title()` method. - -*render_entity_attributes(self, entity)* - Renders all attributes and relations in the 'attributes' section . The - :attr:`skip_none` attribute controls the display of `None` valued attributes. - -*render_entity_relations(self, entity)* - Renders all relations in the 'relations' section. - -*render_side_boxes(self, entity, boxes)* - Renders side boxes on the right side of the content. This will generate a box - for each relation in the 'sidebox' section, as well as explicit box - appobjects selectable in this context. - -The placement of relations in the relations section or in side boxes -can be controlled through the :ref:`primary_view_configuration` mechanism. - -*content_navigation_components(self, context)* - This method is applicable only for entity type implementing the interface - `IPrevNext`. This interface is for entities which can be linked to a previous - and/or next entity. This method will render the navigation links between - entities of this type, either at the top or at the bottom of the page - given the context (navcontent{top|bottom}). - -Also, please note that by setting the following attributes in your -subclass, you can already customize some of the rendering: - -*show_attr_label* - Renders the attribute label next to the attribute value if set to `True`. - Otherwise, does only display the attribute value. - -*show_rel_label* - Renders the relation label next to the relation value if set to `True`. - Otherwise, does only display the relation value. - -*skip_none* - Does not render an attribute value that is None if set to `True`. - -*main_related_section* - Renders the relations of the entity if set to `True`. - -A good practice is for you to identify the content of your entity type for which -the default rendering does not answer your need so that you can focus on the specific -method (from the list above) that needs to be modified. We do not advise you to -overwrite ``render_entity`` unless you want a completely different layout. - - -Example of customization and creation -````````````````````````````````````` - -We'll show you now an example of a ``primary`` view and how to customize it. - -If you want to change the way a ``BlogEntry`` is displayed, just -override the method ``cell_call()`` of the view ``primary`` in -``BlogDemo/views.py``. - -.. sourcecode:: python - - from cubicweb.selectors import is_instance - from cubicweb.web.views.primary import Primaryview - - class BlogEntryPrimaryView(PrimaryView): - __select__ = PrimaryView.__select__ & is_instance('BlogEntry') - - def render_entity_attributes(self, entity): - self.w(u'

published on %s

' % - entity.publish_date.strftime('%Y-%m-%d')) - super(BlogEntryPrimaryView, self).render_entity_attributes(entity) - - -The above source code defines a new primary view for -``BlogEntry``. The `__reid__` class attribute is not repeated there since it -is inherited through the `primary.PrimaryView` class. - -The selector for this view chains the selector of the inherited class -with its own specific criterion. - -The view method ``self.w()`` is used to output data. Here `lines -08-09` output HTML for the publication date of the entry. - -.. image:: ../../images/lax-book_09-new-view-blogentry_en.png - :alt: blog entries now look much nicer - -Let us now improve the primary view of a blog - -.. sourcecode:: python - - from logilab.mtconverter import xml_escape - from cubicweb.selectors import is_instance, one_line_rset - from cubicweb.web.views.primary import Primaryview - - class BlogPrimaryView(PrimaryView): - __regid__ = 'primary' - __select__ = PrimaryView.__select__ & is_instance('Blog') - rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s' - - def render_entity_relations(self, entity): - rset = self._cw.execute(self.rql, {'b' : entity.eid}) - for entry in rset.entities(): - self.w(u'

%s

' % entry.view('inblogcontext')) - - class BlogEntryInBlogView(EntityView): - __regid__ = 'inblogcontext' - __select__ = is_instance('BlogEntry') - - def cell_call(self, row, col): - entity = self.cw_rset.get_entity(row, col) - self.w(u'%s' % - entity.absolute_url(), - xml_escape(entity.content[:50]), - xml_escape(entity.description)) - -This happens in two places. First we override the -render_entity_relations method of a Blog's primary view. Here we want -to display our blog entries in a custom way. - -At `line 10`, a simple request is made to build a result set with all -the entities linked to the current ``Blog`` entity by the relationship -``entry_of``. The part of the framework handling the request knows -about the schema and infers that such entities have to be of the -``BlogEntry`` kind and retrieves them (in the prescribed publish_date -order). - -The request returns a selection of data called a result set. Result -set objects have an .entities() method returning a generator on -requested entities (going transparently through the `ORM` layer). - -At `line 13` the view 'inblogcontext' is applied to each blog entry to -output HTML. (Note that the 'inblogcontext' view is not defined -whatsoever in *CubicWeb*. You are absolutely free to define whole view -families.) We juste arrange to wrap each blogentry output in a 'p' -html element. - -Next, we define the 'inblogcontext' view. This is NOT a primary view, -with its well-defined sections (title, metadata, attribtues, -relations/boxes). All a basic view has to define is cell_call. - -Since views are applied to result sets which can be tables of data, we -have to recover the entity from its (row,col)-coordinates (`line -20`). Then we can spit some HTML. - -.. warning:: - - Be careful: all strings manipulated in *CubicWeb* are actually - unicode strings. While web browsers are usually tolerant to - incoherent encodings they are being served, we should not abuse - it. Hence we have to properly escape our data. The xml_escape() - function has to be used to safely fill (X)HTML elements from Python - unicode strings. - -Assuming we added entries to the blog titled `MyLife`, displaying it -now allows to read its description and all its entries. - -.. image:: ../../images/lax-book_10-blog-with-two-entries_en.png - :alt: a blog and all its entries +.. automodule:: cubicweb.web.views.primary \ No newline at end of file diff -r 6f32f142e2da -r ea1360938033 web/views/primary.py --- a/web/views/primary.py Thu Sep 22 19:02:39 2011 +0200 +++ b/web/views/primary.py Thu Sep 22 19:13:58 2011 +0200 @@ -15,7 +15,341 @@ # # You should have received a copy of the GNU Lesser General Public License along # with CubicWeb. If not, see . -"""The default primary view""" +""" +The *primary* view is supposed to render a maximum of informations about the +entity. + +.. _primary_view_layout: + +Layout +`````` + +The primary view has the following layout. + +.. image:: ../../images/primaryview_template.png + +.. _primary_view_configuration: + +Primary view configuration +`````````````````````````` + +If you want to customize the primary view of an entity, overriding the primary +view class may not be necessary. For simple adjustments (attributes or relations +display locations and styles), a much simpler way is to use uicfg. + +Attributes/relations display location +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In the primary view, there are three sections where attributes and +relations can be displayed (represented in pink in the image above): + +* 'attributes' +* 'relations' +* 'sideboxes' + +**Attributes** can only be displayed in the attributes section (default + behavior). They can also be hidden. By default, attributes of type `Password` + and `Bytes` are hidden. + +For instance, to hide the ``title`` attribute of the ``Blog`` entity: + +.. sourcecode:: python + + from cubicweb.web import uicfg + uicfg.primaryview_section.tag_attribute(('Blog', 'title'), 'hidden') + +**Relations** can be either displayed in one of the three sections or hidden. + +For relations, there are two methods: + +* ``tag_object_of`` for modifying the primary view of the object +* ``tag_subject_of`` for modifying the primary view of the subject + +These two methods take two arguments: + +* a triplet ``(subject, relation_name, object)``, where subject or object can be replaced with ``'*'`` +* the section name or ``hidden`` + +.. sourcecode:: python + + pv_section = uicfg.primaryview_section + # hide every relation `entry_of` in the `Blog` primary view + pv_section.tag_object_of(('*', 'entry_of', 'Blog'), 'hidden') + + # display `entry_of` relations in the `relations` + # section in the `BlogEntry` primary view + pv_section.tag_subject_of(('BlogEntry', 'entry_of', '*'), 'relations') + + +Display content +^^^^^^^^^^^^^^^ + +You can use ``primaryview_display_ctrl`` to customize the display of attributes +or relations. Values of ``primaryview_display_ctrl`` are dictionaries. + + +Common keys for attributes and relations are: + +* ``vid``: specifies the regid of the view for displaying the attribute or the relation. + + If ``vid`` is not specified, the default value depends on the section: + * ``attributes`` section: 'reledit' view + * ``relations`` section: 'autolimited' view + * ``sideboxes`` section: 'sidebox' view + +* ``order``: int used to control order within a section. When not specified, + automatically set according to order in which tags are added. + +* ``label``: label for the relations section or side box + +* ``showlabel``: boolean telling whether the label is displayed + +.. sourcecode:: python + + # let us remind the schema of a blog entry + class BlogEntry(EntityType): + title = String(required=True, fulltextindexed=True, maxsize=256) + publish_date = Date(default='TODAY') + content = String(required=True, fulltextindexed=True) + entry_of = SubjectRelation('Blog', cardinality='?*') + + # now, we want to show attributes + # with an order different from that in the schema definition + view_ctrl = uicfg.primaryview_display_ctrl + for index, attr in enumerate('title', 'content', 'publish_date'): + view_ctrl.tag_attribute(('BlogEntry', attr), {'order': index}) + +By default, relations displayed in the 'relations' section are being displayed by +the 'autolimited' view. This view will use comma separated values, or list view +and/or limit your rset if there is too much items in it (and generate the "view +all" link in this case). + +You can control this view by setting the following values in the +`primaryview_display_ctrl` relation tag: + +* `limit`, maximum number of entities to display. The value of the + 'navigation.related-limit' cwproperty is used by default (which is 8 by default). + If None, no limit. + +* `use_list_limit`, number of entities until which they should be display as a list + (eg using the 'list' view). Below that limit, the 'csv' view is used. If None, + display using 'csv' anyway. + +* `subvid`, the subview identifier (eg view that should be used of each item in the + list) + +Notice you can also use the `filter` key to set up a callback taking the related +result set as argument and returning it filtered, to do some arbitrary filtering +that can't be done using rql for instance. + + + + +.. sourcecode:: python + + pv_section = uicfg.primaryview_section + # in `CWUser` primary view, display `created_by` + # relations in relations section + pv_section.tag_object_of(('*', 'created_by', 'CWUser'), 'relations') + + # display this relation as a list, sets the label, + # limit the number of results and filters on comments + def filter_comment(rset): + return rset.filtered_rset(lambda x: x.e_schema == 'Comment') + pv_ctrl = uicfg.primaryview_display_ctrl + pv_ctrl.tag_object_of(('*', 'created_by', 'CWUser'), + {'vid': 'list', 'label': _('latest comment(s):'), + 'limit': True, + 'filter': filter_comment}) + +.. warning:: with the ``primaryview_display_ctrl`` rtag, the subject or the + object of the relation is ignored for respectively ``tag_object_of`` or + ``tag_subject_of``. To avoid warnings during execution, they should be set to + ``'*'``. + +Rendering methods and attributes +```````````````````````````````` + +The basic layout of a primary view is as in the +:ref:`primary_view_layout` section. This layout is actually drawn by +the `render_entity` method. + +The methods you may want to modify while customizing a ``PrimaryView`` +are: + +*render_entity_title(self, entity)* + Renders the entity title, by default using entity's :meth:`dc_title()` method. + +*render_entity_attributes(self, entity)* + Renders all attributes and relations in the 'attributes' section . The + :attr:`skip_none` attribute controls the display of `None` valued attributes. + +*render_entity_relations(self, entity)* + Renders all relations in the 'relations' section. + +*render_side_boxes(self, entity, boxes)* + Renders side boxes on the right side of the content. This will generate a box + for each relation in the 'sidebox' section, as well as explicit box + appobjects selectable in this context. + +The placement of relations in the relations section or in side boxes +can be controlled through the :ref:`primary_view_configuration` mechanism. + +*content_navigation_components(self, context)* + This method is applicable only for entity type implementing the interface + `IPrevNext`. This interface is for entities which can be linked to a previous + and/or next entity. This method will render the navigation links between + entities of this type, either at the top or at the bottom of the page + given the context (navcontent{top|bottom}). + +Also, please note that by setting the following attributes in your +subclass, you can already customize some of the rendering: + +*show_attr_label* + Renders the attribute label next to the attribute value if set to `True`. + Otherwise, does only display the attribute value. + +*show_rel_label* + Renders the relation label next to the relation value if set to `True`. + Otherwise, does only display the relation value. + +*skip_none* + Does not render an attribute value that is None if set to `True`. + +*main_related_section* + Renders the relations of the entity if set to `True`. + +A good practice is for you to identify the content of your entity type for which +the default rendering does not answer your need so that you can focus on the specific +method (from the list above) that needs to be modified. We do not advise you to +overwrite ``render_entity`` unless you want a completely different layout. + + +Example of customization and creation +````````````````````````````````````` + +We'll show you now an example of a ``primary`` view and how to customize it. + +If you want to change the way a ``BlogEntry`` is displayed, just +override the method ``cell_call()`` of the view ``primary`` in +``BlogDemo/views.py``. + +.. sourcecode:: python + + from cubicweb.selectors import is_instance + from cubicweb.web.views.primary import Primaryview + + class BlogEntryPrimaryView(PrimaryView): + __select__ = PrimaryView.__select__ & is_instance('BlogEntry') + + def render_entity_attributes(self, entity): + self.w(u'

published on %s

' % + entity.publish_date.strftime('%Y-%m-%d')) + super(BlogEntryPrimaryView, self).render_entity_attributes(entity) + + +The above source code defines a new primary view for +``BlogEntry``. The `__reid__` class attribute is not repeated there since it +is inherited through the `primary.PrimaryView` class. + +The selector for this view chains the selector of the inherited class +with its own specific criterion. + +The view method ``self.w()`` is used to output data. Here `lines +08-09` output HTML for the publication date of the entry. + +.. image:: ../../images/lax-book_09-new-view-blogentry_en.png + :alt: blog entries now look much nicer + +Let us now improve the primary view of a blog + +.. sourcecode:: python + + from logilab.mtconverter import xml_escape + from cubicweb.selectors import is_instance, one_line_rset + from cubicweb.web.views.primary import Primaryview + + class BlogPrimaryView(PrimaryView): + __regid__ = 'primary' + __select__ = PrimaryView.__select__ & is_instance('Blog') + rql = 'Any BE ORDERBY D DESC WHERE BE entry_of B, BE publish_date D, B eid %(b)s' + + def render_entity_relations(self, entity): + rset = self._cw.execute(self.rql, {'b' : entity.eid}) + for entry in rset.entities(): + self.w(u'

%s

' % entry.view('inblogcontext')) + + class BlogEntryInBlogView(EntityView): + __regid__ = 'inblogcontext' + __select__ = is_instance('BlogEntry') + + def cell_call(self, row, col): + entity = self.cw_rset.get_entity(row, col) + self.w(u'%s' % + entity.absolute_url(), + xml_escape(entity.content[:50]), + xml_escape(entity.description)) + +This happens in two places. First we override the +render_entity_relations method of a Blog's primary view. Here we want +to display our blog entries in a custom way. + +At `line 10`, a simple request is made to build a result set with all +the entities linked to the current ``Blog`` entity by the relationship +``entry_of``. The part of the framework handling the request knows +about the schema and infers that such entities have to be of the +``BlogEntry`` kind and retrieves them (in the prescribed publish_date +order). + +The request returns a selection of data called a result set. Result +set objects have an .entities() method returning a generator on +requested entities (going transparently through the `ORM` layer). + +At `line 13` the view 'inblogcontext' is applied to each blog entry to +output HTML. (Note that the 'inblogcontext' view is not defined +whatsoever in *CubicWeb*. You are absolutely free to define whole view +families.) We juste arrange to wrap each blogentry output in a 'p' +html element. + +Next, we define the 'inblogcontext' view. This is NOT a primary view, +with its well-defined sections (title, metadata, attribtues, +relations/boxes). All a basic view has to define is cell_call. + +Since views are applied to result sets which can be tables of data, we +have to recover the entity from its (row,col)-coordinates (`line +20`). Then we can spit some HTML. + +.. warning:: + + Be careful: all strings manipulated in *CubicWeb* are actually + unicode strings. While web browsers are usually tolerant to + incoherent encodings they are being served, we should not abuse + it. Hence we have to properly escape our data. The xml_escape() + function has to be used to safely fill (X)HTML elements from Python + unicode strings. + +Assuming we added entries to the blog titled `MyLife`, displaying it +now allows to read its description and all its entries. + +.. image:: ../../images/lax-book_10-blog-with-two-entries_en.png + :alt: a blog and all its entries + +Views that may be used to display an entity's attribute or relation +``````````````````````````````````````````````````````````````````` + +Yoy may easily the display of an attribute or relation by simply configuring the +view using one of `primaryview_display_ctrl` or `reledit_ctrl` to use one of the +views describled below. For instance: + +.. sourcecode:: python + + primaryview_display_ctrl.tag_attribute(('Foo', 'bar'), {'vid': 'attribute'}) + + +.. autoclass:: AttributeView +.. autoclass:: URLAttributeView + +""" __docformat__ = "restructuredtext en" _ = unicode @@ -305,6 +639,8 @@ It will try to display nicely according to the number of items in the result set. + + XXX include me in the doc """ __regid__ = 'autolimited' @@ -347,8 +683,10 @@ class URLAttributeView(EntityView): - """use this view for attributes whose value is an url and that you want - to display as clickable link + """:__regid__: *urlattr* + + This view will wrap an attribute value (hence expect a string) into an '' + HTML tag to display a clickable link. """ __regid__ = 'urlattr' __select__ = EntityView.__select__ & match_kwargs('rtype') @@ -359,12 +697,10 @@ self.w(u'%s' % (url, url)) class AttributeView(EntityView): - """use this view on an entity as an alternative to more sophisticated - views such as reledit. + """:__regid__: *attribute* - Ex. usage: - - uicfg.primaryview_display_ctrl.tag_attribute(('Foo', 'bar'), {'vid': 'attribute'}) + This view is generally used to disable the *reledit* feature. It works on + both relations and attributes. """ __regid__ = 'attribute' __select__ = EntityView.__select__ & match_kwargs('rtype') @@ -382,12 +718,14 @@ class ToolbarLayout(component.Layout): + # XXX include me in the doc __select__ = match_context('ctxtoolbar') def render(self, w): if self.init_rendering(): self.cw_extra_kwargs['view'].render_body(w) + ## default primary ui configuration ########################################### _pvs = uicfg.primaryview_section