changeset 879:2e0e9cf9efb4

blog (atom feed): fixed atom handling: - atom generation code moved from backend to Libervia - replaced hardcoded urls by urls based on request path - added xmpp: uri as alternate links - feed id was not unique (it was the node), now it is the quoted xmpp uri - atom:updated was using current time which is wrong, now take the more recent item "updated" value - link elements where missing attributes (e.g.: type) - added correct http(s) links for each items - microblog items (without title) now use an abstract of content as title - use 20 as max_items for atom feeds (may change in the future) - removed bad use of encode
author Goffi <goffi@goffi.org>
date Wed, 09 Mar 2016 17:33:00 +0100
parents 2aaac0605ae2
children ccbad50e1426
files src/server/blog.py
diffstat 1 files changed, 112 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/src/server/blog.py	Sat Mar 05 13:55:24 2016 +0100
+++ b/src/server/blog.py	Wed Mar 09 17:33:00 2016 +0100
@@ -23,11 +23,13 @@
 from sat.core.log import getLogger
 log = getLogger(__name__)
 from sat.tools import common
+from sat.tools import xml_tools
 from dbus.exceptions import DBusException
 from twisted.internet import defer
 from twisted.web import server
 from twisted.web.resource import Resource
 from twisted.words.protocols.jabber.jid import JID
+from twisted.words.xish import domish
 from jinja2 import Environment, PackageLoader
 from datetime import datetime
 import re
@@ -38,10 +40,15 @@
 from libervia.server.html_tools import sanitizeHtml, convertNewLinesToXHTML
 from libervia.server.constants import Const as C
 
+NS_ATOM = 'http://www.w3.org/2005/Atom'
+ATOM_MAX_ITEMS = 20
 PARAMS_TO_GET = (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION)
 re_strip_empty_div = re.compile(r"<div ?/>|<div> *?</div>")
 
 # TODO: chech disco features and use max_items when RSM is not available
+# FIXME: Deferred are not used
+# FIXME: change navigation links handling, this is is fragile
+# TODO: refactorise this
 
 
 def getDefaultQueryData(request):
@@ -174,22 +181,20 @@
         request.extra_dict = {} # will be used for RSM and MAM
         self.parseURLParams(request)
         if request.item_id:
+            # FIXME: this part seems useless
             # we want a specific item
-            item_ids = [request.item_id]
+            # item_ids = [request.item_id]
             # max_items = 1
             max_items = 0 # FIXME
         else:
-            item_ids = []
             # max_items = int(request.extra_dict['rsm_max']) # FIXME
             max_items = 0
             # TODO: use max_items only when RSM is not available
 
         if request.atom:
             request.extra_dict.update(request.mam_extra)
-            self.host.bridge.mbGetAtom(pub_jid.userhost(), '', max_items, item_ids,
-                                       request.extra_dict, C.SERVICE_PROFILE,
-                                       lambda feed: self.renderAtomFeed(feed, request),
-                                       lambda failure: self.renderError(failure, request, pub_jid))
+            self.getAtom(pub_jid, ATOM_MAX_ITEMS, request.extra_dict, request.extra_comments_dict, request, profile)
+
         elif request.item_id:
             # we can't merge mam_extra now because we'll use item_ids
             self.getItemById(pub_jid, request.item_id, request.extra_dict,
@@ -377,6 +382,107 @@
                                                    max_comments, extra_dict, extra_comments_dict,
                                                    C.SERVICE_PROFILE, callback=getResult)
 
