comparison src/server/blog.py @ 586:3eb3a2c0c011

browser and server side: uses RSM (XEP-0059)
author souliane <souliane@mailoo.org>
date Fri, 28 Nov 2014 00:31:27 +0100
parents e588335b6aa8
children c8cca1a373dd
comparison
equal deleted inserted replaced
585:bade589dbd5a 586:3eb3a2c0c011
15 # GNU Affero General Public License for more details. 15 # GNU Affero General Public License for more details.
16 16
17 # You should have received a copy of the GNU Affero General Public License 17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 from sat.core.i18n import _ 20 from sat.core.i18n import _, D_
21 from sat_frontends.tools.strings import addURLToText 21 from sat_frontends.tools.strings import addURLToText
22 from sat.core.log import getLogger 22 from sat.core.log import getLogger
23 log = getLogger(__name__) 23 log = getLogger(__name__)
24 24
25 from twisted.internet import defer 25 from twisted.internet import defer
128 else: 128 else:
129 def got_jid(pub_jid_s): 129 def got_jid(pub_jid_s):
130 pub_jid = JID(pub_jid_s) 130 pub_jid = JID(pub_jid_s)
131 d2 = defer.Deferred() 131 d2 = defer.Deferred()
132 item_id = None 132 item_id = None
133 try: 133 atom = None
134 max_items = int(request.args['max_items'][0]) 134 rsm_ = {}
135 except (ValueError, KeyError): 135
136 max_items = 10
137 if len(request.postpath) > 1: 136 if len(request.postpath) > 1:
138 if request.postpath[1] == 'atom.xml': # return the atom feed 137 if request.postpath[1] == 'atom.xml': # return the atom feed
139 d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None) 138 atom = True
140 self.host.bridge.getLastGroupBlogsAtom(pub_jid.userhost(), max_items, C.SERVICE_PROFILE, d2.callback, d2.errback) 139 else:
141 return 140 try: # check if the given path is a valid UUID
142 try: # check if the given path is a valid UUID 141 uuid.UUID(request.postpath[1])
143 uuid.UUID(request.postpath[1]) 142 item_id = request.postpath[1]
144 item_id = request.postpath[1] 143 except ValueError:
145 except ValueError: 144 pass
146 pass 145
146 # retrieve RSM request data from URL parameters
147 try:
148 max_items = int(request.args['max'][0])
149 except (ValueError, KeyError):
150 max_items = C.RSM_MAX_ITEMS if item_id else C.RSM_MAX_COMMENTS
151 rsm_['max'] = unicode(max_items)
152 try:
153 rsm_['index'] = request.args['index'][0]
154 except (ValueError, KeyError):
155 try:
156 rsm_['before'] = request.args['before'][0]
157 except KeyError:
158 try:
159 rsm_['after'] = request.args['after'][0]
160 except KeyError:
161 pass
162
163 if atom is not None:
164 d2.addCallbacks(self.render_atom_feed, self.render_error_blog, [request], None, [request, prof_found], None)
165 self.host.bridge.getGroupBlogsAtom(pub_jid.userhost(), rsm_, C.SERVICE_PROFILE, d2.callback, d2.errback)
166 return
167
147 d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None) 168 d2.addCallbacks(self.render_html_blog, self.render_error_blog, [request, prof_found], None, [request, prof_found], None)
148 if item_id: # display one message and its comments 169 if item_id:
149 self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], C.SERVICE_PROFILE, d2.callback, d2.errback) 170 if max_items > 0: # display one message and its comments
150 else: # display the last messages without comment 171 self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [item_id], {}, max_items, C.SERVICE_PROFILE, d2.callback, d2.errback)
151 self.host.bridge.getLastGroupBlogs(pub_jid.userhost(), max_items, C.SERVICE_PROFILE, d2.callback, d2.errback) 172 else: # display one message, count its comments
173 self.host.bridge.getGroupBlogs(pub_jid.userhost(), [item_id], {}, True, C.SERVICE_PROFILE, d2.callback, d2.errback)
174 else:
175 if max_items == 1: # display one message and its comments
176 self.host.bridge.getGroupBlogsWithComments(pub_jid.userhost(), [], rsm_, C.RSM_MAX_COMMENTS, C.SERVICE_PROFILE, d2.callback, d2.errback)
177 else: # display the last messages, count their comments
178 self.host.bridge.getGroupBlogs(pub_jid.userhost(), [], rsm_, True, C.SERVICE_PROFILE, d2.callback, d2.errback)
152 179
153 d1 = defer.Deferred() 180 d1 = defer.Deferred()
154 JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', C.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback)) 181 JID(self.host.bridge.asyncGetParamA('JabberID', 'Connection', 'value', C.SERVER_SECURITY_LIMIT, prof_found, callback=d1.callback, errback=d1.errback))
155 d1.addCallbacks(got_jid) 182 d1.addCallbacks(got_jid)
156 183
157 return server.NOT_DONE_YET 184 return server.NOT_DONE_YET
158 185
159 def render_html_blog(self, mblog_data, request, profile): 186 def render_html_blog(self, mblog_data, request, profile):
160 """Retrieve the user parameters before actually rendering the static blog 187 """Retrieve the user parameters before actually rendering the static blog
161 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) 188
189 @param mblog_data (list): couple (list, dict) with:
190 - a list of microblog data, or a list of couple containing:
191 - microblog data (main item)
192 - couple (comments data, RSM response data for the comments)
193 - RSM response data for the main items
162 @param request: HTTP request 194 @param request: HTTP request
163 @param profile 195 @param profile
164 """ 196 """
165 d_list = [] 197 d_list = []
166 options = {} 198 options = {}
182 214
183 def __render_html_blog(self, mblog_data, options, request, profile): 215 def __render_html_blog(self, mblog_data, options, request, profile):
184 """Actually render the static blog. If mblog_data is a list of dict, we are missing 216 """Actually render the static blog. If mblog_data is a list of dict, we are missing
185 the comments items so we just display the main items. If mblog_data is a list of couple, 217 the comments items so we just display the main items. If mblog_data is a list of couple,
186 each couple is associating a main item data with the list of its comments, so we render all. 218 each couple is associating a main item data with the list of its comments, so we render all.
187 @param mblog_data: list of microblog data or list of couple (microblog data, list of microblog data) 219
220 @param mblog_data (list): couple (list, dict) with:
221 - a list of microblog data, or a list of couple containing:
222 - microblog data (main item)
223 - couple (comments data, RSM response data for the comments)
224 - RSM response data for the main items
188 @param options: dict defining the blog's parameters 225 @param options: dict defining the blog's parameters
189 @param request: the HTTP request 226 @param request: the HTTP request
190 @profile 227 @profile
191 """ 228 """
192 if not isinstance(options, dict): 229 if not isinstance(options, dict):
223 'keywords': getOption(C.STATIC_BLOG_PARAM_KEYWORDS), 260 'keywords': getOption(C.STATIC_BLOG_PARAM_KEYWORDS),
224 'description': getOption(C.STATIC_BLOG_PARAM_DESCRIPTION), 261 'description': getOption(C.STATIC_BLOG_PARAM_DESCRIPTION),
225 'title': getOption(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user, 262 'title': getOption(C.STATIC_BLOG_PARAM_TITLE) or "%s's microblog" % user,
226 'favicon': os.path.normpath(root_url + getOption('avatar')), 263 'favicon': os.path.normpath(root_url + getOption('avatar')),
227 'banner_elt': getImageOption(C.STATIC_BLOG_PARAM_BANNER, getOption(C.STATIC_BLOG_PARAM_TITLE) or user)}) 264 'banner_elt': getImageOption(C.STATIC_BLOG_PARAM_BANNER, getOption(C.STATIC_BLOG_PARAM_TITLE) or user)})
228 mblog_data = [(entry if isinstance(entry, tuple) else (entry, [])) for entry in mblog_data] 265 mblog_data, main_rsm = mblog_data
229 mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('published', 0)))) 266 display_single = len(mblog_data) == 1
230 for entry in mblog_data: 267
231 self.__render_html_entry(entry[0], base_url, request) 268 # build the navigation links
232 comments = sorted(entry[1], key=lambda entry: (float(entry.get('published', 0)))) 269 count = int(main_rsm['count']) if 'count' in main_rsm else 0
270 if count > 0:
271 index = int(main_rsm['index'])
272 if index > 0:
273 before_link = ("%(base)s?before=%(item_id)s" % {'base': base_url, 'item_id': main_rsm['first']}).encode('utf-8')
274 if display_single:
275 before_link += '&max=1'
276 tmp_text = D_("Later message")
277 class_ = 'later_message'
278 else:
279 tmp_text = D_("Later messages")
280 class_ = 'later_messages'
281 before_tag = """<a href="%(link)s" class="%(class)s">%(text)s</a>""" % {'link': before_link, 'class': class_, 'text': tmp_text}
282 else:
283 before_tag = None
284 if index + len(mblog_data) < count:
285 after_link = ("%(base)s?after=%(item_id)s" % {'base': base_url, 'item_id': main_rsm['last']}).encode('utf-8')
286 if display_single:
287 after_link += '&max=1'
288 text = D_("Older message")
289 class_ = 'older_message'
290 else:
291 text = D_("Older messages")
292 class_ = 'older_messages'
293 after_tag = """<a href="%(link)s" class="%(class)s">%(text)s</a>""" % {'link': after_link, 'class': class_, 'text': text}
294 else:
295 after_tag = None
296
297 # display navigation header
298 request.write("""<div class="header">""")
299 if before_tag:
300 request.write(before_tag)
301 request.write("&nbsp;")
302 if display_single and after_tag:
303 request.write(after_tag)
304 request.write("""</div>""")
305
306 mblog_data = [(entry if isinstance(entry, tuple) else (entry, ([], {}))) for entry in mblog_data]
307 mblog_data = sorted(mblog_data, key=lambda entry: (-float(entry[0].get('updated', 0))))
308 for main_data, comments_data in mblog_data:
309 self.__render_html_entry(main_data, base_url, request)
310 comments, comments_rsm = comments_data
311
312 # eventually display the link to show all comments
313 comments_count = int(main_data['comments_count'])
314 delta = comments_count - len(comments)
315 if display_single and delta > 0:
316 link = ("%(base)s/%(item_id)s?max=%(max)s" % {'base': base_url,
317 'item_id': main_data['id'],
318 'max': main_data['comments_count']}).encode('utf-8')
319 text = D_("Show %(count)d previous %(comments)s") % {'count': delta,
320 'comments': D_('comments') if delta > 1 else D_('comment')}
321 request.write("""<a href="%(link)s" class="comments_link">%(text)s</a>""" % {'link': link, 'text': text})
322
323 comments = sorted(comments, key=lambda entry: (float(entry.get('published', 0))))
233 for comment in comments: 324 for comment in comments:
234 self.__render_html_entry(comment, base_url, request) 325 self.__render_html_entry(comment, base_url, request)
326
327 # display navigation footer
328 request.write("""<div class="footer">""")
329 if not display_single and after_tag:
330 request.write(after_tag)
331 request.write("""</div>""")
332
235 request.write('</body></html>') 333 request.write('</body></html>')
236 request.finish() 334 request.finish()
237 335
238 def __render_html_entry(self, entry, base_url, request): 336 def __render_html_entry(self, entry, base_url, request):
239 """Render one microblog entry. 337 """Render one microblog entry.
242 @param request: the HTTP request 340 @param request: the HTTP request
243 """ 341 """
244 timestamp = float(entry.get('published', 0)) 342 timestamp = float(entry.get('published', 0))
245 datetime_ = datetime.fromtimestamp(timestamp) 343 datetime_ = datetime.fromtimestamp(timestamp)
246 is_comment = entry['type'] == 'comment' 344 is_comment = entry['type'] == 'comment'
247 if is_comment:
248 author = (_("comment from %s") % entry['author']).encode('utf-8')
249 item_link = ''
250 else:
251 author = '&nbsp;'
252 item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8')
253 345
254 def getText(key): 346 def getText(key):
255 if ('%s_xhtml' % key) in entry: 347 if ('%s_xhtml' % key) in entry:
256 return entry['%s_xhtml' % key].encode('utf-8') 348 return entry['%s_xhtml' % key].encode('utf-8')
257 elif key in entry: 349 elif key in entry:
262 def addMainItemLink(elem): 354 def addMainItemLink(elem):
263 if not item_link or not elem: 355 if not item_link or not elem:
264 return elem 356 return elem
265 return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem} 357 return """<a href="%(link)s" class="item_link">%(elem)s</a>""" % {'link': item_link, 'elem': elem}
266 358
267 header = addMainItemLink("""<div class="mblog_header"> 359 if is_comment:
268 <div class="mblog_metadata"> 360 author = (_("from %s") % entry['author']).encode('utf-8')
269 <div class="mblog_author">%(author)s</div> 361 item_link = ''
270 <div class="mblog_timestamp">%(date)s</div> 362 footer = ''
271 </div> 363 else:
272 </div>""" % {'author': author, 'date': datetime_}) 364 author = '&nbsp;'
365 item_link = ("%(base)s/%(item_id)s" % {'base': base_url, 'item_id': entry['id']}).encode('utf-8')
366 comments_count = int(entry['comments_count'])
367 comments_text = (D_('comments') if comments_count > 1 else D_('comment')).encode('utf-8')
368 footer = addMainItemLink("""<div class="mblog_footer mblog_footer_main">
369 <div class="mblog_metadata">
370 <div class="mblog_comments">%(count)s %(comments)s</div>
371 </div>
372 </div>""" % {'count': comments_count,
373 'comments': comments_text})
374
375 header = """<div class="mblog_header %(class)s">
376 <div class="mblog_metadata">
377 <div class="mblog_author">%(author)s</div>
378 <div class="mblog_timestamp">%(date)s</div>
379 </div>
380 </div>""" % {'author': author, 'date': datetime_,
381 'class': '' if is_comment else 'mblog_header_main'}
382 if not is_comment:
383 header = addMainItemLink(header)
273 384
274 title = addMainItemLink(getText('title')) 385 title = addMainItemLink(getText('title'))
275 body = getText('content') 386 body = getText('content')
276 if title: # insert the title within the body 387 if title: # insert the title within the body
277 body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body} 388 body = """<h1>%(title)s</h1>\n%(body)s""" % {'title': title, 'body': body}
278 389
279 request.write("""<div class="mblog_entry %(extra_style)s"> 390 request.write("""<div class="mblog_entry %(extra_style)s">
280 %(header)s 391 %(header)s
281 <span class="mblog_content">%(content)s</span> 392 <span class="mblog_content">%(content)s</span>
282 </div>""" % 393 %(footer)s
283 {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '', 394 </div>""" % {'extra_style': 'mblog_comment' if entry['type'] == 'comment' else '',
284 'item_link': item_link, 395 'item_link': item_link,
285 'header': header, 396 'header': header,
286 'content': body}) 397 'content': body,
398 'footer': footer})
287 399
288 def render_atom_feed(self, feed, request): 400 def render_atom_feed(self, feed, request):
289 request.write(feed.encode('utf-8')) 401 request.write(feed.encode('utf-8'))
290 request.finish() 402 request.finish()
291 403