Mercurial > kallithea
changeset 8019:6c9e8aa906cb
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.
author | Mads Kiilerich <mads@kiilerich.com> |
---|---|
date | Mon, 09 Dec 2019 02:29:04 +0100 |
parents | 68e802950fe4 |
children | 18d146a04fde |
files | kallithea/lib/feeds.py |
diffstat | 1 files changed, 101 insertions(+), 8 deletions(-) [+] |
line wrap: on
line diff
--- a/kallithea/lib/feeds.py Thu Dec 19 21:23:33 2019 +0100 +++ b/kallithea/lib/feeds.py Mon Dec 09 02:29:04 2019 +0100 @@ -22,38 +22,131 @@ 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')