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 = '&nbsp;' 486 self.author = '&nbsp;'
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')