comparison sat_frontends/quick_frontend/quick_blog.py @ 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 26edcf3a30eb
children 003b8b4b56a7
comparison
equal deleted inserted replaced
2623:49533de4540b 2624:56f94936df1e
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 _, D_ 20 # from sat.core.i18n import _, D_
21 from sat.core.log import getLogger 21 from sat.core.log import getLogger
22
22 log = getLogger(__name__) 23 log = getLogger(__name__)
23 24
24 25
25 from sat_frontends.quick_frontend.constants import Const as C 26 from sat_frontends.quick_frontend.constants import Const as C
26 from sat_frontends.quick_frontend import quick_widgets 27 from sat_frontends.quick_frontend import quick_widgets
27 from sat_frontends.tools import jid 28 from sat_frontends.tools import jid
28 from sat.tools.common import data_format 29 from sat.tools.common import data_format
29 30
30 try: 31 try:
31 # FIXME: to be removed when an acceptable solution is here 32 # FIXME: to be removed when an acceptable solution is here
32 unicode('') # XXX: unicode doesn't exist in pyjamas 33 unicode("") # XXX: unicode doesn't exist in pyjamas
33 except (TypeError, AttributeError): # Error raised is not the same depending on pyjsbuild options 34 except (
35 TypeError,
36 AttributeError,
37 ): # Error raised is not the same depending on pyjsbuild options
34 unicode = str 38 unicode = str
35 39
36 ENTRY_CLS = None 40 ENTRY_CLS = None
37 COMMENTS_CLS = None 41 COMMENTS_CLS = None
38 42
43 def __init__(self, data): 47 def __init__(self, data):
44 """ 48 """
45 @param data(dict): microblog data as return by bridge methods 49 @param data(dict): microblog data as return by bridge methods
46 if data values are not defined, set default values 50 if data values are not defined, set default values
47 """ 51 """
48 self.id = data['id'] 52 self.id = data["id"]
49 self.title = data.get('title') 53 self.title = data.get("title")
50 self.title_rich = None 54 self.title_rich = None
51 self.title_xhtml = data.get('title_xhtml') 55 self.title_xhtml = data.get("title_xhtml")
52 self.tags = list(data_format.dict2iter('tag', data)) 56 self.tags = list(data_format.dict2iter("tag", data))
53 self.content = data.get('content') 57 self.content = data.get("content")
54 self.content_rich = None 58 self.content_rich = None
55 self.content_xhtml = data.get('content_xhtml') 59 self.content_xhtml = data.get("content_xhtml")
56 self.author = data['author'] 60 self.author = data["author"]
57 try: 61 try:
58 author_jid = data['author_jid'] 62 author_jid = data["author_jid"]
59 self.author_jid = jid.JID(author_jid) if author_jid else None 63 self.author_jid = jid.JID(author_jid) if author_jid else None
60 except KeyError: 64 except KeyError:
61 self.author_jid = None 65 self.author_jid = None
62 66
63 try: 67 try:
64 self.author_verified = C.bool(data['author_jid_verified']) 68 self.author_verified = C.bool(data["author_jid_verified"])
65 except KeyError: 69 except KeyError:
66 self.author_verified = False 70 self.author_verified = False
67 71
68 try: 72 try:
69 self.updated = float(data['updated']) # XXX: int doesn't work here (pyjamas bug) 73 self.updated = float(
74 data["updated"]
75 ) # XXX: int doesn't work here (pyjamas bug)
70 except KeyError: 76 except KeyError:
71 self.updated = None 77 self.updated = None
72 78
73 try: 79 try:
74 self.published = float(data['published']) # XXX: int doesn't work here (pyjamas bug) 80 self.published = float(
81 data["published"]
82 ) # XXX: int doesn't work here (pyjamas bug)
75 except KeyError: 83 except KeyError:
76 self.published = None 84 self.published = None
77 85
78 self.comments = data.get('comments') 86 self.comments = data.get("comments")
79 try: 87 try:
80 self.comments_service = jid.JID(data['comments_service']) 88 self.comments_service = jid.JID(data["comments_service"])
81 except KeyError: 89 except KeyError:
82 self.comments_service = None 90 self.comments_service = None
83 self.comments_node = data.get('comments_node') 91 self.comments_node = data.get("comments_node")
84 92
85 # def loadComments(self): 93 # def loadComments(self):
86 # """Load all the comments""" 94 # """Load all the comments"""
87 # index = str(main_entry.comments_count - main_entry.hidden_count) 95 # index = str(main_entry.comments_count - main_entry.hidden_count)
88 # rsm = {'max': str(main_entry.hidden_count), 'index': index} 96 # rsm = {'max': str(main_entry.hidden_count), 'index': index}
138 """ 146 """
139 items, metadata = items_tuple 147 items, metadata = items_tuple
140 for item, comments in items: 148 for item, comments in items:
141 self.addEntry(item, comments, service=service, node=node, with_update=False) 149 self.addEntry(item, comments, service=service, node=node, with_update=False)
142 150
143 def addEntry(self, item=None, comments=None, service=None, node=None, with_update=True, editable=False, edit_entry=False): 151 def addEntry(
152 self,
153 item=None,
154 comments=None,
155 service=None,
156 node=None,
157 with_update=True,
158 editable=False,
159 edit_entry=False,
160 ):
144 """Add a microblog entry 161 """Add a microblog entry
145 162
146 @param editable (bool): True if the entry can be modified 163 @param editable (bool): True if the entry can be modified
147 @param item (dict, None): blog item data, or None for an empty entry 164 @param item (dict, None): blog item data, or None for an empty entry
148 @param comments (list, None): list of comments data if available 165 @param comments (list, None): list of comments data if available
176 193
177 class Entry(EntriesManager): 194 class Entry(EntriesManager):
178 """Graphical representation of an Item 195 """Graphical representation of an Item
179 This class must be overriden by frontends""" 196 This class must be overriden by frontends"""
180 197
181 def __init__(self, manager, item_data=None, comments_data=None, service=None, node=None): 198 def __init__(
199 self, manager, item_data=None, comments_data=None, service=None, node=None
200 ):
182 """ 201 """
183 @param blog(QuickBlog): the parent QuickBlog 202 @param blog(QuickBlog): the parent QuickBlog
184 @param manager(EntriesManager): the parent EntriesManager 203 @param manager(EntriesManager): the parent EntriesManager
185 @param item_data(dict, None): dict containing the blog item data, or None for an empty entry 204 @param item_data(dict, None): dict containing the blog item data, or None for an empty entry
186 @param comments_data(list, None): list of comments data 205 @param comments_data(list, None): list of comments data
192 self.editable = False 211 self.editable = False
193 self.reset(item_data) 212 self.reset(item_data)
194 self.blog.id2entries[self.item.id] = self 213 self.blog.id2entries[self.item.id] = self
195 if self.item.comments: 214 if self.item.comments:
196 node_tuple = (self.item.comments_service, self.item.comments_node) 215 node_tuple = (self.item.comments_service, self.item.comments_node)
197 self.blog.node2entries.setdefault(node_tuple,[]).append(self) 216 self.blog.node2entries.setdefault(node_tuple, []).append(self)
198 217
199 def reset(self, item_data): 218 def reset(self, item_data):
200 """Reset the entry with given data 219 """Reset the entry with given data
201 220
202 used during init (it's a set and not a reset then) 221 used during init (it's a set and not a reset then)
203 or later (e.g. message sent, or cancellation of an edition 222 or later (e.g. message sent, or cancellation of an edition
204 @param idem_data(dict, None): data as in __init__ 223 @param idem_data(dict, None): data as in __init__
205 """ 224 """
206 if item_data is None: 225 if item_data is None:
207 self.new = True 226 self.new = True
208 item_data = {'id': None, 227 item_data = {
209 # TODO: find a best author value 228 "id": None,
210 'author': self.blog.host.whoami.node 229 # TODO: find a best author value
211 } 230 "author": self.blog.host.whoami.node,
231 }
212 else: 232 else:
213 self.new = False 233 self.new = False
214 self.item = Item(item_data) 234 self.item = Item(item_data)
215 self.author_jid = self.blog.host.whoami.bare if self.new else self.item.author_jid 235 self.author_jid = self.blog.host.whoami.bare if self.new else self.item.author_jid
216 if self.author_jid is None and self.service and self.service.node: 236 if self.author_jid is None and self.service and self.service.node:
217 self.author_jid = self.service 237 self.author_jid = self.service
218 self.mode = C.ENTRY_MODE_TEXT if self.item.content_xhtml is None else C.ENTRY_MODE_XHTML 238 self.mode = (
239 C.ENTRY_MODE_TEXT if self.item.content_xhtml is None else C.ENTRY_MODE_XHTML
240 )
219 241
220 def refresh(self): 242 def refresh(self):
221 """Refresh the display when data have been modified""" 243 """Refresh the display when data have been modified"""
222 pass 244 pass
223 245
224 def setEditable(self, editable=True): 246 def setEditable(self, editable=True):
225 """tell if the entry can be edited or not 247 """tell if the entry can be edited or not
226 248
227 @param editable(bool): True if the entry can be edited 249 @param editable(bool): True if the entry can be edited
228 """ 250 """
229 #XXX: we don't use @property as property setter doesn't play well with pyjamas 251 # XXX: we don't use @property as property setter doesn't play well with pyjamas
230 raise NotImplementedError 252 raise NotImplementedError
231 253
232 def addComments(self, comments_data): 254 def addComments(self, comments_data):
233 """Add comments to this entry by calling addEntry repeatidly 255 """Add comments to this entry by calling addEntry repeatidly
234 256
247 def send(self): 269 def send(self):
248 """Send entry according to parent QuickBlog configuration and current level""" 270 """Send entry according to parent QuickBlog configuration and current level"""
249 271
250 # keys to keep other than content*, title* and tag* 272 # keys to keep other than content*, title* and tag*
251 # FIXME: see how to avoid comments node hijacking (someone could bind his post to another post's comments node) 273 # FIXME: see how to avoid comments node hijacking (someone could bind his post to another post's comments node)
252 keys_to_keep = ('id', 'comments', 'author', 'author_jid', 'published') 274 keys_to_keep = ("id", "comments", "author", "author_jid", "published")
253 275
254 mb_data = {} 276 mb_data = {}
255 for key in keys_to_keep: 277 for key in keys_to_keep:
256 value = getattr(self.item, key) 278 value = getattr(self.item, key)
257 if value is not None: 279 if value is not None:
258 mb_data[key] = unicode(value) 280 mb_data[key] = unicode(value)
259 281
260 for prefix in ('content', 'title'): 282 for prefix in ("content", "title"):
261 for suffix in ('', '_rich', '_xhtml'): 283 for suffix in ("", "_rich", "_xhtml"):
262 name = '{}{}'.format(prefix, suffix) 284 name = "{}{}".format(prefix, suffix)
263 value = getattr(self.item, name) 285 value = getattr(self.item, name)
264 if value is not None: 286 if value is not None:
265 mb_data[name] = value 287 mb_data[name] = value
266 288
267 data_format.iter2dict('tag', self.item.tags, mb_data) 289 data_format.iter2dict("tag", self.item.tags, mb_data)
268 290
269 if self.blog.new_message_target not in (C.PUBLIC, C.GROUP): 291 if self.blog.new_message_target not in (C.PUBLIC, C.GROUP):
270 raise NotImplementedError 292 raise NotImplementedError
271 293
272 if self.level == 0: 294 if self.level == 0:
273 mb_data["allow_comments"] = C.BOOL_TRUE 295 mb_data["allow_comments"] = C.BOOL_TRUE
274 296
275 if self.blog.new_message_target == C.GROUP: 297 if self.blog.new_message_target == C.GROUP:
276 data_format.iter2dict('group', self.blog.targets, mb_data) 298 data_format.iter2dict("group", self.blog.targets, mb_data)
277 299
278 self.blog.host.bridge.mbSend( 300 self.blog.host.bridge.mbSend(
279 unicode(self.service or ''), 301 unicode(self.service or ""),
280 self.node or '', 302 self.node or "",
281 mb_data, 303 mb_data,
282 profile=self.blog.profile) 304 profile=self.blog.profile,
305 )
283 306
284 def delete(self): 307 def delete(self):
285 """Remove this Entry from parent manager 308 """Remove this Entry from parent manager
286 309
287 This doesn't delete any entry in PubSub, just locally 310 This doesn't delete any entry in PubSub, just locally
288 all children entries will be recursively removed too 311 all children entries will be recursively removed too
289 """ 312 """
290 # XXX: named delete and not remove to avoid conflict with pyjamas 313 # XXX: named delete and not remove to avoid conflict with pyjamas
291 log.debug(u"deleting entry {}".format('EDIT ENTRY' if self.new else self.item.id)) 314 log.debug(u"deleting entry {}".format("EDIT ENTRY" if self.new else self.item.id))
292 for child in self.entries: 315 for child in self.entries:
293 child.delete() 316 child.delete()
294 try: 317 try:
295 self.manager.entries.remove(self) 318 self.manager.entries.remove(self)
296 except ValueError: 319 except ValueError:
301 if not self.new: 324 if not self.new:
302 # we must remove references to self 325 # we must remove references to self
303 # in QuickBlog's dictionary 326 # in QuickBlog's dictionary
304 del self.blog.id2entries[self.item.id] 327 del self.blog.id2entries[self.item.id]
305 if self.item.comments: 328 if self.item.comments:
306 comments_tuple = (self.item.comments_service, 329 comments_tuple = (self.item.comments_service, self.item.comments_node)
307 self.item.comments_node)
308 other_entries = self.blog.node2entries[comments_tuple].remove(self) 330 other_entries = self.blog.node2entries[comments_tuple].remove(self)
309 if not other_entries: 331 if not other_entries:
310 del self.blog.node2entries[comments_tuple] 332 del self.blog.node2entries[comments_tuple]
311 333
312 def retract(self): 334 def retract(self):
314 336
315 if there is a comments node, it will be purged too 337 if there is a comments node, it will be purged too
316 """ 338 """
317 # TODO: manage several comments nodes case. 339 # TODO: manage several comments nodes case.
318 if self.item.comments: 340 if self.item.comments:
319 self.blog.host.bridge.psNodeDelete(unicode(self.item.comments_service) or "", self.item.comments_node, profile=self.blog.profile) 341 self.blog.host.bridge.psNodeDelete(
320 self.blog.host.bridge.mbRetract(unicode(self.service or ""), self.node or "", self.item.id, profile=self.blog.profile) 342 unicode(self.item.comments_service) or "",
343 self.item.comments_node,
344 profile=self.blog.profile,
345 )
346 self.blog.host.bridge.mbRetract(
347 unicode(self.service or ""),
348 self.node or "",
349 self.item.id,
350 profile=self.blog.profile,
351 )
321 352
322 353
323 class QuickBlog(EntriesManager, quick_widgets.QuickWidget): 354 class QuickBlog(EntriesManager, quick_widgets.QuickWidget):
324
325 def __init__(self, host, targets, profiles=None): 355 def __init__(self, host, targets, profiles=None):
326 """Panel used to show microblog 356 """Panel used to show microblog
327 357
328 @param targets (tuple(unicode)): contact groups displayed in this panel. 358 @param targets (tuple(unicode)): contact groups displayed in this panel.
329 If empty, show all microblogs from all contacts. targets is also used 359 If empty, show all microblogs from all contacts. targets is also used
330 to know where to send new messages. 360 to know where to send new messages.
331 """ 361 """
332 EntriesManager.__init__(self, None) 362 EntriesManager.__init__(self, None)
333 self.id2entries = {} # used to find an entry with it's item id 363 self.id2entries = {} # used to find an entry with it's item id
334 # must be kept up-to-date by Entry 364 # must be kept up-to-date by Entry
335 self.node2entries = {} # same as above, values are lists in case of 365 self.node2entries = {} # same as above, values are lists in case of
336 # two entries link to the same comments node 366 # two entries link to the same comments node
337 if not targets: 367 if not targets:
338 targets = () # XXX: we use empty tuple instead of None to workaround a pyjamas bug 368 targets = () # XXX: we use empty tuple instead of None to workaround a pyjamas bug
339 quick_widgets.QuickWidget.__init__(self, host, targets, C.PROF_KEY_NONE) 369 quick_widgets.QuickWidget.__init__(self, host, targets, C.PROF_KEY_NONE)
340 self._targets_type = C.ALL 370 self._targets_type = C.ALL
341 else: 371 else:
342 assert isinstance(targets[0], basestring) 372 assert isinstance(targets[0], basestring)
343 quick_widgets.QuickWidget.__init__(self, host, targets[0], C.PROF_KEY_NONE) 373 quick_widgets.QuickWidget.__init__(self, host, targets[0], C.PROF_KEY_NONE)
354 return C.GROUP 384 return C.GROUP
355 else: 385 else:
356 raise ValueError("Unkown targets type") 386 raise ValueError("Unkown targets type")
357 387
358 def __str__(self): 388 def __str__(self):
359 return u"Blog Widget [target: {}, profile: {}]".format(', '.join(self.targets), self.profile) 389 return u"Blog Widget [target: {}, profile: {}]".format(
390 ", ".join(self.targets), self.profile
391 )
360 392
361 def _getResultsCb(self, data, rt_session): 393 def _getResultsCb(self, data, rt_session):
362 remaining, results = data 394 remaining, results = data
363 log.debug("Got {got_len} results, {rem_len} remaining".format(got_len=len(results), rem_len=remaining)) 395 log.debug(
396 "Got {got_len} results, {rem_len} remaining".format(
397 got_len=len(results), rem_len=remaining
398 )
399 )
364 for result in results: 400 for result in results:
365 service, node, failure, items, metadata = result 401 service, node, failure, items, metadata = result
366 if not failure: 402 if not failure:
367 self._addMBItemsWithComments((items, metadata), service=jid.JID(service)) 403 self._addMBItemsWithComments((items, metadata), service=jid.JID(service))
368 404
376 def _getResults(self, rt_session): 412 def _getResults(self, rt_session):
377 """Manage results from mbGetFromMany RT Session 413 """Manage results from mbGetFromMany RT Session
378 414
379 @param rt_session(str): session id as returned by mbGetFromMany 415 @param rt_session(str): session id as returned by mbGetFromMany
380 """ 416 """
381 self.host.bridge.mbGetFromManyWithCommentsRTResult(rt_session, profile=self.profile, 417 self.host.bridge.mbGetFromManyWithCommentsRTResult(
382 callback=lambda data:self._getResultsCb(data, rt_session), 418 rt_session,
383 errback=self._getResultsEb) 419 profile=self.profile,
420 callback=lambda data: self._getResultsCb(data, rt_session),
421 errback=self._getResultsEb,
422 )
384 423
385 def getAll(self): 424 def getAll(self):
386 """Get all (micro)blogs from self.targets""" 425 """Get all (micro)blogs from self.targets"""
426
387 def gotSession(rt_session): 427 def gotSession(rt_session):
388 self._getResults(rt_session) 428 self._getResults(rt_session)
389 429
390 if self._targets_type in (C.ALL, C.GROUP): 430 if self._targets_type in (C.ALL, C.GROUP):
391 targets = tuple(self.targets) if self._targets_type is C.GROUP else () 431 targets = tuple(self.targets) if self._targets_type is C.GROUP else ()
392 self.host.bridge.mbGetFromManyWithComments(self._targets_type, targets, 10, 10, {}, {"subscribe":C.BOOL_TRUE}, profile=self.profile, callback=gotSession) 432 self.host.bridge.mbGetFromManyWithComments(
433 self._targets_type,
434 targets,
435 10,
436 10,
437 {},
438 {"subscribe": C.BOOL_TRUE},
439 profile=self.profile,
440 callback=gotSession,
441 )
393 own_pep = self.host.whoami.bare 442 own_pep = self.host.whoami.bare
394 self.host.bridge.mbGetFromManyWithComments(C.JID, (unicode(own_pep),), 10, 10, {}, {}, profile=self.profile, callback=gotSession) 443 self.host.bridge.mbGetFromManyWithComments(
395 else: 444 C.JID,
396 raise NotImplementedError(u'{} target type is not managed'.format(self._targets_type)) 445 (unicode(own_pep),),
446 10,
447 10,
448 {},
449 {},
450 profile=self.profile,
451 callback=gotSession,
452 )
453 else:
454 raise NotImplementedError(
455 u"{} target type is not managed".format(self._targets_type)
456 )
397 457
398 def isJidAccepted(self, jid_): 458 def isJidAccepted(self, jid_):
399 """Tell if a jid is actepted and must be shown in this panel 459 """Tell if a jid is actepted and must be shown in this panel
400 460
401 @param jid_(jid.JID): jid to check 461 @param jid_(jid.JID): jid to check
402 @return: True if the jid is accepted 462 @return: True if the jid is accepted
403 """ 463 """
404 if self._targets_type == C.ALL: 464 if self._targets_type == C.ALL:
405 return True 465 return True
406 assert self._targets_type is C.GROUP # we don't manage other types for now 466 assert self._targets_type is C.GROUP # we don't manage other types for now
407 for group in self.targets: 467 for group in self.targets:
408 if self.host.contact_lists[self.profile].isEntityInGroup(jid_, group): 468 if self.host.contact_lists[self.profile].isEntityInGroup(jid_, group):
409 return True 469 return True
410 return False 470 return False
411 471
420 @param groups(list[unicode], None): groups which can receive this entry 480 @param groups(list[unicode], None): groups which can receive this entry
421 None to accept everything 481 None to accept everything
422 @param profile: %(doc_profile)s 482 @param profile: %(doc_profile)s
423 """ 483 """
424 try: 484 try:
425 entry = self.id2entries[mb_data['id']] 485 entry = self.id2entries[mb_data["id"]]
426 except KeyError: 486 except KeyError:
427 # The entry is new 487 # The entry is new
428 try: 488 try:
429 parent_entries = self.node2entries[(service, node)] 489 parent_entries = self.node2entries[(service, node)]
430 except: 490 except:
431 # The node is unknown, 491 # The node is unknown,
432 # we need to check that we can accept the entry 492 # we need to check that we can accept the entry
433 if (self.isJidAccepted(service) 493 if (
434 or (groups is None and service == self.host.profiles[self.profile].whoami.bare) 494 self.isJidAccepted(service)
435 or (groups and groups.intersection(self.targets))): 495 or (
496 groups is None
497 and service == self.host.profiles[self.profile].whoami.bare
498 )
499 or (groups and groups.intersection(self.targets))
500 ):
436 self.addEntry(mb_data, service=service, node=node) 501 self.addEntry(mb_data, service=service, node=node)
437 else: 502 else:
438 # the entry is a comment in a known node 503 # the entry is a comment in a known node
439 for parent_entry in parent_entries: 504 for parent_entry in parent_entries:
440 parent_entry.addEntry(mb_data, service=service, node=node) 505 parent_entry.addEntry(mb_data, service=service, node=node)