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