Merge pull request #133 from alistairmagee/subcategory
A plugin to add subcategories to Article categories
This commit is contained in:
64
subcategory/README.md
Normal file
64
subcategory/README.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#Subcategory Plugin#
|
||||||
|
|
||||||
|
Adds support for subcategories in addition to article categories.
|
||||||
|
|
||||||
|
Subcategories are heirachial. Each subcategory has a parent, which is either a
|
||||||
|
regular category or another subcategory. Subcategories with the same name but
|
||||||
|
different parents are not the same. Their articles won't be grouped together
|
||||||
|
under that name.
|
||||||
|
|
||||||
|
Feeds can be generated for each subcategory just like categories and tags.
|
||||||
|
|
||||||
|
|
||||||
|
##Usage##
|
||||||
|
|
||||||
|
Subcategories are an extension to categories. Add subcategories to an article's
|
||||||
|
category metadata using a `/` like this:
|
||||||
|
|
||||||
|
Category: Regular Category/Sub-Category/Sub-Sub-category
|
||||||
|
|
||||||
|
then create a `subcategory.html` template in your theme similar to the
|
||||||
|
`category.html` or `tag.html`
|
||||||
|
|
||||||
|
In your templates `article.category` continues to act the same way. Your
|
||||||
|
subcategories are stored in a list `aricles.subcategories`. To create a
|
||||||
|
breadcrumb style navigation you might try something like this:
|
||||||
|
|
||||||
|
<nav class="breadcrumb">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<a href="{{ SITEURL }}/{{ arcticle.categor.url }}">{{ article.category}}</a>
|
||||||
|
</li>
|
||||||
|
{% for subcategory in article.subcategories %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ SITEURL }}/{{ category.url }}>{{ subcategory }}</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
##Settings##
|
||||||
|
|
||||||
|
Consistent with the default settings for Tags and Categories, the default
|
||||||
|
settings for subcategoris are:
|
||||||
|
|
||||||
|
'SUBCATEGORY_SAVE_AS' = os.path.join('subcategory', '{savepath}.html')
|
||||||
|
'SUBCATEGORY_URL' = 'subcategory/(fullurl).html'
|
||||||
|
|
||||||
|
`savepath` and `fullurl` are generated recursively, using slugs. So the full
|
||||||
|
url would be:
|
||||||
|
|
||||||
|
category-slug/sub-category-slug/sub-sub-category-slug
|
||||||
|
|
||||||
|
with `savepath` being similar but joined using `os.path.join`
|
||||||
|
|
||||||
|
Similarily you can save a subcategory feeds by adding one of the following
|
||||||
|
to your pelicanconf file
|
||||||
|
|
||||||
|
SUBCATEGORY_FEED_ATOM = 'feeds/%s.atom.xml'
|
||||||
|
SUBCATEGORY_FEED_RSS = 'feeds/%s.rss.xml'
|
||||||
|
|
||||||
|
and this will create a feed with `fullurl` of the subcategory. Eg.
|
||||||
|
|
||||||
|
feeds/category/subcategory.atom.xml
|
||||||
1
subcategory/__init__.py
Normal file
1
subcategory/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .subcategory import *
|
||||||
93
subcategory/subcategory.py
Normal file
93
subcategory/subcategory.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
@Author: Alistair Magee
|
||||||
|
|
||||||
|
Adds support for subcategories on pelican articles
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from collections import defaultdict
|
||||||
|
from pelican import signals
|
||||||
|
from pelican.urlwrappers import URLWrapper, Category
|
||||||
|
from operator import attrgetter
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from six import text_type
|
||||||
|
|
||||||
|
class SubCategory(URLWrapper):
|
||||||
|
def __init__(self, name, parent, *args, **kwargs):
|
||||||
|
super(SubCategory, self).__init__(name, *args, **kwargs)
|
||||||
|
self.parent = parent
|
||||||
|
if isinstance(self.parent, SubCategory):
|
||||||
|
self.savepath = os.path.join(self.parent.savepath, self.slug)
|
||||||
|
self.fullurl = '{}/{}'.format(self.parent.fullurl, self.slug)
|
||||||
|
else: #parent is a category
|
||||||
|
self.savepath = os.path.join(self.parent.slug, self.slug)
|
||||||
|
self.fullurl = '{}/{}'.format(self.parent.slug, self.slug)
|
||||||
|
|
||||||
|
def as_dict(self):
|
||||||
|
d = self.__dict__
|
||||||
|
d['name'] = self.name
|
||||||
|
d['savepath'] = self.savepath
|
||||||
|
d['fullurl'] = self.fullurl
|
||||||
|
d['parent'] = self.parent
|
||||||
|
return d
|
||||||
|
|
||||||
|
def get_subcategories(generator, metadata):
|
||||||
|
if 'SUBCATEGORY_SAVE_AS' not in generator.settings:
|
||||||
|
generator.settings['SUBCATEGORY_SAVE_AS'] = os.path.join(
|
||||||
|
'subcategory', '{savepath}.html')
|
||||||
|
if 'SUBCATEGORY_URL' not in generator.settings:
|
||||||
|
generator.settings['SUBCATEGORY_URL'] = 'subcategory/{fullurl}.html'
|
||||||
|
category_list = text_type(metadata.get('category')).split('/')
|
||||||
|
category = (category_list.pop(0)).strip()
|
||||||
|
category = Category(category, generator.settings)
|
||||||
|
metadata['category'] = category
|
||||||
|
#generate a list of subcategories with their parents
|
||||||
|
sub_list = []
|
||||||
|
parent = category
|
||||||
|
for subcategory in category_list:
|
||||||
|
subcategory.strip()
|
||||||
|
subcategory = SubCategory(subcategory, parent, generator.settings)
|
||||||
|
sub_list.append(subcategory)
|
||||||
|
parent = subcategory
|
||||||
|
metadata['subcategories'] = sub_list
|
||||||
|
|
||||||
|
def organize_subcategories(generator):
|
||||||
|
generator.subcategories = defaultdict(list)
|
||||||
|
for article in generator.articles:
|
||||||
|
subcategories = article.metadata.get('subcategories')
|
||||||
|
for cat in subcategories:
|
||||||
|
generator.subcategories[cat].append(article)
|
||||||
|
|
||||||
|
def generate_subcategories(generator, writer):
|
||||||
|
write = partial(writer.write_file,
|
||||||
|
relative_urls=generator.settings['RELATIVE_URLS'])
|
||||||
|
subcategory_template = generator.get_template('subcategory')
|
||||||
|
for subcat, articles in generator.subcategories.items():
|
||||||
|
articles.sort(key=attrgetter('date'), reverse=True)
|
||||||
|
dates = [article for article in generator.dates if article in articles]
|
||||||
|
write(subcat.save_as, subcategory_template, generator.context,
|
||||||
|
subcategory=subcat, articles=articles, dates=dates,
|
||||||
|
paginated={'articles': articles, 'dates': dates},
|
||||||
|
page_name=subcat.page_name, all_articles=generator.articles)
|
||||||
|
|
||||||
|
def generate_subcategory_feeds(generator, writer):
|
||||||
|
for subcat, articles in generator.subcategories.items():
|
||||||
|
articles.sort(key=attrgetter('date'), reverse=True)
|
||||||
|
if generator.settings.get('SUBCATEGORY_FEED_ATOM'):
|
||||||
|
writer.write_feed(articles, generator.context,
|
||||||
|
generator.settings['SUBCATEGORY_FEED_ATOM']
|
||||||
|
% subcat.fullurl)
|
||||||
|
if generator.settings.get('SUBCATEGORY_FEED_RSS'):
|
||||||
|
writer.write_feed(articles, generator.context,
|
||||||
|
generator.settings['SUBCATEGORY_FEED_RSS']
|
||||||
|
% subcat.fullurl, feed_type='rss')
|
||||||
|
|
||||||
|
def generate(generator, writer):
|
||||||
|
generate_subcategory_feeds(generator, writer)
|
||||||
|
generate_subcategories(generator, writer)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
signals.article_generator_context.connect(get_subcategories)
|
||||||
|
signals.article_generator_finalized.connect(organize_subcategories)
|
||||||
|
signals.article_writer_finalized.connect(generate)
|
||||||
Reference in New Issue
Block a user