changeset 324:65e6d72f3877

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.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 29 Mar 2015 23:08:37 -0700
parents 412537e91e45
children c750dc21d55d
files piecrust/baking/baker.py piecrust/baking/single.py piecrust/rendering.py piecrust/serving.py tests/test_baking_baker.py tests/test_serving.py
diffstat 6 files changed, 192 insertions(+), 40 deletions(-) [+]
line wrap: on
line diff
--- 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
--- 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)
 
--- 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
--- 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
--- 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"
+
--- 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
+