Mercurial > libervia-web
comparison src/server/blog.py @ 725:c1abaa91a121
server (blog): update to the new mechanism in XEP-0277 + improvement:
- use the new bridge methods mbGetFromManyWithComments and mbGet
- use PEP namespace
- add dedicated URL parameter "comments_max" instead of dirty hack
author | souliane <souliane@mailoo.org> |
---|---|
date | Thu, 10 Sep 2015 15:17:18 +0200 |
parents | 29b84af2ff7b |
children | e949b7c7ed9c |
comparison
equal
deleted
inserted
replaced
724:994be887e843 | 725:c1abaa91a121 |
---|---|
34 import re | 34 import re |
35 import os | 35 import os |
36 | 36 |
37 from libervia.server.html_tools import sanitizeHtml, convertNewLinesToXHTML | 37 from libervia.server.html_tools import sanitizeHtml, convertNewLinesToXHTML |
38 from libervia.server.constants import Const as C | 38 from libervia.server.constants import Const as C |
39 | |
40 | |
41 NS_MICROBLOG = 'urn:xmpp:microblog:0' | |
39 | 42 |
40 | 43 |
41 class TemplateProcessor(object): | 44 class TemplateProcessor(object): |
42 | 45 |
43 THEME = 'default' | 46 THEME = 'default' |
147 d.addCallbacks(lambda pub_jid_s: self.gotJID(pub_jid_s, request, prof_found)) | 150 d.addCallbacks(lambda pub_jid_s: self.gotJID(pub_jid_s, request, prof_found)) |
148 return server.NOT_DONE_YET | 151 return server.NOT_DONE_YET |
149 | 152 |
150 def gotJID(self, pub_jid_s, request, profile): | 153 def gotJID(self, pub_jid_s, request, profile): |
151 pub_jid = JID(pub_jid_s) | 154 pub_jid = JID(pub_jid_s) |
152 d = defer.Deferred() | |
153 | 155 |
154 self.parseURLParams(request) | 156 self.parseURLParams(request) |
155 item_id, rsm_ = request.item_id, request.rsm_data | 157 if request.item_id: |
156 max_items = int(rsm_['max_']) | 158 item_ids = [request.item_id] |
159 max_items = 1 | |
160 else: | |
161 item_ids = [] | |
162 max_items = int(request.extra_dict['rsm_max']) | |
157 | 163 |
158 if request.atom: | 164 if request.atom: |
159 d.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, profile], None) | 165 self.host.bridge.mbGetAtom(pub_jid.userhost(), NS_MICROBLOG, max_items, item_ids, |
160 self.host.bridge.getGroupBlogsAtom(pub_jid.userhost(), rsm_, C.SERVICE_PROFILE, d.callback, d.errback) | 166 request.extra_dict, C.SERVICE_PROFILE, |
161 return | 167 lambda feed: self.render_atom_feed(feed, request), |
162 | 168 lambda failure: self.render_error_blog(failure, request, profile)) |
163 d.addCallbacks(self.render_html_blog, self.render_error_blog, [request, profile], None, [request, profile], None) | 169 elif request.item_id: |
164 if item_id: | 170 self.getItemById(pub_jid, request.item_id, request.extra_dict, |
165 if max_items > 0: # display one message and its comments | 171 request.extra_comments_dict, request, profile) |
166 self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], {}, max_items, C.SERVICE_PROFILE, d.callback, d.errback) | |
167 else: # display one message, count its comments | |
168 self.host.bridge.getGroupBlogs(pub_jid.userhost(), [item_id], {}, True, C.SERVICE_PROFILE, d.callback, d.errback) | |
169 else: | 172 else: |
170 if max_items == 1: # display one message and its comments | 173 self.getItems(pub_jid, max_items, request.extra_dict, |
171 self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [], rsm_, C.RSM_MAX_COMMENTS, C.SERVICE_PROFILE, d.callback, d.errback) | 174 request.extra_comments_dict, request, profile) |
172 else: # display the last messages, count their comments | 175 |
173 self.host.bridge.getGroupBlogs(pub_jid.userhost(), [], rsm_, True, C.SERVICE_PROFILE, d.callback, d.errback) | |
174 | |
175 def parseURLParams(self, request): | 176 def parseURLParams(self, request): |
176 """Parse the request URL parameters. | 177 """Parse the request URL parameters. |
177 | 178 |
178 @param request: HTTP request | 179 @param request: HTTP request |
179 """ | 180 """ |
189 request.item_id = request.postpath[1] | 190 request.item_id = request.postpath[1] |
190 except ValueError: | 191 except ValueError: |
191 pass | 192 pass |
192 | 193 |
193 self.parseURLParamsRSM(request) | 194 self.parseURLParamsRSM(request) |
194 request.display_single = (request.item_id is not None) or int(request.rsm_data['max_']) == 1 | 195 request.display_single = (request.item_id is not None) or int(request.extra_dict['rsm_max']) == 1 |
196 self.parseURLParamsCommentsRSM(request) | |
195 | 197 |
196 def parseURLParamsRSM(self, request): | 198 def parseURLParamsRSM(self, request): |
197 """Parse RSM request data from the URL parameters. | 199 """Parse RSM request data from the URL parameters for main items |
198 | 200 |
199 @param request: HTTP request | 201 @param request: HTTP request |
200 """ | 202 """ |
201 rsm_ = {} | 203 request.extra_dict = {} |
204 if request.item_id: # XXX: item_id and RSM are not compatible | |
205 return | |
202 try: | 206 try: |
203 rsm_['max_'] = request.args['max'][0] | 207 request.extra_dict['rsm_max'] = request.args['max'][0] |
204 except (ValueError, KeyError): | 208 except (ValueError, KeyError): |
205 rsm_['max_'] = unicode(C.RSM_MAX_ITEMS if request.item_id else C.RSM_MAX_COMMENTS) | 209 request.extra_dict['rsm_max'] = unicode(C.RSM_MAX_ITEMS) |
206 try: | 210 try: |
207 rsm_['index'] = request.args['index'][0] | 211 request.extra_dict['rsm_index'] = request.args['index'][0] |
208 except (ValueError, KeyError): | 212 except (ValueError, KeyError): |
209 try: | 213 try: |
210 rsm_['before'] = request.args['before'][0] | 214 request.extra_dict['rsm_before'] = request.args['before'][0] |
211 except KeyError: | 215 except KeyError: |
212 try: | 216 try: |
213 rsm_['after'] = request.args['after'][0] | 217 request.extra_dict['rsm_after'] = request.args['after'][0] |
214 except KeyError: | 218 except KeyError: |
215 pass | 219 pass |
216 request.rsm_data = rsm_ | 220 |
217 | 221 def parseURLParamsCommentsRSM(self, request): |
218 def render_html_blog(self, mblog_data, request, profile): | 222 """Parse RSM request data from the URL parameters for comments |
223 | |
224 @param request: HTTP request | |
225 """ | |
226 request.extra_comments_dict = {} | |
227 if request.display_single: | |
228 try: | |
229 request.extra_comments_dict['rsm_max'] = request.args['comments_max'][0] | |
230 except (ValueError, KeyError): | |
231 request.extra_comments_dict['rsm_max'] = unicode(C.RSM_MAX_COMMENTS) | |
232 else: | |
233 request.extra_comments_dict['rsm_max'] = "0" | |
234 | |
235 def getItemById(self, pub_jid, item_id, extra_dict, extra_comments_dict, request, profile): | |
236 """ | |
237 | |
238 @param pub_jid (jid.JID): publisher JID | |
239 @param item_id(unicode): ID of the item to retrieve | |
240 @param extra_dict (dict): extra configuration for initial items only | |
241 @param extra_comments_dict (dict): extra configuration for comments only | |
242 @param request: HTTP request | |
243 @param profile | |
244 """ | |
245 | |
246 def gotItems(items): | |
247 items, metadata = items | |
248 item = items[0] # assume there's only one item | |
249 | |
250 def gotCount(items_bis): | |
251 metadata_bis = items_bis[1] | |
252 metadata['rsm_count'] = metadata_bis['rsm_count'] | |
253 index_key = "rsm_index" if metadata_bis.get("rsm_index") else "rsm_count" | |
254 metadata['rsm_index'] = unicode(int(metadata_bis[index_key]) - 1) | |
255 metadata['rsm_first'] = metadata['rsm_last'] = item["id"] | |
256 | |
257 def gotComments(comments): | |
258 # build the items as self.getItems would do it (and as self.render_html_blog expects them to be) | |
259 comments = [(item['comments_service'], item['comments_node'], "", comments[0], comments[1])] | |
260 self.render_html_blog([(item, comments)], metadata, request, profile) | |
261 | |
262 # get the comments | |
263 max_comments = int(extra_comments_dict['rsm_max']) | |
264 self.host.bridge.mbGet(item['comments_service'], item['comments_node'], max_comments, [], | |
265 extra_comments_dict, C.SERVICE_PROFILE, callback=gotComments) | |
266 | |
267 # XXX: retrieve RSM information related to the main item. We can't do it while | |
268 # retrieving the item, because item_ids and rsm should not be used together. | |
269 self.host.bridge.mbGet(pub_jid.userhost(), NS_MICROBLOG, 1, [], | |
270 {"rsm_max": "1", "rsm_after": item["id"]}, C.SERVICE_PROFILE, callback=gotCount) | |
271 | |
272 # get the main item | |
273 self.host.bridge.mbGet(pub_jid.userhost(), NS_MICROBLOG, 1, [item_id], | |
274 extra_dict, C.SERVICE_PROFILE, callback=gotItems) | |
275 | |
276 def getItems(self, pub_jid, max_items, extra_dict, extra_comments_dict, request, profile): | |
277 """ | |
278 | |
279 @param pub_jid (jid.JID): publisher JID | |
280 @param max_items(int): maximum number of item to get, C.NO_LIMIT for no limit | |
281 @param extra_dict (dict): extra configuration for initial items only | |
282 @param extra_comments_dict (dict): extra configuration for comments only | |
283 @param request: HTTP request | |
284 @param profile | |
285 """ | |
286 def getResultCb(data, rt_session): | |
287 remaining, results = data | |
288 for result in results: | |
289 service, node, failure, items, metadata = result | |
290 if not failure: | |
291 self.render_html_blog(items, metadata, request, profile) | |
292 | |
293 if remaining: | |
294 self._getResults(rt_session) | |
295 | |
296 def getResult(rt_session): | |
297 self.host.bridge.mbGetFromManyWithCommentsRTResult(rt_session, C.SERVICE_PROFILE, | |
298 callback=lambda data: getResultCb(data, rt_session), | |
299 errback=lambda failure: self.render_error_blog(failure, request, profile)) | |
300 | |
301 max_comments = int(extra_comments_dict['rsm_max']) | |
302 self.host.bridge.mbGetFromManyWithComments(C.JID, [pub_jid.userhost()], max_items, | |
303 max_comments, extra_dict, extra_comments_dict, | |
304 C.SERVICE_PROFILE, callback=getResult) | |
305 | |
306 def render_html_blog(self, items, metadata, request, profile): | |
219 """Retrieve the user parameters before actually rendering the static blog | 307 """Retrieve the user parameters before actually rendering the static blog |
220 | 308 |
221 @param mblog_data (list): couple (list, dict) with: | 309 @param items(list[tuple(dict, list)]): same as in self.__render_html_blog |
222 - a list of microblog data, or a list of couple containing: | 310 @param metadata(dict): original node metadata |
223 - microblog data (main item) | |
224 - couple (comments data, RSM response data for the comments) | |
225 - RSM response data for the main items | |
226 @param request: HTTP request | 311 @param request: HTTP request |
227 @param profile | 312 @param profile |
228 """ | 313 """ |
229 d_list = [] | 314 d_list = [] |
230 options = {} | 315 options = {} |
239 | 324 |
240 self.getAvatar(profile).addCallbacks(getCallback('avatar'), eb) | 325 self.getAvatar(profile).addCallbacks(getCallback('avatar'), eb) |
241 for param_name in (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION): | 326 for param_name in (C.STATIC_BLOG_PARAM_TITLE, C.STATIC_BLOG_PARAM_BANNER, C.STATIC_BLOG_PARAM_KEYWORDS, C.STATIC_BLOG_PARAM_DESCRIPTION): |
242 self.host.bridge.asyncGetParamA(param_name, C.STATIC_BLOG_KEY, 'value', C.SERVER_SECURITY_LIMIT, profile, callback=getCallback(param_name), errback=eb) | 327 self.host.bridge.asyncGetParamA(param_name, C.STATIC_BLOG_KEY, 'value', C.SERVER_SECURITY_LIMIT, profile, callback=getCallback(param_name), errback=eb) |
243 | 328 |
244 cb = lambda dummy: self.__render_html_blog(mblog_data, options, request, profile) | 329 cb = lambda dummy: self.__render_html_blog(items, metadata, options, request, profile) |
245 defer.DeferredList(d_list).addCallback(cb) | 330 defer.DeferredList(d_list).addCallback(cb) |
246 | 331 |
247 def __render_html_blog(self, mblog_data, options, request, profile): | 332 def __render_html_blog(self, items, metadata, options, request, profile): |
248 """Actually render the static blog. If mblog_data is a list of dict, we are missing | 333 """Actually render the static blog. |
249 the comments items so we just display the main items. If mblog_data is a list of couple, | 334 |
250 each couple is associating a main item data with the list of its comments, so we render all. | 335 If mblog_data is a list of dict, we are missing the comments items so we just |
251 | 336 display the main items. If mblog_data is a list of couple, each couple is |
252 @param mblog_data (list): couple (list, dict) with: | 337 associating a main item data with the list of its comments, so we render all. |
253 - a list of microblog data, or a list of couple containing: | 338 |
254 - microblog data (main item) | 339 @param items(list[tuple(dict, list)]): list of 2-tuple with |
255 - couple (comments data, RSM response data for the comments) | 340 - item(dict): item microblog data |
256 - RSM response data for the main items | 341 - comments_list(list[tuple]): list of 5-tuple with |
342 - service (unicode): pubsub service where the comments node is | |
343 - node (unicode): comments node | |
344 - failure (unicode): empty in case of success, else error message | |
345 - comments(list[dict]): list of microblog data | |
346 - comments_metadata(dict): metadata of the comment node | |
347 @param metadata(dict): original node metadata | |
257 @param options: dict defining the blog's parameters | 348 @param options: dict defining the blog's parameters |
258 @param request: the HTTP request | 349 @param request: the HTTP request |
259 @profile | 350 @param profile |
260 """ | 351 """ |
261 if not isinstance(options, dict): | 352 if not isinstance(options, dict): |
262 options = {} | 353 options = {} |
263 user = sanitizeHtml(profile).encode('utf-8') | 354 user = sanitizeHtml(profile).encode('utf-8') |
264 root_url = '../' * len(request.postpath) | 355 root_url = '../' * len(request.postpath) |
285 'title': title, | 376 'title': title, |
286 'favicon': avatar, | 377 'favicon': avatar, |
287 'banner_img': getImageParams(C.STATIC_BLOG_PARAM_BANNER, avatar, title) | 378 'banner_img': getImageParams(C.STATIC_BLOG_PARAM_BANNER, avatar, title) |
288 } | 379 } |
289 | 380 |
290 mblog_data, main_rsm = mblog_data | 381 items.sort(key=lambda entry: (-float(entry[0].get('updated', 0)))) |
291 mblog_data = [(entry if isinstance(entry, tuple) else (entry, ([], {}))) for entry in mblog_data] | 382 |
292 mblog_data.sort(key=lambda entry: (-float(entry[0].get('updated', 0)))) | 383 data['navlinks'] = NavigationLinks(request, items, metadata, base_url) |
293 | 384 data['messages'] = [] |
294 data['navlinks'] = NavigationLinks(request, mblog_data, main_rsm, base_url) | 385 for item in items: |
295 data['messages'] = [BlogMessage(request, base_url, entry, comments[0]) for entry, comments in mblog_data] | 386 item, comments_list = item |
387 comments, comments_count = [], 0 | |
388 for node_comments in comments_list: | |
389 comments.extend(node_comments[3]) | |
390 try: | |
391 comments_count += int(node_comments[4]['rsm_count']) | |
392 except KeyError: | |
393 pass | |
394 data['messages'].append(BlogMessage(request, base_url, item, comments, comments_count)) | |
296 | 395 |
297 request.write(self.useTemplate(request, 'static_blog', data)) | 396 request.write(self.useTemplate(request, 'static_blog', data)) |
298 request.finish() | 397 request.finish() |
299 | 398 |
300 def render_atom_feed(self, feed, request): | 399 def render_atom_feed(self, feed, request): |
306 request.finish() | 405 request.finish() |
307 | 406 |
308 | 407 |
309 class NavigationLinks(object): | 408 class NavigationLinks(object): |
310 | 409 |
311 def __init__(self, request, mblog_data, rsm_data, base_url): | 410 def __init__(self, request, items, rsm_data, base_url): |
312 """Build the navigation links. | 411 """Build the navigation links. |
313 | 412 |
314 @param mblog_data (dict): the microblogs that are displayed on the page | 413 @param items (list): list of items |
315 @param rsm_data (dict): rsm data | 414 @param rsm_data (dict): rsm data |
316 @param base_url (unicode): the base URL for this user's blog | 415 @param base_url (unicode): the base URL for this user's blog |
317 @return: dict | 416 @return: dict |
318 """ | 417 """ |
319 for key in ('later_message', 'later_messages', 'older_message', 'older_messages'): | 418 for key in ('later_message', 'later_messages', 'older_message', 'older_messages'): |
320 count = int(rsm_data.get('count', 0)) | 419 count = int(rsm_data.get('rsm_count', 0)) |
321 setattr(self, key, '') # key must exist when using the template | 420 setattr(self, key, '') # key must exist when using the template |
322 if count <= 0 or (request.display_single == key.endswith('s')): | 421 if count <= 0 or (request.display_single == key.endswith('s')): |
323 continue | 422 continue |
324 | 423 |
325 index = int(rsm_data['index']) | 424 index = int(rsm_data['rsm_index']) |
326 | 425 |
327 link_data = {'base_url': base_url, 'suffix': ''} | 426 link_data = {'base_url': base_url, 'suffix': ''} |
328 | 427 |
329 if key.startswith('later_message'): | 428 if key.startswith('later_message'): |
330 if index <= 0: | 429 if index <= 0: |
331 continue | 430 continue |
332 link_data['item_id'] = rsm_data['first'] | 431 link_data['item_id'] = rsm_data['rsm_first'] |
333 link_data['post_arg'] = 'before' | 432 link_data['post_arg'] = 'before' |
334 else: | 433 else: |
335 if index + len(mblog_data) >= count: | 434 if index + len(items) >= count: |
336 continue | 435 continue |
337 link_data['item_id'] = rsm_data['last'] | 436 link_data['item_id'] = rsm_data['rsm_last'] |
338 link_data['post_arg'] = 'after' | 437 link_data['post_arg'] = 'after' |
339 | 438 |
340 if request.display_single: | 439 if request.display_single: |
341 link_data['suffix'] = '&max=1' | 440 link_data['suffix'] = '&max=1' |
342 | 441 |
360 self.text = text | 459 self.text = text |
361 | 460 |
362 | 461 |
363 class BlogMessage(object): | 462 class BlogMessage(object): |
364 | 463 |
365 def __init__(self, request, base_url, entry, comments=None): | 464 def __init__(self, request, base_url, entry, comments=None, comments_count=0): |
366 """ | 465 """ |
367 | 466 |
368 @param request: HTTP request | 467 @param request: HTTP request |
369 @param base_url (unicode): the base URL | 468 @param base_url (unicode): the base URL |
370 @param entry (dict{unicode:unicode]): microblog entry received from the backend | 469 @param entry(dict): item microblog data |
371 @param comments (list[dict]): comments | 470 @param comments(list[dict]): list of microblog data |
471 @param comments_count (int): total number of comments | |
372 """ | 472 """ |
373 timestamp = float(entry.get('published', 0)) | 473 timestamp = float(entry.get('published', 0)) |
374 is_comment = entry['type'] == 'comment' | 474 |
475 # FIXME: for now we assume that the comments' depth is only 1 | |
476 is_comment = not entry.get('comments', False) | |
375 | 477 |
376 self.date = datetime.fromtimestamp(timestamp) | 478 self.date = datetime.fromtimestamp(timestamp) |
377 self.type = entry['type'] | 479 self.type = "comment" if is_comment else "main_item" |
378 self.style = 'mblog_comment' if entry['type'] == 'comment' else '' | 480 self.style = 'mblog_comment' if is_comment else '' |
379 self.content = self.getText(entry, 'content') | 481 self.content = self.getText(entry, 'content') |
380 | 482 |
381 if is_comment: | 483 if is_comment: |
382 self.author = (_("from %s") % entry['author']).encode('utf-8') | 484 self.author = (_("from %s") % entry['author']).encode('utf-8') |
383 else: | 485 else: |
384 self.author = ' ' | 486 self.author = ' ' |
385 self.url = (u"%s/%s" % (base_url, entry['id'])).encode('utf-8') | 487 self.url = (u"%s/%s" % (base_url, entry['id'])).encode('utf-8') |
386 self.title = self.getText(entry, 'title') | 488 self.title = self.getText(entry, 'title') |
387 | 489 |
388 comments_count = int(entry['comments_count']) | |
389 count_text = lambda count: D_('comments') if count > 1 else D_('comment') | 490 count_text = lambda count: D_('comments') if count > 1 else D_('comment') |
390 | 491 |
391 self.comments_text = "%s %s" % (comments_count, count_text(comments_count)) | 492 self.comments_text = "%s %s" % (comments_count, count_text(comments_count)) |
392 | 493 |
393 delta = comments_count - len(comments) | 494 delta = comments_count - len(comments) |
394 if request.display_single and delta > 0: | 495 if request.display_single and delta > 0: |
395 prev_url = "%s?max=%s" % (self.url, entry['comments_count']) | 496 prev_url = "%s?comments_max=%s" % (self.url, unicode(comments_count)) |
396 prev_text = D_("show %(count)d previous %(comments)s") % \ | 497 prev_text = D_("show %(count)d previous %(comments)s") % \ |
397 {'count': delta, 'comments': count_text(delta)} | 498 {'count': delta, 'comments': count_text(delta)} |
398 self.all_comments_link = BlogLink(prev_url, "comments_link", prev_text) | 499 self.all_comments_link = BlogLink(prev_url, "comments_link", prev_text) |
399 | 500 |
400 if comments: | 501 if comments: |
401 comments.sort(key=lambda entry: float(entry.get('published', 0))) | 502 comments.sort(key=lambda comment: float(comment.get('published', 0))) |
402 self.comments = [BlogMessage(request, base_url, comment) for comment in comments] | 503 self.comments = [BlogMessage(request, base_url, comment) for comment in comments] |
403 | 504 |
404 def getText(self, entry, key): | 505 def getText(self, entry, key): |
405 if ('%s_xhtml' % key) in entry: | 506 if ('%s_xhtml' % key) in entry: |
406 return entry['%s_xhtml' % key].encode('utf-8') | 507 return entry['%s_xhtml' % key].encode('utf-8') |