Changeset - 6c9e8aa906cb
[Not reviewed]
default
0 1 0
Mads Kiilerich - 6 years ago 2019-12-09 02:29:04
mads@kiilerich.com
feeds: replace webhelpers.feedgenerator with simple mako templating for rendering RSS/Atom XML

Most of the complexity in RSS libraries is in dynamically supporting all kinds
of attributes. For our use, we have a small static set of attributes we use,
and it is simpler to just use mako.

Also, webhelpers is dead, and the alternatives seem quite heavy.
1 file changed with 101 insertions and 8 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/feeds.py
Show inline comments
 
@@ -22,38 +22,131 @@ Shared code for providing RSS and ATOM f
 
import datetime
 
import re
 

	
 
from webhelpers import feedgenerator
 
import mako.template
 

	
 

	
 
language = 'en-us'
 
ttl = "5"
 

	
 

	
 
# From ``django.utils.feedgenerator`` via webhelpers.feedgenerator
 
def rfc2822_date(date):
 
    # We do this ourselves to be timezone aware, email.Utils is not tz aware.
 
    if getattr(date, "tzinfo", False):
 
        time_str = date.strftime('%a, %d %b %Y %H:%M:%S ')
 
        offset = date.tzinfo.utcoffset(date)
 
        timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
 
        hour, minute = divmod(timezone, 60)
 
        return time_str + "%+03d%02d" % (hour, minute)
 
    else:
 
        return date.strftime('%a, %d %b %Y %H:%M:%S -0000')
 

	
 
# From ``django.utils.feedgenerator`` via webhelpers.feedgenerator
 
def rfc3339_date(date):
 
    if getattr(date, "tzinfo", False):
 
        time_str = date.strftime('%Y-%m-%dT%H:%M:%S')
 
        offset = date.tzinfo.utcoffset(date)
 
        timezone = (offset.days * 24 * 60) + (offset.seconds / 60)
 
        hour, minute = divmod(timezone, 60)
 
        return time_str + "%+03d:%02d" % (hour, minute)
 
    else:
 
        return date.strftime('%Y-%m-%dT%H:%M:%SZ')
 

	
 
# From ``django.utils.feedgenerator`` via webhelpers.feedgenerator
 
def get_tag_uri(url, date):
 
    "Creates a TagURI. See http://diveintomark.org/archives/2004/05/28/howto-atom-id"
 
    tag = re.sub('^http://', '', url)
 
    if date is not None:
 
        tag = re.sub('/', ',%s:/' % date.strftime('%Y-%m-%d'), tag, 1)
 
    tag = re.sub('#', '/', tag)
 
    return u'tag:' + tag
 

	
 

	
 
class Attributes(object):
 
    """Simple namespace for attribute dict access in mako and elsewhere"""
 
    def __init__(self, a_dict):
 
        self.__dict__ = a_dict
 

	
 

	
 
class _Feeder(object):
 

	
 
    content_type = None
 
    feed_factory = None  # a webhelpers.feedgenerator
 
    template = None  # subclass must provide a mako.template.Template
 

	
 
    @classmethod
 
    def render(cls, header, entries):
 
        feed = cls.feed_factory(
 
        try:
 
            latest_pubdate = max(
 
                pubdate for pubdate in (e.get('pubdate') for e in entries)
 
                if pubdate
 
            )
 
        except ValueError:  # max() arg is an empty sequence ... or worse
 
            latest_pubdate = datetime.datetime.now()
 

	
 
        return cls.template.render(
 
            language=language,
 
            ttl=ttl,  # rss only
 
            latest_pubdate=latest_pubdate,
 
            rfc2822_date=rfc2822_date,  # for RSS
 
            rfc3339_date=rfc3339_date,  # for Atom
 
            get_tag_uri=get_tag_uri,
 
            entries=[Attributes(e) for e in entries],
 
            **header
 
        )
 
        for e in entries:
 
            feed.add_item(**e)
 
        return feed.writeString('utf-8')
 

	
 

	
 
class AtomFeed(_Feeder):
 

	
 
    content_type = 'application/atom+xml'
 

	
 
    feed_factory = feedgenerator.Atom1Feed
 
    template = mako.template.Template('''\
 
<?xml version="1.0" encoding="utf-8"?>
 
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="${language}">
 
  <title>${title}</title>
 
  <link href="${link}" rel="alternate"></link>
 
  <id>${link}</id>
 
  <updated>${rfc3339_date(latest_pubdate)}</updated>
 
  % for entry in entries:
 
  <entry>
 
    <title>${entry.title}</title>
 
    <link href="${entry.link}" rel="alternate"></link>
 
    <updated>${rfc3339_date(entry.pubdate)}</updated>
 
    <published>${rfc3339_date(entry.pubdate)}</published>
 
    <author>
 
      <name>${entry.author_name}</name>
 
      <email>${entry.author_email}</email>
 
    </author>
 
    <id>${get_tag_uri(entry.link, entry.pubdate)}</id>
 
    <summary type="html">${entry.description}</summary>
 
  </entry>
 
  % endfor
 
</feed>
 
''', default_filters=['x'], output_encoding='utf-8', encoding_errors='replace')
 

	
 

	
 
class RssFeed(_Feeder):
 

	
 
    content_type = 'application/rss+xml'
 

	
 
    feed_factory = feedgenerator.Rss201rev2Feed
 
    template = mako.template.Template('''\
 
<?xml version="1.0" encoding="utf-8"?>
 
<rss version="2.0">
 
  <channel>
 
    <title>${title}</title>
 
    <link>${link}</link>
 
    <description>${description}</description>
 
    <language>${language}</language>
 
    <lastBuildDate>${rfc2822_date(latest_pubdate)}</lastBuildDate>
 
    <ttl>${ttl}</ttl>
 
    % for entry in entries:
 
    <item>
 
      <title>${entry.title}</title>
 
      <link>${entry.link}</link>
 
      <description>${entry.description}</description>
 
      <author>${entry.author_email} (${entry.author_name})</author>
 
      <pubDate>${rfc2822_date(entry.pubdate)}</pubDate>
 
    </item>
 
    % endfor
 
  </channel>
 
</rss>
 
''', default_filters=['x'], output_encoding='utf-8', encoding_errors='replace')
0 comments (0 inline, 0 general)