cubicweb/web/views/xmlrss.py
changeset 11057 0b59724cb3f2
parent 10666 7f6b5f023884
child 11247 518eb10adcd5
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     3 #
       
     4 # This file is part of CubicWeb.
       
     5 #
       
     6 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     7 # terms of the GNU Lesser General Public License as published by the Free
       
     8 # Software Foundation, either version 2.1 of the License, or (at your option)
       
     9 # any later version.
       
    10 #
       
    11 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    13 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    14 # details.
       
    15 #
       
    16 # You should have received a copy of the GNU Lesser General Public License along
       
    17 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    18 """base xml and rss views"""
       
    19 
       
    20 __docformat__ = "restructuredtext en"
       
    21 from cubicweb import _
       
    22 
       
    23 from base64 import b64encode
       
    24 from time import timezone
       
    25 
       
    26 from six.moves import range
       
    27 
       
    28 from logilab.mtconverter import xml_escape
       
    29 
       
    30 from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset,
       
    31                                 appobject_selectable, adaptable)
       
    32 from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
       
    33 from cubicweb.uilib import simple_sgml_tag
       
    34 from cubicweb.web import httpcache, component
       
    35 
       
    36 def encode_bytes(value):
       
    37     return '<![CDATA[%s]]>' % b64encode(value.getvalue())
       
    38 
       
    39 # see cubicweb.sobjects.parser.DEFAULT_CONVERTERS
       
    40 SERIALIZERS = {
       
    41     'String': xml_escape,
       
    42     'Bytes': encode_bytes,
       
    43     'Date': lambda x: x.strftime('%Y-%m-%d'),
       
    44     'Datetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'),
       
    45     'Time': lambda x: x.strftime('%H:%M:%S'),
       
    46     'TZDatetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), # XXX TZ
       
    47     'TZTime': lambda x: x.strftime('%H:%M:%S'),
       
    48     'Interval': lambda x: x.days * 60*60*24 + x.seconds,
       
    49     }
       
    50 
       
    51 # base xml views ##############################################################
       
    52 
       
    53 class XMLView(EntityView):
       
    54     """xml view for entities"""
       
    55     __regid__ = 'xml'
       
    56     title = _('xml export (entities)')
       
    57     templatable = False
       
    58     content_type = 'text/xml'
       
    59     xml_root = 'rset'
       
    60     item_vid = 'xmlitem'
       
    61 
       
    62     def cell_call(self, row, col):
       
    63         self.wview(self.item_vid, self.cw_rset, row=row, col=col)
       
    64 
       
    65     def call(self):
       
    66         """display a list of entities by calling their <item_vid> view"""
       
    67         self.w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
       
    68         self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.cw_rset)))
       
    69         for i in range(self.cw_rset.rowcount):
       
    70             self.cell_call(i, 0)
       
    71         self.w(u'</%s>\n' % self.xml_root)
       
    72 
       
    73 
       
    74 class XMLItemView(EntityView):
       
    75     __regid__ = 'xmlitem'
       
    76 
       
    77     def entity_call(self, entity):
       
    78         """element as an item for an xml feed"""
       
    79         entity.complete()
       
    80         source = entity.cw_metainformation()['source']['uri']
       
    81         self.w(u'<%s eid="%s" cwuri="%s" cwsource="%s">\n'
       
    82                % (entity.cw_etype, entity.eid, xml_escape(entity.cwuri),
       
    83                   xml_escape(source)))
       
    84         for rschema, attrschema in sorted(entity.e_schema.attribute_definitions()):
       
    85             attr = rschema.type
       
    86             if attr in ('eid', 'cwuri'):
       
    87                 continue
       
    88             else:
       
    89                 try:
       
    90                     value = entity.cw_attr_cache[attr]
       
    91                 except KeyError:
       
    92                     # Bytes
       
    93                     continue
       
    94             if value is None:
       
    95                 self.w(u'  <%s/>\n' % attr)
       
    96             else:
       
    97                 try:
       
    98                     value = SERIALIZERS[attrschema](value)
       
    99                 except KeyError:
       
   100                     pass
       
   101                 self.w(u'  <%s>%s</%s>\n' % (attr, value, attr))
       
   102         for relstr in self._cw.list_form_param('relation'):
       
   103             try:
       
   104                 rtype, role = relstr.split('-')
       
   105             except ValueError:
       
   106                 self.error('badly formated relation name %r', relstr)
       
   107                 continue
       
   108             if role == 'subject':
       
   109                 getrschema = entity.e_schema.subjrels
       
   110             elif role == 'object':
       
   111                 getrschema = entity.e_schema.objrels
       
   112             else:
       
   113                 self.error('badly formated relation name %r', relstr)
       
   114                 continue
       
   115             if not rtype in getrschema:
       
   116                 self.error('unexisting relation %r', relstr)
       
   117                 continue
       
   118             self.w(u'  <%s role="%s">\n' % (rtype, role))
       
   119             self.wview('xmlrelateditem', entity.related(rtype, role, safe=True), 'null')
       
   120             self.w(u'  </%s>\n' % rtype)
       
   121         self.w(u'</%s>\n' % (entity.e_schema))
       
   122 
       
   123 
       
   124 class XMLRelatedItemView(EntityView):
       
   125     __regid__ = 'xmlrelateditem'
       
   126     add_div_section = False
       
   127 
       
   128     def entity_call(self, entity):
       
   129         # XXX put unique attributes as xml attribute, they are much probably
       
   130         # used to search existing entities in client data feed, and putting it
       
   131         # here may avoid an extra request to get those attributes values
       
   132         self.w(u'    <%s eid="%s" cwuri="%s"/>\n'
       
   133                % (entity.e_schema, entity.eid, xml_escape(entity.cwuri)))
       
   134 
       
   135 
       
   136 class XMLRelatedItemStateView(XMLRelatedItemView):
       
   137     __select__ = is_instance('State')
       
   138 
       
   139     def entity_call(self, entity):
       
   140         self.w(u'    <%s eid="%s" cwuri="%s" name="%s"/>\n'
       
   141                % (entity.e_schema, entity.eid, xml_escape(entity.cwuri),
       
   142                   xml_escape(entity.name)))
       
   143 
       
   144 
       
   145 class XMLRsetView(AnyRsetView):
       
   146     """dumps raw rset as xml"""
       
   147     __regid__ = 'rsetxml'
       
   148     title = _('xml export')
       
   149     templatable = False
       
   150     content_type = 'text/xml'
       
   151     xml_root = 'rset'
       
   152 
       
   153     def call(self):
       
   154         w = self.w
       
   155         rset, descr = self.cw_rset, self.cw_rset.description
       
   156         eschema = self._cw.vreg.schema.eschema
       
   157         labels = self.columns_labels(tr=False)
       
   158         w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
       
   159         w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
       
   160         for rowindex, row in enumerate(self.cw_rset):
       
   161             w(u' <row>\n')
       
   162             for colindex, val in enumerate(row):
       
   163                 etype = descr[rowindex][colindex]
       
   164                 tag = labels[colindex]
       
   165                 attrs = {}
       
   166                 if '(' in tag:
       
   167                     attrs['expr'] = tag
       
   168                     tag = 'funccall'
       
   169                 if val is not None and not eschema(etype).final:
       
   170                     attrs['eid'] = val
       
   171                     # csvrow.append(val) # val is eid in that case
       
   172                     val = self._cw.view('textincontext', rset,
       
   173                                         row=rowindex, col=colindex)
       
   174                 else:
       
   175                     val = self._cw.view('final', rset, row=rowindex,
       
   176                                         col=colindex, format='text/plain')
       
   177                 w(simple_sgml_tag(tag, val, **attrs))
       
   178             w(u' </row>\n')
       
   179         w(u'</%s>\n' % self.xml_root)
       
   180 
       
   181 
       
   182 # RSS stuff ###################################################################
       
   183 
       
   184 class IFeedAdapter(EntityAdapter):
       
   185     __needs_bw_compat__ = True
       
   186     __regid__ = 'IFeed'
       
   187     __select__ = is_instance('Any')
       
   188 
       
   189     def rss_feed_url(self):
       
   190         """return a URL to the rss feed for this entity"""
       
   191         return self.entity.absolute_url(vid='rss')
       
   192 
       
   193 
       
   194 class RSSFeedURL(Component):
       
   195     __regid__ = 'rss_feed_url'
       
   196     __select__ = non_final_entity()
       
   197 
       
   198     def feed_url(self):
       
   199         return self._cw.build_url(rql=self.cw_rset.limited_rql(), vid='rss')
       
   200 
       
   201 
       
   202 class RSSEntityFeedURL(Component):
       
   203     __regid__ = 'rss_feed_url'
       
   204     __select__ = one_line_rset() & adaptable('IFeed')
       
   205 
       
   206     def feed_url(self):
       
   207         entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
       
   208         return entity.cw_adapt_to('IFeed').rss_feed_url()
       
   209 
       
   210 
       
   211 class RSSIconBox(component.CtxComponent):
       
   212     """just display the RSS icon on uniform result set"""
       
   213     __regid__ = 'rss'
       
   214     __select__ = (component.CtxComponent.__select__
       
   215                   & appobject_selectable('components', 'rss_feed_url'))
       
   216 
       
   217     visible = False
       
   218     order = 999
       
   219 
       
   220     def render(self, w, **kwargs):
       
   221         try:
       
   222             rss = self._cw.uiprops['RSS_LOGO']
       
   223         except KeyError:
       
   224             self.error('missing RSS_LOGO external resource')
       
   225             return
       
   226         urlgetter = self._cw.vreg['components'].select('rss_feed_url', self._cw,
       
   227                                                        rset=self.cw_rset)
       
   228         url = urlgetter.feed_url()
       
   229         w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (xml_escape(url), rss))
       
   230 
       
   231 
       
   232 class RSSView(XMLView):
       
   233     __regid__ = 'rss'
       
   234     title = _('rss export')
       
   235     templatable = False
       
   236     content_type = 'text/xml'
       
   237     http_cache_manager = httpcache.MaxAgeHTTPCacheManager
       
   238     cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
       
   239     item_vid = 'rssitem'
       
   240 
       
   241     def _open(self):
       
   242         req = self._cw
       
   243         self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
       
   244         self.w(u'<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">\n')
       
   245         self.w(u'  <channel>\n')
       
   246         self.w(u'    <title>%s RSS Feed</title>\n'
       
   247                % xml_escape(self.page_title()))
       
   248         self.w(u'    <description>%s</description>\n'
       
   249                % xml_escape(req.form.get('vtitle', '')))
       
   250         params = req.form.copy()
       
   251         params.pop('vid', None)
       
   252         self.w(u'    <link>%s</link>\n' % xml_escape(self._cw.build_url(**params)))
       
   253 
       
   254     def _close(self):
       
   255         self.w(u'  </channel>\n')
       
   256         self.w(u'</rss>')
       
   257 
       
   258     def call(self):
       
   259         """display a list of entities by calling their <item_vid> view"""
       
   260         self._open()
       
   261         for i in range(self.cw_rset.rowcount):
       
   262             self.cell_call(i, 0)
       
   263         self._close()
       
   264 
       
   265     def cell_call(self, row, col):
       
   266         self.wview(self.item_vid, self.cw_rset, row=row, col=col)
       
   267 
       
   268 
       
   269 class RSSItemView(EntityView):
       
   270     __regid__ = 'rssitem'
       
   271     date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
       
   272     add_div_section = False
       
   273 
       
   274     def cell_call(self, row, col):
       
   275         entity = self.cw_rset.complete_entity(row, col)
       
   276         self.w(u'<item>\n')
       
   277         self.w(u'<guid isPermaLink="true">%s</guid>\n'
       
   278                % xml_escape(entity.absolute_url()))
       
   279         self.render_title_link(entity)
       
   280         self.render_description(entity)
       
   281         self._marker('dc:date', entity.dc_date(self.date_format))
       
   282         self.render_entity_creator(entity)
       
   283         self.w(u'</item>\n')
       
   284 
       
   285     def render_description(self, entity):
       
   286         self._marker('description', entity.dc_description(format='text/html'))
       
   287 
       
   288     def render_title_link(self, entity):
       
   289         self._marker('title', entity.dc_long_title())
       
   290         self._marker('link', entity.absolute_url())
       
   291 
       
   292     def render_entity_creator(self, entity):
       
   293         if entity.creator:
       
   294             self._marker('dc:creator', entity.dc_creator())
       
   295 
       
   296     def _marker(self, marker, value):
       
   297         if value:
       
   298             self.w(u'  <%s>%s</%s>\n' % (marker, xml_escape(value), marker))