--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/cubicweb/web/views/xmlrss.py Sat Jan 16 13:48:51 2016 +0100
@@ -0,0 +1,298 @@
+# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
+#
+# This file is part of CubicWeb.
+#
+# CubicWeb is free software: you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+"""base xml and rss views"""
+
+__docformat__ = "restructuredtext en"
+from cubicweb import _
+
+from base64 import b64encode
+from time import timezone
+
+from six.moves import range
+
+from logilab.mtconverter import xml_escape
+
+from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset,
+ appobject_selectable, adaptable)
+from cubicweb.view import EntityView, EntityAdapter, AnyRsetView, Component
+from cubicweb.uilib import simple_sgml_tag
+from cubicweb.web import httpcache, component
+
+def encode_bytes(value):
+ return '<![CDATA[%s]]>' % b64encode(value.getvalue())
+
+# see cubicweb.sobjects.parser.DEFAULT_CONVERTERS
+SERIALIZERS = {
+ 'String': xml_escape,
+ 'Bytes': encode_bytes,
+ 'Date': lambda x: x.strftime('%Y-%m-%d'),
+ 'Datetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'),
+ 'Time': lambda x: x.strftime('%H:%M:%S'),
+ 'TZDatetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), # XXX TZ
+ 'TZTime': lambda x: x.strftime('%H:%M:%S'),
+ 'Interval': lambda x: x.days * 60*60*24 + x.seconds,
+ }
+
+# base xml views ##############################################################
+
+class XMLView(EntityView):
+ """xml view for entities"""
+ __regid__ = 'xml'
+ title = _('xml export (entities)')
+ templatable = False
+ content_type = 'text/xml'
+ xml_root = 'rset'
+ item_vid = 'xmlitem'
+
+ def cell_call(self, row, col):
+ self.wview(self.item_vid, self.cw_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._cw.encoding)
+ self.w(u'<%s size="%s">\n' % (self.xml_root, len(self.cw_rset)))
+ for i in range(self.cw_rset.rowcount):
+ self.cell_call(i, 0)
+ self.w(u'</%s>\n' % self.xml_root)
+
+
+class XMLItemView(EntityView):
+ __regid__ = 'xmlitem'
+
+ def entity_call(self, entity):
+ """element as an item for an xml feed"""
+ entity.complete()
+ source = entity.cw_metainformation()['source']['uri']
+ self.w(u'<%s eid="%s" cwuri="%s" cwsource="%s">\n'
+ % (entity.cw_etype, entity.eid, xml_escape(entity.cwuri),
+ xml_escape(source)))
+ for rschema, attrschema in sorted(entity.e_schema.attribute_definitions()):
+ attr = rschema.type
+ if attr in ('eid', 'cwuri'):
+ continue
+ else:
+ try:
+ value = entity.cw_attr_cache[attr]
+ except KeyError:
+ # Bytes
+ continue
+ if value is None:
+ self.w(u' <%s/>\n' % attr)
+ else:
+ try:
+ value = SERIALIZERS[attrschema](value)
+ except KeyError:
+ pass
+ self.w(u' <%s>%s</%s>\n' % (attr, value, attr))
+ for relstr in self._cw.list_form_param('relation'):
+ try:
+ rtype, role = relstr.split('-')
+ except ValueError:
+ self.error('badly formated relation name %r', relstr)
+ continue
+ if role == 'subject':
+ getrschema = entity.e_schema.subjrels
+ elif role == 'object':
+ getrschema = entity.e_schema.objrels
+ else:
+ self.error('badly formated relation name %r', relstr)
+ continue
+ if not rtype in getrschema:
+ self.error('unexisting relation %r', relstr)
+ continue
+ self.w(u' <%s role="%s">\n' % (rtype, role))
+ self.wview('xmlrelateditem', entity.related(rtype, role, safe=True), 'null')
+ self.w(u' </%s>\n' % rtype)
+ self.w(u'</%s>\n' % (entity.e_schema))
+
+
+class XMLRelatedItemView(EntityView):
+ __regid__ = 'xmlrelateditem'
+ add_div_section = False
+
+ def entity_call(self, entity):
+ # XXX put unique attributes as xml attribute, they are much probably
+ # used to search existing entities in client data feed, and putting it
+ # here may avoid an extra request to get those attributes values
+ self.w(u' <%s eid="%s" cwuri="%s"/>\n'
+ % (entity.e_schema, entity.eid, xml_escape(entity.cwuri)))
+
+
+class XMLRelatedItemStateView(XMLRelatedItemView):
+ __select__ = is_instance('State')
+
+ def entity_call(self, entity):
+ self.w(u' <%s eid="%s" cwuri="%s" name="%s"/>\n'
+ % (entity.e_schema, entity.eid, xml_escape(entity.cwuri),
+ xml_escape(entity.name)))
+
+
+class XMLRsetView(AnyRsetView):
+ """dumps raw rset as xml"""
+ __regid__ = 'rsetxml'
+ title = _('xml export')
+ templatable = False
+ content_type = 'text/xml'
+ xml_root = 'rset'
+
+ def call(self):
+ w = self.w
+ rset, descr = self.cw_rset, self.cw_rset.description
+ eschema = self._cw.vreg.schema.eschema
+ labels = self.columns_labels(tr=False)
+ w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
+ w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
+ for rowindex, row in enumerate(self.cw_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).final:
+ attrs['eid'] = val
+ # csvrow.append(val) # val is eid in that case
+ val = self._cw.view('textincontext', rset,
+ row=rowindex, col=colindex)
+ else:
+ val = self._cw.view('final', rset, 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)
+
+
+# RSS stuff ###################################################################
+
+class IFeedAdapter(EntityAdapter):
+ __needs_bw_compat__ = True
+ __regid__ = 'IFeed'
+ __select__ = is_instance('Any')
+
+ def rss_feed_url(self):
+ """return a URL to the rss feed for this entity"""
+ return self.entity.absolute_url(vid='rss')
+
+
+class RSSFeedURL(Component):
+ __regid__ = 'rss_feed_url'
+ __select__ = non_final_entity()
+
+ def feed_url(self):
+ return self._cw.build_url(rql=self.cw_rset.limited_rql(), vid='rss')
+
+
+class RSSEntityFeedURL(Component):
+ __regid__ = 'rss_feed_url'
+ __select__ = one_line_rset() & adaptable('IFeed')
+
+ def feed_url(self):
+ entity = self.cw_rset.get_entity(self.cw_row or 0, self.cw_col or 0)
+ return entity.cw_adapt_to('IFeed').rss_feed_url()
+
+
+class RSSIconBox(component.CtxComponent):
+ """just display the RSS icon on uniform result set"""
+ __regid__ = 'rss'
+ __select__ = (component.CtxComponent.__select__
+ & appobject_selectable('components', 'rss_feed_url'))
+
+ visible = False
+ order = 999
+
+ def render(self, w, **kwargs):
+ try:
+ rss = self._cw.uiprops['RSS_LOGO']
+ except KeyError:
+ self.error('missing RSS_LOGO external resource')
+ return
+ urlgetter = self._cw.vreg['components'].select('rss_feed_url', self._cw,
+ rset=self.cw_rset)
+ url = urlgetter.feed_url()
+ w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (xml_escape(url), rss))
+
+
+class RSSView(XMLView):
+ __regid__ = 'rss'
+ title = _('rss export')
+ templatable = False
+ content_type = 'text/xml'
+ http_cache_manager = httpcache.MaxAgeHTTPCacheManager
+ cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
+ item_vid = 'rssitem'
+
+ def _open(self):
+ req = self._cw
+ self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
+ self.w(u'<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">\n')
+ self.w(u' <channel>\n')
+ 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._cw.build_url(**params)))
+
+ def _close(self):
+ self.w(u' </channel>\n')
+ self.w(u'</rss>')
+
+ def call(self):
+ """display a list of entities by calling their <item_vid> view"""
+ self._open()
+ for i in range(self.cw_rset.rowcount):
+ self.cell_call(i, 0)
+ self._close()
+
+ def cell_call(self, row, col):
+ self.wview(self.item_vid, self.cw_rset, row=row, col=col)
+
+
+class RSSItemView(EntityView):
+ __regid__ = 'rssitem'
+ date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
+ add_div_section = False
+
+ def cell_call(self, row, col):
+ entity = self.cw_rset.complete_entity(row, col)
+ self.w(u'<item>\n')
+ self.w(u'<guid isPermaLink="true">%s</guid>\n'
+ % xml_escape(entity.absolute_url()))
+ self.render_title_link(entity)
+ self.render_description(entity)
+ self._marker('dc:date', entity.dc_date(self.date_format))
+ self.render_entity_creator(entity)
+ self.w(u'</item>\n')
+
+ def render_description(self, entity):
+ self._marker('description', entity.dc_description(format='text/html'))
+
+ def render_title_link(self, entity):
+ self._marker('title', entity.dc_long_title())
+ self._marker('link', entity.absolute_url())
+
+ def render_entity_creator(self, entity):
+ if entity.creator:
+ self._marker('dc:creator', entity.dc_creator())
+
+ def _marker(self, marker, value):
+ if value:
+ self.w(u' <%s>%s</%s>\n' % (marker, xml_escape(value), marker))