Mercurial > piecrust2
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] |