changeset 515:16e705c58cae

internal: Improve handling of taxonomy term slugification. This paves the way to bring slugification options like transliteration to PieCrust. This change mostly makes sure we use one-way slugification, which means, for serving/previewing, we need to slugify each page's terms to compare to the stuff captured from the URL. It also simplifies things a bit in the code.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 26 Jul 2015 23:16:15 -0700
parents c9c305645e5f
children 73bd408caebc
files piecrust/baking/single.py piecrust/rendering.py piecrust/routing.py piecrust/serving/server.py tests/test_serving.py
diffstat 5 files changed, 71 insertions(+), 33 deletions(-) [+]
line wrap: on
line diff
--- a/piecrust/baking/single.py	Sun Jul 26 23:05:04 2015 -0700
+++ b/piecrust/baking/single.py	Sun Jul 26 23:16:15 2015 -0700
@@ -157,8 +157,7 @@
     def _bakeSingle(self, qualified_page, num, out_path, tax_info=None):
         ctx = PageRenderingContext(qualified_page, page_num=num)
         if tax_info:
-            tax = self.app.getTaxonomy(tax_info.taxonomy_name)
-            ctx.setTaxonomyFilter(tax, tax_info.term)
+            ctx.setTaxonomyFilter(tax_info.term)
 
         rp = render_page(ctx)
 
--- a/piecrust/rendering.py	Sun Jul 26 23:05:04 2015 -0700
+++ b/piecrust/rendering.py	Sun Jul 26 23:16:15 2015 -0700
@@ -6,8 +6,7 @@
 from piecrust.data.builder import (
         DataBuildingContext, build_page_data, build_layout_data)
 from piecrust.data.filters import (
-        PaginationFilter, HasFilterClause, IsFilterClause, AndBooleanClause,
-        page_value_accessor)
+        PaginationFilter, SettingFilterClause, page_value_accessor)
 from piecrust.sources.base import PageSource
 from piecrust.templating.base import TemplateNotFoundError, TemplatingError
 
@@ -167,22 +166,18 @@
             pass_info = self.current_pass_info
             pass_info.used_source_names.add(source.name)
 
-    def setTaxonomyFilter(self, taxonomy, term_value):
-        is_combination = isinstance(term_value, tuple)
+    def setTaxonomyFilter(self, term_value):
+        if not self.page.route.is_taxonomy_route:
+            raise Exception("The page for this context is not tied to a "
+                            "taxonomy route: %s" % self.uri)
+
+        taxonomy = self.app.getTaxonomy(self.page.route.taxonomy_name)
         flt = PaginationFilter(value_accessor=page_value_accessor)
-        if taxonomy.is_multiple:
-            if is_combination:
-                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))
+        flt.addClause(HasTaxonomyTermsFilterClause(
+                taxonomy, term_value, self.page.route.slugifyTaxonomyTerm))
         self.pagination_filter = flt
 
+        is_combination = isinstance(term_value, tuple)
         self.custom_data = {
                 taxonomy.term_name: term_value,
                 'is_multiple_%s' % taxonomy.term_name: is_combination}
@@ -192,6 +187,44 @@
             raise Exception("No rendering pass is currently active.")
 
 
+class HasTaxonomyTermsFilterClause(SettingFilterClause):
+    def __init__(self, taxonomy, value, slugifier):
+        super(HasTaxonomyTermsFilterClause, self).__init__(
+                taxonomy.setting_name, value)
+        self._taxonomy = taxonomy
+        self._slugifier = slugifier
+        self._is_combination = isinstance(self.value, tuple)
+
+    def pageMatches(self, fil, page):
+        if self._taxonomy.is_multiple:
+            # Multiple taxonomy, i.e. it supports multiple terms, like tags.
+            page_values = fil.value_accessor(page, self.name)
+            if page_values is None or not isinstance(page_values, list):
+                return False
+
+            if self._slugifier is not None:
+                page_set = set(map(self._slugifier, page_values))
+            else:
+                page_set = set(page_values)
+
+            if self._is_combination:
+                # Multiple taxonomy, and multiple terms to match. Check that
+                # the ones to match are all in the page's terms.
+                value_set = set(self.value)
+                return value_set.issubset(page_set)
+            else:
+                # Multiple taxonomy, one term to match.
+                return self.value in page_set
+
+        # Single taxonomy. Just compare the values.
+        page_value = fil.value_accessor(page, self.name)
+        if page_value is None:
+            return False
+        if self._slugifier is not None:
+            page_value = self._slugifier(page_value)
+        return page_value == self.value
+
+
 def render_page(ctx):
     eis = ctx.app.env.exec_info_stack
     eis.pushPage(ctx.page, ctx)
