merge, split baseviews (new csvexport, xmlrss and editviews modules) tls-sprint
authorsylvain.thenault@logilab.fr
Wed, 18 Feb 2009 19:26:48 +0100
branchtls-sprint
changeset 824 a5e6acffde30
parent 823 cb8ccbef8fa5
child 825 037be200ff47
merge, split baseviews (new csvexport, xmlrss and editviews modules)
web/test/test_views.py
web/views/actions.py
web/views/baseforms.py
web/views/baseviews.py
web/views/boxes.py
web/views/csvexport.py
web/views/editviews.py
web/views/xbel.py
web/views/xmlrss.py
--- a/web/test/test_views.py	Wed Feb 18 19:19:57 2009 +0100
+++ b/web/test/test_views.py	Wed Feb 18 19:26:48 2009 +0100
@@ -37,11 +37,11 @@
     def test_manual_tests(self):
         rset = self.execute('Any P,F,S WHERE P is EUser, P firstname F, P surname S')
         self.view('table', rset, template=None, displayfilter=True, displaycols=[0,2])
-        rset = self.execute('Any P,F,S LIMIT 1 WHERE P is EUser, P firstname F, P surname S')
-        rset.req.form['rtype'] = 'firstname'
-        self.view('editrelation', rset, template=None)
-        rset.req.form['rtype'] = 'use_email'
-        self.view('editrelation', rset, template=None)
+#        rset = self.execute('Any P,F,S LIMIT 1 WHERE P is EUser, P firstname F, P surname S')
+#        rset.req.form['rtype'] = 'firstname'
+#        self.view('editrelation', rset, template=None)
+#        rset.req.form['rtype'] = 'use_email'
+#        self.view('editrelation', rset, template=None)
         
 
     def test_sortable_js_added(self):
--- a/web/views/actions.py	Wed Feb 18 19:19:57 2009 +0100
+++ b/web/views/actions.py	Wed Feb 18 19:26:48 2009 +0100
@@ -14,8 +14,7 @@
     has_editable_relation, has_permission, has_add_permission,
     )
 from cubicweb.web.action import Action
-from cubicweb.web.views import linksearch_select_url
-from cubicweb.web.views.baseviews import vid_from_rset
+from cubicweb.web.views import linksearch_select_url, vid_from_rset
 
 _ = unicode
 
--- a/web/views/baseforms.py	Wed Feb 18 19:19:57 2009 +0100
+++ b/web/views/baseforms.py	Wed Feb 18 19:26:48 2009 +0100
@@ -639,7 +639,6 @@
     __select__ = (match_kwargs('ptype', 'peid', 'rtype')
                   & specified_etype_implements('Any'))
     
