isoc-ams 0.0.1__py2.py3-none-any.whl → 0.1.3__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- isoc_ams-0.1.3.dist-info/METADATA +303 -0
- isoc_ams-0.1.3.dist-info/RECORD +5 -0
- isoc_ams.py +500 -259
- isoc_ams-0.0.1.dist-info/METADATA +0 -186
- isoc_ams-0.0.1.dist-info/RECORD +0 -5
- {isoc_ams-0.0.1.dist-info → isoc_ams-0.1.3.dist-info}/WHEEL +0 -0
- {isoc_ams-0.0.1.dist-info → isoc_ams-0.1.3.dist-info}/licenses/LICENSE +0 -0
isoc_ams.py
CHANGED
|
@@ -4,83 +4,113 @@
|
|
|
4
4
|
|
|
5
5
|
"""Extract or modify Chapter Data of the ISOC AMS (Salesforce) Database.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
7
|
+
DESCRIPTION
|
|
8
|
+
|
|
9
|
+
This module consists of a Class ISOC_AMS wrapping _ISOC_AMS which subclasses
|
|
10
|
+
the webdriver.<browser> of Selenium. Up to now ownly firefox and chrome
|
|
11
|
+
drivers are implemented and tested.
|
|
12
|
+
|
|
13
|
+
CLASS
|
|
14
|
+
PROPERTIES
|
|
15
|
+
The ISOC_AMS class provides the following properties:
|
|
16
|
+
members_list:
|
|
17
|
+
a list of Chapter members (according to AMS) with data (and links)
|
|
18
|
+
pending_applicants_list:
|
|
19
|
+
a list of pending appplicants (according to AMS) for a Chapter
|
|
20
|
+
membership with data (and links)
|
|
21
|
+
these properties are initialized after login ... and this will take time
|
|
22
|
+
|
|
23
|
+
METHODS
|
|
24
|
+
The ISOC_AMS class provides the following methods:
|
|
25
|
+
build_members_list:
|
|
26
|
+
to build a list of Chapter members with data (and links)
|
|
27
|
+
build_pending_applicants_list:
|
|
28
|
+
to build a list of pending appplicants for a Chapter membership with
|
|
29
|
+
data (and links)
|
|
30
|
+
deny_applicants:
|
|
31
|
+
to deny Chapter membership for a list of applicants
|
|
32
|
+
approve_applicants:
|
|
33
|
+
to approve Chapter membership for a list of applicants
|
|
34
|
+
delete_members:
|
|
35
|
+
to revoke Chapter membership for members from the members list
|
|
36
|
+
difference_from_expected:
|
|
37
|
+
to reread AMS and check if all operations were successfull (not ever
|
|
38
|
+
problem can be detected by the methods)
|
|
39
|
+
|
|
40
|
+
ISOC_AMS will log you in to ISOC.ORG and check your authorization at
|
|
41
|
+
instantiation.
|
|
42
|
+
|
|
43
|
+
To select a webdriver, an ISOC_AMS_WEBDRIVER environment variable can be used.
|
|
44
|
+
E.g.
|
|
45
|
+
ISOC_AMS_WEBDRIVER=Firefox
|
|
46
|
+
|
|
47
|
+
Default is Firefox. Only Firefox and Chrome are allowed for now.
|
|
48
|
+
|
|
49
|
+
FUNCTIONS
|
|
50
|
+
3 functions are provided to support logging:
|
|
51
|
+
log, dlog, strong_message
|
|
52
|
+
(see below)
|
|
53
|
+
|
|
54
|
+
EXAMPLE
|
|
55
|
+
|
|
56
|
+
from isoc_ams import ISOC_AMS
|
|
57
|
+
userid, password = "myuserid", "mysecret"
|
|
58
|
+
|
|
59
|
+
# this will log you in
|
|
60
|
+
# and instantiate an ISOC_AMS object
|
|
61
|
+
ams = ISOC_AMS(userid, password)
|
|
62
|
+
|
|
63
|
+
# this will read the list of members,
|
|
64
|
+
# registered as chapters members
|
|
65
|
+
members = ams.members_list
|
|
66
|
+
|
|
67
|
+
# print the results
|
|
68
|
+
for isoc_id, member in members.items():
|
|
69
|
+
print(isoc_id,
|
|
70
|
+
member["first name"],
|
|
71
|
+
member["last name"],
|
|
72
|
+
member["email"],
|
|
73
|
+
)
|
|
74
|
+
# select members to be deleted
|
|
75
|
+
deletees = <...> # various formats are allowed for operation methods
|
|
76
|
+
delete_members(deletees)
|
|
77
|
+
|
|
78
|
+
# check if all went well
|
|
79
|
+
difference_from_expected()
|
|
80
|
+
|
|
81
|
+
CHANGELOG
|
|
82
|
+
Version 0.0.2
|
|
83
|
+
Allow input if executed as module
|
|
84
|
+
Add dryrun to ISOC_AMS class
|
|
85
|
+
Version 0.1.0
|
|
86
|
+
Improved logging
|
|
87
|
+
minor bug fixes
|
|
88
|
+
Version 0.1.1
|
|
89
|
+
minor bug fixes
|
|
90
|
+
Version 0.1.2
|
|
91
|
+
eliminate not required checks in difference_from_expected()
|
|
92
|
+
Version 0.1.3
|
|
93
|
+
tolerance against refused information e.g.members lists
|
|
69
94
|
|
|
70
95
|
"""
|
|
71
|
-
__version__ = "0.
|
|
96
|
+
__version__ = "0.1.3"
|
|
72
97
|
|
|
73
98
|
from selenium import webdriver
|
|
99
|
+
import selenium.common.exceptions
|
|
74
100
|
from selenium.webdriver.common.by import By
|
|
75
101
|
from selenium.webdriver.support.wait import WebDriverWait, TimeoutException
|
|
76
102
|
from selenium.webdriver.support import expected_conditions as EC
|
|
77
103
|
from datetime import datetime
|
|
104
|
+
import logging
|
|
78
105
|
|
|
79
106
|
import io
|
|
80
107
|
import time
|
|
81
108
|
import sys
|
|
82
109
|
import os
|
|
83
110
|
|
|
111
|
+
_logger = logging.getLogger("AMS")
|
|
112
|
+
_logger.setLevel(logging.DEBUG)
|
|
113
|
+
|
|
84
114
|
_dr = os.environ.get("ISOC_AMS_WEBDRIVER", "firefox").lower()
|
|
85
115
|
|
|
86
116
|
if _dr == "firefox":
|
|
@@ -99,48 +129,157 @@ def _WaitForTextInElement(element):
|
|
|
99
129
|
return element.text
|
|
100
130
|
return _predicate
|
|
101
131
|
|
|
132
|
+
#
|
|
133
|
+
# logging
|
|
134
|
+
#
|
|
135
|
+
|
|
136
|
+
def _init_logging(logfile, debuglog):
|
|
137
|
+
|
|
138
|
+
_logger.normalLogFormat = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s',
|
|
139
|
+
'%Y-%m-%d %H:%M:%S')
|
|
140
|
+
_logger.blankLogFormat = logging.Formatter('%(message)s')
|
|
141
|
+
|
|
142
|
+
if type(logfile) is str:
|
|
143
|
+
lfh = logging.FileHandler(logfile)
|
|
144
|
+
elif isinstance(logfile, io.TextIOBase):
|
|
145
|
+
lfh = logging.StreamHandler(logfile)
|
|
146
|
+
elif logfile is None:
|
|
147
|
+
lfh = logging.NullHandler()
|
|
148
|
+
lfh.setLevel(logging.INFO)
|
|
149
|
+
lfh.setFormatter(_logger.normalLogFormat)
|
|
150
|
+
_logger.addHandler(lfh)
|
|
151
|
+
|
|
152
|
+
if type(debuglog) is str:
|
|
153
|
+
dlh = logging.FileHandler(debuglog)
|
|
154
|
+
elif isinstance(debuglog, io.TextIOBase):
|
|
155
|
+
dlh = logging.StreamHandler(debuglog)
|
|
156
|
+
elif debuglog is None:
|
|
157
|
+
dlh = logging.NullHandler()
|
|
158
|
+
dlh.setLevel(logging.DEBUG)
|
|
159
|
+
dlh.setFormatter(_logger.normalLogFormat)
|
|
160
|
+
_logger.addHandler(dlh)
|
|
161
|
+
|
|
162
|
+
#
|
|
163
|
+
# utilities
|
|
164
|
+
#
|
|
165
|
+
|
|
166
|
+
def log(*args, date: bool = True, level: int = logging.INFO):
|
|
167
|
+
"""Write to log.
|
|
168
|
+
|
|
169
|
+
ARGUMENTS
|
|
170
|
+
args: tuple of message parts
|
|
171
|
+
level: logging level
|
|
172
|
+
date: if False ommit time and level info in logrecord
|
|
173
|
+
"""
|
|
174
|
+
if len(args) > 0:
|
|
175
|
+
msg = (len(args) * "%s ") % args
|
|
176
|
+
else:
|
|
177
|
+
msg = ""
|
|
178
|
+
if date:
|
|
179
|
+
_logger.log(level, msg)
|
|
180
|
+
else:
|
|
181
|
+
for h in _logger.handlers:
|
|
182
|
+
h.setFormatter(_logger.blankLogFormat)
|
|
183
|
+
_logger.log(level, msg)
|
|
184
|
+
for h in _logger.handlers:
|
|
185
|
+
h.setFormatter(_logger.normalLogFormat)
|
|
186
|
+
|
|
187
|
+
def dlog(*args, date: bool = True):
|
|
188
|
+
""" Short for log(*args, date=True, level=logging.DEBUG)."""
|
|
189
|
+
log(*args, date=True, level=logging.DEBUG)
|
|
190
|
+
|
|
191
|
+
def strong_msg(*args, date: bool = True, level: int = logging.INFO):
|
|
192
|
+
"""Write to log emphasized message.
|
|
193
|
+
|
|
194
|
+
ARGUMENTS
|
|
195
|
+
args: tuple of message parts
|
|
196
|
+
level: logging level
|
|
197
|
+
date: if False ommit time and level info in logrecord
|
|
198
|
+
"""
|
|
199
|
+
x = 0
|
|
200
|
+
for t in args:
|
|
201
|
+
x += len(str(t)) + 1
|
|
202
|
+
x = min(x + 1 + 30, 80)
|
|
203
|
+
log("\n" + x * "*", date=False, level=level)
|
|
204
|
+
log(*args, date=date, level=level)
|
|
205
|
+
log(x * "*", date=False, level=level)
|
|
206
|
+
|
|
207
|
+
|
|
102
208
|
|
|
103
209
|
class ISOC_AMS:
|
|
104
210
|
"""Perform admin operations on a Chaper's members list stored in AMS.
|
|
105
211
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
212
|
+
DESCRIPTION
|
|
213
|
+
|
|
214
|
+
This is the main class to interface with the ISOC-AMS system.
|
|
109
215
|
|
|
110
216
|
By default all operations run headless. If you want to follow it on
|
|
111
217
|
a browser window use headless=False.
|
|
112
218
|
|
|
113
|
-
|
|
114
|
-
____
|
|
219
|
+
ARGUMENTS
|
|
115
220
|
user: username (email) for ISO.ORG login
|
|
116
221
|
password: password for ISO.ORG login
|
|
117
|
-
logfile: where to write ISOC_AMS log output
|
|
222
|
+
logfile: where to write ISOC_AMS info-log output
|
|
223
|
+
debuglog: where to write ISOC_AMS debug-level log output
|
|
118
224
|
headless: run without GUI
|
|
119
|
-
|
|
225
|
+
dryrun: only check input, no actions
|
|
120
226
|
"""
|
|
121
227
|
|
|
122
228
|
def __init__(self,
|
|
123
229
|
user: str,
|
|
124
230
|
password: str,
|
|
125
|
-
logfile: io.
|
|
126
|
-
|
|
231
|
+
logfile: io.TextIOBase | str | None = sys.stdout,
|
|
232
|
+
debuglog: io.TextIOBase | str | None = None,
|
|
233
|
+
headless: bool = True,
|
|
234
|
+
dryrun: bool = False,):
|
|
127
235
|
if _dr == "firefox" and headless:
|
|
128
236
|
_options.add_argument("--headless")
|
|
129
237
|
elif _dr == "chrome" and headless:
|
|
130
238
|
_options.add_argument("--headless=new")
|
|
131
|
-
self.
|
|
132
|
-
|
|
133
|
-
self._ams = _ISOC_AMS(
|
|
239
|
+
self._dryrun = dryrun
|
|
240
|
+
_init_logging(logfile, debuglog)
|
|
241
|
+
self._ams = _ISOC_AMS()
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
if self._dryrun:
|
|
246
|
+
strong_msg("START DRYRUN:", "Version:", __version__, "Webdriver is", _dr)
|
|
247
|
+
else:
|
|
248
|
+
strong_msg("START:", "Version:", __version__, "Webdriver is", _dr)
|
|
249
|
+
|
|
250
|
+
try:
|
|
251
|
+
self._ams.login((user, password))
|
|
252
|
+
except selenium.common.exceptions.TimeoutException as e:
|
|
253
|
+
strong_msg("Timeout Error during login", e, level=logging.ERROR)
|
|
254
|
+
log("Will terminate")
|
|
255
|
+
exit(1)
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
self._members_list = self._ams.build_members_list()
|
|
259
|
+
except selenium.common.exceptions.TimeoutException as e:
|
|
260
|
+
self._members_list = None
|
|
261
|
+
strong_msg("Timeout Error during build members list", e, level=logging.ERROR)
|
|
262
|
+
log("no need to exit here we still can approve or reject applicants")
|
|
263
|
+
#
|
|
264
|
+
# no need to exit here we still can approve or reject applicants
|
|
265
|
+
#
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
self._pending_applications_list = self._ams.build_pending_applicants_list()
|
|
269
|
+
except selenium.common.exceptions.TimeoutException as e:
|
|
270
|
+
strong_msg("Timeout Error during build pending applications list", e, level=logging.ERROR)
|
|
271
|
+
log("Will terminate")
|
|
272
|
+
exit(2)
|
|
134
273
|
|
|
135
274
|
@property
|
|
136
|
-
def members_list(self) -> dict:
|
|
275
|
+
def members_list(self) -> dict | None:
|
|
137
276
|
"""Collects data about Chapter members.
|
|
138
277
|
|
|
278
|
+
DESCRIPTION
|
|
139
279
|
Collects the relevant data about ISOC members
|
|
140
280
|
registered as Chapter members in AMS
|
|
141
281
|
|
|
142
|
-
|
|
143
|
-
-------
|
|
282
|
+
RETURNS
|
|
144
283
|
dictionary with the following scheme:
|
|
145
284
|
{<ISOC-ID>:
|
|
146
285
|
{"first name": <first name>,
|
|
@@ -151,109 +290,146 @@ class ISOC_AMS:
|
|
|
151
290
|
...
|
|
152
291
|
}
|
|
153
292
|
|
|
154
|
-
|
|
293
|
+
ISOC-ID are used as keys for the entries
|
|
155
294
|
"""
|
|
156
|
-
if self._members_list is None:
|
|
157
|
-
self._members_list = self._ams.build_members_list()
|
|
158
295
|
return self._members_list
|
|
159
296
|
|
|
160
297
|
@property
|
|
161
|
-
def pending_applications_list(self) -> dict:
|
|
162
|
-
"""Collects data about pending Chapter
|
|
298
|
+
def pending_applications_list(self) -> dict | None:
|
|
299
|
+
"""Collects data about pending Chapter applications.
|
|
163
300
|
|
|
301
|
+
DESCRIPTION
|
|
164
302
|
Collects the relevant data about pending Chapter applicants
|
|
165
303
|
registered as pending Chapter applicants in AMS
|
|
166
304
|
|
|
167
|
-
|
|
168
|
-
-------
|
|
305
|
+
RETURNS
|
|
169
306
|
dictionary with the following scheme:
|
|
170
307
|
{<ISOC-ID>:
|
|
171
308
|
{"name": <name>,
|
|
172
309
|
"email": <Email address>',
|
|
173
310
|
"action link": <url of page to edit this entry>
|
|
311
|
+
"date": <date of application>
|
|
174
312
|
},
|
|
175
313
|
...
|
|
176
314
|
}
|
|
177
315
|
---------------------------------------------
|
|
178
|
-
|
|
316
|
+
ISOC-ID are used as keys for the entries
|
|
179
317
|
"""
|
|
180
|
-
if self._pending_applications_list is None:
|
|
181
|
-
self._pending_applications_list = \
|
|
182
|
-
self._ams.build_pending_applicants_list()
|
|
183
318
|
return self._pending_applications_list
|
|
184
319
|
|
|
185
320
|
def delete_members(self, delete_list: list | dict | str | int):
|
|
186
321
|
"""Delete Member(s) from AMS-list of Chapter members.
|
|
187
322
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
delete_list: list of dict-entrys, or ISOC-IDs, or single entry
|
|
191
|
-
or ISOC-ID
|
|
323
|
+
DESCRIPTION
|
|
324
|
+
deletes delete_list entries from AMS-list of Chapter members
|
|
192
325
|
|
|
193
|
-
|
|
326
|
+
ARGUMENTS
|
|
327
|
+
delete_list: list of dict-entrys, or ISOC-IDs, or single entry
|
|
328
|
+
or an ISOC-ID
|
|
194
329
|
"""
|
|
195
330
|
if type(delete_list) in (str, int):
|
|
196
|
-
delete_list =
|
|
197
|
-
|
|
331
|
+
delete_list = [delete_list]
|
|
332
|
+
self.delete_list = delete_list
|
|
333
|
+
for deletee in map(str, delete_list):
|
|
198
334
|
if deletee in self._members_list:
|
|
199
|
-
|
|
335
|
+
deletee = str(deletee)
|
|
336
|
+
if not self._dryrun:
|
|
337
|
+
self._ams.delete(self._members_list[deletee])
|
|
338
|
+
log("Deleted", deletee,
|
|
339
|
+
self._members_list[deletee]["first name"],
|
|
340
|
+
self._members_list[deletee]["last name"])
|
|
200
341
|
del self._members_list[deletee]
|
|
201
342
|
else:
|
|
202
|
-
|
|
203
|
-
|
|
343
|
+
log("ISOC-ID", deletee,
|
|
344
|
+
"is not in AMS Chapter members list",
|
|
345
|
+
level=logging.ERROR)
|
|
204
346
|
|
|
205
347
|
|
|
206
348
|
def approve_pending_applications(self, approve_list: list | dict | str | int):
|
|
207
349
|
"""Approve pending Members as Chapter members.
|
|
208
350
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
approve_list: list of dict-entrys, or ISOC-IDs, or single entry
|
|
212
|
-
or ISOC-ID
|
|
351
|
+
DESCRIPTION
|
|
352
|
+
approves pending members on approve_list as Chapter members
|
|
213
353
|
|
|
214
|
-
|
|
354
|
+
ARGUMENTS
|
|
355
|
+
approve_list: list of dict-entrys, or ISOC-IDs, or single entry
|
|
356
|
+
or ISOC-ID
|
|
215
357
|
"""
|
|
216
358
|
if type(approve_list) in (int, str):
|
|
217
|
-
approve_list =
|
|
218
|
-
|
|
359
|
+
approve_list = [approve_list]
|
|
360
|
+
self.approve_list = approve_list
|
|
361
|
+
for approvee in map(str, approve_list):
|
|
219
362
|
if approvee in self._pending_applications_list:
|
|
220
|
-
self.
|
|
221
|
-
|
|
363
|
+
if approvee not in self._members_list:
|
|
364
|
+
if not self._dryrun:
|
|
365
|
+
try:
|
|
366
|
+
self._ams.approve(self._pending_applications_list[approvee])
|
|
367
|
+
except selenium.common.exceptions.TimeoutException as e:
|
|
368
|
+
log("Timeout during approval of",
|
|
369
|
+
self._pending_applications_list[approvee]["name"],
|
|
370
|
+
e,
|
|
371
|
+
level=logging.ERROR)
|
|
372
|
+
continue
|
|
373
|
+
|
|
374
|
+
log("Approved",
|
|
375
|
+
self._pending_applications_list[approvee]["name"])
|
|
376
|
+
del self._pending_applications_list[approvee]
|
|
377
|
+
else:
|
|
378
|
+
log(self._pending_applications_list[approvee]["name"],
|
|
379
|
+
approvee,
|
|
380
|
+
"not approved - is already registered as member",
|
|
381
|
+
level=logging.ERROR)
|
|
222
382
|
else:
|
|
223
|
-
|
|
224
|
-
|
|
383
|
+
log("ISOC-ID", approvee,
|
|
384
|
+
"is not in pending applications list",
|
|
385
|
+
level=logging.ERROR)
|
|
225
386
|
|
|
226
387
|
def deny_pending_applications(self,
|
|
227
388
|
deny_list: list | dict | str | int,
|
|
228
389
|
reason: str = "Timeout, did not apply"):
|
|
229
390
|
"""Denies pending Members Chapter membership.
|
|
230
391
|
|
|
231
|
-
|
|
232
|
-
|
|
392
|
+
DESCRIPTION
|
|
393
|
+
denies Chapter membership for members on deny_list
|
|
394
|
+
|
|
395
|
+
ARGUMENTS
|
|
233
396
|
deny_list: list of dict-entrys, or ISOC-IDs, or single entry
|
|
234
397
|
or ISOC-ID
|
|
235
|
-
reason: All denied applicants
|
|
236
|
-
|
|
237
|
-
denies Chapter membership for members on deny_list
|
|
238
|
-
|
|
398
|
+
reason: All denied applicants have to be denied for a reason
|
|
239
399
|
"""
|
|
240
400
|
if type(deny_list) in (str, int):
|
|
241
|
-
deny_list =
|
|
242
|
-
|
|
401
|
+
deny_list = [deny_list],
|
|
402
|
+
self.deny_list = deny_list
|
|
403
|
+
for denyee in map(str, deny_list):
|
|
243
404
|
if denyee in self._pending_applications_list:
|
|
244
|
-
self.
|
|
245
|
-
|
|
405
|
+
if not self._dryrun:
|
|
406
|
+
try:
|
|
407
|
+
self._ams.deny(self._pending_applications_list[denyee],
|
|
408
|
+
reason)
|
|
409
|
+
except selenium.common.exceptions.TimeoutException as e:
|
|
410
|
+
log("Timeout during denial of",
|
|
411
|
+
self._pending_applications_list[denyee]["name"],
|
|
412
|
+
e,
|
|
413
|
+
level=logging.ERROR)
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
log("Denied", denyee,
|
|
417
|
+
self._pending_applications_list[denyee]["name"])
|
|
246
418
|
del self._pending_applications_list[denyee]
|
|
247
419
|
else:
|
|
248
|
-
|
|
249
|
-
|
|
420
|
+
log("ISOC-ID", denyee,
|
|
421
|
+
"is not in pending applications list",
|
|
422
|
+
level=logging.ERROR)
|
|
250
423
|
|
|
251
|
-
def difference_from_expected(self) -> dict:
|
|
424
|
+
def difference_from_expected(self, test=None) -> dict | str:
|
|
252
425
|
"""Compare intended outcome of operations with real outcome.
|
|
253
426
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
427
|
+
DESCRIPTION
|
|
428
|
+
Compares the contents of the ISOC-AMS database with the expected result of
|
|
429
|
+
operations
|
|
430
|
+
|
|
431
|
+
RETURNS
|
|
432
|
+
A dict containing deviations of the inteded outcome:
|
|
257
433
|
{
|
|
258
434
|
"not deleted from members":
|
|
259
435
|
All entries in AMS-Chapter-Members that were supposed
|
|
@@ -265,72 +441,80 @@ class ISOC_AMS:
|
|
|
265
441
|
All entries in pending applications that should be
|
|
266
442
|
removed - either since approved or since denied
|
|
267
443
|
}
|
|
268
|
-
|
|
444
|
+
Or a string with the result of the comoarision.
|
|
269
445
|
"""
|
|
270
|
-
self.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
446
|
+
if not self._dryrun:
|
|
447
|
+
|
|
448
|
+
log(date=False)
|
|
449
|
+
|
|
450
|
+
strong_msg("Check if actions ended up in AMS database")
|
|
451
|
+
log("we have to read the AMS Database tables again to find deviations from expected result after actions :(")
|
|
452
|
+
log("", date=False)
|
|
453
|
+
|
|
454
|
+
not_deleted = {}
|
|
455
|
+
not_approved = {}
|
|
456
|
+
not_removed_from_pending = {}
|
|
457
|
+
|
|
458
|
+
if self.approve_list or self.delete_list:
|
|
459
|
+
new_members_list = self._ams.build_members_list()
|
|
460
|
+
if self.deny_list or self.approve_list:
|
|
461
|
+
new_pending_applications_list = self._ams.build_pending_applicants_list()
|
|
462
|
+
|
|
463
|
+
dlog("Check members list")
|
|
464
|
+
if self.delete_list:
|
|
465
|
+
for nm in new_members_list:
|
|
466
|
+
if nm not in self._members_list:
|
|
467
|
+
dlog(new_members_list[nm]["first name"],
|
|
468
|
+
new_members_list[nm]["last name"],
|
|
469
|
+
"("+nm+")",
|
|
470
|
+
"was not deleted")
|
|
471
|
+
not_deleted[nm] = new_members_list[nm]
|
|
472
|
+
if self.approve_list:
|
|
473
|
+
for nm in self._members_list:
|
|
474
|
+
if nm not in new_members_list:
|
|
475
|
+
dlog(self._members_list[nm]["first name"],
|
|
476
|
+
self._members_list[nm]["last name"],
|
|
477
|
+
"("+nm+")",
|
|
478
|
+
"was not approved")
|
|
479
|
+
not_approved[nm] = self._members_list[nm]
|
|
480
|
+
if self.deny_list:
|
|
481
|
+
for np in new_pending_applications_list:
|
|
482
|
+
if np not in self._pending_applications_list:
|
|
483
|
+
dlog(self._members_list[nm]["name"],
|
|
484
|
+
"("+nm+")",
|
|
485
|
+
"was not removed from pending aoolications")
|
|
486
|
+
not_removed_from_pending[np] = new_pending_applications_list[np]
|
|
487
|
+
|
|
488
|
+
result = {}
|
|
489
|
+
if not_deleted:
|
|
490
|
+
result["not deleted from members"] = not_deleted
|
|
491
|
+
if not_approved:
|
|
492
|
+
result["not approved from pending applicants list"] = not_approved
|
|
493
|
+
if not_removed_from_pending:
|
|
494
|
+
result["not removed from pending applicants list"] = not_removed_from_pending
|
|
495
|
+
if not result:
|
|
496
|
+
result = "everything OK"
|
|
497
|
+
dlog(result)
|
|
498
|
+
return result
|
|
499
|
+
else:
|
|
500
|
+
dlog("DRYRUN: No results expected")
|
|
501
|
+
return "Dryrun: No results expected"
|
|
291
502
|
|
|
292
503
|
class _ISOC_AMS(Driver):
|
|
293
504
|
|
|
294
|
-
def __init__(self,
|
|
505
|
+
def __init__(self, logfile: str = sys.stdout):
|
|
295
506
|
|
|
296
507
|
super().__init__(_options)
|
|
297
508
|
self.windows = {}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
self.logfile = open(self.log, "a")
|
|
301
|
-
self.login(user, password)
|
|
509
|
+
|
|
510
|
+
#-------------------------------------------------------
|
|
302
511
|
|
|
303
512
|
def __del__(self):
|
|
304
513
|
self.quit()
|
|
305
514
|
|
|
306
|
-
#
|
|
307
|
-
# utilities
|
|
308
|
-
#
|
|
309
|
-
|
|
310
|
-
def log(self, *args, date=True, **kwargs):
|
|
311
|
-
if date:
|
|
312
|
-
print("AMS", datetime.now().isoformat(" ", timespec="seconds"),
|
|
313
|
-
*args,
|
|
314
|
-
file=self.logfile,
|
|
315
|
-
**kwargs)
|
|
316
|
-
else:
|
|
317
|
-
print(
|
|
318
|
-
*args,
|
|
319
|
-
file=self.logfile,
|
|
320
|
-
**kwargs)
|
|
321
|
-
|
|
322
|
-
def strong_msg(self, *args, **kwargs):
|
|
323
|
-
x = 0
|
|
324
|
-
for t in args:
|
|
325
|
-
x += len(str(t)) + 1
|
|
326
|
-
x = x + 1 + 30
|
|
327
|
-
self.log("\n" + x * "*", date=False)
|
|
328
|
-
self.log(*args, **kwargs)
|
|
329
|
-
self.log(x * "*", date=False)
|
|
330
|
-
|
|
331
515
|
def activate_window(self, name: str, url: str | None = None, refresh: bool = False):
|
|
332
516
|
if self.windows.get(name):
|
|
333
|
-
|
|
517
|
+
dlog("switching to window", name)
|
|
334
518
|
self.switch_to.window(self.windows[name])
|
|
335
519
|
if refresh:
|
|
336
520
|
self.navigate().refresh()
|
|
@@ -338,7 +522,7 @@ class _ISOC_AMS(Driver):
|
|
|
338
522
|
self.get(url)
|
|
339
523
|
return True
|
|
340
524
|
elif url:
|
|
341
|
-
|
|
525
|
+
dlog("switching to NEW window", name)
|
|
342
526
|
self.switch_to.new_window('tab')
|
|
343
527
|
self.windows[name] = self.current_window_handle
|
|
344
528
|
self.get(url)
|
|
@@ -356,19 +540,19 @@ class _ISOC_AMS(Driver):
|
|
|
356
540
|
else:
|
|
357
541
|
elem = WebDriverWait(self, timeout).until(cond)
|
|
358
542
|
return elem
|
|
359
|
-
except TimeoutException:
|
|
360
|
-
|
|
543
|
+
except TimeoutException as e:
|
|
544
|
+
strong_msg(message, e, level=logging.ERROR)
|
|
361
545
|
raise
|
|
362
546
|
|
|
363
547
|
#
|
|
364
548
|
# setup session, init windows
|
|
365
549
|
#
|
|
366
550
|
|
|
367
|
-
def login(self,
|
|
551
|
+
def login(self, credentials):
|
|
368
552
|
# Sign on user and navigate to the Chapter leaders page,
|
|
369
553
|
|
|
370
|
-
|
|
371
|
-
|
|
554
|
+
log(date=False)
|
|
555
|
+
log("logging in")
|
|
372
556
|
|
|
373
557
|
# go to community home page after succesfullogin
|
|
374
558
|
self.get("https://community.internetsociety.org/s/home-community")
|
|
@@ -383,23 +567,35 @@ class _ISOC_AMS(Driver):
|
|
|
383
567
|
"document.getElementById('signInName').value='%s';"
|
|
384
568
|
"document.getElementById('password').value='%s';"
|
|
385
569
|
"arguments[0].click();"
|
|
386
|
-
%
|
|
570
|
+
% credentials,
|
|
387
571
|
elem)
|
|
388
572
|
|
|
389
573
|
# self.set_window_size(1600, 300)
|
|
390
|
-
|
|
574
|
+
dlog("log in started")
|
|
391
575
|
# community portal
|
|
392
|
-
self.waitfor(EC.presence_of_element_located,
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
576
|
+
# self.waitfor(EC.presence_of_element_located,
|
|
577
|
+
# "siteforceStarterBody",
|
|
578
|
+
# by=By.CLASS_NAME,
|
|
579
|
+
# message=)
|
|
580
|
+
|
|
581
|
+
try:
|
|
582
|
+
elem = WebDriverWait(self, 10).until(
|
|
583
|
+
EC.any_of(
|
|
584
|
+
EC.presence_of_element_located((By.CLASS_NAME, "siteforceStarterBody")),
|
|
585
|
+
EC.visibility_of_element_located((By.CSS_SELECTOR, "form div.error p"))))
|
|
586
|
+
except TimeoutException:
|
|
587
|
+
strong_msg("timelimit exceeded while waiting "
|
|
588
|
+
"for Community portal to open", level=logging.ERROR)
|
|
589
|
+
raise
|
|
590
|
+
if elem.tag_name == "p":
|
|
591
|
+
strong_msg(elem.text, level=logging.ERROR)
|
|
592
|
+
exit(1)
|
|
397
593
|
|
|
398
|
-
|
|
594
|
+
dlog("now on community portal")
|
|
399
595
|
|
|
400
596
|
# open chapter Leader Portal
|
|
401
597
|
self.get("https://community.internetsociety.org/leader")
|
|
402
|
-
|
|
598
|
+
dlog("waiting for Chapter Leader portal")
|
|
403
599
|
|
|
404
600
|
# look if menue appears to be ready (and grab link to reports page)
|
|
405
601
|
reports_ref = self.waitfor(EC.element_to_be_clickable,
|
|
@@ -419,8 +615,8 @@ class _ISOC_AMS(Driver):
|
|
|
419
615
|
)
|
|
420
616
|
|
|
421
617
|
self.windows["leader"] = self.current_window_handle
|
|
422
|
-
|
|
423
|
-
|
|
618
|
+
log("Now on Chapter Leader portal")
|
|
619
|
+
log(date=False)
|
|
424
620
|
|
|
425
621
|
# get lists (in an extra "reports" tab)
|
|
426
622
|
self.reports_link = reports_ref.get_attribute('href')
|
|
@@ -439,8 +635,8 @@ class _ISOC_AMS(Driver):
|
|
|
439
635
|
# reason is Active Chapter Members doesn't give us the link to
|
|
440
636
|
# act on the list (to delete members)
|
|
441
637
|
|
|
442
|
-
|
|
443
|
-
|
|
638
|
+
log(date=False)
|
|
639
|
+
log("start build members list")
|
|
444
640
|
self.create_report_page("Members",
|
|
445
641
|
"Active Chapter Members")
|
|
446
642
|
self.load_report("Members")
|
|
@@ -453,8 +649,8 @@ class _ISOC_AMS(Driver):
|
|
|
453
649
|
|
|
454
650
|
for k, v in members.items():
|
|
455
651
|
v["action link"] = contacts.get(v["email"])
|
|
456
|
-
|
|
457
|
-
|
|
652
|
+
log("members list finished / ", len(members), "collected")
|
|
653
|
+
log(date=False)
|
|
458
654
|
return members
|
|
459
655
|
|
|
460
656
|
def build_pending_applicants_list(self) -> dict:
|
|
@@ -468,9 +664,9 @@ class _ISOC_AMS(Driver):
|
|
|
468
664
|
# reason is the page referred to in the reports page doesn't give
|
|
469
665
|
# us the ISOC-ID
|
|
470
666
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
667
|
+
log(date=False)
|
|
668
|
+
log("start build pending applications")
|
|
669
|
+
dlog("Creating page for Pending Applications")
|
|
474
670
|
msg = "timelimit exceeded while waiting " \
|
|
475
671
|
"for report page for Pending Application report"
|
|
476
672
|
cond = (EC.presence_of_element_located,
|
|
@@ -478,13 +674,14 @@ class _ISOC_AMS(Driver):
|
|
|
478
674
|
"table")
|
|
479
675
|
self.activate_window("report",
|
|
480
676
|
url=self.group_application_link)
|
|
677
|
+
dlog("Pending applications", "page created")
|
|
481
678
|
pendings = self.get_table(self.get_pendings)
|
|
482
|
-
|
|
483
|
-
|
|
679
|
+
log("Pending applications list finished / ", len(pendings), "collected")
|
|
680
|
+
log(date=False)
|
|
484
681
|
return pendings
|
|
485
682
|
|
|
486
683
|
def create_report_page(self, subject, button_title):
|
|
487
|
-
|
|
684
|
+
dlog("Creating page for", subject)
|
|
488
685
|
msg = "timelimit exceeded while waiting " \
|
|
489
686
|
"for report page for " + subject + " report"
|
|
490
687
|
self.activate_window("report",
|
|
@@ -496,10 +693,10 @@ class _ISOC_AMS(Driver):
|
|
|
496
693
|
))
|
|
497
694
|
time.sleep(1)
|
|
498
695
|
self.execute_script('arguments[0].click();', elem)
|
|
499
|
-
|
|
696
|
+
dlog(subject, "page created")
|
|
500
697
|
|
|
501
698
|
def load_report(self, subject):
|
|
502
|
-
|
|
699
|
+
dlog("Loading", subject)
|
|
503
700
|
cond = EC.presence_of_element_located;
|
|
504
701
|
val = "iframe"
|
|
505
702
|
msg = "timelimit exceeded while waiting " \
|
|
@@ -517,7 +714,7 @@ class _ISOC_AMS(Driver):
|
|
|
517
714
|
"iframe.isView")))
|
|
518
715
|
self.waitfor(EC.presence_of_element_located, "//table//tbody//td",
|
|
519
716
|
message=msg)
|
|
520
|
-
|
|
717
|
+
dlog("got list of", subject)
|
|
521
718
|
|
|
522
719
|
def get_table(self, reader: callable):
|
|
523
720
|
# this is a wrapper for reading tables
|
|
@@ -532,19 +729,15 @@ class _ISOC_AMS(Driver):
|
|
|
532
729
|
break
|
|
533
730
|
return int(s[:i])
|
|
534
731
|
if reader == self.get_members:
|
|
535
|
-
|
|
732
|
+
dlog('collecting the following fields: "ISOC-ID", "first name", '
|
|
536
733
|
'"last name", "email"')
|
|
537
734
|
if reader == self.get_member_contacts:
|
|
538
|
-
|
|
735
|
+
dlog('collecting the following fields: '
|
|
539
736
|
'"action link" (for taking actions), '
|
|
540
737
|
'"email" (to connect with members list)')
|
|
541
738
|
if reader == self.get_pendings:
|
|
542
|
-
|
|
739
|
+
dlog('collecting the following fields: "name", "email", '
|
|
543
740
|
'"action link", "date"')
|
|
544
|
-
# if reader == self.get_pendings:
|
|
545
|
-
# self.log('collecting the following fields: "name", "email", '
|
|
546
|
-
# '"contact link", "action link", "date"')
|
|
547
|
-
|
|
548
741
|
|
|
549
742
|
if reader == self.get_pendings:
|
|
550
743
|
tableselector = "table.uiVirtualDataTable tbody tr"
|
|
@@ -564,33 +757,32 @@ class _ISOC_AMS(Driver):
|
|
|
564
757
|
total_elem = self.find_element(By.CSS_SELECTOR, total_selector)
|
|
565
758
|
WebDriverWait(self, 10).until(_WaitForTextInElement(total_elem))
|
|
566
759
|
total = getint(total_elem.text)
|
|
567
|
-
|
|
568
|
-
|
|
760
|
+
dlog("Total (records expected):", total)
|
|
761
|
+
dlog("Waiting for Total to stabilise")
|
|
569
762
|
# wait a few seconds for total to become stable
|
|
570
763
|
time.sleep(3)
|
|
571
764
|
total = getint(total_elem.text)
|
|
572
|
-
|
|
765
|
+
dlog("Total (records expected):", total)
|
|
573
766
|
data = {}
|
|
574
767
|
while total > len(data):
|
|
575
768
|
time.sleep(3)
|
|
576
769
|
rows = self.find_elements(
|
|
577
770
|
By.CSS_SELECTOR, tableselector)
|
|
578
|
-
|
|
771
|
+
dlog("calling reader with", len(rows), "table rows, ",
|
|
579
772
|
"(collected records so far:", len(data),")")
|
|
580
773
|
scr_to = reader(rows, data)
|
|
581
774
|
if getint(total_elem.text) != total:
|
|
582
775
|
total = getint(total_elem.text)
|
|
583
|
-
|
|
776
|
+
dlog("Total was updated, now:", total)
|
|
584
777
|
if len(data) < total:
|
|
585
778
|
self.execute_script('arguments[0].scrollIntoView(true);', scr_to)
|
|
586
779
|
else:
|
|
587
|
-
|
|
780
|
+
dlog("records collected / total", len(data), " /", total)
|
|
588
781
|
return data
|
|
589
782
|
|
|
590
783
|
def get_members(self, rows, members):
|
|
591
784
|
for row in rows:
|
|
592
785
|
cells = row.find_elements(By.CSS_SELECTOR, "td")
|
|
593
|
-
# self.log(row.text.replace("\n"," / "))
|
|
594
786
|
if cells and cells[0].text and cells[0].text not in members.keys():
|
|
595
787
|
member = {}
|
|
596
788
|
member["first name"] = cells[1].text
|
|
@@ -602,9 +794,7 @@ class _ISOC_AMS(Driver):
|
|
|
602
794
|
|
|
603
795
|
def get_member_contacts(self, rows, members):
|
|
604
796
|
for row in rows:
|
|
605
|
-
# self.log(row.text.replace("\n"," / "))
|
|
606
797
|
cells = row.find_elements(By.CSS_SELECTOR, "td")
|
|
607
|
-
# self.log(len(cells), "cells")
|
|
608
798
|
if cells and \
|
|
609
799
|
len(cells) > 11 and \
|
|
610
800
|
cells[11].text and \
|
|
@@ -618,7 +808,6 @@ class _ISOC_AMS(Driver):
|
|
|
618
808
|
def get_pendings(self, rows, pendings):
|
|
619
809
|
for row in rows:
|
|
620
810
|
cells = row.find_elements(By.CSS_SELECTOR, ".slds-cell-edit")
|
|
621
|
-
# self.log(row.text.replace("\n"," / "))
|
|
622
811
|
if cells and cells[3].text:
|
|
623
812
|
pending = {}
|
|
624
813
|
pending["name"] = cells[4].text
|
|
@@ -631,10 +820,9 @@ class _ISOC_AMS(Driver):
|
|
|
631
820
|
get_attribute('href')
|
|
632
821
|
pending["date"] = datetime.strptime(
|
|
633
822
|
cells[10].text, "%m/%d/%Y")
|
|
634
|
-
# . strftime("%Y-%m-%d ") + " 00:00:00"
|
|
635
823
|
pendings[cells[6].text] = pending
|
|
636
824
|
orow = row
|
|
637
|
-
return
|
|
825
|
+
return orowselenium.common.exceptions.TimeoutException
|
|
638
826
|
|
|
639
827
|
#
|
|
640
828
|
# operations on data
|
|
@@ -642,8 +830,8 @@ class _ISOC_AMS(Driver):
|
|
|
642
830
|
|
|
643
831
|
def deny(self, entry, reason):
|
|
644
832
|
time_to_wait = 100
|
|
645
|
-
|
|
646
|
-
|
|
833
|
+
log(date=False)
|
|
834
|
+
log("start denial for", entry["name"])
|
|
647
835
|
# operation will take place in an own tab
|
|
648
836
|
self.activate_window("action",
|
|
649
837
|
url=entry["action link"])
|
|
@@ -663,7 +851,7 @@ class _ISOC_AMS(Driver):
|
|
|
663
851
|
until(EC.presence_of_element_located((
|
|
664
852
|
By.CSS_SELECTOR, 'button.slds-modal__close')))
|
|
665
853
|
|
|
666
|
-
|
|
854
|
+
dlog("select a reason for denial to feed AMS's couriosity")
|
|
667
855
|
elem = self.waitfor(EC.element_to_be_clickable,
|
|
668
856
|
"//div"
|
|
669
857
|
"[contains(concat(' ',normalize-space(@class),' '),"
|
|
@@ -673,7 +861,7 @@ class _ISOC_AMS(Driver):
|
|
|
673
861
|
time.sleep(1) # for what ist worth?
|
|
674
862
|
self.execute_script('arguments[0].click();', elem)
|
|
675
863
|
###
|
|
676
|
-
|
|
864
|
+
dlog("Waiting for combobox, chose 'other'")
|
|
677
865
|
|
|
678
866
|
elem = self.waitfor(EC.element_to_be_clickable,
|
|
679
867
|
"//lightning-base-combobox-item"
|
|
@@ -689,11 +877,11 @@ class _ISOC_AMS(Driver):
|
|
|
689
877
|
"//input",
|
|
690
878
|
message="timelimit exceeded while waiting "
|
|
691
879
|
"for deny reason 'Other - Details'")
|
|
692
|
-
|
|
880
|
+
log(f"we'll give '{reason}' as reason")
|
|
693
881
|
time.sleep(1)
|
|
694
882
|
# elem.send_keys(reason)
|
|
695
883
|
self.execute_script(f'arguments[0].value="{reason}";', elem)
|
|
696
|
-
|
|
884
|
+
dlog("finally click next")
|
|
697
885
|
|
|
698
886
|
elem = self.waitfor(EC.element_to_be_clickable,
|
|
699
887
|
"//flowruntime-navigation-bar"
|
|
@@ -706,15 +894,15 @@ class _ISOC_AMS(Driver):
|
|
|
706
894
|
try:
|
|
707
895
|
WebDriverWait(self, 15).until(EC.staleness_of(d_close))
|
|
708
896
|
except TimeoutException:
|
|
709
|
-
|
|
710
|
-
|
|
897
|
+
strong_msg("Timeout: Maybe operation was not performed")
|
|
898
|
+
log(date=False)
|
|
711
899
|
return False
|
|
712
|
-
|
|
900
|
+
log("done")
|
|
713
901
|
return True
|
|
714
902
|
|
|
715
903
|
def approve(self, entry):
|
|
716
|
-
|
|
717
|
-
|
|
904
|
+
log(date=False)
|
|
905
|
+
log("start approval for", entry["name"])
|
|
718
906
|
|
|
719
907
|
self.activate_window("action",
|
|
720
908
|
url=entry["action link"])
|
|
@@ -727,7 +915,7 @@ class _ISOC_AMS(Driver):
|
|
|
727
915
|
"waiting for details page for " +
|
|
728
916
|
entry["name"] + " to complete")
|
|
729
917
|
|
|
730
|
-
|
|
918
|
+
dlog("starting with approval")
|
|
731
919
|
time.sleep(1) # for what ist worth?
|
|
732
920
|
self.execute_script('arguments[0].click();', elem)
|
|
733
921
|
|
|
@@ -735,7 +923,7 @@ class _ISOC_AMS(Driver):
|
|
|
735
923
|
until(EC.presence_of_element_located((
|
|
736
924
|
By.CSS_SELECTOR, 'button.slds-modal__close')))
|
|
737
925
|
|
|
738
|
-
|
|
926
|
+
dlog("finally click next")
|
|
739
927
|
elem = self.waitfor(EC.element_to_be_clickable,
|
|
740
928
|
"//flowruntime-navigation-bar"
|
|
741
929
|
"/footer"
|
|
@@ -748,17 +936,18 @@ class _ISOC_AMS(Driver):
|
|
|
748
936
|
try:
|
|
749
937
|
WebDriverWait(self, 15).until(EC.staleness_of(d_close))
|
|
750
938
|
except TimeoutException:
|
|
751
|
-
|
|
752
|
-
|
|
939
|
+
strong_msg("Timeout: Maybe operation was not performed",
|
|
940
|
+
level=logging.ERROR)
|
|
941
|
+
log(date=False)
|
|
753
942
|
return False
|
|
754
|
-
|
|
943
|
+
log("done")
|
|
755
944
|
return True
|
|
756
945
|
|
|
757
946
|
|
|
758
947
|
def delete(self, entry):
|
|
759
|
-
|
|
948
|
+
log(date=False)
|
|
760
949
|
name = entry["first name"] + " " + entry["last name"]
|
|
761
|
-
|
|
950
|
+
log("start delete", name, "from AMS Chapter members list" )
|
|
762
951
|
|
|
763
952
|
self.activate_window("action",
|
|
764
953
|
url=entry["action link"])
|
|
@@ -781,10 +970,11 @@ class _ISOC_AMS(Driver):
|
|
|
781
970
|
try:
|
|
782
971
|
WebDriverWait(self, 15).until(EC.staleness_of(d_close))
|
|
783
972
|
except TimeoutException:
|
|
784
|
-
|
|
785
|
-
|
|
973
|
+
strong_msg("Timeout: Maybe operation was not performed",
|
|
974
|
+
level=logging.ERROR)
|
|
975
|
+
log(date=False)
|
|
786
976
|
return False
|
|
787
|
-
|
|
977
|
+
log("done")
|
|
788
978
|
return True
|
|
789
979
|
|
|
790
980
|
|
|
@@ -792,27 +982,78 @@ if __name__ == "__main__":
|
|
|
792
982
|
from getpass import getpass
|
|
793
983
|
headless = True
|
|
794
984
|
if "-h" in sys.argv:
|
|
795
|
-
headless=False
|
|
985
|
+
headless = False
|
|
986
|
+
inp = False
|
|
987
|
+
if "-i" in sys.argv:
|
|
988
|
+
inp = True
|
|
989
|
+
dryrun = False
|
|
990
|
+
if "-d" in sys.argv:
|
|
991
|
+
dryrun = True
|
|
992
|
+
debug = False
|
|
993
|
+
if "--debug" in sys.argv:
|
|
994
|
+
debug = True
|
|
995
|
+
|
|
796
996
|
print("Username", end=":")
|
|
797
997
|
user_id = input()
|
|
798
998
|
password = getpass()
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
999
|
+
if debug:
|
|
1000
|
+
ams = ISOC_AMS(
|
|
1001
|
+
user_id,
|
|
1002
|
+
password,
|
|
1003
|
+
headless=headless,
|
|
1004
|
+
dryrun=dryrun,
|
|
1005
|
+
logfile=sys.stdout,
|
|
1006
|
+
debuglog=None,
|
|
1007
|
+
)
|
|
1008
|
+
else:
|
|
1009
|
+
ams = ISOC_AMS(
|
|
1010
|
+
user_id,
|
|
1011
|
+
password,
|
|
1012
|
+
headless=headless,
|
|
1013
|
+
dryrun=dryrun,
|
|
1014
|
+
logfile=sys.stdout,
|
|
1015
|
+
)
|
|
803
1016
|
members = ams.members_list
|
|
804
1017
|
pendings = ams.pending_applications_list
|
|
805
1018
|
|
|
806
|
-
|
|
1019
|
+
strong_msg("MEMBERS")
|
|
807
1020
|
i = 0
|
|
808
1021
|
for k, v in members.items():
|
|
809
1022
|
i += 1
|
|
810
|
-
|
|
1023
|
+
log(i, k, v["first name"], v["last name"], v["email"], date=False)
|
|
811
1024
|
|
|
812
|
-
|
|
1025
|
+
strong_msg("PENDING APPLICATIONS")
|
|
813
1026
|
i = 0
|
|
814
1027
|
for k, v in pendings.items():
|
|
815
1028
|
i += 1
|
|
816
1029
|
# print(i, k, v)
|
|
817
|
-
|
|
818
|
-
|
|
1030
|
+
log(i, k, v["name"], v["email"], v["date"].isoformat()[:10], date=False)
|
|
1031
|
+
|
|
1032
|
+
if inp:
|
|
1033
|
+
log('READING COMMANDS:')
|
|
1034
|
+
import re
|
|
1035
|
+
patt = re.compile(r'(approve|deny|delete):?\s*([\d, ]+)')
|
|
1036
|
+
func = {"approve": ams.approve_pending_applications,
|
|
1037
|
+
"deny": ams.deny_pending_applications,
|
|
1038
|
+
"delete": ams.delete_members,
|
|
1039
|
+
}
|
|
1040
|
+
splitter = re.compile(r'[\s,]+')
|
|
1041
|
+
for rec in sys.stdin:
|
|
1042
|
+
rec = rec.strip()
|
|
1043
|
+
if m := patt.match(rec):
|
|
1044
|
+
command = m.group(1)
|
|
1045
|
+
keys = splitter.split(m.group(2))
|
|
1046
|
+
func[command](keys)
|
|
1047
|
+
else:
|
|
1048
|
+
log(rec, "contains an error", level=logging.ERROR)
|
|
1049
|
+
log("EOF of command input")
|
|
1050
|
+
|
|
1051
|
+
result = ams.difference_from_expected()
|
|
1052
|
+
if type(result) is not str:
|
|
1053
|
+
for data in result.items():
|
|
1054
|
+
log(data[0])
|
|
1055
|
+
for k, v in data[1].items():
|
|
1056
|
+
if "members" in data[0]:
|
|
1057
|
+
log(" ", v["first name"], v["last name"], v["email"], "("+k+")", date=False)
|
|
1058
|
+
else:
|
|
1059
|
+
log(" ", v["name"], v["email"], "("+k+")", date=False)
|