Mercurial > piecrust2
changeset 857:d231a10d18f9
refactor: Make the data providers and blog archives source functional.
Also, because of a behaviour change in Jinja, the blog archives sources is
now offering monthly archives by itself.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 08 Jun 2017 08:49:33 -0700 |
parents | 9bb22bbe093c |
children | 58e28ba02fb7 |
files | piecrust/dataproviders/base.py piecrust/dataproviders/blog.py piecrust/sources/blogarchives.py piecrust/sources/list.py |
diffstat | 4 files changed, 202 insertions(+), 118 deletions(-) [+] |
line wrap: on
line diff
--- a/piecrust/dataproviders/base.py Tue Jun 06 01:23:25 2017 -0700 +++ b/piecrust/dataproviders/base.py Thu Jun 08 08:49:33 2017 -0700 @@ -10,10 +10,9 @@ debug_render_invoke_dynamic = [] def __init__(self, source, page): + self._sources = [source] self._page = page - self._sources = [] - if source is not None: - self._sources.append(source) + self._app = source.app def _addSource(self, source): self._sources.append(source)
--- a/piecrust/dataproviders/blog.py Tue Jun 06 01:23:25 2017 -0700 +++ b/piecrust/dataproviders/blog.py Thu Jun 08 08:49:33 2017 -0700 @@ -2,6 +2,7 @@ import collections.abc from piecrust.dataproviders.base import DataProvider from piecrust.dataproviders.pageiterator import PageIterator +from piecrust.sources.list import ListSource from piecrust.sources.taxonomy import Taxonomy @@ -15,146 +16,159 @@ def __init__(self, source, page): super().__init__(source, page) + self._posts = None self._yearly = None self._monthly = None self._taxonomies = {} + self._archives_built = False self._ctx_set = False + def _addSource(self, source): + raise Exception("The blog data provider doesn't support " + "combining multiple sources.") + @property def posts(self): - return self._posts() + self._buildPosts() + return self._posts @property def years(self): - return self._buildYearlyArchive() + self._buildArchives() + return self._yearly @property def months(self): - return self._buildMonthlyArchive() + self._buildArchives() + return self._montly def __getitem__(self, name): - if name == 'posts': - return self._posts() - elif name == 'years': - return self._buildYearlyArchive() - elif name == 'months': - return self._buildMonthlyArchive() + self._buildArchives() + return self._taxonomies[name] - if self._source.app.config.get('site/taxonomies/' + name) is not None: - return self._buildTaxonomy(name) - - raise KeyError("No such item: %s" % name) + def __getattr__(self, name): + self._buildArchives() + try: + return self._taxonomies[name] + except KeyError: + raise AttributeError("No such taxonomy: %s" % name) def __iter__(self): - keys = ['posts', 'years', 'months'] - keys += list(self._source.app.config.get('site/taxonomies').keys()) - return iter(keys) + self._buildPosts() + self._buildArchives() + return ['posts', 'years', 'months'] + list(self._taxonomies.keys()) def __len__(self): - return 3 + len(self._source.app.config.get('site/taxonomies')) + self._buildPosts() + self._buildArchives() + return 3 + len(self._taxonomies) def _debugRenderTaxonomies(self): - return list(self._source.app.config.get('site/taxonomies').keys()) + return list(self._app.config.get('site/taxonomies').keys()) + + def _buildPosts(self): + if self._posts is None: + it = PageIterator(self._sources[0], current_page=self._page) + it._iter_event += self._onIteration + self._posts = it - def _posts(self): - it = PageIterator(self._source, current_page=self._page) - it._iter_event += self._onIteration - return it + def _buildArchives(self): + if self._archives_built: + return + + yearly_index = {} + monthly_index = {} + tax_index = {} - def _buildYearlyArchive(self): - if self._yearly is not None: - return self._yearly + taxonomies = [] + tax_names = list(self._app.config.get('site/taxonomies').keys()) + for tn in tax_names: + tax_cfg = self._app.config.get('site/taxonomies/' + tn) + taxonomies.append(Taxonomy(tn, tax_cfg)) + tax_index[tn] = {} - self._yearly = [] - yearly_index = {} - for post in self._source.getPages(): + app = self._app + page = self._page + source = self._sources[0] + + for item in source.getAllContents(): + post = app.getPage(source, item) + year = post.datetime.strftime('%Y') + month = post.datetime.strftime('%B %Y') posts_this_year = yearly_index.get(year) if posts_this_year is None: timestamp = time.mktime( (post.datetime.year, 1, 1, 0, 0, 0, 0, 0, -1)) - posts_this_year = BlogArchiveEntry(self._page, year, timestamp) - self._yearly.append(posts_this_year) + posts_this_year = BlogArchiveEntry( + source, page, year, timestamp) yearly_index[year] = posts_this_year - - posts_this_year._data_source.append(post) - self._yearly = sorted(self._yearly, - key=lambda e: e.timestamp, - reverse=True) - self._onIteration() - return self._yearly + posts_this_year._items.append(post.content_item) - def _buildMonthlyArchive(self): - if self._monthly is not None: - return self._monthly - - self._monthly = [] - for post in self._source.getPages(): - month = post.datetime.strftime('%B %Y') - - posts_this_month = next( - filter(lambda m: m.name == month, self._monthly), - None) + posts_this_month = monthly_index.get(month) if posts_this_month is None: timestamp = time.mktime( (post.datetime.year, post.datetime.month, 1, 0, 0, 0, 0, 0, -1)) posts_this_month = BlogArchiveEntry( - self._page, month, timestamp) - self._monthly.append(posts_this_month) + source, page, month, timestamp) + monthly_index[month] = posts_this_month + posts_this_month._items.append(post.content_item) - posts_this_month._data_source.append(post) - self._monthly = sorted(self._monthly, - key=lambda e: e.timestamp, - reverse=True) - self._onIteration() - return self._monthly - - def _buildTaxonomy(self, tax_name): - if tax_name in self._taxonomies: - return self._taxonomies[tax_name] - - tax_cfg = self._page.app.config.get('site/taxonomies/' + tax_name) - tax = Taxonomy(tax_name, tax_cfg) + for tax in taxonomies: + post_term = post.config.get(tax.setting_name) + if post_term is None: + continue - posts_by_tax_value = {} - for post in self._source.getPages(): - tax_values = post.config.get(tax.setting_name) - if tax_values is None: - continue - if not isinstance(tax_values, list): - tax_values = [tax_values] - for val in tax_values: - posts = posts_by_tax_value.setdefault(val, []) - posts.append(post) + posts_this_tax = tax_index[tax.name] + if tax.is_multiple: + for val in post_term: + entry = posts_this_tax.get(val) + if entry is None: + entry = BlogTaxonomyEntry(source, page, val) + posts_this_tax[val] = entry + entry._items.append(post.content_item) + else: + entry = posts_this_tax.get(val) + if entry is None: + entry = BlogTaxonomyEntry(source, page, post_term) + posts_this_tax[val] = entry + entry._items.append(post.content_item) - entries = [] - for value, ds in posts_by_tax_value.items(): - source = ArraySource(self._page.app, ds) - entries.append(BlogTaxonomyEntry(self._page, source, value)) - self._taxonomies[tax_name] = sorted(entries, key=lambda k: k.name) + self._yearly = list(sorted( + yearly_index.values(), + key=lambda e: e.timestamp, reverse=True)) + self._monthly = list(sorted( + monthly_index.values(), + key=lambda e: e.timestamp, reverse=True)) - self._onIteration() - return self._taxonomies[tax_name] + self._taxonomies = {} + for tax_name, entries in tax_index.items(): + self._taxonomies[tax_name] = list(entries.values()) + + self._onIteration(None) - def _onIteration(self): + self._archives_built = True + + def _onIteration(self, it): if not self._ctx_set: - eis = self._page.app.env.exec_info_stack - if eis.current_page_info: - eis.current_page_info.render_ctx.addUsedSource(self._source) + rcs = self._app.env.render_ctx_stack + if rcs.current_ctx: + rcs.current_ctx.addUsedSource(self._sources[0]) self._ctx_set = True -class BlogArchiveEntry(object): +class BlogArchiveEntry: debug_render = ['name', 'timestamp', 'posts'] debug_render_invoke = ['name', 'timestamp', 'posts'] - def __init__(self, page, name, timestamp): + def __init__(self, source, page, name, timestamp): self.name = name self.timestamp = timestamp + self._source = source self._page = page - self._data_source = [] + self._items = [] self._iterator = None def __str__(self): @@ -172,26 +186,28 @@ def _load(self): if self._iterator is not None: return - source = ArraySource(self._page.app, self._data_source) - self._iterator = PageIterator(source, current_page=self._page) + + src = ListSource(self._source, self._items) + self._iterator = PageIterator(src, current_page=self._page) -class BlogTaxonomyEntry(object): +class BlogTaxonomyEntry: debug_render = ['name', 'post_count', 'posts'] debug_render_invoke = ['name', 'post_count', 'posts'] - def __init__(self, page, source, property_value): + def __init__(self, source, page, term): + self.term = term + self._source = source self._page = page - self._source = source - self._property_value = property_value + self._items = [] self._iterator = None def __str__(self): - return self._property_value + return self.term @property def name(self): - return self._property_value + return self.term @property def posts(self): @@ -201,11 +217,12 @@ @property def post_count(self): - return self._source.page_count + return len(self._items) def _load(self): if self._iterator is not None: return - self._iterator = PageIterator(self._source, current_page=self._page) + src = ListSource(self._source, self._items) + self._iterator = PageIterator(src, current_page=self._page)
--- a/piecrust/sources/blogarchives.py Tue Jun 06 01:23:25 2017 -0700 +++ b/piecrust/sources/blogarchives.py Thu Jun 08 08:49:33 2017 -0700 @@ -1,5 +1,7 @@ +import time import logging import datetime +import collections from piecrust.data.filters import PaginationFilter, IFilterClause from piecrust.dataproviders.pageiterator import ( PageIterator, HardCodedFilterIterator, DateSortIterator) @@ -11,6 +13,7 @@ from piecrust.routing import RouteParameter from piecrust.sources.base import ContentItem from piecrust.sources.generator import GeneratorSourceBase +from piecrust.sources.list import ListSource logger = logging.getLogger(__name__) @@ -67,22 +70,8 @@ it._wrapAsSort(DateSortIterator, reverse=False) ctx.custom_data['archives'] = it - def _bakeDirtyYears(self, ctx, all_years, dirty_years): - route = self.app.getGeneratorRoute(self.name) - if route is None: - raise Exception( - "No routes have been defined for generator: %s" % - self.name) - - logger.debug("Using archive page: %s" % self.page_ref) - fac = self.page_ref.getFactory() - - for y in dirty_years: - extra_route_metadata = {'year': y} - - logger.debug("Queuing: %s [%s]" % (fac.ref_spec, y)) - ctx.queueBakeJob(fac, route, extra_route_metadata, str(y)) - ctx.runJobQueue() + ctx.custom_data['monthly_archives'] = _MonthlyArchiveData( + self.inner_source, year) class IsFromYearFilterClause(IFilterClause): @@ -93,8 +82,56 @@ return (page.datetime.year == self.year) -def _date_sorter(it): - return sorted(it, key=lambda x: x.datetime) +class _MonthlyArchiveData(collections.abc.Mapping): + def __init__(self, inner_source, year): + self._inner_source = inner_source + self._year = year + self._months = None + + def __iter__(self): + self._load() + return iter(self._months) + + def __len__(self): + self._load() + return len(self._months) + + def __getitem__(self, i): + self._load() + return self._months[i] + + def _load(self): + if self._months is not None: + return + + month_index = {} + src = self._inner_source + app = src.app + for item in self._inner_source.getAllContents(): + page = app.getPage(src, item) + + if page.datetime.year != self._year: + continue + + month = page.datetime.month + + posts_this_month = month_index.get(month) + if posts_this_month is None: + posts_this_month = [] + month_index[month] = posts_this_month + posts_this_month.append(page.content_item) + + self._months = [] + for m, ptm in month_index.items(): + timestamp = time.mktime((self._year, m, 1, 0, 0, 0, 0, 0, -1)) + + it = PageIterator(ListSource(self._inner_source, ptm)) + it._wrapAsSort(DateSortIterator, reverse=False) + + self._months.append({ + 'timestamp': timestamp, + 'posts': it + }) class BlogArchivesPipelineRecordEntry(PagePipelineRecordEntry):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/sources/list.py Thu Jun 08 08:49:33 2017 -0700 @@ -0,0 +1,31 @@ +from piecrust.sources.base import ContentSource + + +class ListSource(ContentSource): + def __init__(self, inner_source, items): + super().__init__( + inner_source.app, inner_source.name, inner_source.config) + + self.inner_source = inner_source + self.items = items + + def openItem(self, item, mode='r', **kwargs): + return self.inner_source.openItem(item, mode, **kwargs) + + def getItemMtime(self, item): + return self.inner_source.getItemMtime(item) + + def getContents(self, group): + return self.items + + def getRelatedContents(self, item, relationship): + return self.inner_source.getRelatedContents(item, relationship) + + def findContent(self, route_params): + # Can't find items... we could find stuff that's not in our list? + raise NotImplementedError( + "The list source doesn't support finding items.") + + def getSupportedRouteParameters(self): + return self.inner_source.getSupportedRouteParameters() +