xmlrss.py 10.6 KB
Newer Older
Sylvain Thénault's avatar
Sylvain Thénault committed
1
# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
2
3
4
5
6
7
8
9
10
# 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.
#
11
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
12
13
14
15
16
17
# 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/>.
18
"""base xml and rss views"""
19

20
from cubicweb import _
21

22
from base64 import b64encode
23
24
25
26
from time import timezone

from logilab.mtconverter import xml_escape

27
from cubicweb.predicates import (is_instance, non_final_entity, one_line_rset,
Sylvain Thénault's avatar
Sylvain Thénault committed
28
                                 appobject_selectable, adaptable)
29
30
from cubicweb.view import EntityView, AnyRsetView, Component
from cubicweb.entity import EntityAdapter
Sylvain Thénault's avatar
Sylvain Thénault committed
31
from cubicweb.uilib import simple_sgml_tag
32
from cubicweb.web import httpcache, component
33

Sylvain Thénault's avatar
Sylvain Thénault committed
34

35
36
37
38
39
40
41
42
43
44
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'),
45
46
    'TZDatetime': lambda x: x.strftime('%Y-%m-%d %H:%M:%S'), # XXX TZ
    'TZTime': lambda x: x.strftime('%H:%M:%S'),
47
48
    'Interval': lambda x: x.days * 60*60*24 + x.seconds,
    }
49
50

# base xml views ##############################################################
51

52
class XMLView(EntityView):
53
    """xml view for entities"""
54
    __regid__ = 'xml'
55
    title = _('xml export (entities)')
56
57
58
59
    templatable = False
    content_type = 'text/xml'
    xml_root = 'rset'
    item_vid = 'xmlitem'
60

61
    def cell_call(self, row, col):
62
        self.wview(self.item_vid, self.cw_rset, row=row, col=col)
63

64
65
    def call(self):
        """display a list of entities by calling their <item_vid> view"""
66
67
        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)))
68
        for i in range(self.cw_rset.rowcount):
69
70
71
72
            self.cell_call(i, 0)
        self.w(u'</%s>\n' % self.xml_root)


73
class XMLItemView(EntityView):
74
    __regid__ = 'xmlitem'
75

76
77
78
    def entity_call(self, entity):
        """element as an item for an xml feed"""
        entity.complete()
79
        source = entity.cw_source[0].name
80
        self.w(u'<%s eid="%s" cwuri="%s" cwsource="%s">\n'
81
               % (entity.cw_etype, entity.eid, xml_escape(entity.cwuri),
82
                  xml_escape(source)))
83
        for rschema, attrschema in sorted(entity.e_schema.attribute_definitions()):
84
            attr = rschema.type
85
86
            if attr in ('eid', 'cwuri'):
                continue
87
88
            else:
                try:
89
                    value = entity.cw_attr_cache[attr]
90
91
92
                except KeyError:
                    # Bytes
                    continue
93
94
95
            if value is None:
                self.w(u'  <%s/>\n' % attr)
            else:
Julien Cristau's avatar
Julien Cristau committed
96
                if attrschema in SERIALIZERS:
97
                    value = SERIALIZERS[attrschema](value)
98
                self.w(u'  <%s>%s</%s>\n' % (attr, value, attr))
99
100
101
102
103
104
105
106
107
108
109
110
111
        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
Sylvain Thénault's avatar
Sylvain Thénault committed
112
            if rtype not in getrschema:
113
114
115
                self.error('unexisting relation %r', relstr)
                continue
            self.w(u'  <%s role="%s">\n' % (rtype, role))
116
            self.wview('xmlrelateditem', entity.related(rtype, role, safe=True), 'null')
117
            self.w(u'  </%s>\n' % rtype)
118
119
        self.w(u'</%s>\n' % (entity.e_schema))

120

121
122
class XMLRelatedItemView(EntityView):
    __regid__ = 'xmlrelateditem'
123
    add_div_section = False
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141

    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)))


142
class XMLRsetView(AnyRsetView):
143
    """dumps raw rset as xml"""
144
    __regid__ = 'rsetxml'
145
146
147
148
    title = _('xml export')
    templatable = False
    content_type = 'text/xml'
    xml_root = 'rset'
149

150
151
    def call(self):
        w = self.w
152
        rset, descr = self.cw_rset, self.cw_rset.description
Sandrine Ribeau's avatar
Sandrine Ribeau committed
153
        eschema = self._cw.vreg.schema.eschema
154
        labels = self.columns_labels(tr=False)
155
        w(u'<?xml version="1.0" encoding="%s"?>\n' % self._cw.encoding)
156
        w(u'<%s query="%s">\n' % (self.xml_root, xml_escape(rset.printable_rql())))
157
        for rowindex, row in enumerate(self.cw_rset):
158
159
160
161
162
163
164
165
            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'
166
                if val is not None and not eschema(etype).final:
