web/views/baseviews.py
branchtls-sprint
changeset 824 a5e6acffde30
parent 795 63f4f81e19f1
child 985 6a25c58a1c23
equal deleted inserted replaced
823:cb8ccbef8fa5 824:a5e6acffde30
    12 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    12 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    13 """
    13 """
    14 __docformat__ = "restructuredtext en"
    14 __docformat__ = "restructuredtext en"
    15 
    15 
    16 from warnings import warn
    16 from warnings import warn
    17 from time import timezone
       
    18 
    17 
    19 from rql import nodes
    18 from rql import nodes
    20 
    19 
    21 from logilab.common.decorators import cached
    20 from logilab.mtconverter import TransformError, html_escape
    22 from logilab.mtconverter import TransformError, html_escape, xml_escape
    21 
    23 
    22 from cubicweb import Unauthorized, NoSelectableObject
    24 from cubicweb import Unauthorized, NoSelectableObject, typed_eid
    23 from cubicweb.selectors import yes, empty_rset
    25 from cubicweb.selectors import (yes, empty_rset, nonempty_rset, one_line_rset,
       
    26                                 non_final_entity, match_search_state,  match_form_params)
       
    27 from cubicweb.view import EntityView, AnyRsetView, View
    24 from cubicweb.view import EntityView, AnyRsetView, View
    28 from cubicweb.common.uilib import (cut, printable_value,  UnicodeCSVWriter,
    25 from cubicweb.common.uilib import cut, printable_value
    29                                    ajax_replace_url, rql_for_eid, simple_sgml_tag)
       
    30 from cubicweb.web.httpcache import MaxAgeHTTPCacheManager
       
    31 from cubicweb.web.views import vid_from_rset, linksearch_select_url
       
    32 
    26 
    33 _ = unicode
    27 _ = unicode
    34 
    28 
    35 class NullView(AnyRsetView):
    29 class NullView(AnyRsetView):
    36     """default view when no result has been found"""
    30     """default view when no result has been found"""
    85             else:
    79             else:
    86                 self.w(_('%s seconds') % int(value.seconds))
    80                 self.w(_('%s seconds') % int(value.seconds))
    87             return
    81             return
    88         self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime))
    82         self.wdata(printable_value(self.req, etype, value, props, displaytime=displaytime))
    89 
    83 
    90 
       
    91 class EditableFinalView(FinalView):
       
    92     """same as FinalView but enables inplace-edition when possible"""
       
    93     id = 'editable-final'
       
    94                 
       
    95     def cell_call(self, row, col, props=None, displaytime=False):
       
    96         etype = self.rset.description[row][col]
       
    97         value = self.rset.rows[row][col]
       
    98         entity, rtype = self.rset.related_entity(row, col)
       
    99         if entity is not None:
       
   100             self.w(entity.view('reledit', rtype=rtype))
       
   101         else:
       
   102             super(EditableFinalView, self).cell_call(row, col, props, displaytime)
       
   103         
    84         
   104 PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity',
    85 PRIMARY_SKIP_RELS = set(['is', 'is_instance_of', 'identity',
   105                          'owned_by', 'created_by', 
    86                          'owned_by', 'created_by', 
   106                          'in_state', 'wf_info_for', 'require_permission',
    87                          'in_state', 'wf_info_for', 'require_permission',
   107                          'from_entity', 'to_entity',
    88                          'from_entity', 'to_entity',
   169                 comp.dispatch(w=self.w, view=self)
   150                 comp.dispatch(w=self.w, view=self)
   170         self.w(u'</div>')
   151         self.w(u'</div>')
   171         
   152         
   172     def iter_attributes(self, entity):
   153     def iter_attributes(self, entity):
   173         for rschema, targetschema in entity.e_schema.attribute_definitions():
   154         for rschema, targetschema in entity.e_schema.attribute_definitions():
   174             attr = rschema.type
   155             if rschema.type in self.skip_attrs:
   175             if attr in self.skip_attrs:
   156                 continue
   176                continue
       
   177             yield rschema, targetschema
   157             yield rschema, targetschema
   178             
   158             
   179     def iter_relations(self, entity):
   159     def iter_relations(self, entity):
   180         skip = set(self.skip_rels)
   160         skip = set(self.skip_rels)
   181         skip.update(PRIMARY_SKIP_RELS)
   161         skip.update(PRIMARY_SKIP_RELS)
   296                                                     self.req._('see them all'))
   276                                                     self.req._('see them all'))
   297                 value +=  '</div>'
   277                 value +=  '</div>'
   298         label = display_name(self.req, rschema.type, role)
   278         label = display_name(self.req, rschema.type, role)
   299         self.field(label, value, show_label=show_label, w=self.w, tr=False)
   279         self.field(label, value, show_label=show_label, w=self.w, tr=False)
   300 
   280 
   301 
       
   302 class SideBoxView(EntityView):
       
   303     """side box usually displaying some related entities in a primary view"""
       
   304     id = 'sidebox'
       
   305     
       
   306     def call(self, boxclass='sideBox', title=u''):
       
   307         """display a list of entities by calling their <item_vid> view
       
   308         """
       
   309         if title:
       
   310             self.w(u'<div class="sideBoxTitle"><span>%s</span></div>' % title)
       
   311         self.w(u'<div class="%s"><div class="sideBoxBody">' % boxclass)
       
   312         # if not too much entities, show them all in a list
       
   313         maxrelated = self.req.property_value('navigation.related-limit')
       
   314         if self.rset.rowcount <= maxrelated:
       
   315             if len(self.rset) == 1:
       
   316                 self.wview('incontext', self.rset, row=0)
       
   317             elif 1 < len(self.rset) < 5:
       
   318                 self.wview('csv', self.rset)
       
   319             else:
       
   320                 self.wview('simplelist', self.rset)
       
   321         # else show links to display related entities
       
   322         else:
       
   323             self.rset.limit(maxrelated)
       
   324             rql = self.rset.printable_rql(encoded=False)
       
   325             self.wview('simplelist', self.rset)
       
   326             self.w(u'[<a href="%s">%s</a>]' % (self.build_url(rql=rql),
       
   327                                                self.req._('see them all')))
       
   328         self.w(u'</div>\n</div>\n')
       
   329 
       
   330 
       
   331  
   281  
   332 class SecondaryView(EntityView):
   282 class SecondaryView(EntityView):
   333     id = 'secondary'
   283     id = 'secondary'
   334     title = _('secondary')
   284     title = _('secondary')
   335     
   285     
   339         """
   289         """
   340         entity = self.entity(row, col)
   290         entity = self.entity(row, col)
   341         self.w(u'&nbsp;')
   291         self.w(u'&nbsp;')
   342         self.wview('oneline', self.rset, row=row, col=col)
   292         self.wview('oneline', self.rset, row=row, col=col)
   343 
   293 
       
   294 
   344 class OneLineView(EntityView):
   295 class OneLineView(EntityView):
   345     id = 'oneline'
   296     id = 'oneline'
   346     title = _('oneline') 
   297     title = _('oneline') 
   347 
   298 
   348     def cell_call(self, row, col):
   299     def cell_call(self, row, col):
   350         """
   301         """
   351         entity = self.entity(row, col)
   302         entity = self.entity(row, col)
   352         self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
   303         self.w(u'<a href="%s">' % html_escape(entity.absolute_url()))
   353         self.w(html_escape(self.view('text', self.rset, row=row, col=col)))
   304         self.w(html_escape(self.view('text', self.rset, row=row, col=col)))
   354         self.w(u'</a>')
   305         self.w(u'</a>')
       
   306 
   355 
   307 
   356 class TextView(EntityView):
   308 class TextView(EntityView):
   357     """the simplest text view for an entity"""
   309     """the simplest text view for an entity"""
   358     id = 'text'
   310     id = 'text'
   359     title = _('text')
   311     title = _('text')
   413     id = 'textincontext'
   365     id = 'textincontext'
   414     title = None # not listed as a possible view
   366     title = None # not listed as a possible view
   415     def cell_call(self, row, col):
   367     def cell_call(self, row, col):
   416         entity = self.entity(row, col)
   368         entity = self.entity(row, col)
   417         self.w(entity.dc_title())
   369         self.w(entity.dc_title())
   418         
   370 
       
   371 
   419 class OutOfContextTextView(InContextTextView):
   372 class OutOfContextTextView(InContextTextView):
   420     id = 'textoutofcontext'
   373     id = 'textoutofcontext'
   421 
   374 
   422     def cell_call(self, row, col):
   375     def cell_call(self, row, col):
   423         entity = self.entity(row, col)
   376         entity = self.entity(row, col)
   441 
   394 
   442     def cell_call(self, row, col):
   395     def cell_call(self, row, col):
   443         self.w(u'<a href="%s">' % self.entity(row, col).absolute_url())
   396         self.w(u'<a href="%s">' % self.entity(row, col).absolute_url())
   444         self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col)))
   397         self.w(html_escape(self.view('textoutofcontext', self.rset, row=row, col=col)))
   445         self.w(u'</a>')
   398         self.w(u'</a>')
       
   399 
   446             
   400             
   447 # list and table related views ################################################
   401 # list views ##################################################################
   448     
   402     
   449 class ListView(EntityView):
   403 class ListView(EntityView):
   450     id = 'list'
   404     id = 'list'
   451     title = _('list')
   405     title = _('list')
   452     item_vid = 'listitem'
   406     item_vid = 'listitem'
   543     id = 'treeitem'
   497     id = 'treeitem'
   544     
   498     
   545     def cell_call(self, row, col):
   499     def cell_call(self, row, col):
   546         self.wview('incontext', self.rset, row=row, col=col)
   500         self.wview('incontext', self.rset, row=row, col=col)
   547 
   501 
   548 
   502 # context specific views ######################################################
   549 # xml and xml/rss views #######################################################
       
   550     
       
   551 class XmlView(EntityView):
       
   552     id = 'xml'
       
   553     title = _('xml')
       
   554     templatable = False
       
   555     content_type = 'text/xml'
       
   556     xml_root = 'rset'
       
   557     item_vid = 'xmlitem'
       
   558     
       
   559     def cell_call(self, row, col):
       
   560         self.wview(self.item_vid, self.rset, row=row, col=col)
       
   561         
       
   562     def call(self):
       
   563         """display a list of entities by calling their <item_vid> view"""
       
   564         self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
       
   565         self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.rset)))
       
   566         for i in xrange(self.rset.rowcount):
       
   567             self.cell_call(i, 0)
       
   568         self.w(u'</%s>\n' % self.xml_root)
       
   569 
       
   570 
       
   571 class XmlItemView(EntityView):
       
   572     id = 'xmlitem'
       
   573 
       
   574     def cell_call(self, row, col):
       
   575         """ element as an item for an xml feed """
       
   576         entity = self.complete_entity(row, col)
       
   577         self.w(u'<%s>\n' % (entity.e_schema))
       
   578         for rschema, attrschema in entity.e_schema.attribute_definitions():
       
   579             attr = rschema.type
       
   580             try:
       
   581                 value = entity[attr]
       
   582             except KeyError:
       
   583                 # Bytes
       
   584                 continue
       
   585             if value is not None:
       
   586                 if attrschema == 'Bytes':
       
   587                     from base64 import b64encode
       
   588                     value = '<![CDATA[%s]]>' % b64encode(value.getvalue())
       
   589                 elif isinstance(value, basestring):
       
   590                     value = xml_escape(value)
       
   591                 self.w(u'  <%s>%s</%s>\n' % (attr, value, attr))
       
   592         self.w(u'</%s>\n' % (entity.e_schema))
       
   593 
       
   594 
       
   595     
       
   596 class XMLRsetView(AnyRsetView):
       
   597     """dumps xml in CSV"""
       
   598     id = 'rsetxml'
       
   599     title = _('xml export')
       
   600     templatable = False
       
   601     content_type = 'text/xml'
       
   602     xml_root = 'rset'
       
   603         
       
   604     def call(self):
       
   605         w = self.w
       
   606         rset, descr = self.rset, self.rset.description
       
   607         eschema = self.schema.eschema
       
   608         labels = self.columns_labels(False)
       
   609         w(u'<?xml version="1.0" encoding="%s"?>\n' % self.req.encoding)
       
   610         w(u'<%s query="%s">\n' % (self.xml_root, html_escape(rset.printable_rql())))
       
   611         for rowindex, row in enumerate(self.rset):
       
   612             w(u' <row>\n')
       
   613             for colindex, val in enumerate(row):
       
   614                 etype = descr[rowindex][colindex]
       
   615                 tag = labels[colindex]
       
   616                 attrs = {}
       
   617                 if '(' in tag:
       
   618                     attrs['expr'] = tag
       
   619                     tag = 'funccall'
       
   620                 if val is not None and not eschema(etype).is_final():
       
   621                     attrs['eid'] = val
       
   622                     # csvrow.append(val) # val is eid in that case
       
   623                     val = self.view('textincontext', rset,
       
   624                                     row=rowindex, col=colindex)
       
   625                 else:
       
   626                     val = self.view('final', rset, displaytime=True,
       
   627                                     row=rowindex, col=colindex, format='text/plain')
       
   628                 w(simple_sgml_tag(tag, val, **attrs))
       
   629             w(u' </row>\n')
       
   630         w(u'</%s>\n' % self.xml_root)
       
   631     
       
   632 
       
   633 class RssView(XmlView):
       
   634     id = 'rss'
       
   635     title = _('rss')
       
   636     templatable = False
       
   637     content_type = 'text/xml'
       
   638     http_cache_manager = MaxAgeHTTPCacheManager
       
   639     cache_max_age = 60*60*2 # stay in http cache for 2 hours by default 
       
   640     
       
   641     def cell_call(self, row, col):
       
   642         self.wview('rssitem', self.rset, row=row, col=col)
       
   643         
       
   644     def call(self):
       
   645         """display a list of entities by calling their <item_vid> view"""
       
   646         req = self.req
       
   647         self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
       
   648         self.w(u'''<rdf:RDF
       
   649  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
       
   650  xmlns:dc="http://purl.org/dc/elements/1.1/"
       
   651  xmlns="http://purl.org/rss/1.0/"
       
   652 >''')
       
   653         self.w(u'  <channel rdf:about="%s">\n' % html_escape(req.url()))
       
   654         self.w(u'    <title>%s RSS Feed</title>\n' % html_escape(self.page_title()))
       
   655         self.w(u'    <description>%s</description>\n' % html_escape(req.form.get('vtitle', '')))
       
   656         params = req.form.copy()
       
   657         params.pop('vid', None)
       
   658         self.w(u'    <link>%s</link>\n' % html_escape(self.build_url(**params)))
       
   659         self.w(u'    <items>\n')
       
   660         self.w(u'      <rdf:Seq>\n')
       
   661         for entity in self.rset.entities():
       
   662             self.w(u'      <rdf:li resource="%s" />\n' % html_escape(entity.absolute_url()))
       
   663         self.w(u'      </rdf:Seq>\n')
       
   664         self.w(u'    </items>\n')
       
   665         self.w(u'  </channel>\n')
       
   666         for i in xrange(self.rset.rowcount):
       
   667             self.cell_call(i, 0)
       
   668         self.w(u'</rdf:RDF>')
       
   669 
       
   670 
       
   671 class RssItemView(EntityView):
       
   672     id = 'rssitem'
       
   673     date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
       
   674 
       
   675     def cell_call(self, row, col):
       
   676         entity = self.complete_entity(row, col)
       
   677         self.w(u'<item rdf:about="%s">\n' % html_escape(entity.absolute_url()))
       
   678         self._marker('title', entity.dc_long_title())
       
   679         self._marker('link', entity.absolute_url())
       
   680         self._marker('description', entity.dc_description())
       
   681         self._marker('dc:date', entity.dc_date(self.date_format))
       
   682         if entity.creator:
       
   683             self.w(u'<author>')
       
   684             self._marker('name', entity.creator.name())
       
   685             email = entity.creator.get_email()
       
   686             if email:
       
   687                 self._marker('email', email)
       
   688             self.w(u'</author>')
       
   689         self.w(u'</item>\n')
       
   690         
       
   691     def _marker(self, marker, value):
       
   692         if value:
       
   693             self.w(u'  <%s>%s</%s>\n' % (marker, html_escape(value), marker))
       
   694 
       
   695 
       
   696 class CSVMixIn(object):
       
   697     """mixin class for CSV views"""
       
   698     templatable = False
       
   699     content_type = "text/comma-separated-values"    
       
   700     binary = True # avoid unicode assertion
       
   701     csv_params = {'dialect': 'excel',
       
   702                   'quotechar': '"',
       
   703                   'delimiter': ';',
       
   704                   'lineterminator': '\n'}
       
   705     
       
   706     def set_request_content_type(self):
       
   707         """overriden to set a .csv filename"""
       
   708         self.req.set_content_type(self.content_type, filename='cubicwebexport.csv')
       
   709             
       
   710     def csvwriter(self, **kwargs):
       
   711         params = self.csv_params.copy()
       
   712         params.update(kwargs)
       
   713         return UnicodeCSVWriter(self.w, self.req.encoding, **params)
       
   714 
       
   715     
       
   716 class CSVRsetView(CSVMixIn, AnyRsetView):
       
   717     """dumps rset in CSV"""
       
   718     id = 'csvexport'
       
   719     title = _('csv export')
       
   720         
       
   721     def call(self):
       
   722         writer = self.csvwriter()
       
   723         writer.writerow(self.columns_labels())
       
   724         rset, descr = self.rset, self.rset.description
       
   725         eschema = self.schema.eschema
       
   726         for rowindex, row in enumerate(rset):
       
   727             csvrow = []
       
   728             for colindex, val in enumerate(row):
       
   729                 etype = descr[rowindex][colindex]
       
   730                 if val is not None and not eschema(etype).is_final():
       
   731                     # csvrow.append(val) # val is eid in that case
       
   732                     content = self.view('textincontext', rset, 
       
   733                                         row=rowindex, col=colindex)
       
   734                 else:
       
   735                     content = self.view('final', rset,
       
   736                                         displaytime=True, format='text/plain',
       
   737                                         row=rowindex, col=colindex)
       
   738                 csvrow.append(content)                    
       
   739             writer.writerow(csvrow)
       
   740     
       
   741     
       
   742 class CSVEntityView(CSVMixIn, EntityView):
       
   743     """dumps rset's entities (with full set of attributes) in CSV"""
       
   744     id = 'ecsvexport'
       
   745     title = _('csv entities export')
       
   746 
       
   747     def call(self):
       
   748         """
       
   749         the generated CSV file will have a table per entity type
       
   750         found in the resultset. ('table' here only means empty
       
   751         lines separation between table contents)
       
   752         """
       
   753         req = self.req
       
   754         rows_by_type = {}
       
   755         writer = self.csvwriter()
       
   756         rowdef_by_type = {}
       
   757         for index in xrange(len(self.rset)):
       
   758             entity = self.complete_entity(index)
       
   759             if entity.e_schema not in rows_by_type:
       
   760                 rowdef_by_type[entity.e_schema] = [rs for rs, at in entity.e_schema.attribute_definitions()
       
   761                                                    if at != 'Bytes']
       
   762                 rows_by_type[entity.e_schema] = [[display_name(req, rschema.type)
       
   763                                                   for rschema in rowdef_by_type[entity.e_schema]]]
       
   764             rows = rows_by_type[entity.e_schema]
       
   765             rows.append([entity.printable_value(rs.type, format='text/plain')
       
   766                          for rs in rowdef_by_type[entity.e_schema]])
       
   767         for etype, rows in rows_by_type.items():
       
   768             writer.writerows(rows)
       
   769             # use two empty lines as separator
       
   770             writer.writerows([[], []])        
       
   771     
       
   772 
       
   773 ## Work in progress ###########################################################
       
   774 
       
   775 class SearchForAssociationView(EntityView):
       
   776     """view called by the edition view when the user asks
       
   777     to search for something to link to the edited eid
       
   778     """
       
   779     id = 'search-associate'
       
   780     __select__ = (one_line_rset() & match_search_state('linksearch')
       
   781                   & non_final_entity())
       
   782     
       
   783     title = _('search for association')
       
   784 
       
   785     def cell_call(self, row, col):
       
   786         rset, vid, divid, paginate = self.filter_box_context_info()
       
   787         self.w(u'<div id="%s">' % divid)
       
   788         self.pagination(self.req, rset, w=self.w)
       
   789         self.wview(vid, rset, 'noresult')
       
   790         self.w(u'</div>')
       
   791 
       
   792     @cached
       
   793     def filter_box_context_info(self):
       
   794         entity = self.entity(0, 0)
       
   795         role, eid, rtype, etype = self.req.search_state[1]
       
   796         assert entity.eid == typed_eid(eid)
       
   797         # the default behaviour is to fetch all unrelated entities and display
       
   798         # them. Use fetch_order and not fetch_unrelated_order as sort method
       
   799         # since the latter is mainly there to select relevant items in the combo
       
   800         # box, it doesn't give interesting result in this context
       
   801         rql = entity.unrelated_rql(rtype, etype, role,
       
   802                                    ordermethod='fetch_order',
       
   803                                    vocabconstraints=False)
       
   804         rset = self.req.execute(rql, {'x' : entity.eid}, 'x')
       
   805         #vid = vid_from_rset(self.req, rset, self.schema)
       
   806         return rset, 'list', "search-associate-content", True
       
   807 
       
   808 
       
   809 class OutOfContextSearch(EntityView):
       
   810     id = 'outofcontext-search'
       
   811     def cell_call(self, row, col):
       
   812         entity = self.entity(row, col)
       
   813         erset = entity.as_rset()
       
   814         if self.req.match_search_state(erset):
       
   815             self.w(u'<a href="%s" title="%s">%s</a>&nbsp;<a href="%s" title="%s">[...]</a>' % (
       
   816                 html_escape(linksearch_select_url(self.req, erset)),
       
   817                 self.req._('select this entity'),
       
   818                 html_escape(entity.view('textoutofcontext')),
       
   819                 html_escape(entity.absolute_url(vid='primary')),
       
   820                 self.req._('view detail for this entity')))
       
   821         else:
       
   822             entity.view('outofcontext', w=self.w)
       
   823             
       
   824             
       
   825 class EditRelationView(EntityView):
       
   826     """Note: This is work in progress
       
   827 
       
   828     This view is part of the edition view refactoring.
       
   829     It is still too big and cluttered with strange logic, but it's a start
       
   830 
       
   831     The main idea is to be able to call an edition view for a specific
       
   832     relation. For example :
       
   833        self.wview('editrelation', person_rset, rtype='firstname')
       
   834        self.wview('editrelation', person_rset, rtype='works_for')
       
   835     """
       
   836     id = 'editrelation'
       
   837 
       
   838     __select__ = match_form_params('rtype')
       
   839     
       
   840     # TODO: inlineview, multiple edit, (widget view ?)
       
   841     def cell_call(self, row, col, rtype=None, role='subject', targettype=None,
       
   842                  showlabel=True):
       
   843         self.req.add_js( ('cubicweb.ajax.js', 'cubicweb.edition.js') )
       
   844         entity = self.entity(row, col)
       
   845         rtype = self.req.form.get('rtype', rtype)
       
   846         showlabel = self.req.form.get('showlabel', showlabel)
       
   847         assert rtype is not None, "rtype is mandatory for 'edirelation' view"
       
   848         targettype = self.req.form.get('targettype', targettype)
       
   849         role = self.req.form.get('role', role)
       
   850         category = entity.rtags.get_category(rtype, targettype, role)
       
   851         if category in ('primary', 'secondary') or self.schema.rschema(rtype).is_final():
       
   852             if hasattr(entity, '%s_format' % rtype):
       
   853                 formatwdg = entity.get_widget('%s_format' % rtype, role)
       
   854                 self.w(formatwdg.edit_render(entity))
       
   855                 self.w(u'<br/>')
       
   856             wdg = entity.get_widget(rtype, role)
       
   857             if showlabel:
       
   858                 self.w(u'%s' % wdg.render_label(entity))
       
   859             self.w(u'%s %s %s' %
       
   860                    (wdg.render_error(entity), wdg.edit_render(entity),
       
   861                     wdg.render_help(entity),))
       
   862         else:
       
   863             self._render_generic_relation(entity, rtype, role)
       
   864 
       
   865     def _render_generic_relation(self, entity, relname, role):
       
   866         text = self.req.__('add %s %s %s' % (entity.e_schema, relname, role))
       
   867         # pending operations
       
   868         operations = self.req.get_pending_operations(entity, relname, role)
       
   869         if operations['insert'] or operations['delete'] or 'unfold' in self.req.form:
       
   870             self.w(u'<h3>%s</h3>' % text)
       
   871             self._render_generic_relation_form(operations, entity, relname, role)
       
   872         else:
       
   873             divid = "%s%sreledit" % (relname, role)
       
   874             url = ajax_replace_url(divid, rql_for_eid(entity.eid), 'editrelation',
       
   875                                    {'unfold' : 1, 'relname' : relname, 'role' : role})
       
   876             self.w(u'<a href="%s">%s</a>' % (url, text))
       
   877             self.w(u'<div id="%s"></div>' % divid)
       
   878         
       
   879 
       
   880     def _build_opvalue(self, entity, relname, target, role):
       
   881         if role == 'subject':
       
   882             return '%s:%s:%s' % (entity.eid, relname, target)
       
   883         else:
       
   884             return '%s:%s:%s' % (target, relname, entity.eid)
       
   885         
       
   886     
       
   887     def _render_generic_relation_form(self, operations, entity, relname, role):
       
   888         rqlexec = self.req.execute
       
   889         for optype, targets in operations.items():
       
   890             for target in targets:
       
   891                 self._render_pending(optype, entity, relname, target, role)
       
   892                 opvalue = self._build_opvalue(entity, relname, target, role)
       
   893                 self.w(u'<a href="javascript: addPendingDelete(\'%s\', %s);">-</a> '
       
   894                        % (opvalue, entity.eid))
       
   895                 rset = rqlexec('Any X WHERE X eid %(x)s', {'x': target}, 'x')
       
   896                 self.wview('oneline', rset)
       
   897         # now, unrelated ones
       
   898         self._render_unrelated_selection(entity, relname, role)
       
   899 
       
   900     def _render_pending(self, optype, entity, relname, target, role):
       
   901         opvalue = self._build_opvalue(entity, relname, target, role)
       
   902         self.w(u'<input type="hidden" name="__%s" value="%s" />'
       
   903                % (optype, opvalue))
       
   904         if optype == 'insert':
       
   905             checktext = '-'
       
   906         else:
       
   907             checktext = '+'
       
   908         rset = self.req.execute('Any X WHERE X eid %(x)s', {'x': target}, 'x')
       
   909         self.w(u"""[<a href="javascript: cancelPending%s('%s:%s:%s')">%s</a>"""
       
   910                % (optype.capitalize(), relname, target, role,
       
   911                   self.view('oneline', rset)))
       
   912 
       
   913     def _render_unrelated_selection(self, entity, relname, role):
       
   914         rschema = self.schema.rschema(relname)
       
   915         if role == 'subject':
       
   916             targettypes = rschema.objects(entity.e_schema)
       
   917         else:
       
   918             targettypes = rschema.subjects(entity.e_schema)
       
   919         self.w(u'<select onselect="addPendingInsert(this.selected.value);">')
       
   920         for targettype in targettypes:
       
   921             unrelated = entity.unrelated(relname, targettype, role) # XXX limit
       
   922             for rowindex, row in enumerate(unrelated):
       
   923                 teid = row[0]
       
   924                 opvalue = self._build_opvalue(entity, relname, teid, role)
       
   925                 self.w(u'<option name="__insert" value="%s>%s</option>'
       
   926                        % (opvalue, self.view('text', unrelated, row=rowindex)))
       
   927         self.w(u'</select>')
       
   928 
       
   929 
   503 
   930 class TextSearchResultView(EntityView):
   504 class TextSearchResultView(EntityView):
   931     """this view is used to display full-text search
   505     """this view is used to display full-text search
   932 
   506 
   933     It tries to highlight part of data where the search word appears.
   507     It tries to highlight part of data where the search word appears.
   960                         contexts.append(ctx)
   534                         contexts.append(ctx)
   961                 value = u'\n' + highlighted.join(contexts)
   535                 value = u'\n' + highlighted.join(contexts)
   962                 self.w(value.replace('\n', '<br/>'))            
   536                 self.w(value.replace('\n', '<br/>'))            
   963 
   537 
   964 
   538 
   965 class TooltipView(OneLineView):
   539 class TooltipView(EntityView):
   966     """A entity view used in a tooltip"""
   540     """A entity view used in a tooltip"""
   967     id = 'tooltip'
   541     id = 'tooltip'
   968     title = None # don't display in possible views
       
   969     def cell_call(self, row, col):
   542     def cell_call(self, row, col):
   970         self.wview('oneline', self.rset, row=row, col=col)
   543         self.wview('oneline', self.rset, row=row, col=col)
       
   544 
       
   545 
       
   546 # XXX bw compat
       
   547 
       
   548 from logilab.common.deprecation import class_moved
   971 
   549 
   972 try:
   550 try:
   973     from cubicweb.web.views.tableview import TableView
   551     from cubicweb.web.views.tableview import TableView
   974     from logilab.common.deprecation import class_moved
       
   975     TableView = class_moved(TableView)
   552     TableView = class_moved(TableView)
   976 except ImportError:
   553 except ImportError:
   977     pass # gae has no tableview module (yet)
   554     pass # gae has no tableview module (yet)
       
   555 
       
   556 from cubicweb.web.views import boxes, xmlrss
       
   557 SideBoxView = class_moved(boxes.SideBoxView)
       
   558 XmlView = class_moved(xmlrss.XmlView)
       
   559 XmlItemView = class_moved(xmlrss.XmlItemView)
       
   560 XmlRsetView = class_moved(xmlrss.XmlRsetView)
       
   561 RssView = class_moved(xmlrss.RssView)
       
   562 RssItemView = class_moved(xmlrss.RssItemView)
       
   563