Начало

This commit is contained in:
2022-01-15 16:41:37 +03:00
commit 7e8c591b51
59 changed files with 2790 additions and 0 deletions

100
subcategory/README.md Normal file
View 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
View File

@ -0,0 +1 @@
from .subcategory import *

135
subcategory/subcategory.py Normal file
View 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)