Начало
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