-    
     EDITION_BODY = u'''\
 <div id="div-%(parenteid)s-%(rtype)s-%(eid)s" class="inlinedform">
  <div class="iformBody">
@@ -670,8 +669,6 @@
             self.w(self.req._('no such entity type %s') % etype)
             return
         self.edit_form(entity, ptype, peid, rtype, role, **kwargs)
-    
-    
 
 
 class InlineEntityEditionForm(InlineFormMixIn, EditionForm):
@@ -728,7 +725,6 @@
         ctx['count'] = entity.row + 1
         return ctx
     
-    
 
 class CopyEditionForm(EditionForm):
     id = 'copy'
@@ -773,7 +769,6 @@
         return self.req._('element copied')
        
     
-
 class TableEditForm(FormMixIn, EntityView):
     id = 'muledit'
     title = _('multiple edit')
@@ -874,126 +869,10 @@
                                   'widget': wobj.edit_render(entity)})
         w(u'</tr>')
         return '\n'.join(html)
-        
-
-class UnrelatedDivs(EntityView):
-    id = 'unrelateddivs'
-    __select__ = match_form_params('relation')
-
-    @property
-    def limit(self):
-        if self.req.form.get('__force_display'):
-            return None
-        return self.req.property_value('navigation.related-limit') + 1
-
-    def cell_call(self, row, col):
-        entity = self.entity(row, col)
-        relname, target = self.req.form.get('relation').rsplit('_', 1)
-        rschema = self.schema.rschema(relname)
-        hidden = 'hidden' in self.req.form
-        is_cell = 'is_cell' in self.req.form
-        self.w(self.build_unrelated_select_div(entity, rschema, target,
-                                               is_cell=is_cell, hidden=hidden))
-
-    def build_unrelated_select_div(self, entity, rschema, target,
-                                   is_cell=False, hidden=True):
-        options = []
-        divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid)
-        selectid = 'select%s_%s_%s' % (rschema.type, target, entity.eid)
-        if rschema.symetric or target == 'subject':
-            targettypes = rschema.objects(entity.e_schema)
-            etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
-        else:
-            targettypes = rschema.subjects(entity.e_schema)
-            etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
-        etypes = cut(etypes, self.req.property_value('navigation.short-line-size'))
-        options.append('<option>%s %s</option>' % (self.req._('select a'), etypes))
-        options += self._get_select_options(entity, rschema, target)
-        options += self._get_search_options(entity, rschema, target, targettypes)
-        if 'Basket' in self.schema: # XXX
-            options += self._get_basket_options(entity, rschema, target, targettypes)
-        relname, target = self.req.form.get('relation').rsplit('_', 1)
-        return u"""\
-<div class="%s" id="%s">
-  <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
-    %s
-  </select>
-</div>
-""" % (hidden and 'hidden' or '', divid, selectid, html_escape(dumps(entity.eid)),
-       is_cell and 'true' or 'null', relname, '\n'.join(options))
-
-    def _get_select_options(self, entity, rschema, target):
-        """add options to search among all entities of each possible type"""
-        options = []
-        eid = entity.eid
-        pending_inserts = self.req.get_pending_inserts(eid)
-        rtype = rschema.type
-        for eview, reid in entity.vocabulary(rschema, target, self.limit):
-            if reid is None:
-                options.append('<option class="separator">-- %s --</option>' % html_escape(eview))
-            else:
-                optionid = relation_id(eid, rtype, target, reid)
-                if optionid not in pending_inserts:
-                    # prefix option's id with letters to make valid XHTML wise
-                    options.append('<option id="id%s" value="%s">%s</option>' %
-                                   (optionid, reid, html_escape(eview)))
-        return options
-
-    def _get_search_options(self, entity, rschema, target, targettypes):
-        """add options to search among all entities of each possible type"""
-        options = []
-        _ = self.req._
-        for eschema in targettypes:
-            mode = '%s:%s:%s:%s' % (target, entity.eid, rschema.type, eschema)
-            url = self.build_url(entity.rest_path(), vid='search-associate',
-                                 __mode=mode)
-            options.append((eschema.display_name(self.req),
-                            '<option value="%s">%s %s</option>' % (
-                html_escape(url), _('Search for'), eschema.display_name(self.req))))
-        return [o for l, o in sorted(options)]
-
-    def _get_basket_options(self, entity, rschema, target, targettypes):
-        options = []
-        rtype = rschema.type
-        _ = self.req._
-        for basketeid, basketname in self._get_basket_links(self.req.user.eid,
-                                                            target, targettypes):
-            optionid = relation_id(entity.eid, rtype, target, basketeid)
-            options.append('<option id="%s" value="%s">%s %s</option>' % (
-                optionid, basketeid, _('link to each item in'), html_escape(basketname)))
-        return options
-
-    def _get_basket_links(self, ueid, target, targettypes):
-        targettypes = set(targettypes)
-        for basketeid, basketname, elements in self._get_basket_info(ueid):
-            baskettypes = elements.column_types(0)
-            # if every elements in the basket can be attached to the
-            # edited entity
-            if baskettypes & targettypes:
-                yield basketeid, basketname
-            
-    def _get_basket_info(self, ueid):
-        basketref = []
-        basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
-        basketresultset = self.req.execute(basketrql, {'x': ueid}, 'x')
-        for result in basketresultset:
-            basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
-            rset = self.req.execute(basketitemsrql, {'x': result[0]}, 'x')
-            basketref.append((result[0], result[1], rset))
-        return basketref
 
 
-class ComboboxView(EntityView):
-    """the view used in combobox (unrelated entities)
+# XXX bw compat
 
-    THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
-    """
-    id = 'combobox'
-    title = None
-    
-    def cell_call(self, row, col):
-        """the combo-box view for an entity: same as text out of context view
-        by default
-        """
-        self.wview('textoutofcontext', self.rset, row=row, col=col)
-
+from logilab.common.deprecation import class_moved
+from cubicweb.web.views import editviews
+ComboboxView = class_moved(editviews.ComboboxView)
--- a/web/views/baseviews.py	Wed Feb 18 19:19:57 2009 +0100
+++ b/web/views/baseviews.py	Wed Feb 18 19:26:48 2009 +0100
@@ -14,21 +14,15 @@
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
-from time import timezone
 
 from rql import nodes
 
-from logilab.common.decorators import cached
-from logilab.mtconverter import TransformError, html_escape, xml_escape
+from logilab.mtconverter import TransformError, html_escape
 
-from cubicweb import Unauthorized, NoSelectableObject, typed_eid
-from cubicweb.selectors import (yes, empty_rset, nonempty_rset, one_line_rset,
-                                non_final_entity, match_search_state,  match_form_params)
+from cubicweb import Unauthorized, NoSelectableObject
+from cubicweb.selectors import yes, empty_rset
 from cubicweb.view import EntityView, AnyRsetView, View
-from cubicweb.common.uilib import (cut, printable_value,  UnicodeCSVWriter,
-                                   ajax_replace_url, rql_for_eid, simple_sgml_tag)
-from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
-from cubicweb.web.views import vid_from_rset, linksearch_select_url
+from cubicweb.common.uilib import cut, printable_value
 
 _ = unicode
 
@@ -87,19 +81,6 @@
             return
         self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime))
 
-
-class EditableFinalView(FinalView):
-    """same as FinalView but enables inplace-edition when possible"""
-    id = 'editable-final'
-                
-    def cell_call(self, row, col, props=None, displaytime=False):
-        etype = self.rset.description[row][col]
-        value = self.rset.rows[row][col]
-        entity, rtype = self.rset.related_entity(row, col)
-        if entity is not None:
-            self.w(entity.view('reledit', rtype=rtype))
-        else:
-            super(EditableFinalView, self).cell_call(row, col, props, displaytime)
         
 PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity',
                          'owned_by', 'created_by', 
@@ -171,9 +152,8 @@
         
     def iter_attributes(self, entity):
         for rschema, targetschema in entity.e_schema.attribute_definitions():
-            attr = rschema.type
-            if attr in self.skip_attrs:
-               continue
+            if rschema.type in self.skip_attrs:
+                continue
             yield rschema, targetschema
             
     def iter_relations(self, entity):
@@ -298,36 +278,6 @@
         label = display_name(self.req, rschema.type, role)
         self.field(label, value, show_label=show_label, w=self.w, tr=False)
 
-
-class SideBoxView(EntityView):
-    """side box usually displaying some related entities in a primary view"""
-    id = 'sidebox'
-    
-    def call(self, boxclass='sideBox', title=u''):
-        """display a list of entities by calling their <item_vid> view
-        """
-        if title:
-            self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % title)
-        self.w(u'<div class="%s"><div class="sideBoxBody">' % boxclass)
-        # if not too much entities, show them all in a list
-        maxrelated = self.req.property_value('navigation.related-limit')
-        if self.rset.rowcount <= maxrelated:
-            if len(self.rset) == 1:
-                self.wview('incontext', self.rset, row=0)
-            elif 1 < len(self.rset) < 5:
-                self.wview('csv', self.rset)
-            else:
-                self.wview('simplelist', self.rset)
-        # else show links to display related entities
-        else:
-            self.rset.limit(maxrelated)
-            rql = self.rset.printable_rql(encoded=False)
-            self.wview('simplelist', self.rset)
-            self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
-                                               self.req._('see them all')))
-        self.w(u'</div>\n</div>\n')
-
-
  
 class SecondaryView(EntityView):
     id = 'secondary'
@@ -341,6 +291,7 @@
         self.w(u'&nbsp;')
         self.wview('oneline', self.rset, row=row, col=col)
 
+
 class OneLineView(EntityView):
     id = 'oneline'
     title = _('oneline') 
@@ -353,6 +304,7 @@
         self.w(html_escape(self.view('text', self.rset, row=row, col=col)))
         self.w(u'</a>')
 
+
 class TextView(EntityView):
     """the simplest text view for an entity"""
     id = 'text'
@@ -415,7 +367,8 @@
     def cell_call(self, row, col):
         entity = self.entity(row, col)
         self.w(entity.dc_title())
-        
+
+
 class OutOfContextTextView(InContextTextView):
     id = 'textoutofcontext'
 
@@ -443,8 +396,9 @@
         self.w(u'<a href="%s">' % self.entity(row, col).absolute_url())
         self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col)))
         self.w(u'</a>')
+
             
-# list and table related views ################################################
+# list views ##################################################################
     
 class ListView(EntityView):
     id = 'list'
@@ -545,387 +499,7 @@
     def cell_call(self, row, col):
         self.wview('incontext', self.rset, row=row, col=col)
 
-
-# xml and xml/rss views #######################################################
-    
-class XmlView(EntityView):
-    id = 'xml'
-    title = _('xml')
-    templatable = False
-    content_type = 'text/xml'
-    xml_root = 'rset'
-    item_vid = 'xmlitem'
-    
-    def cell_call(self, row, col):
-        self.wview(self.item_vid, self.rset, row=row, col=col)
-        
-    def call(self):
-        """display a list of entities by calling their <item_vid> view"""
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
-        self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.rset)))
-        for i in xrange(self.rset.rowcount):
-            self.cell_call(i, 0)
-        self.w(u'</%s>\n' % self.xml_root)
-
-
-class XmlItemView(EntityView):
-    id = 'xmlitem'
-
-    def cell_call(self, row, col):
-        """ element as an item for an xml feed """
-        entity = self.complete_entity(row, col)
-        self.w(u'<%s>\n' % (entity.e_schema))
-        for rschema, attrschema in entity.e_schema.attribute_definitions():
-            attr = rschema.type
-            try:
-                value = entity[attr]
-            except KeyError:
-                # Bytes
-                continue
-            if value is not None:
-                if attrschema == 'Bytes':
-                    from base64 import b64encode
-                    value = '<![CDATA[%s]]>' % b64encode(value.getvalue())
-                elif isinstance(value, basestring):
-                    value = xml_escape(value)
-                self.w(u'  <%s>%s</%s>\n' % (attr, value, attr))
-        self.w(u'</%s>\n' % (entity.e_schema))
-
-
-    
-class XMLRsetView(AnyRsetView):
-    """dumps xml in CSV"""
-    id = 'rsetxml'
-    title = _('xml export')
-    templatable = False
-    content_type = 'text/xml'
-    xml_root = 'rset'
-        
-    def call(self):
-        w = self.w
-        rset, descr = self.rset, self.rset.description
-        eschema = self.schema.eschema
-        labels = self.columns_labels(False)
-        w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
-        w(u'<%s query="%s">\n' % (self.xml_root, html_escape(rset.printable_rql())))
-        for rowindex, row in enumerate(self.rset):
-            w(u' <row>\n')
-            for colindex, val in enumerate(row):
-                etype = descr[rowindex][colindex]
-                tag = labels[colindex]
-                attrs = {}
-                if '(' in tag:
-                    attrs['expr'] = tag
-                    tag = 'funccall'
-                if val is not None and not eschema(etype).is_final():
-                    attrs['eid'] = val
-                    # csvrow.append(val) # val is eid in that case
-                    val = self.view('textincontext', rset,
-                                    row=rowindex, col=colindex)
-                else:
-                    val = self.view('final', rset, displaytime=True,
-                                    row=rowindex, col=colindex, format='text/plain')
-                w(simple_sgml_tag(tag, val, **attrs))
-            w(u' </row>\n')
-        w(u'</%s>\n' % self.xml_root)
-    
-
-class RssView(XmlView):
-    id = 'rss'
-    title = _('rss')
-    templatable = False
-    content_type = 'text/xml'
-    http_cache_manager = MaxAgeHTTPCacheManager
-    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
-    
-    def cell_call(self, row, col):
-        self.wview('rssitem', self.rset, row=row, col=col)
-        
-    def call(self):
-        """display a list of entities by calling their <item_vid> view"""
-        req = self.req
-        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
-        self.w(u'''<rdf:RDF
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:dc="http://purl.org/dc/elements/1.1/"
- xmlns="http://purl.org/rss/1.0/"
->''')
-        self.w(u'  <channel rdf:about="%s">\n' % html_escape(req.url()))
-        self.w(u'    <title>%s RSS Feed</title>\n' % html_escape(self.page_title()))
-        self.w(u'    <description>%s</description>\n' % html_escape(req.form.get('vtitle', '')))
-        params = req.form.copy()
-        params.pop('vid', None)
-        self.w(u'    <link>%s</link>\n' % html_escape(self.build_url(**params)))
-        self.w(u'    <items>\n')
-        self.w(u'      <rdf:Seq>\n')
-        for entity in self.rset.entities():
-            self.w(u'      <rdf:li resource="%s" />\n' % html_escape(entity.absolute_url()))
-        self.w(u'      </rdf:Seq>\n')
-        self.w(u'    </items>\n')
-        self.w(u'  </channel>\n')
-        for i in xrange(self.rset.rowcount):
-            self.cell_call(i, 0)
-        self.w(u'</rdf:RDF>')
-
-
-class RssItemView(EntityView):
-    id = 'rssitem'
-    date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
-
-    def cell_call(self, row, col):
-        entity = self.complete_entity(row, col)
-        self.w(u'<item rdf:about="%s">\n' % html_escape(entity.absolute_url()))
-        self._marker('title', entity.dc_long_title())
-        self._marker('link', entity.absolute_url())
-        self._marker('description', entity.dc_description())
-        self._marker('dc:date', entity.dc_date(self.date_format))
-        if entity.creator:
-            self.w(u'<author>')
-            self._marker('name', entity.creator.name())
-            email = entity.creator.get_email()
-            if email:
-                self._marker('email', email)
-            self.w(u'</author>')
-        self.w(u'</item>\n')
-        
-    def _marker(self, marker, value):
-        if value:
-            self.w(u'  <%s>%s</%s>\n' % (marker, html_escape(value), marker))
-
-
-class CSVMixIn(object):
-    """mixin class for CSV views"""
-    templatable = False
-    content_type = "text/comma-separated-values"    
-    binary = True # avoid unicode assertion
-    csv_params = {'dialect': 'excel',
-                  'quotechar': '"',
-                  'delimiter': ';',
-                  'lineterminator': '\n'}
-    
-    def set_request_content_type(self):
-        """overriden to set a .csv filename"""
-        self.req.set_content_type(self.content_type, filename='cubicwebexport.csv')
-            
-    def csvwriter(self, **kwargs):
-        params = self.csv_params.copy()
-        params.update(kwargs)
-        return UnicodeCSVWriter(self.w, self.req.encoding, **params)
-
-    
-class CSVRsetView(CSVMixIn, AnyRsetView):
-    """dumps rset in CSV"""
-    id = 'csvexport'
-    title = _('csv export')
-        
-    def call(self):
-        writer = self.csvwriter()
-        writer.writerow(self.columns_labels())
-        rset, descr = self.rset, self.rset.description
-        eschema = self.schema.eschema
-        for rowindex, row in enumerate(rset):
-            csvrow = []
-            for colindex, val in enumerate(row):
-                etype = descr[rowindex][colindex]
-                if val is not None and not eschema(etype).is_final():
-                    # csvrow.append(val) # val is eid in that case
-                    content = self.view('textincontext', rset, 
-                                        row=rowindex, col=colindex)
-                else:
-                    content = self.view('final', rset,
-                                        displaytime=True, format='text/plain',
-                                        row=rowindex, col=colindex)
-                csvrow.append(content)                    
-            writer.writerow(csvrow)
-    
-    
-class CSVEntityView(CSVMixIn, EntityView):
-    """dumps rset's entities (with full set of attributes) in CSV"""
-    id = 'ecsvexport'
-    title = _('csv entities export')
-
-    def call(self):
-        """
-        the generated CSV file will have a table per entity type
-        found in the resultset. ('table' here only means empty
-        lines separation between table contents)
-        """
-        req = self.req
-        rows_by_type = {}
-        writer = self.csvwriter()
-        rowdef_by_type = {}
-        for index in xrange(len(self.rset)):
-            entity = self.complete_entity(index)
-            if entity.e_schema not in rows_by_type:
-                rowdef_by_type[entity.e_schema] = [rs for rs, at in entity.e_schema.attribute_definitions()
-                                                   if at != 'Bytes']
-                rows_by_type[entity.e_schema] = [[display_name(req, rschema.type)
-                                                  for rschema in rowdef_by_type[entity.e_schema]]]
-            rows = rows_by_type[entity.e_schema]
-            rows.append([entity.printable_value(rs.type, format='text/plain')
-                         for rs in rowdef_by_type[entity.e_schema]])
-        for etype, rows in rows_by_type.items():
-            writer.writerows(rows)
-            # use two empty lines as separator
-            writer.writerows([[], []])        
-    
-
-## Work in progress ###########################################################
-
-class SearchForAssociationView(EntityView):
-    """view called by the edition view when the user asks
-    to search for something to link to the edited eid
-    """
-    id = 'search-associate'
-    __select__ = (one_line_rset() & match_search_state('linksearch')
-                  & non_final_entity())
-    
-    title = _('search for association')
-
-    def cell_call(self, row, col):
-        rset, vid, divid, paginate = self.filter_box_context_info()
-        self.w(u'<div id="%s">' % divid)
-        self.pagination(self.req, rset, w=self.w)
-        self.wview(vid, rset, 'noresult')
-        self.w(u'</div>')
-
-    @cached
-    def filter_box_context_info(self):
-        entity = self.entity(0, 0)
-        role, eid, rtype, etype = self.req.search_state[1]
-        assert entity.eid == typed_eid(eid)
-        # the default behaviour is to fetch all unrelated entities and display
-        # them. Use fetch_order and not fetch_unrelated_order as sort method
-        # since the latter is mainly there to select relevant items in the combo
-        # box, it doesn't give interesting result in this context
-        rql = entity.unrelated_rql(rtype, etype, role,
-                                   ordermethod='fetch_order',
-                                   vocabconstraints=False)
-        rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
-        #vid = vid_from_rset(self.req, rset, self.schema)
-        return rset, 'list', "search-associate-content", True
-
-
-class OutOfContextSearch(EntityView):
-    id = 'outofcontext-search'
-    def cell_call(self, row, col):
-        entity = self.entity(row, col)
-        erset = entity.as_rset()
-        if self.req.match_search_state(erset):
-            self.w(u'<a href="%s" title="%s">%s</a>&nbsp;<a href="%s" title="%s">[...]</a>' % (
-                html_escape(linksearch_select_url(self.req, erset)),
-                self.req._('select this entity'),
-                html_escape(entity.view('textoutofcontext')),
-                html_escape(entity.absolute_url(vid='primary')),
-                self.req._('view detail for this entity')))
-        else:
-            entity.view('outofcontext', w=self.w)
-            
-            
-class EditRelationView(EntityView):
-    """Note: This is work in progress
-
-    This view is part of the edition view refactoring.
-    It is still too big and cluttered with strange logic, but it's a start
-
-    The main idea is to be able to call an edition view for a specific
-    relation. For example :
-       self.wview('editrelation', person_rset, rtype='firstname')
-       self.wview('editrelation', person_rset, rtype='works_for')
-    """
-    id = 'editrelation'
-
-    __select__ = match_form_params('rtype')
-    
-    # TODO: inlineview, multiple edit, (widget view ?)
-    def cell_call(self, row, col, rtype=None, role='subject', targettype=None,
-                 showlabel=True):
-        self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
-        entity = self.entity(row, col)
-        rtype = self.req.form.get('rtype', rtype)
-        showlabel = self.req.form.get('showlabel', showlabel)
-        assert rtype is not None, "rtype is mandatory for 'edirelation' view"
-        targettype = self.req.form.get('targettype', targettype)
-        role = self.req.form.get('role', role)
-        category = entity.rtags.get_category(rtype, targettype, role)
-        if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final():
-            if hasattr(entity, '%s_format' % rtype):
-                formatwdg = entity.get_widget('%s_format' % rtype, role)
-                self.w(formatwdg.edit_render(entity))
-                self.w(u'<br/>')
-            wdg = entity.get_widget(rtype, role)
-            if showlabel:
-                self.w(u'%s' % wdg.render_label(entity))
-            self.w(u'%s %s %s' %
-                   (wdg.render_error(entity), wdg.edit_render(entity),
-                    wdg.render_help(entity),))
-        else:
-            self._render_generic_relation(entity, rtype, role)
-
-    def _render_generic_relation(self, entity, relname, role):
-        text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role))
-        # pending operations
-        operations = self.req.get_pending_operations(entity, relname, role)
-        if operations['insert'] or operations['delete'] or 'unfold' in self.req.form:
-            self.w(u'<h3>%s</h3>' % text)
-            self._render_generic_relation_form(operations, entity, relname, role)
-        else:
-            divid = "%s%sreledit" % (relname, role)
-            url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation',
-                                   {'unfold' : 1, 'relname' : relname, 'role' : role})
-            self.w(u'<a href="%s">%s</a>' % (url, text))
-            self.w(u'<div id="%s"></div>' % divid)
-        
-
-    def _build_opvalue(self, entity, relname, target, role):
-        if role == 'subject':
-            return '%s:%s:%s' % (entity.eid, relname, target)
-        else:
-            return '%s:%s:%s' % (target, relname, entity.eid)
-        
-    
-    def _render_generic_relation_form(self, operations, entity, relname, role):
-        rqlexec = self.req.execute
-        for optype, targets in operations.items():
-            for target in targets:
-                self._render_pending(optype, entity, relname, target, role)
-                opvalue = self._build_opvalue(entity, relname, target, role)
-                self.w(u'<a href="javascript: addPendingDelete(\'%s\', %s);">-</a> '
-                       % (opvalue, entity.eid))
-                rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x')
-                self.wview('oneline', rset)
-        # now, unrelated ones
-        self._render_unrelated_selection(entity, relname, role)
-
-    def _render_pending(self, optype, entity, relname, target, role):
-        opvalue = self._build_opvalue(entity, relname, target, role)
-        self.w(u'<input type="hidden" name="__%s" value="%s" />'
-               % (optype, opvalue))
-        if optype == 'insert':
-            checktext = '-'
-        else:
-            checktext = '+'
-        rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x')
-        self.w(u"""[<a href="javascript: cancelPending%s('%s:%s:%s')">%s</a>"""
-               % (optype.capitalize(), relname, target, role,
-                  self.view('oneline', rset)))
-
-    def _render_unrelated_selection(self, entity, relname, role):
-        rschema = self.schema.rschema(relname)
-        if role == 'subject':
-            targettypes = rschema.objects(entity.e_schema)
-        else:
-            targettypes = rschema.subjects(entity.e_schema)
-        self.w(u'<select onselect="addPendingInsert(this.selected.value);">')
-        for targettype in targettypes:
-            unrelated = entity.unrelated(relname, targettype, role) # XXX limit
-            for rowindex, row in enumerate(unrelated):
-                teid = row[0]
-                opvalue = self._build_opvalue(entity, relname, teid, role)
-                self.w(u'<option name="__insert" value="%s>%s</option>'
-                       % (opvalue, self.view('text', unrelated, row=rowindex)))
-        self.w(u'</select>')
-
+# context specific views ######################################################
 
 class TextSearchResultView(EntityView):
     """this view is used to display full-text search
@@ -962,16 +536,28 @@
                 self.w(value.replace('\n', '<br/>'))            
 
 
-class TooltipView(OneLineView):
+class TooltipView(EntityView):
     """A entity view used in a tooltip"""
     id = 'tooltip'
-    title = None # don't display in possible views
     def cell_call(self, row, col):
         self.wview('oneline', self.rset, row=row, col=col)
 
+
+# XXX bw compat
+
+from logilab.common.deprecation import class_moved
+
 try:
     from cubicweb.web.views.tableview import TableView
-    from logilab.common.deprecation import class_moved
     TableView = class_moved(TableView)
 except ImportError:
     pass # gae has no tableview module (yet)
+
+from cubicweb.web.views import boxes, xmlrss
+SideBoxView = class_moved(boxes.SideBoxView)
+XmlView = class_moved(xmlrss.XmlView)
+XmlItemView = class_moved(xmlrss.XmlItemView)
+XmlRsetView = class_moved(xmlrss.XmlRsetView)
+RssView = class_moved(xmlrss.RssView)
+RssItemView = class_moved(xmlrss.RssItemView)
+            
--- a/web/views/boxes.py	Wed Feb 18 19:19:57 2009 +0100
+++ b/web/views/boxes.py	Wed Feb 18 19:26:48 2009 +0100
@@ -20,6 +20,7 @@
 from cubicweb.selectors import (any_rset, appobject_selectable,
                                 match_user_groups, non_final_entity)
 from cubicweb.web.htmlwidgets import BoxWidget, BoxMenu, BoxHtml, RawBoxItem
+from cubicweb.view import EntityView
 from cubicweb.web.box import BoxTemplate
 
 _ = unicode
@@ -223,3 +224,31 @@
         if not box.is_empty():
             box.render(self.w)
 
+# helper classes ##############################################################
+
+class SideBoxView(EntityView):
+    """helper view class to display some entities in a sidebox"""
+    id = 'sidebox'
+    
+    def call(self, boxclass='sideBox', title=u''):
+        """display a list of entities by calling their <item_vid> view"""
+        if title:
+            self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % title)
+        self.w(u'<div class="%s"><div class="sideBoxBody">' % boxclass)
+        # if not too much entities, show them all in a list
+        maxrelated = self.req.property_value('navigation.related-limit')
+        if self.rset.rowcount <= maxrelated:
+            if len(self.rset) == 1:
+                self.wview('incontext', self.rset, row=0)
+            elif 1 < len(self.rset) < 5:
+                self.wview('csv', self.rset)
+            else:
+                self.wview('simplelist', self.rset)
+        # else show links to display related entities
+        else:
+            self.rset.limit(maxrelated)
+            rql = self.rset.printable_rql(encoded=False)
+            self.wview('simplelist', self.rset)
+            self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
+                                               self.req._('see them all')))
+        self.w(u'</div>\n</div>\n')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/csvexport.py	Wed Feb 18 19:26:48 2009 +0100
@@ -0,0 +1,87 @@
+"""csv export views
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from cubicweb.common.uilib import UnicodeCSVWriter
+from cubicweb.view import EntityView, AnyRsetView
+
+class CSVMixIn(object):
+    """mixin class for CSV views"""
+    templatable = False
+    content_type = "text/comma-separated-values"    
+    binary = True # avoid unicode assertion
+    csv_params = {'dialect': 'excel',
+                  'quotechar': '"',
+                  'delimiter': ';',
+                  'lineterminator': '\n'}
+    
+    def set_request_content_type(self):
+        """overriden to set a .csv filename"""
+        self.req.set_content_type(self.content_type, filename='cubicwebexport.csv')
+            
+    def csvwriter(self, **kwargs):
+        params = self.csv_params.copy()
+        params.update(kwargs)
+        return UnicodeCSVWriter(self.w, self.req.encoding, **params)
+
+    
+class CSVRsetView(CSVMixIn, AnyRsetView):
+    """dumps raw result set in CSV"""
+    id = 'csvexport'
+    title = _('csv export')
+        
+    def call(self):
+        writer = self.csvwriter()
+        writer.writerow(self.columns_labels())
+        rset, descr = self.rset, self.rset.description
+        eschema = self.schema.eschema
+        for rowindex, row in enumerate(rset):
+            csvrow = []
+            for colindex, val in enumerate(row):
+                etype = descr[rowindex][colindex]
+                if val is not None and not eschema(etype).is_final():
+                    # csvrow.append(val) # val is eid in that case
+                    content = self.view('textincontext', rset, 
+                                        row=rowindex, col=colindex)
+                else:
+                    content = self.view('final', rset,
+                                        displaytime=True, format='text/plain',
+                                        row=rowindex, col=colindex)
+                csvrow.append(content)                    
+            writer.writerow(csvrow)
+    
+    
+class CSVEntityView(CSVMixIn, EntityView):
+    """dumps rset's entities (with full set of attributes) in CSV
+
+    the generated CSV file will have a table per entity type found in the
+    resultset. ('table' here only means empty lines separation between table
+    contents)
+    """
+    id = 'ecsvexport'
+    title = _('csv entities export')
+
+    def call(self):
+        req = self.req
+        rows_by_type = {}
+        writer = self.csvwriter()
+        rowdef_by_type = {}
+        for index in xrange(len(self.rset)):
+            entity = self.complete_entity(index)
+            if entity.e_schema not in rows_by_type:
+                rowdef_by_type[entity.e_schema] = [rs for rs, at in entity.e_schema.attribute_definitions()
+                                                   if at != 'Bytes']
+                rows_by_type[entity.e_schema] = [[display_name(req, rschema.type)
+                                                  for rschema in rowdef_by_type[entity.e_schema]]]
+            rows = rows_by_type[entity.e_schema]
+            rows.append([entity.printable_value(rs.type, format='text/plain')
+                         for rs in rowdef_by_type[entity.e_schema]])
+        for rows in rows_by_type.itervalues():
+            writer.writerows(rows)
+            # use two empty lines as separator
+            writer.writerows([[], []])        
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/editviews.py	Wed Feb 18 19:26:48 2009 +0100
@@ -0,0 +1,310 @@
+"""Some views used to help to the edition process
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from simplejson import dumps
+
+from logilab.common.decorators import cached
+from logilab.mtconverter import html_escape
+
+from cubicweb import typed_eid
+from cubicweb.view import EntityView
+from cubicweb.selectors import (one_line_rset, non_final_entity,
+                                match_search_state, match_form_params)
+from cubicweb.common.uilib import cut
+from cubicweb.web.views import linksearch_select_url
+from cubicweb.web.form import relation_id
+from cubicweb.web.views.baseviews import FinalView
+
+_ = unicode
+
+class SearchForAssociationView(EntityView):
+    """view called by the edition view when the user asks to search for
+    something to link to the edited eid
+    """
+    id = 'search-associate'
+    __select__ = (one_line_rset() & match_search_state('linksearch')
+                  & non_final_entity())
+    
+    title = _('search for association')
+
+    def cell_call(self, row, col):
+        rset, vid, divid, paginate = self.filter_box_context_info()
+        self.w(u'<div id="%s">' % divid)
+        self.pagination(self.req, rset, w=self.w)
+        self.wview(vid, rset, 'noresult')
+        self.w(u'</div>')
+
+    @cached
+    def filter_box_context_info(self):
+        entity = self.entity(0, 0)
+        role, eid, rtype, etype = self.req.search_state[1]
+        assert entity.eid == typed_eid(eid)
+        # the default behaviour is to fetch all unrelated entities and display
+        # them. Use fetch_order and not fetch_unrelated_order as sort method
+        # since the latter is mainly there to select relevant items in the combo
+        # box, it doesn't give interesting result in this context
+        rql = entity.unrelated_rql(rtype, etype, role,
+                                   ordermethod='fetch_order',
+                                   vocabconstraints=False)
+        rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
+        return rset, 'list', "search-associate-content", True
+
+
+class OutOfContextSearch(EntityView):
+    id = 'outofcontext-search'
+    def cell_call(self, row, col):
+        entity = self.entity(row, col)
+        erset = entity.as_rset()
+        if self.req.match_search_state(erset):
+            self.w(u'<a href="%s" title="%s">%s</a>&nbsp;<a href="%s" title="%s">[...]</a>' % (
+                html_escape(linksearch_select_url(self.req, erset)),
+                self.req._('select this entity'),
+                html_escape(entity.view('textoutofcontext')),
+                html_escape(entity.absolute_url(vid='primary')),
+                self.req._('view detail for this entity')))
+        else:
+            entity.view('outofcontext', w=self.w)
+
+        
+class UnrelatedDivs(EntityView):
+    id = 'unrelateddivs'
+    __select__ = match_form_params('relation')
+
+    @property
+    def limit(self):
+        if self.req.form.get('__force_display'):
+            return None
+        return self.req.property_value('navigation.related-limit') + 1
+
+    def cell_call(self, row, col):
+        entity = self.entity(row, col)
+        relname, target = self.req.form.get('relation').rsplit('_', 1)
+        rschema = self.schema.rschema(relname)
+        hidden = 'hidden' in self.req.form
+        is_cell = 'is_cell' in self.req.form
+        self.w(self.build_unrelated_select_div(entity, rschema, target,
+                                               is_cell=is_cell, hidden=hidden))
+
+    def build_unrelated_select_div(self, entity, rschema, target,
+                                   is_cell=False, hidden=True):
+        options = []
+        divid = 'div%s_%s_%s' % (rschema.type, target, entity.eid)
+        selectid = 'select%s_%s_%s' % (rschema.type, target, entity.eid)
+        if rschema.symetric or target == 'subject':
+            targettypes = rschema.objects(entity.e_schema)
+            etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
+        else:
+            targettypes = rschema.subjects(entity.e_schema)
+            etypes = '/'.join(sorted(etype.display_name(self.req) for etype in targettypes))
+        etypes = cut(etypes, self.req.property_value('navigation.short-line-size'))
+        options.append('<option>%s %s</option>' % (self.req._('select a'), etypes))
+        options += self._get_select_options(entity, rschema, target)
+        options += self._get_search_options(entity, rschema, target, targettypes)
+        if 'Basket' in self.schema: # XXX
+            options += self._get_basket_options(entity, rschema, target, targettypes)
+        relname, target = self.req.form.get('relation').rsplit('_', 1)
+        return u"""\
+<div class="%s" id="%s">
+  <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
+    %s
+  </select>
+</div>
+""" % (hidden and 'hidden' or '', divid, selectid, html_escape(dumps(entity.eid)),
+       is_cell and 'true' or 'null', relname, '\n'.join(options))
+
+    def _get_select_options(self, entity, rschema, target):
+        """add options to search among all entities of each possible type"""
+        options = []
+        eid = entity.eid
+        pending_inserts = self.req.get_pending_inserts(eid)
+        rtype = rschema.type
+        for eview, reid in entity.vocabulary(rschema, target, self.limit):
+            if reid is None:
+                options.append('<option class="separator">-- %s --</option>' % html_escape(eview))
+            else:
+                optionid = relation_id(eid, rtype, target, reid)
+                if optionid not in pending_inserts:
+                    # prefix option's id with letters to make valid XHTML wise
+                    options.append('<option id="id%s" value="%s">%s</option>' %
+                                   (optionid, reid, html_escape(eview)))
+        return options
+
+    def _get_search_options(self, entity, rschema, target, targettypes):
+        """add options to search among all entities of each possible type"""
+        options = []
+        _ = self.req._
+        for eschema in targettypes:
+            mode = '%s:%s:%s:%s' % (target, entity.eid, rschema.type, eschema)
+            url = self.build_url(entity.rest_path(), vid='search-associate',
+                                 __mode=mode)
+            options.append((eschema.display_name(self.req),
+                            '<option value="%s">%s %s</option>' % (
+                html_escape(url), _('Search for'), eschema.display_name(self.req))))
+        return [o for l, o in sorted(options)]
+
+    def _get_basket_options(self, entity, rschema, target, targettypes):
+        options = []
+        rtype = rschema.type
+        _ = self.req._
+        for basketeid, basketname in self._get_basket_links(self.req.user.eid,
+                                                            target, targettypes):
+            optionid = relation_id(entity.eid, rtype, target, basketeid)
+            options.append('<option id="%s" value="%s">%s %s</option>' % (
+                optionid, basketeid, _('link to each item in'), html_escape(basketname)))
+        return options
+
+    def _get_basket_links(self, ueid, target, targettypes):
+        targettypes = set(targettypes)
+        for basketeid, basketname, elements in self._get_basket_info(ueid):
+            baskettypes = elements.column_types(0)
+            # if every elements in the basket can be attached to the
+            # edited entity
+            if baskettypes & targettypes:
+                yield basketeid, basketname
+            
+    def _get_basket_info(self, ueid):
+        basketref = []
+        basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
+        basketresultset = self.req.execute(basketrql, {'x': ueid}, 'x')
+        for result in basketresultset:
+            basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
+            rset = self.req.execute(basketitemsrql, {'x': result[0]}, 'x')
+            basketref.append((result[0], result[1], rset))
+        return basketref
+
+
+class ComboboxView(EntityView):
+    """the view used in combobox (unrelated entities)
+
+    THIS IS A TEXT VIEW. DO NOT HTML_ESCAPE
+    """
+    id = 'combobox'
+    title = None
+    
+    def cell_call(self, row, col):
+        """the combo-box view for an entity: same as text out of context view
+        by default
+        """
+        self.wview('textoutofcontext', self.rset, row=row, col=col)
+            
+            
+# class EditRelationView(EntityView):
+#     """Note: This is work in progress
+
+#     This view is part of the edition view refactoring.
+#     It is still too big and cluttered with strange logic, but it's a start
+
+#     The main idea is to be able to call an edition view for a specific
+#     relation. For example :
+#        self.wview('editrelation', person_rset, rtype='firstname')
+#        self.wview('editrelation', person_rset, rtype='works_for')
+#     """
+#     id = 'editrelation'
+
+#     __select__ = match_form_params('rtype')
+    
+#     # TODO: inlineview, multiple edit, (widget view ?)
+#     def cell_call(self, row, col, rtype=None, role='subject', targettype=None,
+#                  showlabel=True):
+#         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
+#         entity = self.entity(row, col)
+#         rtype = self.req.form.get('rtype', rtype)
+#         showlabel = self.req.form.get('showlabel', showlabel)
+#         assert rtype is not None, "rtype is mandatory for 'edirelation' view"
+#         targettype = self.req.form.get('targettype', targettype)
+#         role = self.req.form.get('role', role)
+#         category = entity.rtags.get_category(rtype, targettype, role)
+#         if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final():
+#             if hasattr(entity, '%s_format' % rtype):
+#                 formatwdg = entity.get_widget('%s_format' % rtype, role)
+#                 self.w(formatwdg.edit_render(entity))
+#                 self.w(u'<br/>')
+#             wdg = entity.get_widget(rtype, role)
+#             if showlabel:
+#                 self.w(u'%s' % wdg.render_label(entity))
+#             self.w(u'%s %s %s' %
+#                    (wdg.render_error(entity), wdg.edit_render(entity),
+#                     wdg.render_help(entity),))
+#         else:
+#             self._render_generic_relation(entity, rtype, role)
+
+#     def _render_generic_relation(self, entity, relname, role):
+#         text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role))
+#         # pending operations
+#         operations = self.req.get_pending_operations(entity, relname, role)
+#         if operations['insert'] or operations['delete'] or 'unfold' in self.req.form:
+#             self.w(u'<h3>%s</h3>' % text)
+#             self._render_generic_relation_form(operations, entity, relname, role)
+#         else:
+#             divid = "%s%sreledit" % (relname, role)
+#             url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation',
+#                                    {'unfold' : 1, 'relname' : relname, 'role' : role})
+#             self.w(u'<a href="%s">%s</a>' % (url, text))
+#             self.w(u'<div id="%s"></div>' % divid)
+        
+
+#     def _build_opvalue(self, entity, relname, target, role):
+#         if role == 'subject':
+#             return '%s:%s:%s' % (entity.eid, relname, target)
+#         else:
+#             return '%s:%s:%s' % (target, relname, entity.eid)
+        
+    
+#     def _render_generic_relation_form(self, operations, entity, relname, role):
+#         rqlexec = self.req.execute
+#         for optype, targets in operations.items():
+#             for target in targets:
+#                 self._render_pending(optype, entity, relname, target, role)
+#                 opvalue = self._build_opvalue(entity, relname, target, role)
+#                 self.w(u'<a href="javascript: addPendingDelete(\'%s\', %s);">-</a> '
+#                        % (opvalue, entity.eid))
+#                 rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x')
+#                 self.wview('oneline', rset)
+#         # now, unrelated ones
+#         self._render_unrelated_selection(entity, relname, role)
+
+#     def _render_pending(self, optype, entity, relname, target, role):
+#         opvalue = self._build_opvalue(entity, relname, target, role)
+#         self.w(u'<input type="hidden" name="__%s" value="%s" />'
+#                % (optype, opvalue))
+#         if optype == 'insert':
+#             checktext = '-'
+#         else:
+#             checktext = '+'
+#         rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x')
+#         self.w(u"""[<a href="javascript: cancelPending%s('%s:%s:%s')">%s</a>"""
+#                % (optype.capitalize(), relname, target, role,
+#                   self.view('oneline', rset)))
+
+#     def _render_unrelated_selection(self, entity, relname, role):
+#         rschema = self.schema.rschema(relname)
+#         if role == 'subject':
+#             targettypes = rschema.objects(entity.e_schema)
+#         else:
+#             targettypes = rschema.subjects(entity.e_schema)
+#         self.w(u'<select onselect="addPendingInsert(this.selected.value);">')
+#         for targettype in targettypes:
+#             unrelated = entity.unrelated(relname, targettype, role) # XXX limit
+#             for rowindex, row in enumerate(unrelated):
+#                 teid = row[0]
+#                 opvalue = self._build_opvalue(entity, relname, teid, role)
+#                 self.w(u'<option name="__insert" value="%s>%s</option>'
+#                        % (opvalue, self.view('text', unrelated, row=rowindex)))
+#         self.w(u'</select>')
+
+
+class EditableFinalView(FinalView):
+    """same as FinalView but enables inplace-edition when possible"""
+    id = 'editable-final'
+                
+    def cell_call(self, row, col, props=None, displaytime=False):
+        entity, rtype = self.rset.related_entity(row, col)
+        if entity is not None:
+            self.w(entity.view('reledit', rtype=rtype))
+        else:
+            super(EditableFinalView, self).cell_call(row, col, props, displaytime)
--- a/web/views/xbel.py	Wed Feb 18 19:19:57 2009 +0100
+++ b/web/views/xbel.py	Wed Feb 18 19:26:48 2009 +0100
@@ -1,7 +1,7 @@
 """xbel views
 
 :organization: Logilab
-:copyright: 2001-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
 """
 __docformat__ = "restructuredtext en"
@@ -10,7 +10,8 @@
 from logilab.mtconverter import html_escape
 
 from cubicweb.selectors import implements
-from cubicweb.web.views.baseviews import XmlView, EntityView
+from cubicweb.view import EntityView
+from cubicweb.web.views.xmlrss import XmlView
 
 
 class XbelView(XmlView):
@@ -46,6 +47,7 @@
 
     def url(self, entity):
         return entity.absolute_url()
+
         
 class XbelItemBookmarkView(XbelItemView):
     __select__ = implements('Bookmark')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/views/xmlrss.py	Wed Feb 18 19:26:48 2009 +0100
@@ -0,0 +1,162 @@
+"""base xml and rss views
+
+
+:organization: Logilab
+:copyright: 2001-2009 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+:contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
+"""
+__docformat__ = "restructuredtext en"
+
+from time import timezone
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.view import EntityView, AnyRsetView
+from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
+from cubicweb.common.uilib import simple_sgml_tag
+
+
+class XmlView(EntityView):
+    """xml view for entities"""
+    id = 'xml'
+    title = _('xml')
+    templatable = False
+    content_type = 'text/xml'
+    xml_root = 'rset'
+    item_vid = 'xmlitem'
+    
+    def cell_call(self, row, col):
+        self.wview(self.item_vid, self.rset, row=row, col=col)
+        
+    def call(self):
+        """display a list of entities by calling their <item_vid> view"""
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+        self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.rset)))
+        for i in xrange(self.rset.rowcount):
+            self.cell_call(i, 0)
+        self.w(u'</%s>\n' % self.xml_root)
+
+
+class XmlItemView(EntityView):
+    id = 'xmlitem'
+
+    def cell_call(self, row, col):
+        """ element as an item for an xml feed """
+        entity = self.complete_entity(row, col)
+        self.w(u'<%s>\n' % (entity.e_schema))
+        for rschema, attrschema in entity.e_schema.attribute_definitions():
+            attr = rschema.type
+            try:
+                value = entity[attr]
+            except KeyError:
+                # Bytes
+                continue
+            if value is not None:
+                if attrschema == 'Bytes':
+                    from base64 import b64encode
+                    value = '<![CDATA[%s]]>' % b64encode(value.getvalue())
+                elif isinstance(value, basestring):
+                    value = xml_escape(value)
+                self.w(u'  <%s>%s</%s>\n' % (attr, value, attr))
+        self.w(u'</%s>\n' % (entity.e_schema))
+
+
+    
+class XmlRsetView(AnyRsetView):
+    """dumps raw rset as xml"""
+    id = 'rsetxml'
+    title = _('xml export')
+    templatable = False
+    content_type = 'text/xml'
+    xml_root = 'rset'
+        
+    def call(self):
+        w = self.w
+        rset, descr = self.rset, self.rset.description
+        eschema = self.schema.eschema
+        labels = self.columns_labels(False)
+        w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
+        w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
+        for rowindex, row in enumerate(self.rset):
+            w(u' <row>\n')
+            for colindex, val in enumerate(row):
+                etype = descr[rowindex][colindex]
+                tag = labels[colindex]
+                attrs = {}
+                if '(' in tag:
+                    attrs['expr'] = tag
+                    tag = 'funccall'
+                if val is not None and not eschema(etype).is_final():
+                    attrs['eid'] = val
+                    # csvrow.append(val) # val is eid in that case
+                    val = self.view('textincontext', rset,
+                                    row=rowindex, col=colindex)
+                else:
+                    val = self.view('final', rset, displaytime=True,
+                                    row=rowindex, col=colindex, format='text/plain')
+                w(simple_sgml_tag(tag, val, **attrs))
+            w(u' </row>\n')
+        w(u'</%s>\n' % self.xml_root)
+    
+
+class RssView(XmlView):
+    id = 'rss'
+    title = _('rss')
+    templatable = False
+    content_type = 'text/xml'
+    http_cache_manager = MaxAgeHTTPCacheManager
+    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
+    
+    def cell_call(self, row, col):
+        self.wview('rssitem', self.rset, row=row, col=col)
+        
+    def call(self):
+        """display a list of entities by calling their <item_vid> view"""
+        req = self.req
+        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
+        self.w(u'''<rdf:RDF
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns="http://purl.org/rss/1.0/"
+>''')
+        self.w(u'  <channel rdf:about="%s">\n' % xml_escape(req.url()))
+        self.w(u'    <title>%s RSS Feed</title>\n' % xml_escape(self.page_title()))
+        self.w(u'    <description>%s</description>\n' % xml_escape(req.form.get('vtitle', '')))
+        params = req.form.copy()
+        params.pop('vid', None)
+        self.w(u'    <link>%s</link>\n' % xml_escape(self.build_url(**params)))
+        self.w(u'    <items>\n')
+        self.w(u'      <rdf:Seq>\n')
+        for entity in self.rset.entities():
+            self.w(u'      <rdf:li resource="%s" />\n' % xml_escape(entity.absolute_url()))
+        self.w(u'      </rdf:Seq>\n')
+        self.w(u'    </items>\n')
+        self.w(u'  </channel>\n')
+        for i in xrange(self.rset.rowcount):
+            self.cell_call(i, 0)
+        self.w(u'</rdf:RDF>')
+
+
+class RssItemView(EntityView):
+    id = 'rssitem'
+    date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
+
+    def cell_call(self, row, col):
+        entity = self.complete_entity(row, col)
+        self.w(u'<item rdf:about="%s">\n' % xml_escape(entity.absolute_url()))
+        self._marker('title', entity.dc_long_title())
+        self._marker('link', entity.absolute_url())
+        self._marker('description', entity.dc_description())
+        self._marker('dc:date', entity.dc_date(self.date_format))
+        if entity.creator:
+            self.w(u'<author>')
+            self._marker('name', entity.creator.name())
+            email = entity.creator.get_email()
+            if email:
+                self._marker('email', email)
+            self.w(u'</author>')
+        self.w(u'</item>\n')
+        
+    def _marker(self, marker, value):
+        if value:
+            self.w(u'  <%s>%s</%s>\n' % (marker, xml_escape(value), marker))