Mercurial > piecrust2
comparison piecrust/dataproviders/blog.py @ 857:d231a10d18f9
refactor: Make the data providers and blog archives source functional.
Also, because of a behaviour change in Jinja, the blog archives sources is
now offering monthly archives by itself.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 08 Jun 2017 08:49:33 -0700 |
parents | 08e02c2a2a1a |
children | f4608e2e80ce |
comparison
equal
deleted
inserted
replaced
856:9bb22bbe093c | 857:d231a10d18f9 |
---|---|
1 import time | 1 import time |
2 import collections.abc | 2 import collections.abc |
3 from piecrust.dataproviders.base import DataProvider | 3 from piecrust.dataproviders.base import DataProvider |
4 from piecrust.dataproviders.pageiterator import PageIterator | 4 from piecrust.dataproviders.pageiterator import PageIterator |
5 from piecrust.sources.list import ListSource | |
5 from piecrust.sources.taxonomy import Taxonomy | 6 from piecrust.sources.taxonomy import Taxonomy |
6 | 7 |
7 | 8 |
8 class BlogDataProvider(DataProvider, collections.abc.Mapping): | 9 class BlogDataProvider(DataProvider, collections.abc.Mapping): |
9 PROVIDER_NAME = 'blog' | 10 PROVIDER_NAME = 'blog' |
13 debug_render_dynamic = (['_debugRenderTaxonomies'] + | 14 debug_render_dynamic = (['_debugRenderTaxonomies'] + |
14 DataProvider.debug_render_dynamic) | 15 DataProvider.debug_render_dynamic) |
15 | 16 |
16 def __init__(self, source, page): | 17 def __init__(self, source, page): |
17 super().__init__(source, page) | 18 super().__init__(source, page) |
19 self._posts = None | |
18 self._yearly = None | 20 self._yearly = None |
19 self._monthly = None | 21 self._monthly = None |
20 self._taxonomies = {} | 22 self._taxonomies = {} |
23 self._archives_built = False | |
21 self._ctx_set = False | 24 self._ctx_set = False |
22 | 25 |
26 def _addSource(self, source): | |
27 raise Exception("The blog data provider doesn't support " | |
28 "combining multiple sources.") | |
29 | |
23 @property | 30 @property |
24 def posts(self): | 31 def posts(self): |
25 return self._posts() | 32 self._buildPosts() |
33 return self._posts | |
26 | 34 |
27 @property | 35 @property |
28 def years(self): | 36 def years(self): |
29 return self._buildYearlyArchive() | 37 self._buildArchives() |
38 return self._yearly | |
30 | 39 |
31 @property | 40 @property |
32 def months(self): | 41 def months(self): |
33 return self._buildMonthlyArchive() | 42 self._buildArchives() |
43 return self._montly | |
34 | 44 |
35 def __getitem__(self, name): | 45 def __getitem__(self, name): |
36 if name == 'posts': | 46 self._buildArchives() |
37 return self._posts() | 47 return self._taxonomies[name] |
38 elif name == 'years': | 48 |
39 return self._buildYearlyArchive() | 49 def __getattr__(self, name): |
40 elif name == 'months': | 50 self._buildArchives() |
41 return self._buildMonthlyArchive() | 51 try: |
42 | 52 return self._taxonomies[name] |
43 if self._source.app.config.get('site/taxonomies/' + name) is not None: | 53 except KeyError: |
44 return self._buildTaxonomy(name) | 54 raise AttributeError("No such taxonomy: %s" % name) |
45 | |
46 raise KeyError("No such item: %s" % name) | |
47 | 55 |
48 def __iter__(self): | 56 def __iter__(self): |
49 keys = ['posts', 'years', 'months'] | 57 self._buildPosts() |
50 keys += list(self._source.app.config.get('site/taxonomies').keys()) | 58 self._buildArchives() |
51 return iter(keys) | 59 return ['posts', 'years', 'months'] + list(self._taxonomies.keys()) |
52 | 60 |
53 def __len__(self): | 61 def __len__(self): |
54 return 3 + len(self._source.app.config.get('site/taxonomies')) | 62 self._buildPosts() |
63 self._buildArchives() | |
64 return 3 + len(self._taxonomies) | |
55 | 65 |
56 def _debugRenderTaxonomies(self): | 66 def _debugRenderTaxonomies(self): |
57 return list(self._source.app.config.get('site/taxonomies').keys()) | 67 return list(self._app.config.get('site/taxonomies').keys()) |
58 | 68 |
59 def _posts(self): | 69 def _buildPosts(self): |
60 it = PageIterator(self._source, current_page=self._page) | 70 if self._posts is None: |
61 it._iter_event += self._onIteration | 71 it = PageIterator(self._sources[0], current_page=self._page) |
62 return it | 72 it._iter_event += self._onIteration |
63 | 73 self._posts = it |
64 def _buildYearlyArchive(self): | 74 |
65 if self._yearly is not None: | 75 def _buildArchives(self): |
66 return self._yearly | 76 if self._archives_built: |
67 | 77 return |
68 self._yearly = [] | 78 |
69 yearly_index = {} | 79 yearly_index = {} |
70 for post in self._source.getPages(): | 80 monthly_index = {} |
81 tax_index = {} | |
82 | |
83 taxonomies = [] | |
84 tax_names = list(self._app.config.get('site/taxonomies').keys()) | |
85 for tn in tax_names: | |
86 tax_cfg = self._app.config.get('site/taxonomies/' + tn) | |
87 taxonomies.append(Taxonomy(tn, tax_cfg)) | |
88 tax_index[tn] = {} | |
89 | |
90 app = self._app | |
91 page = self._page | |
92 source = self._sources[0] | |
93 | |
94 for item in source.getAllContents(): | |
95 post = app.getPage(source, item) | |
96 | |
71 year = post.datetime.strftime('%Y') | 97 year = post.datetime.strftime('%Y') |
98 month = post.datetime.strftime('%B %Y') | |
72 | 99 |
73 posts_this_year = yearly_index.get(year) | 100 posts_this_year = yearly_index.get(year) |
74 if posts_this_year is None: | 101 if posts_this_year is None: |
75 timestamp = time.mktime( | 102 timestamp = time.mktime( |
76 (post.datetime.year, 1, 1, 0, 0, 0, 0, 0, -1)) | 103 (post.datetime.year, 1, 1, 0, 0, 0, 0, 0, -1)) |
77 posts_this_year = BlogArchiveEntry(self._page, year, timestamp) | 104 posts_this_year = BlogArchiveEntry( |
78 self._yearly.append(posts_this_year) | 105 source, page, year, timestamp) |
79 yearly_index[year] = posts_this_year | 106 yearly_index[year] = posts_this_year |
80 | 107 posts_this_year._items.append(post.content_item) |
81 posts_this_year._data_source.append(post) | 108 |
82 self._yearly = sorted(self._yearly, | 109 posts_this_month = monthly_index.get(month) |
83 key=lambda e: e.timestamp, | |
84 reverse=True) | |
85 self._onIteration() | |
86 return self._yearly | |
87 | |
88 def _buildMonthlyArchive(self): | |
89 if self._monthly is not None: | |
90 return self._monthly | |
91 | |
92 self._monthly = [] | |
93 for post in self._source.getPages(): | |
94 month = post.datetime.strftime('%B %Y') | |
95 | |
96 posts_this_month = next( | |
97 filter(lambda m: m.name == month, self._monthly), | |
98 None) | |
99 if posts_this_month is None: | 110 if posts_this_month is None: |
100 timestamp = time.mktime( | 111 timestamp = time.mktime( |
101 (post.datetime.year, post.datetime.month, 1, | 112 (post.datetime.year, post.datetime.month, 1, |
102 0, 0, 0, 0, 0, -1)) | 113 0, 0, 0, 0, 0, -1)) |
103 posts_this_month = BlogArchiveEntry( | 114 posts_this_month = BlogArchiveEntry( |
104 self._page, month, timestamp) | 115 source, page, month, timestamp) |
105 self._monthly.append(posts_this_month) | 116 monthly_index[month] = posts_this_month |
106 | 117 posts_this_month._items.append(post.content_item) |
107 posts_this_month._data_source.append(post) | 118 |
108 self._monthly = sorted(self._monthly, | 119 for tax in taxonomies: |
109 key=lambda e: e.timestamp, | 120 post_term = post.config.get(tax.setting_name) |
110 reverse=True) | 121 if post_term is None: |
111 self._onIteration() | 122 continue |
112 return self._monthly | 123 |
113 | 124 posts_this_tax = tax_index[tax.name] |
114 def _buildTaxonomy(self, tax_name): | 125 if tax.is_multiple: |
115 if tax_name in self._taxonomies: | 126 for val in post_term: |
116 return self._taxonomies[tax_name] | 127 entry = posts_this_tax.get(val) |
117 | 128 if entry is None: |
118 tax_cfg = self._page.app.config.get('site/taxonomies/' + tax_name) | 129 entry = BlogTaxonomyEntry(source, page, val) |
119 tax = Taxonomy(tax_name, tax_cfg) | 130 posts_this_tax[val] = entry |
120 | 131 entry._items.append(post.content_item) |
121 posts_by_tax_value = {} | 132 else: |
122 for post in self._source.getPages(): | 133 entry = posts_this_tax.get(val) |
123 tax_values = post.config.get(tax.setting_name) | 134 if entry is None: |
124 if tax_values is None: | 135 entry = BlogTaxonomyEntry(source, page, post_term) |
125 continue | 136 posts_this_tax[val] = entry |
126 if not isinstance(tax_values, list): | 137 entry._items.append(post.content_item) |
127 tax_values = [tax_values] | 138 |
128 for val in tax_values: | 139 self._yearly = list(sorted( |
129 posts = posts_by_tax_value.setdefault(val, []) | 140 yearly_index.values(), |
130 posts.append(post) | 141 key=lambda e: e.timestamp, reverse=True)) |
131 | 142 self._monthly = list(sorted( |
132 entries = [] | 143 monthly_index.values(), |
133 for value, ds in posts_by_tax_value.items(): | 144 key=lambda e: e.timestamp, reverse=True)) |
134 source = ArraySource(self._page.app, ds) | 145 |
135 entries.append(BlogTaxonomyEntry(self._page, source, value)) | 146 self._taxonomies = {} |
136 self._taxonomies[tax_name] = sorted(entries, key=lambda k: k.name) | 147 for tax_name, entries in tax_index.items(): |
137 | 148 self._taxonomies[tax_name] = list(entries.values()) |
138 self._onIteration() | 149 |
139 return self._taxonomies[tax_name] | 150 self._onIteration(None) |
140 | 151 |
141 def _onIteration(self): | 152 self._archives_built = True |
153 | |
154 def _onIteration(self, it): | |
142 if not self._ctx_set: | 155 if not self._ctx_set: |
143 eis = self._page.app.env.exec_info_stack | 156 rcs = self._app.env.render_ctx_stack |
144 if eis.current_page_info: | 157 if rcs.current_ctx: |
145 eis.current_page_info.render_ctx.addUsedSource(self._source) | 158 rcs.current_ctx.addUsedSource(self._sources[0]) |
146 self._ctx_set = True | 159 self._ctx_set = True |
147 | 160 |
148 | 161 |
149 class BlogArchiveEntry(object): | 162 class BlogArchiveEntry: |
150 debug_render = ['name', 'timestamp', 'posts'] | 163 debug_render = ['name', 'timestamp', 'posts'] |
151 debug_render_invoke = ['name', 'timestamp', 'posts'] | 164 debug_render_invoke = ['name', 'timestamp', 'posts'] |
152 | 165 |
153 def __init__(self, page, name, timestamp): | 166 def __init__(self, source, page, name, timestamp): |
154 self.name = name | 167 self.name = name |
155 self.timestamp = timestamp | 168 self.timestamp = timestamp |
169 self._source = source | |
156 self._page = page | 170 self._page = page |
157 self._data_source = [] | 171 self._items = [] |
158 self._iterator = None | 172 self._iterator = None |
159 | 173 |
160 def __str__(self): | 174 def __str__(self): |
161 return self.name | 175 return self.name |
162 | 176 |
170 return self._iterator | 184 return self._iterator |
171 | 185 |
172 def _load(self): | 186 def _load(self): |
173 if self._iterator is not None: | 187 if self._iterator is not None: |
174 return | 188 return |
175 source = ArraySource(self._page.app, self._data_source) | 189 |
176 self._iterator = PageIterator(source, current_page=self._page) | 190 src = ListSource(self._source, self._items) |
177 | 191 self._iterator = PageIterator(src, current_page=self._page) |
178 | 192 |
179 class BlogTaxonomyEntry(object): | 193 |
194 class BlogTaxonomyEntry: | |
180 debug_render = ['name', 'post_count', 'posts'] | 195 debug_render = ['name', 'post_count', 'posts'] |
181 debug_render_invoke = ['name', 'post_count', 'posts'] | 196 debug_render_invoke = ['name', 'post_count', 'posts'] |
182 | 197 |
183 def __init__(self, page, source, property_value): | 198 def __init__(self, source, page, term): |
199 self.term = term | |
200 self._source = source | |
184 self._page = page | 201 self._page = page |
185 self._source = source | 202 self._items = [] |
186 self._property_value = property_value | |
187 self._iterator = None | 203 self._iterator = None |
188 | 204 |
189 def __str__(self): | 205 def __str__(self): |
190 return self._property_value | 206 return self.term |
191 | 207 |
192 @property | 208 @property |
193 def name(self): | 209 def name(self): |
194 return self._property_value | 210 return self.term |
195 | 211 |
196 @property | 212 @property |
197 def posts(self): | 213 def posts(self): |
198 self._load() | 214 self._load() |
199 self._iterator.reset() | 215 self._iterator.reset() |
200 return self._iterator | 216 return self._iterator |
201 | 217 |
202 @property | 218 @property |
203 def post_count(self): | 219 def post_count(self): |
204 return self._source.page_count | 220 return len(self._items) |
205 | 221 |
206 def _load(self): | 222 def _load(self): |
207 if self._iterator is not None: | 223 if self._iterator is not None: |
208 return | 224 return |
209 | 225 |
210 self._iterator = PageIterator(self._source, current_page=self._page) | 226 src = ListSource(self._source, self._items) |
211 | 227 self._iterator = PageIterator(src, current_page=self._page) |
228 |