commit fd7fd94994a1a6ebfd0e7db25418fe90c58c9c16
parent 2c52314e76c8d826bbd0c89a415d88ef962a29f7
Author: Dan Callaghan <djc@djc.id.au>
Date: Sun, 23 Nov 2008 10:52:21 +1000
getting closer to what I am thinking of
--HG--
rename : blog.py => itemtypes.py
rename : templates/_entry.xml => templates/BlogEntry.xml
rename : templates/_entry.xml => templates/ReadingLogEntry.xml
Diffstat:
8 files changed, 319 insertions(+), 322 deletions(-)
diff --git a/app.py b/app.py
@@ -5,17 +5,17 @@ import os, sys
sys.path.insert(0, os.path.dirname(__file__))
sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'lib'))
-import cgi, re, datetime
+import cgi, re, datetime, urllib
from itertools import chain
-from genshi.template import TemplateLoader
+import genshi.template
from webob import Request, Response
from webob import exc
from recaptcha.client import captcha
import config
-import blog
+from itemtypes import *
-template_loader = TemplateLoader(
+template_loader = genshi.template.TemplateLoader(
os.path.join(os.path.dirname(__file__), 'templates'),
variable_lookup='strict',
auto_reload=True)
@@ -31,13 +31,8 @@ class Constance(object):
self.req = Request(environ)
self.req.charset = self.encoding
-
- self.blog_entries = blog.BlogEntrySet(self.config.get('blog', 'dir'))
- readinglog_filename = self.config.get('readinglog', 'filename')
- if readinglog_filename:
- self.readinglog_entries = blog.ReadingLogEntrySet(readinglog_filename)
- else:
- self.readinglog_entries = frozenset()
+
+ self.item_sets = eval(self.config.get('global', 'item_sets'))
def __iter__(self):
try:
@@ -57,11 +52,15 @@ class Constance(object):
(r'/blog/([^/]+)/comments/\+new$', 'add_post_comment')]
urls = [(re.compile(patt), method) for patt, method in urls]
def dispatch(self, path_info):
- for patt, method_name in self.urls:
- match = patt.match(path_info)
- if match:
- return getattr(self, method_name)(
- *[x.decode(self.encoding, 'ignore') for x in match.groups()])
+ path_info = urllib.unquote(path_info).decode(self.encoding)
+ for item_set in self.item_sets:
+ try:
+ item = item_set.get(path_info)
+ except NotExistError, e:
+ pass
+ else:
+ rendered = item.render('text/html').render('xhtml')
+ return Response(rendered, content_type='text/html')
# no matching URI found, so give a 404
raise exc.HTTPNotFound().exception
diff --git a/blog.py b/blog.py
@@ -1,184 +0,0 @@
-import os, re, uuid, email
-from datetime import datetime
-import genshi
-import yaml
-
-
-def count(iterable):
- count = 0
- for _ in iterable:
- count += 1
- return count
-
-def cleanup_metadata(header_items):
- cleaned = {}
- for k, v in header_items:
- k = k.lower()
- if k.endswith('date'):
- v = datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
- else:
- v = v.decode('utf8') # XXX encoding
- 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
-
-class EntryForbiddenError(ValueError): pass
-
-class CommentingForbiddenError(ValueError): pass # XXX why all the different types?
-
-class CommentNotFoundError(ValueError): pass
-
-class CommentForbiddenError(ValueError): pass
-
-
-class DirectoryEntrySet(object):
-
- def __init__(self, base_dir):
- self.base_dir = base_dir
- assert os.path.isdir(self.base_dir), self.base_dir
-
- def __contains__(self, key):
- return os.path.exists(os.path.join(self.base_dir, key))
-
- def __getitem__(self, key):
- key = key.encode('utf8') # XXX don't hardcode
- if key not in self: raise KeyError(key)
- return self.entry_class(self.base_dir, key)
-
- def __len__(self):
- return count(filename
- for filename in os.listdir(self.base_dir)
- if not filename.startswith('.'))
-
- def __iter__(self):
- assert isinstance(self.base_dir, str)
- return (self.entry_class(self.base_dir, filename)
- for filename in os.listdir(self.base_dir)
- if not filename.startswith('.'))
-
-
-class YamlEntrySet(object):
-
- def __init__(self, filename):
- self.filename = filename
- assert os.path.isfile(self.filename), self.filename
-
- def __iter__(self):
- return (self.entry_class(d)
- for d in yaml.load_all(open(self.filename, 'r')))
-
-
-class BlogEntry(object):
-
- def __init__(self, entries_dir, id):
- assert isinstance(id, str), id
- self.id = id.decode('utf8') # XXX shouldn't hardcode the encoding
- self.dir = os.path.join(entries_dir, id)
- self.comments_dir = os.path.join(self.dir, 'comments')
-
- if not os.path.exists(self.dir):
- raise EntryNotFoundError()
- if not os.access(self.dir, os.R_OK):
- raise EntryForbiddenError()
-
- # not really a MIME document, but parse it like one
- msg = email.message_from_file(open(os.path.join(self.dir, 'content.txt'), 'r'))
- self.metadata = cleanup_metadata(msg.items())
- self.body = msg.get_payload().decode('utf8') # XXX encoding
- self.title = self.metadata['title']
-
- raw_tags = self.metadata.get('tags', '').strip()
- if raw_tags:
- self.tags = frozenset(tag.strip() for tag in raw_tags.split(','))
- else:
- self.tags = frozenset()
-
- 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['guid']
-
- def comments(self):
- return CommentSet(self.comments_dir)
-
- def has_comments(self):
- """
- Returns True if this Entry could *possibly* have comments, although it
- may still have no comments (yet).
- """
- return os.path.isdir(self.comments_dir) and \
- os.access(self.comments_dir, os.R_OK)
-
- def add_comment(self, metadata, content):
- if not os.access(self.comments_dir, os.W_OK):
- raise CommentingForbiddenError()
- # XXX write to temp file
- guid = uuid.uuid4().get_hex()
- f = open(os.path.join(self.comments_dir, guid), 'w')
- for k, v in metadata.iteritems():
- f.write('%s: %s\n' % (k, v.encode('utf8'))) # XXX encoding
- f.write('\n')
- f.write(content.encode('utf8')) # XXX encoding
-
-
-class BlogEntrySet(DirectoryEntrySet):
-
- entry_class = BlogEntry
-
-
-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.isbn = yaml_dict.get('ISBN', None)
- self.rating = yaml_dict.get('Rating', None)
- self.tags = frozenset()
- self.guid = yaml_dict['GUID']
-
- def has_comments(self):
- return False
-
-
-class ReadingLogEntrySet(YamlEntrySet):
-
- entry_class = ReadingLogEntry
-
-
-class Comment(object):
-
- def __init__(self, comments_dir, id):
- path = os.path.join(comments_dir, id)
- if not os.path.exists(path):
- raise CommentNotFoundError()
- if not os.access(path, os.R_OK):
- raise CommentForbiddenError()
-
- self.id = id
- msg = email.message_from_file(open(path, 'r'))
- self.metadata = cleanup_metadata(msg.items())
- self.body = msg.get_payload().decode('utf8') # XXX encoding
-
- self.author = self.metadata.get('from', None)
- self.author_url = self.metadata.get('author-url', None)
- self.date = datetime.fromtimestamp(os.path.getmtime(path))
-
- def author_name(self):
- return self.author or u'Anonymous'
-
-
-class CommentSet(DirectoryEntrySet):
-
- entry_class = Comment
diff --git a/config.defaults b/config.defaults
@@ -30,6 +30,8 @@ entries_in_feed = 20
# utf8!
encoding = utf8
+item_sets = [BlogEntrySet('./entries'), ReadingLogEntrySet('./reading_log.yaml')]
+
[links]
# The name of the YAML file containing links to be placed at the bottom of
@@ -38,9 +40,6 @@ filename =
[blog]
-# The directory containing blog entries.
-dir = ./entries
-
# Require reCAPTCHA verification for comment submission?
require_captcha = False
@@ -52,9 +51,6 @@ recaptcha_privkey =
[readinglog]
-# The name of the file containing a YAML stream of readinglog entries.
-filename =
-
# Should LibraryThing covers be shown for readinglog entries?
# See also librarything_devkey below.
show_covers = False
diff --git a/itemtypes.py b/itemtypes.py
@@ -0,0 +1,229 @@
+
+"""
+This module defines the various item types which are supported by Constance.
+
+An "item" is anything which can be referenced individually by a URL and
+rendered, and/or appears in a stream of items. As a minimum each type of item
+must have the following data attached to it:
+
+ * datetime of original publication (as the `publication_date` attribute)
+ * datetime at which the content of the item was last modified (as the
+ `modified_date` attribute)
+ * unique URI path within the collection (as the `uri_path` attribute)
+
+Each item type defines a collection type whose name ends in Set (e.g.
+BlogEntrySet). The collection can be iterated across using iter(), which yields
+a sequence of item instances (if a collection does not support iteration its
+iterator yields nothing). Individual items can also be fetched from the
+collection by calling its `get` attribute with a URI path; either an item
+instance is returned, or KeyError is raised if the URI path is not known.
+"""
+
+import os, re, uuid, email
+from datetime import datetime
+import genshi.template
+import yaml
+
+template_loader = genshi.template.TemplateLoader(
+ os.path.join(os.path.dirname(__file__), 'templates'),
+ variable_lookup='strict',
+ auto_reload=True)
+
+__all__ = ['NoAccessError', 'NotExistError', 'UnsupportedFormatError',
+ 'BlogEntrySet', 'ReadingLogEntrySet']
+
+def count(iterable):
+ count = 0
+ for _ in iterable:
+ count += 1
+ return count
+
+def cleanup_metadata(header_items):
+ cleaned = {}
+ for k, v in header_items:
+ k = k.lower()
+ if k.endswith('date'):
+ v = datetime.strptime(v, '%Y-%m-%d %H:%M:%S')
+ else:
+ v = v.decode('utf8') # XXX encoding
+ 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 NoAccessError(StandardError): pass
+
+class NotExistError(StandardError): pass
+
+class UnsupportedFormatError(StandardError): pass
+
+
+class BlogEntry(object):
+
+ def __init__(self, entries_dir, id, uri_path):
+ assert isinstance(id, str), id
+ self.id = id.decode('utf8') # XXX shouldn't hardcode the encoding
+ self.uri_path = uri_path
+ self.dir = os.path.join(entries_dir, id)
+ self.comments_dir = os.path.join(self.dir, 'comments')
+
+ if not os.path.exists(self.dir):
+ raise EntryNotFoundError()
+ if not os.access(self.dir, os.R_OK):
+ raise EntryForbiddenError()
+
+ # not really a MIME document, but parse it like one
+ msg = email.message_from_file(open(os.path.join(self.dir, 'content.txt'), 'r'))
+ self.metadata = cleanup_metadata(msg.items())
+ self.body = msg.get_payload().decode('utf8') # XXX encoding
+ self.title = self.metadata['title']
+
+ raw_tags = self.metadata.get('tags', '').strip()
+ if raw_tags:
+ self.tags = frozenset(tag.strip() for tag in raw_tags.split(','))
+ else:
+ self.tags = frozenset()
+
+ 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['guid']
+
+ def render(self, format):
+ if format == 'text/html':
+ template = template_loader.load(self.__class__.__name__ + '.xml')
+ return template.generate(item=self)
+ else:
+ raise UnsupportedFormatError(format)
+
+ def comments(self):
+ return CommentSet(self.comments_dir)
+
+ def has_comments(self):
+ """
+ Returns True if this Entry could *possibly* have comments, although it
+ may still have no comments (yet).
+ """
+ return os.path.isdir(self.comments_dir) and \
+ os.access(self.comments_dir, os.R_OK)
+
+ def add_comment(self, metadata, content):
+ if not os.access(self.comments_dir, os.W_OK):
+ raise CommentingForbiddenError()
+ # XXX write to temp file
+ guid = uuid.uuid4().get_hex()
+ f = open(os.path.join(self.comments_dir, guid), 'w')
+ for k, v in metadata.iteritems():
+ f.write('%s: %s\n' % (k, v.encode('utf8'))) # XXX encoding
+ f.write('\n')
+ f.write(content.encode('utf8')) # XXX encoding
+
+
+class BlogEntrySet(object):
+
+ def __init__(self, base_dir, prefix='/blog'):
+ self.base_dir = base_dir
+ assert os.path.isdir(self.base_dir), self.base_dir
+ self.prefix = prefix
+ self.entry_patt = re.compile(re.escape(prefix) + r'/([^/]+)/?$')
+
+ def get(self, path_info):
+ m = self.entry_patt.match(path_info)
+ if m is None:
+ raise NotExistError(path_info)
+ id = m.group(1).encode('utf8') # XXX don't hardcode
+ if not os.path.isdir(os.path.join(self.base_dir, id)):
+ raise NotExistError(path_info)
+ return BlogEntry(self.base_dir, id, path_info)
+
+ def __iter__(self):
+ assert isinstance(self.base_dir, str)
+ return (BlogEntry(self.base_dir, filename, self.prefix + '/' + filename.encode('utf8'))
+ for filename in os.listdir(self.base_dir)
+ if not filename.startswith('.'))
+
+
+class ReadingLogEntry(object):
+
+ def __init__(self, yaml_dict, uri_path):
+ self.uri_path = uri_path
+ 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.isbn = yaml_dict.get('ISBN', None)
+ self.rating = yaml_dict.get('Rating', None)
+ self.tags = frozenset()
+ self.guid = yaml_dict['GUID']
+
+ def render(self, format):
+ if format == 'text/html':
+ template = template_loader.load(self.__class__.__name__ + '.xml')
+ return template.generate(item=self)
+ else:
+ raise UnsupportedFormatError(format)
+
+ def has_comments(self):
+ return False
+
+
+class ReadingLogEntrySet(object):
+
+ def __init__(self, filename, prefix='/reading'):
+ self.filename = filename
+ assert os.path.isfile(self.filename), self.filename
+ self.prefix = prefix
+
+ def get(self, path_info):
+ raise NotExistError(path_info)
+
+ def __iter__(self):
+ return (ReadingLogEntry(d, self.prefix + '/#ReadingLogEntry-' + idify(d['Title']))
+ for d in yaml.load_all(open(self.filename, 'r')))
+
+
+class Comment(object):
+
+ def __init__(self, comments_dir, id):
+ path = os.path.join(comments_dir, id)
+ if not os.path.exists(path):
+ raise CommentNotFoundError()
+ if not os.access(path, os.R_OK):
+ raise CommentForbiddenError()
+
+ self.id = id
+ msg = email.message_from_file(open(path, 'r'))
+ self.metadata = cleanup_metadata(msg.items())
+ self.body = msg.get_payload().decode('utf8') # XXX encoding
+
+ self.author = self.metadata.get('from', None)
+ self.author_url = self.metadata.get('author-url', None)
+ self.date = datetime.fromtimestamp(os.path.getmtime(path))
+
+ def author_name(self):
+ return self.author or u'Anonymous'
+
+
+class CommentSet(object):
+
+ def __init__(self, base_dir):
+ self.base_dir = base_dir
+ assert os.path.isdir(self.base_dir), self.base_dir
+
+ def __len__(self):
+ return count(filename
+ for filename in os.listdir(self.base_dir)
+ if not filename.startswith('.'))
+
+ def __iter__(self):
+ assert isinstance(self.base_dir, str)
+ return (Comment(self.base_dir, filename)
+ for filename in os.listdir(self.base_dir)
+ if not filename.startswith('.'))
diff --git a/templates/BlogEntry.xml b/templates/BlogEntry.xml
@@ -0,0 +1,38 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ class="item BlogEntry">
+
+<xi:include href="_fragments.xml" />
+
+<?python
+from viewutils import markdown, mini_markdown, tag_list
+from recaptcha.client import captcha
+?>
+
+<h3 id="BlogEntry-${item.id}">${mini_markdown(item.title)}</h3>
+
+<div class="date">
+ ${item.publication_date.strftime(str('%-1d %b %Y'))}
+ <!-- XXX script_name --><a href="${item.uri_path}" rel="bookmark" class="permalink" title="permalink">#</a>
+</div>
+
+<div py:if="item.tags" class="tags">
+ tagged: ${tag_list('', item.tags)} <!-- XXX script_name -->
+</div>
+
+<div class="body">
+ ${markdown(item.body)}
+</div>
+
+<!-- XXX comments -->
+
+<div class="commentslink" py:if="item.has_comments()">
+ <!-- XXX script_name --><a href="${item.uri_path}#comments" py:choose="len(item.comments())">
+ <py:when test="0">no comments »</py:when>
+ <py:when test="1">1 comment »</py:when>
+ <py:otherwise>${len(item.comments())} comments »</py:otherwise>
+ </a>
+</div>
+
+</div>
diff --git a/templates/ReadingLogEntry.xml b/templates/ReadingLogEntry.xml
@@ -0,0 +1,35 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:py="http://genshi.edgewall.org/"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ class="item ReadingLogEntry">
+
+<xi:include href="_fragments.xml" />
+
+<?python
+from viewutils import markdown, mini_markdown, tag_list
+from recaptcha.client import captcha
+?>
+
+<span py:def="stars(rating)" py:strip="True">
+<img src="${uri('static', 'images', 'star.png')}" alt="[star]" py:for="_ in range(int(rating))" /><img src="${uri('static', 'images', 'star-half.png')}" alt="[half-star]" py:if="rating > int(rating)" /><img src="${uri('static', 'images', 'star-off.png')}" alt="" py:for="_ in range(int(5 - rating))" />
+</span>
+
+<!-- XXX img py:if="config.getboolean('readinglog', 'show_covers') and entry.isbn" class="cover"
+ src="http://covers.librarything.com/devkey/${config.get('readinglog', 'librarything_devkey')}/small/isbn/${entry.isbn}"
+ alt="Cover image for ${entry.title}" /-->
+
+<h3 class="title" id="ReadingLogEntry-${item.id}">
+ <a py:strip="not entry.url" href="${item.url}">${mini_markdown(item.title)}</a>
+ <span py:if="item.author" class="author">by ${item.author}</span>
+</h3>
+
+<div class="date">
+ ${item.publication_date.strftime(str('%-1d %b %Y'))}
+ <a href="${item.uri_path}" rel="bookmark" class="permalink" title="permalink">#</a>
+</div>
+
+<div py:if="item.rating" class="rating">
+ ${stars(item.rating)}
+</div>
+
+</div>
diff --git a/templates/_entry.xml b/templates/_entry.xml
@@ -1,113 +0,0 @@
-<div xmlns="http://www.w3.org/1999/xhtml"
- xmlns:py="http://genshi.edgewall.org/"
- xmlns:xi="http://www.w3.org/2001/XInclude"
- py:strip="True"
- py:def="show_entry(entry, show_comments=True)">
-
-<xi:include href="_fragments.xml" />
-
-<?python
-import blog
-from viewutils import markdown, mini_markdown, tag_list
-from recaptcha.client import captcha
-?>
-
-<div class="entry" py:if="isinstance(entry, blog.BlogEntry)">
-
- <h3 class="entrytitle" id="entry-${entry.id}">${mini_markdown(entry.title)}</h3>
-
- <div class="entrydate">
- ${entry.publication_date.strftime(str('%-1d %b %Y'))}
- <a href="${uri('blog', entry.id)}" rel="bookmark" class="permalink" title="permalink">#</a>
- </div>
-
- <div py:if="entry.tags" class="entrytags">
- tagged: ${tag_list(environ.get('SCRIPT_NAME', ''), entry.tags)}
- </div>
-
- <div class="entrybody">
- ${markdown(entry.body)}
- </div>
-
- <div class="entrycommentslink" py:if="not show_comments and entry.has_comments()">
- <a href="${uri('blog', entry.id)}#comments" py:choose="len(entry.comments())">
- <py:when test="0">no comments »</py:when>
- <py:when test="1">1 comment »</py:when>
- <py:otherwise>${len(entry.comments())} comments »</py:otherwise>
- </a>
- </div>
-
- <div class="commentblock" py:if="show_comments and entry.has_comments()"
- py:with="comments = sorted(entry.comments(), key=lambda c: c.date)">
- <h4 class="comments" py:choose="len(comments)">
- <py:when test="0">No comments</py:when>
- <py:when test="1">1 comment</py:when>
- <py:otherwise>${len(entry.comments())} comments</py:otherwise>
- </h4>
- <div py:for="n, comment in enumerate(comments)" id="comment-${comment.id}">
- ${markdown(comment.body, safe_mode='escape')}
- <p class="commentmeta">
- ― <a py:strip="not comment.author_url" href="${comment.author_url}">${comment.author_name()}</a>
- <span class="commentdatetime">${comment.date.strftime(str('%-1d %b %Y %H:%M'))}</span>
- <a href="#comment-${comment.id}" class="permalink" title="permalink">#</a>
- </p>
- ${block_sep()}
- </div>
- <div class="commentform"><form method="post" action="${uri('blog', entry.id, 'comments', '+new')}">
- <p>
- <label for="commentform-from">Name</label>
- <input type="text" id="commentform-from" name="from" />
- </p>
- <p>
- <label for="commentform-author-email">E-mail address (not published)</label>
- <input type="text" id="commentform-author-email" name="author-email" />
- </p>
- <p>
- <label for="commentform-author-url">URL</label>
- <input type="text" id="commentform-author-url" name="author-url" />
- </p>
- <p>
- <label for="commentform-comment">Comment (use <a href="http://daringfireball.net/projects/markdown/syntax">Markdown</a>, no HTML)</label>
- <textarea id="commentform-comment" name="comment" rows="7" cols="30"></textarea>
- </p>
- <py:if test="config.getboolean('blog', 'require_captcha')">
- <script type="text/javascript">
- RecaptchaOptions = {theme: 'white'};
- </script>
- ${Markup(captcha.displayhtml(config.get('blog', 'recaptcha_pubkey')))}
- </py:if>
- <p><button type="submit">Submit</button></p>
- </form></div>
- </div>
-
-</div>
-
-<span py:def="stars(rating)" py:strip="True">
-<img src="${uri('static', 'images', 'star.png')}" alt="[star]" py:for="_ in range(int(rating))" /><img src="${uri('static', 'images', 'star-half.png')}" alt="[half-star]" py:if="rating > int(rating)" /><img src="${uri('static', 'images', 'star-off.png')}" alt="" py:for="_ in range(int(5 - rating))" />
-</span>
-
-<div class="entry readinglog" py:if="isinstance(entry, blog.ReadingLogEntry)">
-
- <img py:if="config.getboolean('readinglog', 'show_covers') and entry.isbn" class="cover"
- src="http://covers.librarything.com/devkey/${config.get('readinglog', 'librarything_devkey')}/small/isbn/${entry.isbn}"
- alt="Cover image for ${entry.title}" />
-
- <h3 class="entrytitle" id="entry-${entry.id}">
- <a py:strip="not entry.url" href="${entry.url}">${mini_markdown(entry.title)}</a>
- <py:if test="entry.author">
- <span class="author">by ${entry.author}</span>
- </py:if>
- </h3>
-
- <div class="entrydate">
- ${entry.publication_date.strftime(str('%-1d %b %Y'))}
- <a href="${uri('reading', '')}#entry-${entry.id}" rel="bookmark" class="permalink" title="permalink">#</a>
- </div>
-
- <div py:if="entry.rating" class="rating">
- ${stars(entry.rating)}
- </div>
-
-</div>
-
-</div>
diff --git a/templates/_fragments.xml b/templates/_fragments.xml
@@ -10,7 +10,4 @@ import urllib
<span py:def="inline_sep()" class="inlinesep">·</span>
<div py:def="block_sep()" class="blocksep">~</div>
-<span py:def="uri(*components)" py:strip="True">${'/'.join([environ.get('SCRIPT_NAME', '')] + [urllib.quote(component.encode(config.get('global', 'encoding')), '+') for component in components])}</span>
-<span py:def="abs_uri(*components)" py:strip="True">${environ['APP_URI']}/${'/'.join(urllib.quote(component.encode(config.get('global', 'encoding')), '+') for component in components)}</span>
-
</div>