Mercurial > libervia-backend
comparison sat_frontends/jp/cmd_event.py @ 2562:26edcf3a30eb
core, setup: huge cleaning:
- moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention
- move twisted directory to root
- removed all hacks from setup.py, and added missing dependencies, it is now clean
- use https URL for website in setup.py
- removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed
- renamed sat.sh to sat and fixed its installation
- added python_requires to specify Python version needed
- replaced glib2reactor which use deprecated code by gtk3reactor
sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author | Goffi <goffi@goffi.org> |
---|---|
date | Mon, 02 Apr 2018 19:44:50 +0200 |
parents | frontends/src/jp/cmd_event.py@772447ec070f |
children | a5b96950b81a |
comparison
equal
deleted
inserted
replaced
2561:bd30dc3ffe5a | 2562:26edcf3a30eb |
---|---|
1 #!/usr/bin/env python2 | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # jp: a SàT command line tool | |
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org) | |
6 | |
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 | |
9 # the Free Software Foundation, either version 3 of the License, or | |
10 # (at your option) any later version. | |
11 | |
12 # This program is distributed in the hope that it will be useful, | |
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 # GNU Affero General Public License for more details. | |
16 | |
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/>. | |
19 | |
20 | |
21 import base | |
22 from sat.core.i18n import _ | |
23 from sat.tools.common.ansi import ANSI as A | |
24 from sat_frontends.jp.constants import Const as C | |
25 from sat_frontends.jp import common | |
26 from functools import partial | |
27 from dateutil import parser as du_parser | |
28 import calendar | |
29 import time | |
30 | |
31 __commands__ = ["Event"] | |
32 | |
33 OUTPUT_OPT_TABLE = u'table' | |
34 | |
35 # TODO: move date parsing to base, it may be useful for other commands | |
36 | |
37 | |
38 class Get(base.CommandBase): | |
39 | |
40 def __init__(self, host): | |
41 base.CommandBase.__init__(self, | |
42 host, | |
43 'get', | |
44 use_output=C.OUTPUT_DICT, | |
45 use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, | |
46 use_verbose=True, | |
47 help=_(u'get event data')) | |
48 self.need_loop=True | |
49 | |
50 def add_parser_options(self): | |
51 pass | |
52 | |
53 def eventInviteeGetCb(self, result): | |
54 event_date, event_data = result | |
55 event_data['date'] = event_date | |
56 self.output(event_data) | |
57 self.host.quit() | |
58 | |
59 def start(self): | |
60 self.host.bridge.eventGet( | |
61 self.args.service, | |
62 self.args.node, | |
63 self.args.item, | |
64 self.profile, | |
65 callback=self.eventInviteeGetCb, | |
66 errback=partial(self.errback, | |
67 msg=_(u"can't get event data: {}"), | |
68 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
69 | |
70 | |
71 class EventBase(object): | |
72 | |
73 def add_parser_options(self): | |
74 self.parser.add_argument("-i", "--id", type=base.unicode_decoder, default=u'', help=_(u"ID of the PubSub Item")) | |
75 self.parser.add_argument("-d", "--date", type=unicode, help=_(u"date of the event")) | |
76 self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', | |
77 metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) | |
78 | |
79 def parseFields(self): | |
80 return dict(self.args.fields) if self.args.fields else {} | |
81 | |
82 def parseDate(self): | |
83 if self.args.date: | |
84 try: | |
85 date = int(self.args.date) | |
86 except ValueError: | |
87 try: | |
88 date_time = du_parser.parse(self.args.date, dayfirst=not (u'-' in self.args.date)) | |
89 except ValueError as e: | |
90 self.parser.error(_(u"Can't parse date: {msg}").format(msg=e)) | |
91 if date_time.tzinfo is None: | |
92 date = calendar.timegm(date_time.timetuple()) | |
93 else: | |
94 date = time.mktime(date_time.timetuple()) | |
95 else: | |
96 date = -1 | |
97 return date | |
98 | |
99 | |
100 class Create(EventBase, base.CommandBase): | |
101 def __init__(self, host): | |
102 super(Create, self).__init__(host, 'create', use_pubsub=True, pubsub_flags={C.NODE}, help=_('create or replace event')) | |
103 EventBase.__init__(self) | |
104 self.need_loop=True | |
105 | |
106 def eventCreateCb(self, node): | |
107 self.disp(_(u'Event created successfuly on node {node}').format(node=node)) | |
108 self.host.quit() | |
109 | |
110 def start(self): | |
111 fields = self.parseFields() | |
112 date = self.parseDate() | |
113 self.host.bridge.eventCreate( | |
114 date, | |
115 fields, | |
116 self.args.service, | |
117 self.args.node, | |
118 self.args.id, | |
119 self.profile, | |
120 callback=self.eventCreateCb, | |
121 errback=partial(self.errback, | |
122 msg=_(u"can't create event: {}"), | |
123 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
124 | |
125 | |
126 class Modify(EventBase, base.CommandBase): | |
127 def __init__(self, host): | |
128 super(Modify, self).__init__(host, 'modify', use_pubsub=True, pubsub_flags={C.NODE}, help=_('modify an existing event')) | |
129 EventBase.__init__(self) | |
130 self.need_loop=True | |
131 | |
132 def start(self): | |
133 fields = self.parseFields() | |
134 date = 0 if not self.args.date else self.parseDate() | |
135 self.host.bridge.eventModify( | |
136 self.args.service, | |
137 self.args.node, | |
138 self.args.id, | |
139 date, | |
140 fields, | |
141 self.profile, | |
142 callback=self.host.quit, | |
143 errback=partial(self.errback, | |
144 msg=_(u"can't update event data: {}"), | |
145 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
146 | |
147 | |
148 class InviteeGet(base.CommandBase): | |
149 | |
150 def __init__(self, host): | |
151 base.CommandBase.__init__(self, | |
152 host, | |
153 'get', | |
154 use_output=C.OUTPUT_DICT, | |
155 use_pubsub=True, pubsub_flags={C.NODE}, | |
156 use_verbose=True, | |
157 help=_(u'get event attendance')) | |
158 self.need_loop=True | |
159 | |
160 def add_parser_options(self): | |
161 pass | |
162 | |
163 def eventInviteeGetCb(self, event_data): | |
164 self.output(event_data) | |
165 self.host.quit() | |
166 | |
167 def start(self): | |
168 self.host.bridge.eventInviteeGet( | |
169 self.args.service, | |
170 self.args.node, | |
171 self.profile, | |
172 callback=self.eventInviteeGetCb, | |
173 errback=partial(self.errback, | |
174 msg=_(u"can't get event data: {}"), | |
175 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
176 | |
177 | |
178 class InviteeSet(base.CommandBase): | |
179 def __init__(self, host): | |
180 super(InviteeSet, self).__init__(host, 'set', use_output=C.OUTPUT_DICT, use_pubsub=True, pubsub_flags={C.NODE}, help=_('set event attendance')) | |
181 self.need_loop=True | |
182 | |
183 def add_parser_options(self): | |
184 self.parser.add_argument("-f", "--field", type=base.unicode_decoder, action='append', nargs=2, dest='fields', | |
185 metavar=(u"KEY", u"VALUE"), help=_(u"configuration field to set")) | |
186 | |
187 def start(self): | |
188 fields = dict(self.args.fields) if self.args.fields else {} | |
189 self.host.bridge.eventInviteeSet( | |
190 self.args.service, | |
191 self.args.node, | |
192 fields, | |
193 self.profile, | |
194 callback=self.host.quit, | |
195 errback=partial(self.errback, | |
196 msg=_(u"can't set event data: {}"), | |
197 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
198 | |
199 | |
200 class InviteesList(base.CommandBase): | |
201 | |
202 def __init__(self, host): | |
203 extra_outputs = {'default': self.default_output} | |
204 base.CommandBase.__init__(self, | |
205 host, | |
206 'list', | |
207 use_output=C.OUTPUT_DICT_DICT, | |
208 extra_outputs=extra_outputs, | |
209 use_pubsub=True, pubsub_flags={C.NODE}, | |
210 use_verbose=True, | |
211 help=_(u'get event attendance')) | |
212 self.need_loop=True | |
213 | |
214 def add_parser_options(self): | |
215 self.parser.add_argument('-m', '--missing', action='store_true', help=_(u'show missing people (invited but no R.S.V.P. so far)')) | |
216 self.parser.add_argument('-R', '--no-rsvp', action='store_true', help=_(u"don't show people which gave R.S.V.P.")) | |
217 | |
218 def _attend_filter(self, attend, row): | |
219 if attend == u'yes': | |
220 attend_color = C.A_SUCCESS | |
221 elif attend == u'no': | |
222 attend_color = C.A_FAILURE | |
223 else: | |
224 attend_color = A.FG_WHITE | |
225 return A.color(attend_color, attend) | |
226 | |
227 def _guests_filter(self, guests): | |
228 return u'(' + unicode(guests) + ')' if guests else u'' | |
229 | |
230 def default_output(self, event_data): | |
231 data = [] | |
232 attendees_yes = 0 | |
233 attendees_maybe = 0 | |
234 attendees_no = 0 | |
235 attendees_missing = 0 | |
236 guests = 0 | |
237 guests_maybe = 0 | |
238 for jid_, jid_data in event_data.iteritems(): | |
239 jid_data[u'jid'] = jid_ | |
240 try: | |
241 guests_int = int(jid_data['guests']) | |
242 except (ValueError, KeyError): | |
243 pass | |
244 attend = jid_data.get(u'attend',u'') | |
245 if attend == 'yes': | |
246 attendees_yes += 1 | |
247 guests += guests_int | |
248 elif attend == 'maybe': | |
249 attendees_maybe += 1 | |
250 guests_maybe += guests_int | |
251 elif attend == 'no': | |
252 attendees_no += 1 | |
253 jid_data[u'guests'] = '' | |
254 else: | |
255 attendees_missing += 1 | |
256 jid_data[u'guests'] = '' | |
257 data.append(jid_data) | |
258 | |
259 show_table = OUTPUT_OPT_TABLE in self.args.output_opts | |
260 | |
261 table = common.Table.fromDict(self.host, | |
262 data, | |
263 (u'nick',) + ((u'jid',) if self.host.verbosity else ()) + (u'attend', 'guests'), | |
264 headers=None, | |
265 filters = { u'nick': A.color(C.A_HEADER, u'{}' if show_table else u'{} '), | |
266 u'jid': u'{}' if show_table else u'{} ', | |
267 u'attend': self._attend_filter, | |
268 u'guests': u'{}' if show_table else self._guests_filter, | |
269 }, | |
270 defaults = { u'nick': u'', | |
271 u'attend': u'', | |
272 u'guests': 1 | |
273 } | |
274 ) | |
275 if show_table: | |
276 table.display() | |
277 else: | |
278 table.display_blank(show_header=False, col_sep=u'') | |
279 | |
280 if not self.args.no_rsvp: | |
281 self.disp(u'') | |
282 self.disp(A.color( | |
283 C.A_SUBHEADER, | |
284 _(u'Attendees: '), | |
285 A.RESET, | |
286 unicode(len(data)), | |
287 _(u' ('), | |
288 C.A_SUCCESS, | |
289 _(u'yes: '), | |
290 unicode(attendees_yes), | |
291 A.FG_WHITE, | |
292 _(u', maybe: '), | |
293 unicode(attendees_maybe), | |
294 u', ', | |
295 C.A_FAILURE, | |
296 _(u'no: '), | |
297 unicode(attendees_no), | |
298 A.RESET, | |
299 u')' | |
300 )) | |
301 self.disp(A.color(C.A_SUBHEADER, _(u'confirmed guests: '), A.RESET, unicode(guests))) | |
302 self.disp(A.color(C.A_SUBHEADER, _(u'unconfirmed guests: '), A.RESET, unicode(guests_maybe))) | |
303 self.disp(A.color(C.A_SUBHEADER, _(u'total: '), A.RESET, unicode(guests+guests_maybe))) | |
304 if attendees_missing: | |
305 self.disp('') | |
306 self.disp(A.color(C.A_SUBHEADER, _(u'missing people (no reply): '), A.RESET, unicode(attendees_missing))) | |
307 | |
308 def eventInviteesListCb(self, event_data, prefilled_data): | |
309 """fill nicknames and keep only requested people | |
310 | |
311 @param event_data(dict): R.S.V.P. answers | |
312 @param prefilled_data(dict): prefilled data with all people | |
313 only filled if --missing is used | |
314 """ | |
315 if self.args.no_rsvp: | |
316 for jid_ in event_data: | |
317 # if there is a jid in event_data | |
318 # it must be there in prefilled_data too | |
319 # so no need to check for KeyError | |
320 del prefilled_data[jid_] | |
321 else: | |
322 # we replace empty dicts for existing people with R.S.V.P. data | |
323 prefilled_data.update(event_data) | |
324 | |
325 # we get nicknames for everybody, make it easier for organisers | |
326 for jid_, data in prefilled_data.iteritems(): | |
327 id_data = self.host.bridge.identityGet(jid_, self.profile) | |
328 data[u'nick'] = id_data.get(u'nick', u'') | |
329 | |
330 self.output(prefilled_data) | |
331 self.host.quit() | |
332 | |
333 def getList(self, prefilled_data={}): | |
334 self.host.bridge.eventInviteesList( | |
335 self.args.service, | |
336 self.args.node, | |
337 self.profile, | |
338 callback=partial(self.eventInviteesListCb, | |
339 prefilled_data=prefilled_data), | |
340 errback=partial(self.errback, | |
341 msg=_(u"can't get event data: {}"), | |
342 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
343 | |
344 def psNodeAffiliationsGetCb(self, affiliations): | |
345 # we fill all affiliations with empty data | |
346 # answered one will be filled in eventInviteesListCb | |
347 # we only consider people with "publisher" affiliation as invited, creators are not, and members can just observe | |
348 prefilled = {jid_: {} for jid_, affiliation in affiliations.iteritems() if affiliation in (u'publisher',)} | |
349 self.getList(prefilled) | |
350 | |
351 def start(self): | |
352 if self.args.no_rsvp and not self.args.missing: | |
353 self.parser.error(_(u"you need to use --missing if you use --no-rsvp")) | |
354 if self.args.missing: | |
355 self.host.bridge.psNodeAffiliationsGet( | |
356 self.args.service, | |
357 self.args.node, | |
358 self.profile, | |
359 callback=self.psNodeAffiliationsGetCb, | |
360 errback=partial(self.errback, | |
361 msg=_(u"can't get event data: {}"), | |
362 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
363 else: | |
364 self.getList() | |
365 | |
366 | |
367 class InviteeInvite(base.CommandBase): | |
368 | |
369 def __init__(self, host): | |
370 base.CommandBase.__init__(self, host, 'invite', use_pubsub=True, pubsub_flags={C.NODE, C.SINGLE_ITEM}, help=_(u'invite someone to the event through email')) | |
371 self.need_loop=True | |
372 | |
373 def add_parser_options(self): | |
374 self.parser.add_argument("-e", "--email", action="append", type=base.unicode_decoder, default=[], help='email(s) to send the invitation to') | |
375 self.parser.add_argument("-N", "--name", type=base.unicode_decoder, default='', help='name of the invitee') | |
376 self.parser.add_argument("-H", "--host-name", type=base.unicode_decoder, default='', help='name of the host') | |
377 self.parser.add_argument("-l", "--lang", type=base.unicode_decoder, default='', help='main language spoken by the invitee') | |
378 self.parser.add_argument("-U", "--url-template", type=base.unicode_decoder, default='', help='template to construct the URL') | |
379 self.parser.add_argument("-S", "--subject", type=base.unicode_decoder, default='', help='subject of the invitation email (default: generic subject)') | |
380 self.parser.add_argument("-b", "--body", type=base.unicode_decoder, default='', help='body of the invitation email (default: generic body)') | |
381 | |
382 def start(self): | |
383 email = self.args.email[0] if self.args.email else None | |
384 emails_extra = self.args.email[1:] | |
385 | |
386 self.host.bridge.eventInvite( | |
387 self.args.service, | |
388 self.args.node, | |
389 self.args.item, | |
390 email, | |
391 emails_extra, | |
392 self.args.name, | |
393 self.args.host_name, | |
394 self.args.lang, | |
395 self.args.url_template, | |
396 self.args.subject, | |
397 self.args.body, | |
398 self.args.profile, | |
399 callback=self.host.quit, | |
400 errback=partial(self.errback, | |
401 msg=_(u"can't create invitation: {}"), | |
402 exit_code=C.EXIT_BRIDGE_ERRBACK)) | |
403 | |
404 | |
405 class Invitee(base.CommandBase): | |
406 subcommands = (InviteeGet, InviteeSet, InviteesList, InviteeInvite) | |
407 | |
408 def __init__(self, host): | |
409 super(Invitee, self).__init__(host, 'invitee', use_profile=False, help=_(u'manage invities')) | |
410 | |
411 | |
412 class Event(base.CommandBase): | |
413 subcommands = (Get, Create, Modify, Invitee) | |
414 | |
415 def __init__(self, host): | |
416 super(Event, self).__init__(host, 'event', use_profile=False, help=_('event management')) |