--- a/piecrust/routing.py	Sun Jul 26 23:05:04 2015 -0700
+++ b/piecrust/routing.py	Sun Jul 26 23:16:15 2015 -0700
@@ -42,6 +42,7 @@
 
         self.source_name = cfg['source']
         self.taxonomy_name = cfg.get('taxonomy')
+        self.taxonomy_term_sep = cfg.get('term_separator', '/')
 
         self.pretty_urls = app.config.get('site/pretty_urls')
         self.trailing_slash = app.config.get('site/trailing_slash')
@@ -191,19 +192,26 @@
 
         return uri
 
+    def getTaxonomyTerms(self, route_metadata):
+        if not self.is_taxonomy_route:
+            raise Exception("This route isn't a taxonomy route.")
+
+        tax = self.app.getTaxonomy(self.taxonomy_name)
+        all_values = route_metadata.get(tax.term_name)
+        if all_values is None:
+            raise Exception("'%s' values couldn't be found in route metadata" %
+                            tax.term_name)
+
+        if self.taxonomy_term_sep in all_values:
+            return tuple(all_values.split(self.taxonomy_term_sep))
+        return all_values
+
     def slugifyTaxonomyTerm(self, term):
         #TODO: add options for transliterating and combining terms.
         if isinstance(term, tuple):
             return '/'.join(term)
         return term
 
-    def unslugifyTaxonomyTerm(self, term):
-        #TODO: same as above.
-        split_terms = term.split('/')
-        if len(split_terms) == 1:
-            return term
-        return tuple(split_terms)
-
     def _uriFormatRepl(self, m):
         name = m.group('name')
         #TODO: fix this hard-coded shit
--- a/piecrust/serving/server.py	Sun Jul 26 23:05:04 2015 -0700
+++ b/piecrust/serving/server.py	Sun Jul 26 23:16:15 2015 -0700
@@ -321,11 +321,11 @@
             if route_terms is None:
                 return None
 
+            tax_terms = route.getTaxonomyTerms(route_metadata)
+            taxonomy_info = (taxonomy, tax_terms)
+
             tax_page_ref = taxonomy.getPageRef(source)
             factory = tax_page_ref.getFactory()
-            tax_terms = route.unslugifyTaxonomyTerm(route_terms)
-            route_metadata[taxonomy.term_name] = tax_terms
-            taxonomy_info = (taxonomy, tax_terms)
 
         # Build the page.
         page = factory.buildPage()
@@ -336,8 +336,8 @@
                                           page_num=page_num,
                                           force_render=True)
         if taxonomy_info is not None:
-            taxonomy, tax_terms = taxonomy_info
-            render_ctx.setTaxonomyFilter(taxonomy, tax_terms)
+            _, tax_terms = taxonomy_info
+            render_ctx.setTaxonomyFilter(tax_terms)
 
         # 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_serving.py	Sun Jul 26 23:05:04 2015 -0700
+++ b/tests/test_serving.py	Sun Jul 26 23:16:15 2015 -0700
@@ -77,11 +77,10 @@
         page = app.getSource('pages').getPage({'slug': '_tag', 'tag': tag})
         route = app.getTaxonomyRoute('tags', 'posts')
         route_metadata = {'slug': '_tag', 'tag': tag}
-        taxonomy = app.getTaxonomy('tags')
 
         qp = QualifiedPage(page, route, route_metadata)
         ctx = PageRenderingContext(qp)
-        ctx.setTaxonomyFilter(taxonomy, tag)
+        ctx.setTaxonomyFilter(tag)
         rp = render_page(ctx)
 
         expected = "Pages in %s\n" % tag
@@ -122,11 +121,10 @@
                                                'category': category})
         route = app.getTaxonomyRoute('categories', 'posts')
         route_metadata = {'slug': '_category', 'category': category}
-        taxonomy = app.getTaxonomy('categories')
 
         qp = QualifiedPage(page, route, route_metadata)
         ctx = PageRenderingContext(qp)
-        ctx.setTaxonomyFilter(taxonomy, category)
+        ctx.setTaxonomyFilter(category)
         rp = render_page(ctx)
 
         expected = "Pages in %s\n" % category