Mercurial > libervia-backend
comparison sat_frontends/jp/cmd_event.py @ 3906:d8baf92cb921
cli (event): update commands following changes in events:
- commands have been update to match changes in bridge method.
- arguments have been completely reworked for `create` and `modify` commands, making it
possible to fine tune events.
- `list` has been removed in favor of `get` which can now display several events at once
rel 372
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 22 Sep 2022 00:01:48 +0200 |
parents | 04283582966f |
children | 524856bd7b19 |
comparison
equal
deleted
inserted
replaced
3905:92482cc80d0b | 3906:d8baf92cb921 |
---|---|
1 #!/usr/bin/env python3 | 1 #!/usr/bin/env python3 |
2 | 2 |
3 | 3 |
4 # jp: a SàT command line tool | 4 # libervia-cli: Libervia CLI frontend |
5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) | 5 # Copyright (C) 2009-2021 Jérôme Poisson (goffi@goffi.org) |
6 | 6 |
7 # This program is free software: you can redistribute it and/or modify | 7 # This program is free software: you can redistribute it and/or modify |
8 # it under the terms of the GNU Affero General Public License as published by | 8 # it under the terms of the GNU Affero General Public License as published by |
9 # the Free Software Foundation, either version 3 of the License, or | 9 # the Free Software Foundation, either version 3 of the License, or |
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 from dateutil import parser as du_parser | 21 import argparse |
22 import calendar | 22 import sys |
23 import time | 23 |
24 from sqlalchemy import desc | |
25 | |
24 from sat.core.i18n import _ | 26 from sat.core.i18n import _ |
27 from sat.core.i18n import _ | |
28 from sat.tools.common import data_format | |
29 from sat.tools.common import data_format | |
30 from sat.tools.common import date_utils | |
25 from sat.tools.common.ansi import ANSI as A | 31 from sat.tools.common.ansi import ANSI as A |
32 from sat.tools.common.ansi import ANSI as A | |
33 from sat_frontends.jp import common | |
26 from sat_frontends.jp.constants import Const as C | 34 from sat_frontends.jp.constants import Const as C |
27 from sat_frontends.jp import common | 35 from sat_frontends.jp.constants import Const as C |
28 from sat.tools.common import data_format | 36 |
29 from . import base | 37 from . import base |
30 | 38 |
31 __commands__ = ["Event"] | 39 __commands__ = ["Event"] |
32 | 40 |
33 OUTPUT_OPT_TABLE = "table" | 41 OUTPUT_OPT_TABLE = "table" |
34 | 42 |
35 # TODO: move date parsing to base, it may be useful for other commands | 43 |
36 | 44 class Get(base.CommandBase): |
37 | |
38 class List(base.CommandBase): | |
39 def __init__(self, host): | 45 def __init__(self, host): |
40 base.CommandBase.__init__( | 46 base.CommandBase.__init__( |
41 self, | 47 self, |
42 host, | 48 host, |
43 "list", | 49 "get", |
44 use_output=C.OUTPUT_LIST_DICT, | 50 use_output=C.OUTPUT_LIST_DICT, |
45 use_pubsub=True, | 51 use_pubsub=True, |
52 pubsub_flags={C.MULTI_ITEMS, C.CACHE}, | |
46 use_verbose=True, | 53 use_verbose=True, |
47 help=_("get list of registered events"), | 54 extra_outputs={ |
55 "default": self.default_output, | |
56 }, | |
57 help=_("get event(s) data"), | |
48 ) | 58 ) |
49 | 59 |
50 def add_parser_options(self): | 60 def add_parser_options(self): |
51 pass | 61 pass |
52 | 62 |
53 async def start(self): | 63 async def start(self): |
54 try: | 64 try: |
55 events = await self.host.bridge.eventsList( | 65 events_data_s = await self.host.bridge.eventsGet( |
66 self.args.service, | |
67 self.args.node, | |
68 self.args.items, | |
69 self.getPubsubExtra(), | |
70 self.profile, | |
71 ) | |
72 except Exception as e: | |
73 self.disp(f"can't get events data: {e}", error=True) | |
74 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
75 else: | |
76 events_data = data_format.deserialise(events_data_s, type_check=list) | |
77 await self.output(events_data) | |
78 self.host.quit() | |
79 | |
80 def default_output(self, events): | |
81 nb_events = len(events) | |
82 for idx, event in enumerate(events): | |
83 names = event["name"] | |
84 name = names.get("") or next(iter(names.values())) | |
85 start = event["start"] | |
86 start_human = date_utils.date_fmt( | |
87 start, "medium", tz_info=date_utils.TZ_LOCAL | |
88 ) | |
89 end = event["end"] | |
90 self.disp(A.color( | |
91 A.BOLD, start_human, A.RESET, " ", | |
92 f"({date_utils.delta2human(start, end)}) ", | |
93 C.A_HEADER, name | |
94 )) | |
95 if self.verbosity > 0: | |
96 descriptions = event.get("descriptions", []) | |
97 if descriptions: | |
98 self.disp(descriptions[0]["description"]) | |
99 if idx < (nb_events-1): | |
100 self.disp("") | |
101 | |
102 | |
103 class CategoryAction(argparse.Action): | |
104 | |
105 def __init__(self, option_strings, dest, nargs=None, metavar=None, **kwargs): | |
106 if nargs is not None or metavar is not None: | |
107 raise ValueError("nargs and metavar must not be used") | |
108 if metavar is not None: | |
109 metavar="TERM WIKIDATA_ID LANG" | |
110 if "--help" in sys.argv: | |
111 # FIXME: dirty workaround to have correct --help message | |
112 # argparse doesn't normally allow variable number of arguments beside "+" | |
113 # and "*", this workaround show METAVAR as 3 arguments were expected, while | |
114 # we can actuall use 1, 2 or 3. | |
115 nargs = 3 | |
116 metavar = ("TERM", "[WIKIDATA_ID]", "[LANG]") | |
117 else: | |
118 nargs = "+" | |
119 | |
120 super().__init__(option_strings, dest, metavar=metavar, nargs=nargs, **kwargs) | |
121 | |
122 def __call__(self, parser, namespace, values, option_string=None): | |
123 categories = getattr(namespace, self.dest) | |
124 if categories is None: | |
125 categories = [] | |
126 setattr(namespace, self.dest, categories) | |
127 | |
128 if not values: | |
129 parser.error("category values must be set") | |
130 | |
131 category = { | |
132 "term": values[0] | |
133 } | |
134 | |
135 if len(values) == 1: | |
136 pass | |
137 elif len(values) == 2: | |
138 value = values[1] | |
139 if value.startswith("Q"): | |
140 category["wikidata_id"] = value | |
141 else: | |
142 category["language"] = value | |
143 elif len(values) == 3: | |
144 __, wd, lang = values | |
145 category["wikidata_id"] = wd | |
146 category["language"] = lang | |
147 else: | |
148 parser.error("Category can't have more than 3 arguments") | |
149 | |
150 categories.append(category) | |
151 | |
152 | |
153 class EventBase: | |
154 def add_parser_options(self): | |
155 self.parser.add_argument( | |
156 "-S", "--start", type=base.date_decoder, metavar="TIME_PATTERN", | |
157 help=_("the start time of the event")) | |
158 end_group = self.parser.add_mutually_exclusive_group() | |
159 end_group.add_argument( | |
160 "-E", "--end", type=base.date_decoder, metavar="TIME_PATTERN", | |
161 help=_("the time of the end of the event")) | |
162 end_group.add_argument( | |
163 "-D", "--duration", help=_("duration of the event")) | |
164 self.parser.add_argument( | |
165 "-H", "--head-picture", help="URL to a picture to use as head-picture" | |
166 ) | |
167 self.parser.add_argument( | |
168 "-d", "--description", help="plain text description the event" | |
169 ) | |
170 self.parser.add_argument( | |
171 "-C", "--category", action=CategoryAction, dest="categories", | |
172 help="Category of the event" | |
173 ) | |
174 self.parser.add_argument( | |
175 "-l", "--location", action="append", nargs="+", metavar="[KEY] VALUE", | |
176 help="Location metadata" | |
177 ) | |
178 rsvp_group = self.parser.add_mutually_exclusive_group() | |
179 rsvp_group.add_argument( | |
180 "--rsvp", action="store_true", help=_("RSVP is requested")) | |
181 rsvp_group.add_argument( | |
182 "--rsvp_json", metavar="JSON", help=_("JSON description of RSVP form")) | |
183 for node_type in ("invitees", "comments", "blog", "schedule"): | |
184 self.parser.add_argument( | |
185 f"--{node_type}", | |
186 nargs=2, | |
187 metavar=("JID", "NODE"), | |
188 help=_("link {node_type} pubsub node").format(node_type=node_type) | |
189 ) | |
190 self.parser.add_argument( | |
191 "-a", "--attachment", action="append", dest="attachments", | |
192 help=_("attach a file") | |
193 ) | |
194 self.parser.add_argument("--website", help=_("website of the event")) | |
195 self.parser.add_argument( | |
196 "--status", choices=["confirmed", "tentative", "cancelled"], | |
197 help=_("status of the event") | |
198 ) | |
199 self.parser.add_argument( | |
200 "-T", "--language", metavar="LANG", action="append", dest="languages", | |
201 help=_("main languages spoken at the event") | |
202 ) | |
203 self.parser.add_argument( | |
204 "--wheelchair", choices=["full", "partial", "no"], | |
205 help=_("is the location accessible by wheelchair") | |
206 ) | |
207 self.parser.add_argument( | |
208 "--external", | |
209 nargs=3, | |
210 metavar=("JID", "NODE", "ITEM"), | |
211 help=_("link to an external event") | |
212 ) | |
213 | |
214 def get_event_data(self): | |
215 if self.args.duration is not None: | |
216 if self.args.start is None: | |
217 self.parser.error("--start must be send if --duration is used") | |
218 # if duration is used, we simply add it to start time to get end time | |
219 self.args.end = base.date_decoder(f"{self.args.start} + {self.args.duration}") | |
220 | |
221 event = {} | |
222 if self.args.name is not None: | |
223 event["name"] = {"": self.args.name} | |
224 | |
225 if self.args.start is not None: | |
226 event["start"] = self.args.start | |
227 | |
228 if self.args.end is not None: | |
229 event["end"] = self.args.end | |
230 | |
231 if self.args.head_picture: | |
232 event["head-picture"] = { | |
233 "sources": [{ | |
234 "url": self.args.head_picture | |
235 }] | |
236 } | |
237 if self.args.description: | |
238 event["descriptions"] = [ | |
239 { | |
240 "type": "text", | |
241 "description": self.args.description | |
242 } | |
243 ] | |
244 if self.args.categories: | |
245 event["categories"] = self.args.categories | |
246 if self.args.location is not None: | |
247 location = {} | |
248 for location_data in self.args.location: | |
249 if len(location_data) == 1: | |
250 location["description"] = location_data[0] | |
251 else: | |
252 key, *values = location_data | |
253 location[key] = " ".join(values) | |
254 event["locations"] = [location] | |
255 | |
256 if self.args.rsvp: | |
257 event["rsvp"] = [{}] | |
258 elif self.args.rsvp_json: | |
259 if isinstance(self.args.rsvp_elt, dict): | |
260 event["rsvp"] = [self.args.rsvp_json] | |
261 else: | |
262 event["rsvp"] = self.args.rsvp_json | |
263 | |
264 for node_type in ("invitees", "comments", "blog", "schedule"): | |
265 value = getattr(self.args, node_type) | |
266 if value: | |
267 service, node = value | |
268 event[node_type] = {"service": service, "node": node} | |
269 | |
270 if self.args.attachments: | |
271 attachments = event["attachments"] = [] | |
272 for attachment in self.args.attachments: | |
273 attachments.append({ | |
274 "sources": [{"url": attachment}] | |
275 }) | |
276 | |
277 extra = {} | |
278 | |
279 for arg in ("website", "status", "languages"): | |
280 value = getattr(self.args, arg) | |
281 if value is not None: | |
282 extra[arg] = value | |
283 if self.args.wheelchair is not None: | |
284 extra["accessibility"] = {"wheelchair": self.args.wheelchair} | |
285 | |
286 if extra: | |
287 event["extra"] = extra | |
288 | |
289 if self.args.external: | |
290 ext_jid, ext_node, ext_item = self.args.external | |
291 event["external"] = { | |
292 "jid": ext_jid, | |
293 "node": ext_node, | |
294 "item": ext_item | |
295 } | |
296 return event | |
297 | |
298 | |
299 class Create(EventBase, base.CommandBase): | |
300 def __init__(self, host): | |
301 super().__init__( | |
302 host, | |
303 "create", | |
304 use_pubsub=True, | |
305 help=_("create or replace event"), | |
306 ) | |
307 | |
308 def add_parser_options(self): | |
309 super().add_parser_options() | |
310 self.parser.add_argument( | |
311 "-i", | |
312 "--id", | |
313 default="", | |
314 help=_("ID of the PubSub Item"), | |
315 ) | |
316 # name is mandatory here | |
317 self.parser.add_argument("name", help=_("name of the event")) | |
318 | |
319 async def start(self): | |
320 if self.args.start is None: | |
321 self.parser.error("--start must be set") | |
322 event_data = self.get_event_data() | |
323 # we check self.args.end after get_event_data because it may be set there id | |
324 # --duration is used | |
325 if self.args.end is None: | |
326 self.parser.error("--end or --duration must be set") | |
327 try: | |
328 await self.host.bridge.eventCreate( | |
329 data_format.serialise(event_data), | |
330 self.args.id, | |
331 self.args.node, | |
332 self.args.service, | |
333 self.profile, | |
334 ) | |
335 except Exception as e: | |
336 self.disp(f"can't create event: {e}", error=True) | |
337 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
338 else: | |
339 self.disp(_("Event created successfuly)")) | |
340 self.host.quit() | |
341 | |
342 | |
343 class Modify(EventBase, base.CommandBase): | |
344 def __init__(self, host): | |
345 super(Modify, self).__init__( | |
346 host, | |
347 "modify", | |
348 use_pubsub=True, | |
349 pubsub_flags={C.SINGLE_ITEM}, | |
350 help=_("modify an existing event"), | |
351 ) | |
352 EventBase.__init__(self) | |
353 | |
354 def add_parser_options(self): | |
355 super().add_parser_options() | |
356 # name is optional here | |
357 self.parser.add_argument("-N", "--name", help=_("name of the event")) | |
358 | |
359 async def start(self): | |
360 event_data = self.get_event_data() | |
361 try: | |
362 await self.host.bridge.eventModify( | |
363 data_format.serialise(event_data), | |
364 self.args.item, | |
56 self.args.service, | 365 self.args.service, |
57 self.args.node, | 366 self.args.node, |
58 self.profile, | 367 self.profile, |
59 ) | 368 ) |
60 except Exception as e: | 369 except Exception as e: |
61 self.disp(f"can't get list of events: {e}", error=True) | 370 self.disp(f"can't update event data: {e}", error=True) |
62 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | 371 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
63 else: | 372 else: |
64 await self.output(events) | |
65 self.host.quit() | 373 self.host.quit() |
66 | 374 |
67 | 375 |
68 class Get(base.CommandBase): | 376 class InviteeGet(base.CommandBase): |
69 def __init__(self, host): | 377 def __init__(self, host): |
70 base.CommandBase.__init__( | 378 base.CommandBase.__init__( |
71 self, | 379 self, |
72 host, | 380 host, |
73 "get", | 381 "get", |
74 use_output=C.OUTPUT_DICT, | 382 use_output=C.OUTPUT_DICT, |
75 use_pubsub=True, | 383 use_pubsub=True, |
76 pubsub_flags={C.SINGLE_ITEM}, | 384 pubsub_flags={C.SINGLE_ITEM}, |
77 use_verbose=True, | 385 use_verbose=True, |
78 help=_("get event data"), | 386 help=_("get event attendance"), |
79 ) | 387 ) |
80 | 388 |
81 def add_parser_options(self): | 389 def add_parser_options(self): |
82 pass | 390 self.parser.add_argument( |
391 "-j", "--jid", action="append", dest="jids", default=[], | |
392 help=_("only retrieve RSVP from those JIDs") | |
393 ) | |
83 | 394 |
84 async def start(self): | 395 async def start(self): |
85 try: | 396 try: |
86 event_tuple = await self.host.bridge.eventGet( | 397 event_data_s = await self.host.bridge.eventInviteeGet( |
87 self.args.service, | 398 self.args.service, |
88 self.args.node, | 399 self.args.node, |
89 self.args.item, | 400 self.args.item, |
401 self.args.jids, | |
402 "", | |
90 self.profile, | 403 self.profile, |
91 ) | 404 ) |
92 except Exception as e: | 405 except Exception as e: |
93 self.disp(f"can't get event data: {e}", error=True) | 406 self.disp(f"can't get event data: {e}", error=True) |
94 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | 407 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
95 else: | 408 else: |
96 event_date, event_data = event_tuple | 409 event_data = data_format.deserialise(event_data_s) |
97 event_data["date"] = event_date | |
98 await self.output(event_data) | 410 await self.output(event_data) |
99 self.host.quit() | 411 self.host.quit() |
100 | 412 |
101 | 413 |
102 class EventBase(object): | 414 class InviteeSet(base.CommandBase): |
415 def __init__(self, host): | |
416 super(InviteeSet, self).__init__( | |
417 host, | |
418 "set", | |
419 use_pubsub=True, | |
420 pubsub_flags={C.SINGLE_ITEM}, | |
421 help=_("set event attendance"), | |
422 ) | |
423 | |
103 def add_parser_options(self): | 424 def add_parser_options(self): |
104 self.parser.add_argument( | |
105 "-i", | |
106 "--id", | |
107 default="", | |
108 help=_("ID of the PubSub Item"), | |
109 ) | |
110 self.parser.add_argument("-d", "--date", type=str, help=_("date of the event")) | |
111 self.parser.add_argument( | 425 self.parser.add_argument( |
112 "-f", | 426 "-f", |
113 "--field", | 427 "--field", |
114 action="append", | 428 action="append", |
115 nargs=2, | 429 nargs=2, |
116 dest="fields", | 430 dest="fields", |
117 metavar=("KEY", "VALUE"), | 431 metavar=("KEY", "VALUE"), |
118 help=_("configuration field to set"), | 432 help=_("configuration field to set"), |
119 ) | 433 ) |
120 | 434 |
121 def parseFields(self): | |
122 return dict(self.args.fields) if self.args.fields else {} | |
123 | |
124 def parseDate(self): | |
125 if self.args.date: | |
126 try: | |
127 date = int(self.args.date) | |
128 except ValueError: | |
129 try: | |
130 date_time = du_parser.parse( | |
131 self.args.date, dayfirst=not ("-" in self.args.date) | |
132 ) | |
133 except ValueError as e: | |
134 self.parser.error(_("Can't parse date: {msg}").format(msg=e)) | |
135 if date_time.tzinfo is None: | |
136 date = calendar.timegm(date_time.timetuple()) | |
137 else: | |
138 date = time.mktime(date_time.timetuple()) | |
139 else: | |
140 date = -1 | |
141 return date | |
142 | |
143 | |
144 class Create(EventBase, base.CommandBase): | |
145 def __init__(self, host): | |
146 super(Create, self).__init__( | |
147 host, | |
148 "create", | |
149 use_pubsub=True, | |
150 help=_("create or replace event"), | |
151 ) | |
152 EventBase.__init__(self) | |
153 | |
154 async def start(self): | 435 async def start(self): |
155 fields = self.parseFields() | 436 # TODO: handle RSVP with XMLUI in a similar way as for `ad-hoc run` |
156 date = self.parseDate() | |
157 try: | |
158 node = await self.host.bridge.eventCreate( | |
159 date, | |
160 fields, | |
161 self.args.service, | |
162 self.args.node, | |
163 self.args.id, | |
164 self.profile, | |
165 ) | |
166 except Exception as e: | |
167 self.disp(f"can't create event: {e}", error=True) | |
168 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
169 else: | |
170 self.disp(_("Event created successfuly on node {node}").format(node=node)) | |
171 self.host.quit() | |
172 | |
173 | |
174 class Modify(EventBase, base.CommandBase): | |
175 def __init__(self, host): | |
176 super(Modify, self).__init__( | |
177 host, | |
178 "modify", | |
179 use_pubsub=True, | |
180 pubsub_flags={C.NODE}, | |
181 help=_("modify an existing event"), | |
182 ) | |
183 EventBase.__init__(self) | |
184 | |
185 async def start(self): | |
186 fields = self.parseFields() | |
187 date = 0 if not self.args.date else self.parseDate() | |
188 try: | |
189 self.host.bridge.eventModify( | |
190 self.args.service, | |
191 self.args.node, | |
192 self.args.id, | |
193 date, | |
194 fields, | |
195 self.profile, | |
196 ) | |
197 except Exception as e: | |
198 self.disp(f"can't update event data: {e}", error=True) | |
199 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
200 else: | |
201 self.host.quit() | |
202 | |
203 | |
204 class InviteeGet(base.CommandBase): | |
205 def __init__(self, host): | |
206 base.CommandBase.__init__( | |
207 self, | |
208 host, | |
209 "get", | |
210 use_output=C.OUTPUT_DICT, | |
211 use_pubsub=True, | |
212 pubsub_flags={C.NODE}, | |
213 use_verbose=True, | |
214 help=_("get event attendance"), | |
215 ) | |
216 | |
217 def add_parser_options(self): | |
218 self.parser.add_argument( | |
219 "-j", "--jid", default="", help=_("bare jid of the invitee") | |
220 ) | |
221 | |
222 async def start(self): | |
223 try: | |
224 event_data = await self.host.bridge.eventInviteeGet( | |
225 self.args.service, | |
226 self.args.node, | |
227 self.args.jid, | |
228 self.profile, | |
229 ) | |
230 except Exception as e: | |
231 self.disp(f"can't get event data: {e}", error=True) | |
232 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | |
233 else: | |
234 await self.output(event_data) | |
235 self.host.quit() | |
236 | |
237 | |
238 class InviteeSet(base.CommandBase): | |
239 def __init__(self, host): | |
240 super(InviteeSet, self).__init__( | |
241 host, | |
242 "set", | |
243 use_output=C.OUTPUT_DICT, | |
244 use_pubsub=True, | |
245 pubsub_flags={C.NODE}, | |
246 help=_("set event attendance"), | |
247 ) | |
248 | |
249 def add_parser_options(self): | |
250 self.parser.add_argument( | |
251 "-f", | |
252 "--field", | |
253 action="append", | |
254 nargs=2, | |
255 dest="fields", | |
256 metavar=("KEY", "VALUE"), | |
257 help=_("configuration field to set"), | |
258 ) | |
259 | |
260 async def start(self): | |
261 fields = dict(self.args.fields) if self.args.fields else {} | 437 fields = dict(self.args.fields) if self.args.fields else {} |
262 try: | 438 try: |
263 self.host.bridge.eventInviteeSet( | 439 self.host.bridge.eventInviteeSet( |
264 self.args.service, | 440 self.args.service, |
265 self.args.node, | 441 self.args.node, |
266 fields, | 442 self.args.item, |
443 data_format.serialise(fields), | |
267 self.profile, | 444 self.profile, |
268 ) | 445 ) |
269 except Exception as e: | 446 except Exception as e: |
270 self.disp(f"can't set event data: {e}", error=True) | 447 self.disp(f"can't set event data: {e}", error=True) |
271 self.host.quit(C.EXIT_BRIDGE_ERRBACK) | 448 self.host.quit(C.EXIT_BRIDGE_ERRBACK) |
568 host, "invitee", use_profile=False, help=_("manage invities") | 745 host, "invitee", use_profile=False, help=_("manage invities") |
569 ) | 746 ) |
570 | 747 |
571 | 748 |
572 class Event(base.CommandBase): | 749 class Event(base.CommandBase): |
573 subcommands = (List, Get, Create, Modify, Invitee) | 750 subcommands = (Get, Create, Modify, Invitee) |
574 | 751 |
575 def __init__(self, host): | 752 def __init__(self, host): |
576 super(Event, self).__init__( | 753 super(Event, self).__init__( |
577 host, "event", use_profile=False, help=_("event management") | 754 host, "event", use_profile=False, help=_("event management") |
578 ) | 755 ) |