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