Начало
This commit is contained in:
100
subcategory/README.md
Normal file
100
subcategory/README.md
Normal file
@ -0,0 +1,100 @@
|
||||
# Subcategory Plugin
|
||||
|
||||
This plugin adds support for subcategories in addition to article categories.
|
||||
|
||||
Subcategories are hierarchical. Each subcategory has a parent, which is either a regular category or another subcategory.
|
||||
|
||||
Feeds can be generated for each subcategory, just like categories and tags.
|
||||
|
||||
## Usage
|
||||
|
||||
### Metadata
|
||||
|
||||
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` templates.
|
||||
|
||||
In your templates, `article.category` continues to act the same way. Your subcategories are stored in the `articles.subcategories` list. To create breadcrumb-style navigation you might try something like this:
|
||||
|
||||
```
|
||||
<nav class="breadcrumb">
|
||||
<ol>
|
||||
<li>
|
||||
<a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category}}</a>
|
||||
</li>
|
||||
{% for subcategory in article.subcategories %}
|
||||
<li>
|
||||
<a href="{{ SITEURL }}/{{ subcategory.url }}">{{ subcategory.shortname }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
```
|
||||
|
||||
### Subcategory folders
|
||||
|
||||
To specify subcategories using folders you can configure `PATH_METADATA`<br>
|
||||
to extract the article path (containing all category and subcategory folders) into the `subcategory_path` metadata. The following settings would use all available subcategories for the hierarchy:
|
||||
|
||||
```
|
||||
PATH_METADATA= '(?P<subcategory_path>.*)/.*'
|
||||
```
|
||||
|
||||
You can limit the depth of generated subcategories by adjusting the regular expression to only include a specific number of path separators (`/`). For example, the following would generate only a single level of subcategories regardless of the folder tree depth:
|
||||
|
||||
```
|
||||
PATH_METADATA= '(?P<subcategory_path>[^/]*/[^/]*)/.*'
|
||||
```
|
||||
|
||||
## Subcategory Names
|
||||
|
||||
Each subcategory's full name is a `/`-separated list of it parents and itself. This is necessary to keep each subcategory unique. It means you can have `Category 1/Foo` and `Category 2/Foo` and they won't interfere with each other. Each subcategory has an attribute `shortname` which is just the name without its parents associated. For example if you had...
|
||||
|
||||
```
|
||||
Category/Sub Category1/Sub Category2
|
||||
```
|
||||
|
||||
... the full name for Sub Category2 would be `Category/Sub Category1/Sub Category2` and the "short name" would be `Sub Category2`.
|
||||
|
||||
If you need to use the slug, it is generated from the short name -- not the full name.
|
||||
|
||||
## Settings
|
||||
|
||||
Consistent with the default settings for Tags and Categories, the default settings for subcategories 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`.
|
||||
|
||||
Similarly, you can save subcategory feeds by adding one of the following to your Pelican configuration 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. For example:
|
||||
|
||||
```
|
||||
feeds/category/subcategory.atom.xml
|
||||
```
|
||||
|
||||
Article urls can also use the values of `subpath` and `suburl` in their definitions. These are equivalent to the `fullurl` and `savepath` of the most specific subcategory. If you have articles that don't have subcategories these values are set to the category slug.
|
||||
|
||||
```
|
||||
ARTICLE_SAVE_AS = os.path.join('{subpath}' 'articles' '{slug}.html')
|
||||
ARTICLE_URL = '{suburl}/articles/{slug}.html'
|
||||
```
|
1
subcategory/__init__.py
Normal file
1
subcategory/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .subcategory import *
|
135
subcategory/subcategory.py
Normal file
135
subcategory/subcategory.py
Normal file
@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author: Alistair Magee
|
||||
|
||||
Adds support for subcategories on pelican articles
|
||||
"""
|
||||
import os
|
||||
from operator import attrgetter
|
||||
from functools import partial
|
||||
|
||||
from pelican import signals
|
||||
from pelican.urlwrappers import URLWrapper, Category
|
||||
from pelican.utils import slugify
|
||||
|
||||
from six import text_type
|
||||
|
||||
class SubCategory(URLWrapper):
|
||||
def __init__(self, name, parent, settings):
|
||||
super(SubCategory, self).__init__(name, settings)
|
||||
self.parent = parent
|
||||
self.shortname = name.split('/')
|
||||
self.shortname = self.shortname.pop()
|
||||
self.slug = slugify(self.shortname, settings.get('SLUG_SUBSTITUIONS', ()))
|
||||
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['shortname'] = self.shortname
|
||||
d['savepath'] = self.savepath
|
||||
d['fullurl'] = self.fullurl
|
||||
d['parent'] = self.parent
|
||||
return d
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.fullurl)
|
||||
|
||||
def _key(self):
|
||||
return self.fullurl
|
||||
|
||||
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'
|
||||
if ('PAGINATED_TEMPLATES' in generator.settings and
|
||||
'subcategory' not in generator.settings['PAGINATED_TEMPLATES']):
|
||||
generator.settings['PAGINATED_TEMPLATES']['subcategory'] = None
|
||||
|
||||
if 'subcategory_path' in metadata:
|
||||
category_list = text_type(metadata.get('subcategory_path')).split('/')
|
||||
else:
|
||||
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.name
|
||||
for subcategory in category_list:
|
||||
subcategory = parent + '/' + subcategory.strip()
|
||||
sub_list.append(subcategory)
|
||||
parent = subcategory
|
||||
metadata['subcategories'] = sub_list
|
||||
|
||||
def create_subcategories(generator):
|
||||
generator.subcategories = []
|
||||
for article in generator.articles:
|
||||
parent = article.category
|
||||
actual_subcategories = []
|
||||
for subcategory in article.subcategories:
|
||||
#following line returns a list of items, tuples in this case
|
||||
sub_cat = [item for item in generator.subcategories
|
||||
if item[0].name == subcategory]
|
||||
if sub_cat:
|
||||
sub_cat[0][1].append(article)
|
||||
parent = sub_cat[0][0]
|
||||
actual_subcategories.append(parent)
|
||||
else:
|
||||
new_sub = SubCategory(subcategory, parent, generator.settings)
|
||||
generator.subcategories.append((new_sub, [article,]))
|
||||
parent = new_sub
|
||||
actual_subcategories.append(parent)
|
||||
article.subcategories = actual_subcategories
|
||||
"""Add subpath and suburl to the article metadata. This allows the
|
||||
the last subcategory's fullurl and savepath to be used when definining
|
||||
Article URL's. If an article has no subcategories, the Category slug
|
||||
is used instead
|
||||
"""
|
||||
try:
|
||||
last_subcat = article.subcategories[-1]
|
||||
article.metadata['subpath'] = last_subcat.savepath
|
||||
article.metadata['suburl'] = last_subcat.fullurl
|
||||
except IndexError: #No Subcategory
|
||||
article.metadata['subpath'] = article.category.slug
|
||||
article.metadata['suburl'] = article.category.slug
|
||||
|
||||
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:
|
||||
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,
|
||||
template_name='subcategory', page_name=subcat.page_name,
|
||||
all_articles=generator.articles)
|
||||
|
||||
def generate_subcategory_feeds(generator, writer):
|
||||
for subcat, articles in generator.subcategories:
|
||||
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(create_subcategories)
|
||||
signals.article_writer_finalized.connect(generate)
|
Reference in New Issue
Block a user