# HG changeset patch # User Ludovic Chabant # Date 1427695717 25200 # Node ID 65e6d72f3877c6e3085ee80f90f0bab38f90c798 # Parent 412537e91e45f79738c787a12fafaa438df6f731 bake/serve: Fix how taxonomy index pages are setup and rendered. * Properly use the taxonomy's setting name where appropriate. * Delete duplicated (and sometimes incorrect) code in 2 places to setup filtering on an index page and consolidate it on the `PageRenderingContext`. * Add unit tests. diff -r 412537e91e45 -r 65e6d72f3877 piecrust/baking/baker.py --- a/piecrust/baking/baker.py Sun Mar 29 23:05:03 2015 -0700 +++ b/piecrust/baking/baker.py Sun Mar 29 23:08:37 2015 -0700 @@ -202,12 +202,12 @@ # pages. if (not prev_entry and cur_entry and cur_entry.was_baked_successfully): - changed_terms = cur_entry.config.get(tax.name) + changed_terms = cur_entry.config.get(tax.setting_name) elif (prev_entry and cur_entry and cur_entry.was_baked_successfully): changed_terms = [] - prev_terms = prev_entry.config.get(tax.name) - cur_terms = cur_entry.config.get(tax.name) + prev_terms = prev_entry.config.get(tax.setting_name) + cur_terms = cur_entry.config.get(tax.setting_name) if tax.is_multiple: if prev_terms is not None: changed_terms += prev_terms diff -r 412537e91e45 -r 65e6d72f3877 piecrust/baking/single.py --- a/piecrust/baking/single.py Sun Mar 29 23:05:03 2015 -0700 +++ b/piecrust/baking/single.py Sun Mar 29 23:08:37 2015 -0700 @@ -55,33 +55,19 @@ def bake(self, factory, route, record_entry, taxonomy_name=None, taxonomy_term=None): - custom_data = None - pagination_filter = None + taxonomy = None route_metadata = dict(factory.metadata) if taxonomy_name and taxonomy_term: - # Must bake a taxonomy listing page... we'll have to add a - # pagination filter for only get matching posts, and the output - # URL will be a bit different. - tax = self.app.getTaxonomy(taxonomy_name) - pagination_filter = PaginationFilter( - value_accessor=page_value_accessor) - if tax.is_multiple: + # TODO: add options for combining and slugifying terms + taxonomy = self.app.getTaxonomy(taxonomy_name) + if taxonomy.is_multiple: if isinstance(taxonomy_term, tuple): - abc = AndBooleanClause() - for t in taxonomy_term: - abc.addClause(HasFilterClause(taxonomy_name, t)) - pagination_filter.addClause(abc) slugified_term = '/'.join(taxonomy_term) else: - pagination_filter.addClause( - HasFilterClause(taxonomy_name, taxonomy_term)) slugified_term = taxonomy_term else: - pagination_filter.addClause( - IsFilterClause(taxonomy_name, taxonomy_term)) slugified_term = taxonomy_term - custom_data = {tax.term_name: taxonomy_term} - route_metadata.update({tax.term_name: slugified_term}) + route_metadata.update({taxonomy.setting_name: slugified_term}) # Generate the URL using the route. page = factory.buildPage() @@ -179,7 +165,7 @@ logger.debug(" p%d -> %s" % (cur_sub, out_path)) ctx, rp = self._bakeSingle(page, sub_uri, cur_sub, out_path, - pagination_filter, custom_data) + taxonomy, taxonomy_term) except Exception as ex: if self.app.debug: logger.exception(ex) @@ -219,13 +205,11 @@ has_more_subs = True def _bakeSingle(self, page, sub_uri, num, out_path, - pagination_filter=None, custom_data=None): + taxonomy=None, taxonomy_term=None): ctx = PageRenderingContext(page, sub_uri) ctx.page_num = num - if pagination_filter: - ctx.pagination_filter = pagination_filter - if custom_data: - ctx.custom_data = custom_data + if taxonomy and taxonomy_term: + ctx.setTaxonomyFilter(taxonomy, taxonomy_term) rp = render_page(ctx) diff -r 412537e91e45 -r 65e6d72f3877 piecrust/rendering.py --- a/piecrust/rendering.py Sun Mar 29 23:05:03 2015 -0700 +++ b/piecrust/rendering.py Sun Mar 29 23:08:37 2015 -0700 @@ -3,6 +3,9 @@ import logging from piecrust.data.builder import (DataBuildingContext, build_page_data, build_layout_data) +from piecrust.data.filters import ( + PaginationFilter, HasFilterClause, IsFilterClause, AndBooleanClause, + page_value_accessor) from piecrust.sources.base import PageSource from piecrust.templating.base import TemplateNotFoundError, TemplatingError from piecrust.uriutil import get_slug @@ -83,6 +86,23 @@ if isinstance(source, PageSource): self.used_source_names.add((source.name, self.current_pass)) + def setTaxonomyFilter(self, taxonomy, term_value): + flt = PaginationFilter(value_accessor=page_value_accessor) + if taxonomy.is_multiple: + if isinstance(term_value, tuple): + abc = AndBooleanClause() + for t in term_value: + abc.addClause(HasFilterClause(taxonomy.setting_name, t)) + flt.addClause(abc) + else: + flt.addClause( + HasFilterClause(taxonomy.setting_name, term_value)) + else: + flt.addClause(IsFilterClause(taxonomy.setting_name, term_value)) + self.pagination_filter = flt + self.custom_data = { + taxonomy.term_name: term_value} + def render_page(ctx): eis = ctx.app.env.exec_info_stack diff -r 412537e91e45 -r 65e6d72f3877 piecrust/serving.py --- a/piecrust/serving.py Sun Mar 29 23:05:03 2015 -0700 +++ b/piecrust/serving.py Sun Mar 29 23:08:37 2015 -0700 @@ -16,9 +16,6 @@ from werkzeug.wsgi import ClosingIterator, wrap_file from jinja2 import FileSystemLoader, Environment from piecrust.app import PieCrust -from piecrust.data.filters import ( - PaginationFilter, HasFilterClause, IsFilterClause, - page_value_accessor) from piecrust.environment import StandardEnvironment from piecrust.processing.base import ProcessorPipeline from piecrust.rendering import PageRenderingContext, render_page @@ -230,6 +227,7 @@ raise RouteNotFoundError("Can't find route for: %s" % req_path) taxonomy = None + term_value = None for route, route_metadata in routes: source = app.getSource(route.source_name) if route.taxonomy is None: @@ -259,14 +257,7 @@ render_ctx = PageRenderingContext(page, req_path, page_num, force_render=True) if taxonomy is not None: - flt = PaginationFilter(value_accessor=page_value_accessor) - if taxonomy.is_multiple: - flt.addClause(HasFilterClause(taxonomy.name, term_value)) - else: - flt.addClause(IsFilterClause(taxonomy.name, term_value)) - render_ctx.pagination_filter = flt - render_ctx.custom_data = { - taxonomy.term_name: term_value} + render_ctx.setTaxonomyFilter(taxonomy, term_value) # See if this page is known to use sources. If that's the case, # just don't use cached rendered segments for that page (but still diff -r 412537e91e45 -r 65e6d72f3877 tests/test_baking_baker.py --- a/tests/test_baking_baker.py Sun Mar 29 23:05:03 2015 -0700 +++ b/tests/test_baking_baker.py Sun Mar 29 23:08:37 2015 -0700 @@ -3,7 +3,6 @@ import pytest from piecrust.baking.baker import PageBaker, Baker from piecrust.baking.records import BakeRecord -from piecrust.routing import Route from .mockutil import get_mock_app, mock_fs, mock_fs_scope @@ -75,6 +74,7 @@ '2010': {'01': {'01': {'post1.html': 'post one'}}}, 'index.html': 'something'} + def test_removed(): fs = (mock_fs() .withPage('pages/foo.md', {'layout': 'none', 'format': 'none'}, 'a foo page') @@ -97,6 +97,7 @@ assert structure == { 'index.html': 'something'} + def test_record_version_change(): fs = (mock_fs() .withPage('pages/foo.md', {'layout': 'none', 'format': 'none'}, 'a foo page')) @@ -122,3 +123,64 @@ finally: BakeRecord.RECORD_VERSION -= 1 + +def test_bake_tags(): + tags = [ + ['foo'], + ['bar', 'whatever'], + ['foo', 'bar']] + + def config_factory(i): + c = {'title': 'Post %d' % (i + 1)} + c['tags'] = tags[i] + return c + + fs = (mock_fs() + .withPages(3, 'posts/2015-03-{idx1:02}_post{idx1:02}.md', + config_factory) + .withPage('pages/_tag.md', {'layout': 'none', 'format': 'none'}, + "Pages in {{tag}}\n" + "{%for p in pagination.posts -%}\n" + "{{p.title}}\n" + "{%endfor%}")) + with mock_fs_scope(fs): + out_dir = fs.path('kitchen/_counter') + app = fs.getApp() + baker = Baker(app, out_dir) + baker.bake() + + s = fs.getStructure('kitchen/_counter/tag') + assert s['foo.html'] == "Pages in foo\nPost 3\nPost 1\n" + assert s['bar.html'] == "Pages in bar\nPost 3\nPost 2\n" + assert s['whatever.html'] == "Pages in whatever\nPost 2\n" + + +def test_bake_categories(): + categories = [ + 'foo', 'bar', 'foo'] + + def config_factory(i): + c = {'title': 'Post %d' % (i + 1)} + c['category'] = categories[i] + return c + + fs = (mock_fs() + .withConfig({'site': {'category_url': 'cat/%category%'}}) + .withPages(3, 'posts/2015-03-{idx1:02}_post{idx1:02}.md', + config_factory) + .withPage('pages/_category.md', {'layout': 'none', 'format': 'none'}, + "Pages in {{category}}\n" + "{%for p in pagination.posts -%}\n" + "{{p.title}}\n" + "{%endfor%}")) + with mock_fs_scope(fs): + out_dir = fs.path('kitchen/_counter') + app = fs.getApp() + baker = Baker(app, out_dir) + baker.bake() + + print(fs.getStructure('kitchen/_counter').keys()) + s = fs.getStructure('kitchen/_counter/cat') + assert s['foo.html'] == "Pages in foo\nPost 3\nPost 1\n" + assert s['bar.html'] == "Pages in bar\nPost 2\n" + diff -r 412537e91e45 -r 65e6d72f3877 tests/test_serving.py --- a/tests/test_serving.py Sun Mar 29 23:05:03 2015 -0700 +++ b/tests/test_serving.py Sun Mar 29 23:08:37 2015 -0700 @@ -1,8 +1,13 @@ import re import pytest import mock +from piecrust.data.filters import ( + PaginationFilter, HasFilterClause, IsFilterClause, + page_value_accessor) +from piecrust.rendering import PageRenderingContext, render_page from piecrust.serving import find_routes from piecrust.sources.base import REALM_USER, REALM_THEME +from .mockutil import mock_fs, mock_fs_scope @pytest.mark.parametrize('uri, route_specs, expected', @@ -33,3 +38,93 @@ assert route.source_name == exp_source assert metadata == exp_md + +@pytest.mark.parametrize( + 'tag, expected_indices', + [ + ('foo', [1, 2, 4, 5, 6]), + ('bar', [2, 3, 4, 6, 8]), + ('whatever', [5, 8]), + ('unique', [7]), + ('missing', None) + ]) +def test_serve_tag_page(tag, expected_indices): + tags = [ + ['foo'], + ['foo', 'bar'], + ['bar'], + ['bar', 'foo'], + ['foo', 'whatever'], + ['foo', 'bar'], + ['unique'], + ['whatever', 'bar']] + + def config_factory(i): + c = {'title': 'Post %d' % (i + 1)} + c['tags'] = list(tags[i]) + return c + + fs = (mock_fs() + .withPages(8, 'posts/2015-03-{idx1:02}_post{idx1:02}.md', + config_factory) + .withPage('pages/_tag.md', {'layout': 'none', 'format': 'none'}, + "Pages in {{tag}}\n" + "{%for p in pagination.posts -%}\n" + "{{p.title}}\n" + "{%endfor%}")) + with mock_fs_scope(fs): + app = fs.getApp() + page = app.getSource('pages').getPage({'slug': '_tag'}) + taxonomy = app.getTaxonomy('tags') + + ctx = PageRenderingContext(page, '/tag/' + tag) + ctx.setTaxonomyFilter(taxonomy, tag) + rp = render_page(ctx) + + expected = "Pages in %s\n" % tag + if expected_indices: + for i in reversed(expected_indices): + expected += "Post %d\n" % i + assert expected == rp.content + + +@pytest.mark.parametrize( + 'category, expected_indices', + [ + ('foo', [1, 2, 4]), + ('bar', [3, 6]), + ('missing', None) + ]) +def test_serve_category_page(category, expected_indices): + categories = [ + 'foo', 'foo', 'bar', 'foo', None, 'bar'] + + def config_factory(i): + c = {'title': 'Post %d' % (i + 1)} + if categories[i]: + c['category'] = categories[i] + return c + + fs = (mock_fs() + .withPages(6, 'posts/2015-03-{idx1:02}_post{idx1:02}.md', + config_factory) + .withPage('pages/_category.md', {'layout': 'none', 'format': 'none'}, + "Pages in {{category}}\n" + "{%for p in pagination.posts -%}\n" + "{{p.title}}\n" + "{%endfor%}")) + with mock_fs_scope(fs): + app = fs.getApp() + page = app.getSource('pages').getPage({'slug': '_category'}) + taxonomy = app.getTaxonomy('categories') + + ctx = PageRenderingContext(page, '/' + category) + ctx.setTaxonomyFilter(taxonomy, category) + rp = render_page(ctx) + + expected = "Pages in %s\n" % category + if expected_indices: + for i in reversed(expected_indices): + expected += "Post %d\n" % i + assert expected == rp.content +