Fixing the bug with the series sort order
I made a post about how the series plugin I was using didn't sort the articles how I wanted here.
Pelican allows you to create extra functionality via plugins and has a number of helpful plugs for various things. The series of posts feature is not implemented in the core Pelican project and I used this plugin to support series. Because of this it's very easy to have a look at the source code directly to see what was going on. The ability to fix your own problems when you run open source is one of the major benefits of open source, a benefit I was reminded of after my recent Canvas LMS frustrations.
I really like how simple the plugin is, here's the bit that matters:
def aggregate_series(generator):
series = defaultdict(list)
# This cycles through all articles in the given generator
# and collects the 'series' metadata, if present.
# The 'series_index' metadata is also stored, if specified
for article in generator.articles:
if 'series' in article.metadata:
article_entry = (
article.metadata.get('series_index', None),
article.metadata['date'],
article
)
series[article.metadata['series']].append(article_entry)
# This uses items() which on Python2 is not a generator
# but we are dealing with a small amount of data so
# there shouldn't be performance issues =)
for series_name, series_articles in series.items():
# This is not DRY but very simple to understand
forced_order_articles = [
art_tup for art_tup in series_articles if art_tup[0] is not None]
date_order_articles = [
art_tup for art_tup in series_articles if art_tup[0] is None]
forced_order_articles.sort(key=itemgetter(0))
date_order_articles.sort(key=itemgetter(1))
all_articles = forced_order_articles + date_order_articles
ordered_articles = [art_tup[2] for art_tup in all_articles]
enumerated_articles = enumerate(ordered_articles)
for index, article in enumerated_articles:
article.series = dict()
article.series['name'] = series_name
article.series['index'] = index + 1
article.series['all'] = ordered_articles
article.series['all_previous'] = ordered_articles[0: index]
article.series['all_next'] = ordered_articles[index + 1:]
if index > 0:
article.series['previous'] = ordered_articles[index - 1]
else:
article.series['previous'] = None
try:
article.series['next'] = ordered_articles[index + 1]
except IndexError:
article.series['next'] = None
One thing that I particularly like is how this doesn't dogmatically use DRY, as this makes understanding the code easier and the fix really easy.
What I have to do is to cast the items for the order into integers before sorting, since if I don't use the right types I'll run into this problem:
>>> "100" < "20"
True
>>> 100 < 20
False
The issue is in how Pelican extracts metadata. All metadata it appears is just extracted as strings, which means that by the time it gets to the plugin system it's all just sorted by string comparisons on this line:
forced_order_articles.sort(key=itemgetter(0))
To fix this I just need a sort key that will covert to int
first:
def integer_index_sort(items):
return int(items[0])
Then use this instead:
forced_order_articles.sort(key=integer_index_sort)
Problem solved!