comparison piecrust/rendering.py @ 711:ab5c6a8ae90a

bake: Replace hard-coded taxonomy support with "generator" system. * Taxonomies are now implemented one or more `TaxonomyGenerator`s. * A `BlogArchivesGenerator` stub is there but non-functional.
author Ludovic Chabant <ludovic@chabant.com>
date Thu, 26 May 2016 19:52:47 -0700
parents 33ab9badfd7a
children a14371c5cda7
comparison
equal deleted inserted replaced
710:e85f29b28b84 711:ab5c6a8ae90a
77 77
78 78
79 class RenderPassInfo(object): 79 class RenderPassInfo(object):
80 def __init__(self): 80 def __init__(self):
81 self.used_source_names = set() 81 self.used_source_names = set()
82 self.used_taxonomy_terms = set()
83 self.used_pagination = False 82 self.used_pagination = False
84 self.pagination_has_more = False 83 self.pagination_has_more = False
85 self.used_assets = False 84 self.used_assets = False
86 85 self._custom_info = {}
87 def merge(self, other): 86
88 self.used_source_names |= other.used_source_names 87 def setCustomInfo(self, key, info):
89 self.used_taxonomy_terms |= other.used_taxonomy_terms 88 self._custom_info[key] = info
90 self.used_pagination = self.used_pagination or other.used_pagination 89
91 self.pagination_has_more = (self.pagination_has_more or 90 def getCustomInfo(self, key, default=None, create_if_missing=False):
92 other.pagination_has_more) 91 if create_if_missing:
93 self.used_assets = self.used_assets or other.used_assets 92 return self._custom_info.setdefault(key, default)
93 return self._custom_info.get(key, default)
94
94 95
95 def _toJson(self): 96 def _toJson(self):
96 data = { 97 data = {
97 'used_source_names': list(self.used_source_names), 98 'used_source_names': list(self.used_source_names),
98 'used_taxonomy_terms': list(self.used_taxonomy_terms),
99 'used_pagination': self.used_pagination, 99 'used_pagination': self.used_pagination,
100 'pagination_has_more': self.pagination_has_more, 100 'pagination_has_more': self.pagination_has_more,
101 'used_assets': self.used_assets} 101 'used_assets': self.used_assets,
102 'custom_info': self._custom_info}
102 return data 103 return data
103 104
104 @staticmethod 105 @staticmethod
105 def _fromJson(data): 106 def _fromJson(data):
106 assert data is not None 107 assert data is not None
107 rpi = RenderPassInfo() 108 rpi = RenderPassInfo()
108 rpi.used_source_names = set(data['used_source_names']) 109 rpi.used_source_names = set(data['used_source_names'])
109 for i in data['used_taxonomy_terms']:
110 terms = i[2]
111 if isinstance(terms, list):
112 terms = tuple(terms)
113 rpi.used_taxonomy_terms.add((i[0], i[1], terms))
114 rpi.used_pagination = data['used_pagination'] 110 rpi.used_pagination = data['used_pagination']
115 rpi.pagination_has_more = data['pagination_has_more'] 111 rpi.pagination_has_more = data['pagination_has_more']
116 rpi.used_assets = data['used_assets'] 112 rpi.used_assets = data['used_assets']
113 rpi._custom_info = data['custom_info']
117 return rpi 114 return rpi
118 115
119 116
120 class PageRenderingContext(object): 117 class PageRenderingContext(object):
121 def __init__(self, qualified_page, page_num=1, force_render=False): 118 def __init__(self, qualified_page, page_num=1,
119 force_render=False, is_from_request=False):
122 self.page = qualified_page 120 self.page = qualified_page
123 self.page_num = page_num 121 self.page_num = page_num
124 self.force_render = force_render 122 self.force_render = force_render
123 self.is_from_request = is_from_request
125 self.pagination_source = None 124 self.pagination_source = None
126 self.pagination_filter = None 125 self.pagination_filter = None
127 self.custom_data = None 126 self.custom_data = None
127 self.render_passes = [None, None] # Same length as RENDER_PASSES
128 self._current_pass = PASS_NONE 128 self._current_pass = PASS_NONE
129
130 self.render_passes = [None, None] # Same length as RENDER_PASSES
131 129
132 @property 130 @property
133 def app(self): 131 def app(self):
134 return self.page.app 132 return self.page.app
135 133
166 self._raiseIfNoCurrentPass() 164 self._raiseIfNoCurrentPass()
167 if isinstance(source, PageSource): 165 if isinstance(source, PageSource):
168 pass_info = self.current_pass_info 166 pass_info = self.current_pass_info
169 pass_info.used_source_names.add(source.name) 167 pass_info.used_source_names.add(source.name)
170 168
171 def setTaxonomyFilter(self, term_value, *, needs_slugifier=False):
172 if not self.page.route.is_taxonomy_route:
173 raise Exception("The page for this context is not tied to a "
174 "taxonomy route: %s" % self.uri)
175
176 slugifier = None
177 if needs_slugifier:
178 slugifier = self.page.route.slugifyTaxonomyTerm
179 taxonomy = self.app.getTaxonomy(self.page.route.taxonomy_name)
180
181 flt = PaginationFilter(value_accessor=page_value_accessor)
182 flt.addClause(HasTaxonomyTermsFilterClause(
183 taxonomy, term_value, slugifier))
184 self.pagination_filter = flt
185
186 is_combination = isinstance(term_value, tuple)
187 self.custom_data = {
188 taxonomy.term_name: term_value,
189 'is_multiple_%s' % taxonomy.term_name: is_combination}
190
191 def _raiseIfNoCurrentPass(self): 169 def _raiseIfNoCurrentPass(self):
192 if self._current_pass == PASS_NONE: 170 if self._current_pass == PASS_NONE:
193 raise Exception("No rendering pass is currently active.") 171 raise Exception("No rendering pass is currently active.")
194
195
196 class HasTaxonomyTermsFilterClause(SettingFilterClause):
197 def __init__(self, taxonomy, value, slugifier):
198 super(HasTaxonomyTermsFilterClause, self).__init__(
199 taxonomy.setting_name, value)
200 self._taxonomy = taxonomy
201 self._slugifier = slugifier
202 self._is_combination = isinstance(self.value, tuple)
203
204 def pageMatches(self, fil, page):
205 if self._taxonomy.is_multiple:
206 # Multiple taxonomy, i.e. it supports multiple terms, like tags.
207 page_values = fil.value_accessor(page, self.name)
208 if page_values is None or not isinstance(page_values, list):
209 return False
210
211 if self._slugifier is not None:
212 page_set = set(map(self._slugifier, page_values))
213 else:
214 page_set = set(page_values)
215
216 if self._is_combination:
217 # Multiple taxonomy, and multiple terms to match. Check that
218 # the ones to match are all in the page's terms.
219 value_set = set(self.value)
220 return value_set.issubset(page_set)
221 else:
222 # Multiple taxonomy, one term to match.
223 return self.value in page_set
224
225 # Single taxonomy. Just compare the values.
226 page_value = fil.value_accessor(page, self.name)
227 if page_value is None:
228 return False
229 if self._slugifier is not None:
230 page_value = self._slugifier(page_value)
231 return page_value == self.value
232 172
233 173
234 def render_page(ctx): 174 def render_page(ctx):
235 eis = ctx.app.env.exec_info_stack 175 eis = ctx.app.env.exec_info_stack
236 eis.pushPage(ctx.page, ctx) 176 eis.pushPage(ctx.page, ctx)
283 if layout_result['pass_info'] is not None: 223 if layout_result['pass_info'] is not None:
284 rp.render_info[PASS_RENDERING] = RenderPassInfo._fromJson( 224 rp.render_info[PASS_RENDERING] = RenderPassInfo._fromJson(
285 layout_result['pass_info']) 225 layout_result['pass_info'])
286 return rp 226 return rp
287 except Exception as ex: 227 except Exception as ex:
228 logger.exception(ex)
288 page_rel_path = os.path.relpath(ctx.page.path, ctx.app.root_dir) 229 page_rel_path = os.path.relpath(ctx.page.path, ctx.app.root_dir)
289 raise Exception("Error rendering page: %s" % page_rel_path) from ex 230 raise Exception("Error rendering page: %s" % page_rel_path) from ex
290 finally: 231 finally:
291 ctx.setCurrentPass(PASS_NONE) 232 ctx.setCurrentPass(PASS_NONE)
292 eis.popPage() 233 eis.popPage()
400 engine = get_template_engine(page.app, engine_name) 341 engine = get_template_engine(page.app, engine_name)
401 342
402 try: 343 try:
403 output = engine.renderFile(full_names, layout_data) 344 output = engine.renderFile(full_names, layout_data)
404 except TemplateNotFoundError as ex: 345 except TemplateNotFoundError as ex:
346 logger.exception(ex)
405 msg = "Can't find template for page: %s\n" % page.path 347 msg = "Can't find template for page: %s\n" % page.path
406 msg += "Looked for: %s" % ', '.join(full_names) 348 msg += "Looked for: %s" % ', '.join(full_names)
407 raise Exception(msg) from ex 349 raise Exception(msg) from ex
408 350
409 pass_info = cpi.render_ctx.render_passes[PASS_RENDERING] 351 pass_info = cpi.render_ctx.render_passes[PASS_RENDERING]