comparison sat_frontends/jp/cmd_event.py @ 3028:ab2696e34d29

Python 3 port: /!\ this is a huge commit /!\ starting from this commit, SàT is needs Python 3.6+ /!\ SàT maybe be instable or some feature may not work anymore, this will improve with time This patch port backend, bridge and frontends to Python 3. Roughly this has been done this way: - 2to3 tools has been applied (with python 3.7) - all references to python2 have been replaced with python3 (notably shebangs) - fixed files not handled by 2to3 (notably the shell script) - several manual fixes - fixed issues reported by Python 3 that where not handled in Python 2 - replaced "async" with "async_" when needed (it's a reserved word from Python 3.7) - replaced zope's "implements" with @implementer decorator - temporary hack to handle data pickled in database, as str or bytes may be returned, to be checked later - fixed hash comparison for password - removed some code which is not needed anymore with Python 3 - deactivated some code which needs to be checked (notably certificate validation) - tested with jp, fixed reported issues until some basic commands worked - ported Primitivus (after porting dependencies like urwid satext) - more manual fixes
author Goffi <goffi@goffi.org>
date Tue, 13 Aug 2019 19:08:41 +0200
parents b2f323237fce
children fee60f17ebac
comparison
equal deleted inserted replaced
3027:ff5bcb12ae60 3028:ab2696e34d29
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 20
21 import base 21 from . import base
22 from sat.core.i18n import _ 22 from sat.core.i18n import _
23 from sat.tools.common.ansi import ANSI as A 23 from sat.tools.common.ansi import ANSI as A
24 from sat_frontends.jp.constants import Const as C 24 from sat_frontends.jp.constants import Const as C
25 from sat_frontends.jp import common 25 from sat_frontends.jp import common
26 from functools import partial 26 from functools import partial
28 import calendar 28 import calendar
29 import time 29 import time
30 30
31 __commands__ = ["Event"] 31 __commands__ = ["Event"]
32 32
33 OUTPUT_OPT_TABLE = u"table" 33 OUTPUT_OPT_TABLE = "table"
34 34
35 # TODO: move date parsing to base, it may be useful for other commands 35 # TODO: move date parsing to base, it may be useful for other commands
36 36
37 37
38 class Get(base.CommandBase): 38 class Get(base.CommandBase):
43 "get", 43 "get",
44 use_output=C.OUTPUT_DICT, 44 use_output=C.OUTPUT_DICT,
45 use_pubsub=True, 45 use_pubsub=True,
46 pubsub_flags={C.SINGLE_ITEM}, 46 pubsub_flags={C.SINGLE_ITEM},
47 use_verbose=True, 47 use_verbose=True,
48 help=_(u"get event data"), 48 help=_("get event data"),
49 ) 49 )
50 self.need_loop = True 50 self.need_loop = True
51 51
52 def add_parser_options(self): 52 def add_parser_options(self):
53 pass 53 pass
65 self.args.item, 65 self.args.item,
66 self.profile, 66 self.profile,
67 callback=self.eventInviteeGetCb, 67 callback=self.eventInviteeGetCb,
68 errback=partial( 68 errback=partial(
69 self.errback, 69 self.errback,
70 msg=_(u"can't get event data: {}"), 70 msg=_("can't get event data: {}"),
71 exit_code=C.EXIT_BRIDGE_ERRBACK, 71 exit_code=C.EXIT_BRIDGE_ERRBACK,
72 ), 72 ),
73 ) 73 )
74 74
75 75
76 class EventBase(object): 76 class EventBase(object):
77 def add_parser_options(self): 77 def add_parser_options(self):
78 self.parser.add_argument( 78 self.parser.add_argument(
79 "-i", 79 "-i",
80 "--id", 80 "--id",
81 type=base.unicode_decoder, 81 default="",
82 default=u"", 82 help=_("ID of the PubSub Item"),
83 help=_(u"ID of the PubSub Item"), 83 )
84 ) 84 self.parser.add_argument(
85 self.parser.add_argument( 85 "-d", "--date", type=str, help=_("date of the event")
86 "-d", "--date", type=unicode, help=_(u"date of the event")
87 ) 86 )
88 self.parser.add_argument( 87 self.parser.add_argument(
89 "-f", 88 "-f",
90 "--field", 89 "--field",
91 type=base.unicode_decoder,
92 action="append", 90 action="append",
93 nargs=2, 91 nargs=2,
94 dest="fields", 92 dest="fields",
95 metavar=(u"KEY", u"VALUE"), 93 metavar=("KEY", "VALUE"),
96 help=_(u"configuration field to set"), 94 help=_("configuration field to set"),
97 ) 95 )
98 96
99 def parseFields(self): 97 def parseFields(self):
100 return dict(self.args.fields) if self.args.fields else {} 98 return dict(self.args.fields) if self.args.fields else {}
101 99
104 try: 102 try:
105 date = int(self.args.date) 103 date = int(self.args.date)
106 except ValueError: 104 except ValueError:
107 try: 105 try:
108 date_time = du_parser.parse( 106 date_time = du_parser.parse(
109 self.args.date, dayfirst=not (u"-" in self.args.date) 107 self.args.date, dayfirst=not ("-" in self.args.date)
110 ) 108 )
111 except ValueError as e: 109 except ValueError as e:
112 self.parser.error(_(u"Can't parse date: {msg}").format(msg=e)) 110 self.parser.error(_("Can't parse date: {msg}").format(msg=e))
113 if date_time.tzinfo is None: 111 if date_time.tzinfo is None:
114 date = calendar.timegm(date_time.timetuple()) 112 date = calendar.timegm(date_time.timetuple())
115 else: 113 else:
116 date = time.mktime(date_time.timetuple()) 114 date = time.mktime(date_time.timetuple())
117 else: 115 else:
129 ) 127 )
130 EventBase.__init__(self) 128 EventBase.__init__(self)
131 self.need_loop = True 129 self.need_loop = True
132 130
133 def eventCreateCb(self, node): 131 def eventCreateCb(self, node):
134 self.disp(_(u"Event created successfuly on node {node}").format(node=node)) 132 self.disp(_("Event created successfuly on node {node}").format(node=node))
135 self.host.quit() 133 self.host.quit()
136 134
137 def start(self): 135 def start(self):
138 fields = self.parseFields() 136 fields = self.parseFields()
139 date = self.parseDate() 137 date = self.parseDate()
145 self.args.id, 143 self.args.id,
146 self.profile, 144 self.profile,
147 callback=self.eventCreateCb, 145 callback=self.eventCreateCb,
148 errback=partial( 146 errback=partial(
149 self.errback, 147 self.errback,
150 msg=_(u"can't create event: {}"), 148 msg=_("can't create event: {}"),
151 exit_code=C.EXIT_BRIDGE_ERRBACK, 149 exit_code=C.EXIT_BRIDGE_ERRBACK,
152 ), 150 ),
153 ) 151 )
154 152
155 153
176 fields, 174 fields,
177 self.profile, 175 self.profile,
178 callback=self.host.quit, 176 callback=self.host.quit,
179 errback=partial( 177 errback=partial(
180 self.errback, 178 self.errback,
181 msg=_(u"can't update event data: {}"), 179 msg=_("can't update event data: {}"),
182 exit_code=C.EXIT_BRIDGE_ERRBACK, 180 exit_code=C.EXIT_BRIDGE_ERRBACK,
183 ), 181 ),
184 ) 182 )
185 183
186 184
192 "get", 190 "get",
193 use_output=C.OUTPUT_DICT, 191 use_output=C.OUTPUT_DICT,
194 use_pubsub=True, 192 use_pubsub=True,
195 pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM}, 193 pubsub_flags={C.NODE, C.ITEM, C.SINGLE_ITEM},
196 use_verbose=True, 194 use_verbose=True,
197 help=_(u"get event attendance"), 195 help=_("get event attendance"),
198 ) 196 )
199 self.need_loop = True 197 self.need_loop = True
200 198
201 def add_parser_options(self): 199 def add_parser_options(self):
202 pass 200 pass
211 self.args.node, 209 self.args.node,
212 self.profile, 210 self.profile,
213 callback=self.eventInviteeGetCb, 211 callback=self.eventInviteeGetCb,
214 errback=partial( 212 errback=partial(
215 self.errback, 213 self.errback,
216 msg=_(u"can't get event data: {}"), 214 msg=_("can't get event data: {}"),
217 exit_code=C.EXIT_BRIDGE_ERRBACK, 215 exit_code=C.EXIT_BRIDGE_ERRBACK,
218 ), 216 ),
219 ) 217 )
220 218
221 219
233 231
234 def add_parser_options(self): 232 def add_parser_options(self):
235 self.parser.add_argument( 233 self.parser.add_argument(
236 "-f", 234 "-f",
237 "--field", 235 "--field",
238 type=base.unicode_decoder,
239 action="append", 236 action="append",
240 nargs=2, 237 nargs=2,
241 dest="fields", 238 dest="fields",
242 metavar=(u"KEY", u"VALUE"), 239 metavar=("KEY", "VALUE"),
243 help=_(u"configuration field to set"), 240 help=_("configuration field to set"),
244 ) 241 )
245 242
246 def start(self): 243 def start(self):
247 fields = dict(self.args.fields) if self.args.fields else {} 244 fields = dict(self.args.fields) if self.args.fields else {}
248 self.host.bridge.eventInviteeSet( 245 self.host.bridge.eventInviteeSet(
251 fields, 248 fields,
252 self.profile, 249 self.profile,
253 callback=self.host.quit, 250 callback=self.host.quit,
254 errback=partial( 251 errback=partial(
255 self.errback, 252 self.errback,
256 msg=_(u"can't set event data: {}"), 253 msg=_("can't set event data: {}"),
257 exit_code=C.EXIT_BRIDGE_ERRBACK, 254 exit_code=C.EXIT_BRIDGE_ERRBACK,
258 ), 255 ),
259 ) 256 )
260 257
261 258
269 use_output=C.OUTPUT_DICT_DICT, 266 use_output=C.OUTPUT_DICT_DICT,
270 extra_outputs=extra_outputs, 267 extra_outputs=extra_outputs,
271 use_pubsub=True, 268 use_pubsub=True,
272 pubsub_flags={C.NODE}, 269 pubsub_flags={C.NODE},
273 use_verbose=True, 270 use_verbose=True,
274 help=_(u"get event attendance"), 271 help=_("get event attendance"),
275 ) 272 )
276 self.need_loop = True 273 self.need_loop = True
277 274
278 def add_parser_options(self): 275 def add_parser_options(self):
279 self.parser.add_argument( 276 self.parser.add_argument(
280 "-m", 277 "-m",
281 "--missing", 278 "--missing",
282 action="store_true", 279 action="store_true",
283 help=_(u"show missing people (invited but no R.S.V.P. so far)"), 280 help=_("show missing people (invited but no R.S.V.P. so far)"),
284 ) 281 )
285 self.parser.add_argument( 282 self.parser.add_argument(
286 "-R", 283 "-R",
287 "--no-rsvp", 284 "--no-rsvp",
288 action="store_true", 285 action="store_true",
289 help=_(u"don't show people which gave R.S.V.P."), 286 help=_("don't show people which gave R.S.V.P."),
290 ) 287 )
291 288
292 def _attend_filter(self, attend, row): 289 def _attend_filter(self, attend, row):
293 if attend == u"yes": 290 if attend == "yes":
294 attend_color = C.A_SUCCESS 291 attend_color = C.A_SUCCESS
295 elif attend == u"no": 292 elif attend == "no":
296 attend_color = C.A_FAILURE 293 attend_color = C.A_FAILURE
297 else: 294 else:
298 attend_color = A.FG_WHITE 295 attend_color = A.FG_WHITE
299 return A.color(attend_color, attend) 296 return A.color(attend_color, attend)
300 297
301 def _guests_filter(self, guests): 298 def _guests_filter(self, guests):
302 return u"(" + unicode(guests) + ")" if guests else u"" 299 return "(" + str(guests) + ")" if guests else ""
303 300
304 def default_output(self, event_data): 301 def default_output(self, event_data):
305 data = [] 302 data = []
306 attendees_yes = 0 303 attendees_yes = 0
307 attendees_maybe = 0 304 attendees_maybe = 0
308 attendees_no = 0 305 attendees_no = 0
309 attendees_missing = 0 306 attendees_missing = 0
310 guests = 0 307 guests = 0
311 guests_maybe = 0 308 guests_maybe = 0
312 for jid_, jid_data in event_data.iteritems(): 309 for jid_, jid_data in event_data.items():
313 jid_data[u"jid"] = jid_ 310 jid_data["jid"] = jid_
314 try: 311 try:
315 guests_int = int(jid_data["guests"]) 312 guests_int = int(jid_data["guests"])
316 except (ValueError, KeyError): 313 except (ValueError, KeyError):
317 pass 314 pass
318 attend = jid_data.get(u"attend", u"") 315 attend = jid_data.get("attend", "")
319 if attend == "yes": 316 if attend == "yes":
320 attendees_yes += 1 317 attendees_yes += 1
321 guests += guests_int 318 guests += guests_int
322 elif attend == "maybe": 319 elif attend == "maybe":
323 attendees_maybe += 1 320 attendees_maybe += 1
324 guests_maybe += guests_int 321 guests_maybe += guests_int
325 elif attend == "no": 322 elif attend == "no":
326 attendees_no += 1 323 attendees_no += 1
327 jid_data[u"guests"] = "" 324 jid_data["guests"] = ""
328 else: 325 else:
329 attendees_missing += 1 326 attendees_missing += 1
330 jid_data[u"guests"] = "" 327 jid_data["guests"] = ""
331 data.append(jid_data) 328 data.append(jid_data)
332 329
333 show_table = OUTPUT_OPT_TABLE in self.args.output_opts 330 show_table = OUTPUT_OPT_TABLE in self.args.output_opts
334 331
335 table = common.Table.fromDict( 332 table = common.Table.fromDict(
336 self.host, 333 self.host,
337 data, 334 data,
338 (u"nick",) 335 ("nick",)
339 + ((u"jid",) if self.host.verbosity else ()) 336 + (("jid",) if self.host.verbosity else ())
340 + (u"attend", "guests"), 337 + ("attend", "guests"),
341 headers=None, 338 headers=None,
342 filters={ 339 filters={
343 u"nick": A.color(C.A_HEADER, u"{}" if show_table else u"{} "), 340 "nick": A.color(C.A_HEADER, "{}" if show_table else "{} "),
344 u"jid": u"{}" if show_table else u"{} ", 341 "jid": "{}" if show_table else "{} ",
345 u"attend": self._attend_filter, 342 "attend": self._attend_filter,
346 u"guests": u"{}" if show_table else self._guests_filter, 343 "guests": "{}" if show_table else self._guests_filter,
347 }, 344 },
348 defaults={u"nick": u"", u"attend": u"", u"guests": 1}, 345 defaults={"nick": "", "attend": "", "guests": 1},
349 ) 346 )
350 if show_table: 347 if show_table:
351 table.display() 348 table.display()
352 else: 349 else:
353 table.display_blank(show_header=False, col_sep=u"") 350 table.display_blank(show_header=False, col_sep="")
354 351
355 if not self.args.no_rsvp: 352 if not self.args.no_rsvp:
356 self.disp(u"") 353 self.disp("")
357 self.disp( 354 self.disp(
358 A.color( 355 A.color(
359 C.A_SUBHEADER, 356 C.A_SUBHEADER,
360 _(u"Attendees: "), 357 _("Attendees: "),
361 A.RESET, 358 A.RESET,
362 unicode(len(data)), 359 str(len(data)),
363 _(u" ("), 360 _(" ("),
364 C.A_SUCCESS, 361 C.A_SUCCESS,
365 _(u"yes: "), 362 _("yes: "),
366 unicode(attendees_yes), 363 str(attendees_yes),
367 A.FG_WHITE, 364 A.FG_WHITE,
368 _(u", maybe: "), 365 _(", maybe: "),
369 unicode(attendees_maybe), 366 str(attendees_maybe),
370 u", ", 367 ", ",
371 C.A_FAILURE, 368 C.A_FAILURE,
372 _(u"no: "), 369 _("no: "),
373 unicode(attendees_no), 370 str(attendees_no),
374 A.RESET, 371 A.RESET,
375 u")", 372 ")",
376 ) 373 )
377 ) 374 )
378 self.disp( 375 self.disp(
379 A.color(C.A_SUBHEADER, _(u"confirmed guests: "), A.RESET, unicode(guests)) 376 A.color(C.A_SUBHEADER, _("confirmed guests: "), A.RESET, str(guests))
380 ) 377 )
381 self.disp( 378 self.disp(
382 A.color( 379 A.color(
383 C.A_SUBHEADER, 380 C.A_SUBHEADER,
384 _(u"unconfirmed guests: "), 381 _("unconfirmed guests: "),
385 A.RESET, 382 A.RESET,
386 unicode(guests_maybe), 383 str(guests_maybe),
387 ) 384 )
388 ) 385 )
389 self.disp( 386 self.disp(
390 A.color( 387 A.color(
391 C.A_SUBHEADER, _(u"total: "), A.RESET, unicode(guests + guests_maybe) 388 C.A_SUBHEADER, _("total: "), A.RESET, str(guests + guests_maybe)
392 ) 389 )
393 ) 390 )
394 if attendees_missing: 391 if attendees_missing:
395 self.disp("") 392 self.disp("")
396 self.disp( 393 self.disp(
397 A.color( 394 A.color(
398 C.A_SUBHEADER, 395 C.A_SUBHEADER,
399 _(u"missing people (no reply): "), 396 _("missing people (no reply): "),
400 A.RESET, 397 A.RESET,
401 unicode(attendees_missing), 398 str(attendees_missing),
402 ) 399 )
403 ) 400 )
404 401
405 def eventInviteesListCb(self, event_data, prefilled_data): 402 def eventInviteesListCb(self, event_data, prefilled_data):
406 """fill nicknames and keep only requested people 403 """fill nicknames and keep only requested people
418 else: 415 else:
419 # we replace empty dicts for existing people with R.S.V.P. data 416 # we replace empty dicts for existing people with R.S.V.P. data
420 prefilled_data.update(event_data) 417 prefilled_data.update(event_data)
421 418
422 # we get nicknames for everybody, make it easier for organisers 419 # we get nicknames for everybody, make it easier for organisers
423 for jid_, data in prefilled_data.iteritems(): 420 for jid_, data in prefilled_data.items():
424 id_data = self.host.bridge.identityGet(jid_, self.profile) 421 id_data = self.host.bridge.identityGet(jid_, self.profile)
425 data[u"nick"] = id_data.get(u"nick", u"") 422 data["nick"] = id_data.get("nick", "")
426 423
427 self.output(prefilled_data) 424 self.output(prefilled_data)
428 self.host.quit() 425 self.host.quit()
429 426
430 def getList(self, prefilled_data={}): 427 def getList(self, prefilled_data={}):
433 self.args.node, 430 self.args.node,
434 self.profile, 431 self.profile,
435 callback=partial(self.eventInviteesListCb, prefilled_data=prefilled_data), 432 callback=partial(self.eventInviteesListCb, prefilled_data=prefilled_data),
436 errback=partial( 433 errback=partial(
437 self.errback, 434 self.errback,
438 msg=_(u"can't get event data: {}"), 435 msg=_("can't get event data: {}"),
439 exit_code=C.EXIT_BRIDGE_ERRBACK, 436 exit_code=C.EXIT_BRIDGE_ERRBACK,
440 ), 437 ),
441 ) 438 )
442 439
443 def psNodeAffiliationsGetCb(self, affiliations): 440 def psNodeAffiliationsGetCb(self, affiliations):
444 # we fill all affiliations with empty data 441 # we fill all affiliations with empty data
445 # answered one will be filled in eventInviteesListCb 442 # answered one will be filled in eventInviteesListCb
446 # we only consider people with "publisher" affiliation as invited, creators are not, and members can just observe 443 # we only consider people with "publisher" affiliation as invited, creators are not, and members can just observe
447 prefilled = { 444 prefilled = {
448 jid_: {} 445 jid_: {}
449 for jid_, affiliation in affiliations.iteritems() 446 for jid_, affiliation in affiliations.items()
450 if affiliation in (u"publisher",) 447 if affiliation in ("publisher",)
451 } 448 }
452 self.getList(prefilled) 449 self.getList(prefilled)
453 450
454 def start(self): 451 def start(self):
455 if self.args.no_rsvp and not self.args.missing: 452 if self.args.no_rsvp and not self.args.missing:
456 self.parser.error(_(u"you need to use --missing if you use --no-rsvp")) 453 self.parser.error(_("you need to use --missing if you use --no-rsvp"))
457 if self.args.missing: 454 if self.args.missing:
458 self.host.bridge.psNodeAffiliationsGet( 455 self.host.bridge.psNodeAffiliationsGet(
459 self.args.service, 456 self.args.service,
460 self.args.node, 457 self.args.node,
461 self.profile, 458 self.profile,
462 callback=self.psNodeAffiliationsGetCb, 459 callback=self.psNodeAffiliationsGetCb,
463 errback=partial( 460 errback=partial(
464 self.errback, 461 self.errback,
465 msg=_(u"can't get event data: {}"), 462 msg=_("can't get event data: {}"),
466 exit_code=C.EXIT_BRIDGE_ERRBACK, 463 exit_code=C.EXIT_BRIDGE_ERRBACK,
467 ), 464 ),
468 ) 465 )
469 else: 466 else:
470 self.getList() 467 self.getList()
476 self, 473 self,
477 host, 474 host,
478 "invite", 475 "invite",
479 use_pubsub=True, 476 use_pubsub=True,
480 pubsub_flags={C.NODE, C.SINGLE_ITEM}, 477 pubsub_flags={C.NODE, C.SINGLE_ITEM},
481 help=_(u"invite someone to the event through email"), 478 help=_("invite someone to the event through email"),
482 ) 479 )
483 self.need_loop = True 480 self.need_loop = True
484 481
485 def add_parser_options(self): 482 def add_parser_options(self):
486 self.parser.add_argument( 483 self.parser.add_argument(
487 "-e", 484 "-e",
488 "--email", 485 "--email",
489 action="append", 486 action="append",
490 type=base.unicode_decoder,
491 default=[], 487 default=[],
492 help="email(s) to send the invitation to", 488 help="email(s) to send the invitation to",
493 ) 489 )
494 self.parser.add_argument( 490 self.parser.add_argument(
495 "-N", 491 "-N",
496 "--name", 492 "--name",
497 type=base.unicode_decoder,
498 default="", 493 default="",
499 help="name of the invitee", 494 help="name of the invitee",
500 ) 495 )
501 self.parser.add_argument( 496 self.parser.add_argument(
502 "-H", 497 "-H",
503 "--host-name", 498 "--host-name",
504 type=base.unicode_decoder,
505 default="", 499 default="",
506 help="name of the host", 500 help="name of the host",
507 ) 501 )
508 self.parser.add_argument( 502 self.parser.add_argument(
509 "-l", 503 "-l",
510 "--lang", 504 "--lang",
511 type=base.unicode_decoder,
512 default="", 505 default="",
513 help="main language spoken by the invitee", 506 help="main language spoken by the invitee",
514 ) 507 )
515 self.parser.add_argument( 508 self.parser.add_argument(
516 "-U", 509 "-U",
517 "--url-template", 510 "--url-template",
518 type=base.unicode_decoder,
519 default="", 511 default="",
520 help="template to construct the URL", 512 help="template to construct the URL",
521 ) 513 )
522 self.parser.add_argument( 514 self.parser.add_argument(
523 "-S", 515 "-S",
524 "--subject", 516 "--subject",
525 type=base.unicode_decoder,
526 default="", 517 default="",
527 help="subject of the invitation email (default: generic subject)", 518 help="subject of the invitation email (default: generic subject)",
528 ) 519 )
529 self.parser.add_argument( 520 self.parser.add_argument(
530 "-b", 521 "-b",
531 "--body", 522 "--body",
532 type=base.unicode_decoder,
533 default="", 523 default="",
534 help="body of the invitation email (default: generic body)", 524 help="body of the invitation email (default: generic body)",
535 ) 525 )
536 526
537 def start(self): 527 def start(self):
552 self.args.body, 542 self.args.body,
553 self.args.profile, 543 self.args.profile,
554 callback=self.host.quit, 544 callback=self.host.quit,
555 errback=partial( 545 errback=partial(
556 self.errback, 546 self.errback,
557 msg=_(u"can't create invitation: {}"), 547 msg=_("can't create invitation: {}"),
558 exit_code=C.EXIT_BRIDGE_ERRBACK, 548 exit_code=C.EXIT_BRIDGE_ERRBACK,
559 ), 549 ),
560 ) 550 )
561 551
562 552
563 class Invitee(base.CommandBase): 553 class Invitee(base.CommandBase):
564 subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite) 554 subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite)
565 555
566 def __init__(self, host): 556 def __init__(self, host):
567 super(Invitee, self).__init__( 557 super(Invitee, self).__init__(
568 host, "invitee", use_profile=False, help=_(u"manage invities") 558 host, "invitee", use_profile=False, help=_("manage invities")
569 ) 559 )
570 560
571 561
572 class Event(base.CommandBase): 562 class Event(base.CommandBase):
573 subcommands = (Get, Create, Modify, Invitee) 563 subcommands = (Get, Create, Modify, Invitee)