commit 66362b13945937f5f8dad3bbf46ee3e98656154c
parent 4d4da972e4a8a39f16fab4e10a610a5fdd622a0d
Author: Dan Callaghan <djc@djc.id.au>
Date: Sun, 8 Jun 2008 15:12:21 +1000
Atom feed for index
committer: Dan Callaghan <djc@djc.id.au>
--HG--
extra : convert_revision : 119bf327c663992ed73d0983791b167e2f58a756
Diffstat:
7 files changed, 105 insertions(+), 21 deletions(-)
diff --git a/app.py b/app.py
@@ -12,6 +12,7 @@ import blog
ENTRIES_DIR = os.path.join(os.path.dirname(__file__), u'entries')
READINGLOG_FILE = os.path.join(os.path.dirname(__file__), u'reading_log')
BASE_URL = ''
+ENTRIES_PER_PAGE = 20
template_loader = TemplateLoader(
os.path.join(os.path.dirname(__file__), 'templates'),
@@ -32,13 +33,25 @@ class BlogApplication(RegexApplication):
self.entries = blog.Entries(ENTRIES_DIR, READINGLOG_FILE)
def index(self):
- rendered = template_loader.load('multiple.xml').generate(
- title=None,
- all_categories=self.entries.categories(),
- entries=self.entries,
- offset=int(self.request.args.get('offset', 0))
- ).render('xhtml')
- return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
+ offset = int(self.request.args.get('offset', 0))
+ sorted_entries = sorted(self.entries, key=lambda e: e.publication_date, reverse=True)[offset:offset + ENTRIES_PER_PAGE]
+ format = self.request.args.get('format', 'html')
+ if format == 'html':
+ rendered = template_loader.load('multiple.xml').generate(
+ title=None,
+ all_categories=self.entries.categories(),
+ sorted_entries=sorted_entries,
+ offset=offset,
+ ).render('xhtml')
+ return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
+ elif format == 'atom':
+ rendered = template_loader.load('multiple_atom.xml').generate(
+ sorted_entries=sorted_entries,
+ feed_updated=sorted_entries[0].modified_date
+ ).render('xml')
+ return HttpResponse(rendered, [('Content-Type', 'application/atom+xml')], 200)
+ else:
+ raise PageNotFound('Unknown format %r' % format)
def post(self, id):
id = id.decode(self.charset) # shouldn't Colubrid do this?
@@ -57,12 +70,14 @@ class BlogApplication(RegexApplication):
categories = self.entries.by_category()
if category not in categories:
raise PageNotFound()
+ offset = int(self.request.args.get('offset', 0))
entries = categories[category]
+ sorted_entries = sorted(entries, key=lambda e: e.publication_date, reverse=True)[offset:offset + ENTRIES_PER_PAGE]
rendered = template_loader.load('multiple.xml').generate(
title=u'%s category' % category,
all_categories=self.entries.categories(),
- entries=entries,
- offset=int(self.request.args.get('offset', 0))
+ sorted_entries=sorted_entries,
+ offset=offset
).render('xhtml')
return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
@@ -71,12 +86,14 @@ class BlogApplication(RegexApplication):
by_tag = self.entries.by_tag()
if tag not in by_tag:
raise PageNotFound()
+ offset = int(self.request.args.get('offset', 0))
entries = by_tag[tag]
+ sorted_entries = sorted(entries, key=lambda e: e.publication_date, reverse=True)[offset:offset + ENTRIES_PER_PAGE]
rendered = template_loader.load('multiple.xml').generate(
title=u'“%s” tag' % tag,
all_categories=self.entries.categories(),
- entries=entries,
- offset=int(self.request.args.get('offset', 0))
+ sorted_entries=sorted_entries,
+ offset=offset
).render('xhtml')
return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
diff --git a/blog.py b/blog.py
@@ -21,6 +21,14 @@ def cleanup_metadata(meta):
cleaned[k] = v
return cleaned
+IDIFY_WHITESPACE_PATT = re.compile(r'(?u)\s+')
+IDIFY_ACCEPT_PATT = re.compile(r'(?u)\w|[-_]')
+def idify(s):
+ # http://www.w3.org/TR/REC-xml/#NT-Name
+ s = s.lower()
+ s = IDIFY_WHITESPACE_PATT.sub(u'-', s)
+ return u''.join(c for c in s if IDIFY_ACCEPT_PATT.match(c))
+
class EntryNotFoundError(ValueError): pass
@@ -112,6 +120,7 @@ class Entry(object):
self.modified_date = datetime.fromtimestamp(os.path.getmtime(os.path.join(self.dir, 'content.txt')))
self.publication_date = self.metadata.get('publication-date', None) or self.modified_date
+ self._guid = self.metadata.get('guid', None)
def comments(self):
return Comments(self.comments_dir)
@@ -124,21 +133,29 @@ class Entry(object):
return os.path.isdir(self.comments_dir) and \
os.access(self.comments_dir, os.R_OK)
+ def guid(self):
+ return self._guid or u'http://www.djc.id.au%s/%s' % (BASE_URL, self.id)
+
class ReadingLogEntry(object):
def __init__(self, yaml_dict):
self.title = yaml_dict['Title']
+ self.id = idify(self.title)
self.author = yaml_dict['Author']
self.publication_date = self.modified_date = self.date = yaml_dict['Date']
self.url = yaml_dict.get('URL', None)
self.rating = yaml_dict.get('Rating', None)
self.categories = frozenset([u'Reading'])
self.tags = frozenset()
+ self._guid = yaml_dict.get('GUID', None)
def has_comments(self):
return False
+ def guid(self):
+ return self._guid or u'http://www.djc.id.au%s/#post-%s' % (BASE_URL, self.id)
+
class Comments(object):
@@ -184,3 +201,6 @@ class Comment(object):
def author_name(self):
return self.author or u'Anonymous'
+
+
+from app import BASE_URL # XXX
diff --git a/export_readinglog_wp.py b/export_readinglog_wp.py
@@ -8,10 +8,10 @@ def export(f):
cn = MySQLdb.connect(host='cruz', user='root', passwd='ELIDED', db='wordpress', use_unicode=True)
cur = cn.cursor()
- cur.execute('SELECT id, post_title, post_date FROM wp_posts INNER JOIN wp_term_relationships ON wp_term_relationships.object_id = wp_posts.id WHERE post_status = %s AND term_taxonomy_id = %s ORDER BY post_date ASC', ('publish', 14))
+ cur.execute('SELECT id, post_title, post_date, guid FROM wp_posts INNER JOIN wp_term_relationships ON wp_term_relationships.object_id = wp_posts.id WHERE post_status = %s AND term_taxonomy_id = %s ORDER BY post_date ASC', ('publish', 14))
for row in cur.fetchall():
- id, title, date = row
- entry = {'Title': title, 'Date': date}
+ id, title, date, guid = row
+ entry = {'Title': title, 'Date': date, 'GUID': guid}
subcur = cn.cursor()
subcur.execute('SELECT meta_key, meta_value FROM wp_postmeta WHERE post_id = %s', (id,))
for key, value in subcur.fetchall():
diff --git a/templates/_commonwrapper.xml b/templates/_commonwrapper.xml
@@ -16,7 +16,7 @@ from app import BASE_URL
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta name="generator" content="Dan’s blogging engine" />
<link rel="stylesheet" type="text/css" href="${BASE_URL}/static/css/common.css" />
- <link rel="alternate" type="application/atom+xml" title="Atom feed" href="${BASE_URL}/feed?format=atom" />
+ <link rel="alternate" type="application/atom+xml" title="Atom feed" href="?format=atom" />
</head>
</py:match>
@@ -32,7 +32,7 @@ from app import BASE_URL
<li id="feeds">
<h2>Feeds</h2>
<ul id="feed">
- <li><a href="${BASE_URL}/feed?format=atom" title="Atom feed for posts">Atom posts</a></li>
+ <li><a rel="alternate" type="application/atom+xml" href="${BASE_URL}/?format=atom" title="Atom feed for posts">Atom posts</a></li>
</ul>
</li>
<li class="categories">
diff --git a/templates/_entry.xml b/templates/_entry.xml
@@ -42,7 +42,7 @@ from viewutils import mini_markdown, tag_list, category_list
</span>
<div class="entry readinglog" py:if="'Reading' in entry.categories">
- <h3 class="entrytitle">
+ <h3 class="entrytitle" id="post-${entry.id}">
<a py:strip="not entry.url" href="${entry.url}">${mini_markdown(entry.title)}</a>
<py:if test="entry.author">
by ${entry.author}
diff --git a/templates/multiple.xml b/templates/multiple.xml
@@ -13,10 +13,8 @@
<h2 class="archives" py:if="title">Archive for the ${title}</h2>
-<py:with vars="sorted_entries = sorted(entries, key=lambda e: e.publication_date, reverse=True);
- show_prev = bool(offset);
- show_next = len(sorted_entries) > offset + 20">
- <py:for each="entry in sorted_entries[offset:offset + 20]">
+<py:with vars="show_prev = bool(offset); show_next = len(sorted_entries) > offset + 20">
+ <py:for each="entry in sorted_entries">
${show_entry(entry, show_comments=False)}
</py:for>
<p py:if="show_prev or show_next">
diff --git a/templates/multiple_atom.xml b/templates/multiple_atom.xml
@@ -0,0 +1,49 @@
+<feed xmlns="http://www.w3.org/2005/Atom"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+<?python
+from app import BASE_URL
+from viewutils import tag_list
+ATOM_TIME_FORMAT = str('%Y-%m-%dT%H:%M:%S+10:00')
+?>
+
+<id>http://www.djc.id.au${BASE_URL}/?format=atom</id>
+<title type="text">djc</title>
+<link rel="self" type="application/atom+xml" href="http://www.djc.id.au${BASE_URL}/?format=atom" />
+<link rel="alternate" href="http://www.djc.id.au${BASE_URL}/" />
+<generator>Dan’s blogging engine</generator>
+<updated>${feed_updated.strftime(ATOM_TIME_FORMAT)}</updated>
+
+<entry py:for="entry in sorted_entries">
+ <id>${entry.guid()}</id>
+ <published>${entry.publication_date.strftime(ATOM_TIME_FORMAT)}</published>
+ <updated>${entry.modified_date.strftime(ATOM_TIME_FORMAT)}</updated>
+ <author>
+ <name>djc</name>
+ </author>
+ <category py:for="category in entry.categories" scheme="http://www.djc.id.au${BASE_URL}/+categories/" term="${category}" />
+ <category py:for="tag in entry.tags" scheme="http://www.djc.id.au${BASE_URL}/+tags/" term="${tag}" />
+ <py:if test="'Reading' not in entry.categories">
+ <link rel="alternate" href="http://www.djc.id.au${BASE_URL}/${entry.id}" />
+ <title type="text">${entry.title}</title>
+ <content type="xhtml" xml:base="http://www.djc.id.au${BASE_URL}/${entry.id}"><xhtml:div>
+ ${entry.body}
+ <p py:if="entry.tags">
+ <img src="${BASE_URL}/static/images/tag_blue.png" alt="Tags:" />
+ ${tag_list(entry.tags)}
+ </p>
+ </xhtml:div></content>
+ </py:if>
+ <py:if test="'Reading' in entry.categories">
+ <title type="text">${entry.title} by ${entry.author}</title>
+ <summary type="text">${entry.rating} stars</summary>
+ <content type="xhtml"><xhtml:div>
+ <p><a href="${entry.url}">${entry.title}</a> by ${entry.author}</p>
+ <p>${entry.rating} stars</p>
+ </xhtml:div></content>
+ </py:if>
+</entry>
+
+</feed>