Mercurial > piecrust2
comparison piecrust/rendering.py @ 338:938be93215cb
bake: Improve render context and bake record, fix incremental bake bugs.
* Used sources and taxonomies are now stored on a per-render-pass basis.
This fixes bugs where sources/taxonomies were used for one pass, but that
pass is skipped on a later bake because its result is cached.
* Bake records are now created for all pages even when they're not baked.
Record collapsing is gone except for taxonomy index pages.
* Bake records now also have sub-entries in order to store information about
each sub-page, since some sub-pages could use sources/taxonomies differently
than others, or be missing from the output. This lets PieCrust handle
clean/dirty states on a sub-page level.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Mon, 06 Apr 2015 19:59:54 -0700 |
parents | b034f6f15e22 |
children | 4b1019bb2533 |
comparison
equal
deleted
inserted
replaced
337:49408002798e | 338:938be93215cb |
---|---|
30 self.page = page | 30 self.page = page |
31 self.uri = uri | 31 self.uri = uri |
32 self.num = num | 32 self.num = num |
33 self.data = None | 33 self.data = None |
34 self.content = None | 34 self.content = None |
35 self.execution_info = None | |
36 | 35 |
37 @property | 36 @property |
38 def app(self): | 37 def app(self): |
39 return self.page.app | 38 return self.page.app |
40 | 39 |
41 | 40 |
42 PASS_NONE = 0 | 41 PASS_NONE = 0 |
43 PASS_FORMATTING = 1 | 42 PASS_FORMATTING = 1 |
44 PASS_RENDERING = 2 | 43 PASS_RENDERING = 2 |
44 | |
45 | |
46 RENDER_PASSES = [PASS_FORMATTING, PASS_RENDERING] | |
47 | |
48 | |
49 class RenderPassInfo(object): | |
50 def __init__(self): | |
51 self.used_source_names = set() | |
52 self.used_taxonomy_terms = set() | |
45 | 53 |
46 | 54 |
47 class PageRenderingContext(object): | 55 class PageRenderingContext(object): |
48 def __init__(self, page, uri, page_num=1, force_render=False): | 56 def __init__(self, page, uri, page_num=1, force_render=False): |
49 self.page = page | 57 self.page = page |
51 self.page_num = page_num | 59 self.page_num = page_num |
52 self.force_render = force_render | 60 self.force_render = force_render |
53 self.pagination_source = None | 61 self.pagination_source = None |
54 self.pagination_filter = None | 62 self.pagination_filter = None |
55 self.custom_data = None | 63 self.custom_data = None |
56 self.use_cache = False | 64 self._current_pass = PASS_NONE |
65 | |
66 self.render_passes = {} | |
67 self.used_pagination = None | |
57 self.used_assets = None | 68 self.used_assets = None |
58 self.used_pagination = None | |
59 self.used_source_names = set() | |
60 self.used_taxonomy_terms = set() | |
61 self.current_pass = PASS_NONE | |
62 | 69 |
63 @property | 70 @property |
64 def app(self): | 71 def app(self): |
65 return self.page.app | 72 return self.page.app |
66 | 73 |
67 @property | 74 @property |
68 def source_metadata(self): | 75 def source_metadata(self): |
69 return self.page.source_metadata | 76 return self.page.source_metadata |
70 | 77 |
78 @property | |
79 def current_pass_info(self): | |
80 return self.render_passes.get(self._current_pass) | |
81 | |
82 def setCurrentPass(self, rdr_pass): | |
83 if rdr_pass != PASS_NONE: | |
84 self.render_passes.setdefault(rdr_pass, RenderPassInfo()) | |
85 self._current_pass = rdr_pass | |
86 | |
71 def setPagination(self, paginator): | 87 def setPagination(self, paginator): |
88 self._raiseIfNoCurrentPass() | |
72 if self.used_pagination is not None: | 89 if self.used_pagination is not None: |
73 raise Exception("Pagination has already been used.") | 90 raise Exception("Pagination has already been used.") |
74 self.used_pagination = paginator | 91 self.used_pagination = paginator |
75 self.addUsedSource(paginator._source) | 92 self.addUsedSource(paginator._source) |
76 | 93 |
77 def addUsedSource(self, source): | 94 def addUsedSource(self, source): |
95 self._raiseIfNoCurrentPass() | |
78 if isinstance(source, PageSource): | 96 if isinstance(source, PageSource): |
79 self.used_source_names.add((source.name, self.current_pass)) | 97 pass_info = self.render_passes[self._current_pass] |
98 pass_info.used_source_names.add(source.name) | |
80 | 99 |
81 def setTaxonomyFilter(self, taxonomy, term_value): | 100 def setTaxonomyFilter(self, taxonomy, term_value): |
82 is_combination = isinstance(term_value, tuple) | 101 is_combination = isinstance(term_value, tuple) |
83 flt = PaginationFilter(value_accessor=page_value_accessor) | 102 flt = PaginationFilter(value_accessor=page_value_accessor) |
84 if taxonomy.is_multiple: | 103 if taxonomy.is_multiple: |
96 | 115 |
97 self.custom_data = { | 116 self.custom_data = { |
98 taxonomy.term_name: term_value, | 117 taxonomy.term_name: term_value, |
99 'is_multiple_%s' % taxonomy.term_name: is_combination} | 118 'is_multiple_%s' % taxonomy.term_name: is_combination} |
100 | 119 |
120 def _raiseIfNoCurrentPass(self): | |
121 if self._current_pass == PASS_NONE: | |
122 raise Exception("No rendering pass is currently active.") | |
123 | |
101 | 124 |
102 def render_page(ctx): | 125 def render_page(ctx): |
103 eis = ctx.app.env.exec_info_stack | 126 eis = ctx.app.env.exec_info_stack |
104 eis.pushPage(ctx.page, ctx) | 127 eis.pushPage(ctx.page, ctx) |
105 try: | 128 try: |
112 page_data = build_page_data(data_ctx) | 135 page_data = build_page_data(data_ctx) |
113 if ctx.custom_data: | 136 if ctx.custom_data: |
114 page_data.update(ctx.custom_data) | 137 page_data.update(ctx.custom_data) |
115 | 138 |
116 # Render content segments. | 139 # Render content segments. |
117 ctx.current_pass = PASS_FORMATTING | 140 ctx.setCurrentPass(PASS_FORMATTING) |
118 repo = ctx.app.env.rendered_segments_repository | 141 repo = ctx.app.env.rendered_segments_repository |
119 if repo and not ctx.force_render: | 142 if repo and not ctx.force_render: |
120 cache_key = ctx.uri | 143 cache_key = ctx.uri |
121 page_time = page.path_mtime | 144 page_time = page.path_mtime |
122 contents = repo.get( | 145 contents = repo.get( |
125 fs_cache_time=page_time) | 148 fs_cache_time=page_time) |
126 else: | 149 else: |
127 contents = _do_render_page_segments(page, page_data) | 150 contents = _do_render_page_segments(page, page_data) |
128 | 151 |
129 # Render layout. | 152 # Render layout. |
130 ctx.current_pass = PASS_RENDERING | 153 ctx.setCurrentPass(PASS_RENDERING) |
131 layout_name = page.config.get('layout') | 154 layout_name = page.config.get('layout') |
132 if layout_name is None: | 155 if layout_name is None: |
133 layout_name = page.source.config.get('default_layout', 'default') | 156 layout_name = page.source.config.get('default_layout', 'default') |
134 null_names = ['', 'none', 'nil'] | 157 null_names = ['', 'none', 'nil'] |
135 if layout_name not in null_names: | 158 if layout_name not in null_names: |
139 output = contents['content'] | 162 output = contents['content'] |
140 | 163 |
141 rp = RenderedPage(page, ctx.uri, ctx.page_num) | 164 rp = RenderedPage(page, ctx.uri, ctx.page_num) |
142 rp.data = page_data | 165 rp.data = page_data |
143 rp.content = output | 166 rp.content = output |
144 rp.execution_info = eis.current_page_info | |
145 return rp | 167 return rp |
146 finally: | 168 finally: |
147 ctx.current_pass = PASS_NONE | 169 ctx.setCurrentPass(PASS_NONE) |
148 eis.popPage() | 170 eis.popPage() |
149 | 171 |
150 | 172 |
151 def render_page_segments(ctx): | 173 def render_page_segments(ctx): |
152 repo = ctx.app.env.rendered_segments_repository | 174 repo = ctx.app.env.rendered_segments_repository |
160 | 182 |
161 | 183 |
162 def _do_render_page_segments_from_ctx(ctx): | 184 def _do_render_page_segments_from_ctx(ctx): |
163 eis = ctx.app.env.exec_info_stack | 185 eis = ctx.app.env.exec_info_stack |
164 eis.pushPage(ctx.page, ctx) | 186 eis.pushPage(ctx.page, ctx) |
165 ctx.current_pass = PASS_FORMATTING | 187 ctx.setCurrentPass(PASS_FORMATTING) |
166 try: | 188 try: |
167 data_ctx = DataBuildingContext(ctx.page, ctx.uri, ctx.page_num) | 189 data_ctx = DataBuildingContext(ctx.page, ctx.uri, ctx.page_num) |
168 page_data = build_page_data(data_ctx) | 190 page_data = build_page_data(data_ctx) |
169 return _do_render_page_segments(ctx.page, page_data) | 191 return _do_render_page_segments(ctx.page, page_data) |
170 finally: | 192 finally: |
171 ctx.current_pass = PASS_NONE | 193 ctx.setCurrentPass(PASS_NONE) |
172 eis.popPage() | 194 eis.popPage() |
173 | 195 |
174 | 196 |
175 def _do_render_page_segments(page, page_data): | 197 def _do_render_page_segments(page, page_data): |
176 app = page.app | 198 app = page.app |