167
168
                    attrs['eid'] = val
                    # csvrow.append(val) # val is eid in that case
169
170
                    val = self._cw.view('textincontext', rset,
                                        row=rowindex, col=colindex)
171
                else:
172
173
                    val = self._cw.view('final', rset, row=rowindex,
                                        col=colindex, format='text/plain')
174
                w(simple_sgml_tag(tag, val, **attrs))
Julien Cristau's avatar
Julien Cristau committed
175
            w(u'\n </row>\n')
176
        w(u'</%s>\n' % self.xml_root)
177

178

179
180
# RSS stuff ###################################################################

181
class IFeedAdapter(EntityAdapter):
182
    __needs_bw_compat__ = True
183
    __regid__ = 'IFeed'
184
    __select__ = is_instance('Any')
185
186

    def rss_feed_url(self):
Rémi Cardona's avatar
Rémi Cardona committed
187
        """return a URL to the rss feed for this entity"""
188
        return self.entity.absolute_url(vid='rss')
189
190


191
class RSSFeedURL(Component):
192
    __regid__ = 'rss_feed_url'
193
    __select__ = non_final_entity()
194

195
    def feed_url(self):
196
        return self._cw.build_url(rql=self.cw_rset.limited_rql(), vid='rss')
197
198
199


class RSSEntityFeedURL(Component):
200
    __regid__ = 'rss_feed_url'
201
    __select__ = one_line_rset() & adaptable('IFeed')
202

203
    def feed_url(self):
204
205
        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()
206

207

208
class RSSIconBox(component.CtxComponent):
209
    """just display the RSS icon on uniform result set"""
210
    __regid__ = 'rss'
211
    __select__ = (component.CtxComponent.__select__
212
                  & appobject_selectable('components', 'rss_feed_url'))
213

214
215
    visible = False
    order = 999
216

217
    def render(self, w, **kwargs):
218
        try:
219
            rss = self._cw.uiprops['RSS_LOGO']
220
221
222
        except KeyError:
            self.error('missing RSS_LOGO external resource')
            return
223
        urlgetter = self._cw.vreg['components'].select('rss_feed_url', self._cw,
224
                                                       rset=self.cw_rset)
225
        url = urlgetter.feed_url()
226
        w(u'<a href="%s"><img src="%s" alt="rss"/></a>\n' % (xml_escape(url), rss))
227

228

229
class RSSView(XMLView):
230
    __regid__ = 'rss'
231
    title = _('rss export')
232
233
    templatable = False
    content_type = 'text/xml'
234
    http_cache_manager = httpcache.MaxAgeHTTPCacheManager
235
    cache_max_age = 60*60*2 # stay in http cache for 2 hours by default
236
    item_vid = 'rssitem'
237
238

    def _open(self):
239
        req = self._cw
240
        self.w(u'<?xml version="1.0" encoding="%s"?>\n' % req.encoding)
241
242
        self.w(u'<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">\n')
        self.w(u'  <channel>\n')
243
244
245
246
        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', '')))
247
248
        params = req.form.copy()
        params.pop('vid', None)
249
        self.w(u'    <link>%s</link>\n' % xml_escape(self._cw.build_url(**params)))
250
251

    def _close(self):
252
        self.w(u'  </channel>\n')
253
254
255
256
257
        self.w(u'</rss>')

    def call(self):
        """display a list of entities by calling their <item_vid> view"""
        self._open()
258
        for i in range(self.cw_rset.rowcount):
259
            self.cell_call(i, 0)
260
        self._close()
261

262
    def cell_call(self, row, col):
263
        self.wview(self.item_vid, self.cw_rset, row=row, col=col)
264

sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
265

266
class RSSItemView(EntityView):
267
    __regid__ = 'rssitem'
268
    date_format = '%%Y-%%m-%%dT%%H:%%M%+03i:00' % (timezone / 3600)
269
    add_div_section = False
270
271

    def cell_call(self, row, col):
272
        entity = self.cw_rset.complete_entity(row, col)
273
        self.w(u'<item>\n')
274
275
        self.w(u'<guid isPermaLink="true">%s</guid>\n'
               % xml_escape(entity.absolute_url()))
276
        self.render_title_link(entity)
277
        self.render_description(entity)
278
279
280
281
        self._marker('dc:date', entity.dc_date(self.date_format))
        self.render_entity_creator(entity)
        self.w(u'</item>\n')

282
283
284
    def render_description(self, entity):
        self._marker('description', entity.dc_description(format='text/html'))

285
    def render_title_link(self, entity):
286
287
        self._marker('title', entity.dc_long_title())
        self._marker('link', entity.absolute_url())
288
289

    def render_entity_creator(self, entity):
290
        if entity.creator:
291
292
            self._marker('dc:creator', entity.dc_creator())

293
294
    def _marker(self, marker, value):
        if value:
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
295
            self.w(u'  <%s>%s</%s>\n' % (marker, xml_escape(value), marker))