+    def getAtom(self, pub_jid, max_items, extra_dict, extra_comments_dict, request, profile):
+        """
+
+        @param pub_jid (jid.JID): publisher JID
+        @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit
+        @param extra_dict (dict): extra configuration for initial items only
+        @param extra_comments_dict (dict): extra configuration for comments only
+        @param request: HTTP request
+        @param profile
+        """
+        def gotItems(data):
+            # Generate a clean atom feed with uri linking to this blog
+            # from microblog data
+            items, metadata= data
+            feed_elt = domish.Element((NS_ATOM, u'feed'))
+            title = _(u"{user}'s blog").format(user=profile)
+            feed_elt.addElement(u'title', content=title)
+            url_path = request.URLPath()
+            base_blog_url = u"{0.scheme}://{0.netloc}/blog/{user}".format(url_path, user=profile)
+
+            # atom link
+            link_feed_elt = feed_elt.addElement('link')
+            link_feed_elt['href'] = u'{base}/atom.xml'.format(base=base_blog_url)
+            link_feed_elt['type'] = u'application/atom+xml'
+            link_feed_elt['rel'] = u'self'
+
+            # blog link
+            link_blog_elt = feed_elt.addElement('link')
+            link_blog_elt['rel'] = u'alternate'
+            link_blog_elt['type'] = u'text/html'
+            link_blog_elt['href'] = base_blog_url
+
+            # blog link XMPP uri
+            blog_xmpp_uri = metadata['uri']
+            link_blog_elt = feed_elt.addElement('link')
+            link_blog_elt['rel'] = u'alternate'
+            link_blog_elt['type'] = u'application/xmpp+xml'
+            link_blog_elt['href'] = blog_xmpp_uri
+
+            feed_elt.addElement('id', content=urllib.quote(blog_xmpp_uri))
+            updated_unix = max([float(item['updated']) for item in items])
+            updated_dt = datetime.fromtimestamp(updated_unix)
+            feed_elt.addElement(u'updated', u'{}Z'.format(updated_dt.isoformat("T")))
+
+            for item in items:
+                entry_elt = feed_elt.addElement(u'entry')
+
+                # Title
+                try:
+                    title = item['title']
+                except KeyError:
+                    # for microblog (without title), we use an abstract of content as title
+                    title = u'{}...'.format(u' '.join(item['content'][:70].split()))
+                entry_elt.addElement(u'title', content=title)
+
+                # HTTP link
+                http_link_elt = entry_elt.addElement(u'link')
+                http_link_elt['rel'] = u'alternate'
+                http_link_elt['type'] = u'text/html'
+                http_link_elt['href'] = u'{base}/{quoted_id}'.format(base=base_blog_url, quoted_id=urllib.quote(item['id']))
+                # XMPP link
+                xmpp_link_elt = entry_elt.addElement(u'link')
+                xmpp_link_elt['rel'] = u'alternate'
+                xmpp_link_elt['type'] = u'application/xmpp+xml'
+                xmpp_link_elt['href'] = u'{blog_uri};item={item_id}'.format(blog_uri=blog_xmpp_uri, item_id=item['id'])
+
+                # date metadata
+                entry_elt.addElement(u'id', content=item['atom_id'])
+                updated = datetime.fromtimestamp(float(item['updated']))
+                entry_elt.addElement(u'updated', u'{}Z'.format(updated.isoformat("T")))
+                published = datetime.fromtimestamp(float(item['published']))
+                entry_elt.addElement(u'published', u'{}Z'.format(published.isoformat("T")))
+
+                # author metadata
+                author_elt = entry_elt.addElement(u'author')
+                author_elt.addElement('name', item.get('author', profile))
+                try:
+                    author_elt.addElement('uri', u'xmpp:{}'.format(item['author_jid']))
+                except KeyError:
+                    pass
+                try:
+                    author_elt.addElement('email', item['author_email'])
+                except KeyError:
+                    pass
+
+                # content
+                try:
+                    content_xhtml = item['content_xhtml']
+                except KeyError:
+                    content_elt = entry_elt.addElement('content', content='content')
+                    content_elt['type'] = 'text'
+                else:
+                    content_elt = entry_elt.addElement('content')
+                    content_elt['type'] = 'xhtml'
+                    content_elt.addChild(xml_tools.ElementParser()(content_xhtml, namespace=C.NS_XHTML))
+
+            atom_feed = u'<?xml version="1.0" encoding="utf-8"?>\n{}'.format(feed_elt.toXml())
+            self.renderAtomFeed(atom_feed, request),
+
+        self.host.bridge.mbGet(pub_jid.userhost(), '', max_items, [], extra_dict, C.SERVICE_PROFILE, callback=gotItems)
+
     ## rendering
 
     def _updateDict(self, value, dict_, key):