wbintegrator_office365 2.2.1__py2.py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. wbintegrator_office365/__init__.py +1 -0
  2. wbintegrator_office365/admin.py +209 -0
  3. wbintegrator_office365/apps.py +5 -0
  4. wbintegrator_office365/configurations/__init__.py +0 -0
  5. wbintegrator_office365/configurations/configurations/__init__.py +23 -0
  6. wbintegrator_office365/dynamic_preferences_registry.py +15 -0
  7. wbintegrator_office365/factories.py +102 -0
  8. wbintegrator_office365/filters.py +237 -0
  9. wbintegrator_office365/importer/__init__.py +3 -0
  10. wbintegrator_office365/importer/api.py +403 -0
  11. wbintegrator_office365/importer/disable_signals.py +43 -0
  12. wbintegrator_office365/importer/parser.py +135 -0
  13. wbintegrator_office365/kpi_handlers/__init__.py +1 -0
  14. wbintegrator_office365/kpi_handlers/calls.py +114 -0
  15. wbintegrator_office365/migrations/0001_initial_squashed_squashed_0003_alter_calendar_owner_alter_calendarevent_organizer_and_more.py +677 -0
  16. wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendarevent_activity_and_more.py +85 -0
  17. wbintegrator_office365/migrations/0003_alter_event_options.py +20 -0
  18. wbintegrator_office365/migrations/__init__.py +0 -0
  19. wbintegrator_office365/models/__init__.py +3 -0
  20. wbintegrator_office365/models/event.py +623 -0
  21. wbintegrator_office365/models/subscription.py +144 -0
  22. wbintegrator_office365/models/tenant.py +62 -0
  23. wbintegrator_office365/serializers.py +266 -0
  24. wbintegrator_office365/tasks.py +108 -0
  25. wbintegrator_office365/tests/__init__.py +0 -0
  26. wbintegrator_office365/tests/conftest.py +28 -0
  27. wbintegrator_office365/tests/test_admin.py +86 -0
  28. wbintegrator_office365/tests/test_models.py +65 -0
  29. wbintegrator_office365/tests/test_tasks.py +318 -0
  30. wbintegrator_office365/tests/test_views.py +128 -0
  31. wbintegrator_office365/tests/tests.py +12 -0
  32. wbintegrator_office365/urls.py +46 -0
  33. wbintegrator_office365/viewsets/__init__.py +31 -0
  34. wbintegrator_office365/viewsets/display.py +306 -0
  35. wbintegrator_office365/viewsets/endpoints.py +52 -0
  36. wbintegrator_office365/viewsets/menu.py +65 -0
  37. wbintegrator_office365/viewsets/titles.py +49 -0
  38. wbintegrator_office365/viewsets/viewsets.py +745 -0
  39. wbintegrator_office365-2.2.1.dist-info/METADATA +10 -0
  40. wbintegrator_office365-2.2.1.dist-info/RECORD +41 -0
  41. wbintegrator_office365-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,403 @@
1
+ import json
2
+ from datetime import timedelta
3
+
4
+ import pandas as pd
5
+ import requests
6
+ from django.conf import settings
7
+ from django.utils import timezone
8
+ from dynamic_preferences.registries import global_preferences_registry
9
+ from rest_framework import status
10
+
11
+ from .parser import parse
12
+
13
+
14
+ class MicrosoftGraphAPI:
15
+ def __init__(self):
16
+ self.authority = getattr(settings, "WBINTEGRATOR_OFFICE365_AUTHORITY", "")
17
+ self.client_id = getattr(settings, "WBINTEGRATOR_OFFICE365_CLIENT_ID", "")
18
+ self.client_secret = getattr(settings, "WBINTEGRATOR_OFFICE365_CLIENT_SECRET", "")
19
+ self.redirect_uri = getattr(settings, "WBINTEGRATOR_OFFICE365_REDIRECT_URI", "")
20
+ self.token_endpoint = getattr(settings, "WBINTEGRATOR_OFFICE365_TOKEN_ENDPOINT", "")
21
+ self.notification_url = getattr(settings, "WBINTEGRATOR_OFFICE365_NOTIFICATION_URL", "")
22
+ self.graph_url = getattr(settings, "WBINTEGRATOR_OFFICE365_GRAPH_URL", "")
23
+
24
+ global_preferences = global_preferences_registry.manager()
25
+ if global_preferences["wbintegrator_office365__access_token"] == "0":
26
+ global_preferences["wbintegrator_office365__access_token"] = self._get_access_token()
27
+
28
+ def _get_administrator_consent(self):
29
+ url = f"{self.authority}/adminconsent?client_id={self.client_id}&state=12345&redirect_uri={self.redirect_uri}"
30
+ response = self._query(url, access_token=False)
31
+ if response:
32
+ return response
33
+ else:
34
+ raise ValueError("get administrator consent does not return response 200")
35
+
36
+ def _get_access_token(self):
37
+ # Get administrator consent
38
+ self._get_administrator_consent()
39
+ # Get an access token
40
+ url = f"{self.authority}{self.token_endpoint}"
41
+ payload = {
42
+ "grant_type": "client_credentials",
43
+ "client_id": self.client_id,
44
+ "scope": "https://graph.microsoft.com/.default",
45
+ "client_secret": self.client_secret,
46
+ }
47
+
48
+ response = self._query(url, method="POST", data=payload, is_json=False, access_token=False)
49
+ data = None
50
+ if response:
51
+ if response.json():
52
+ data = response.json().get("access_token")
53
+ else:
54
+ raise ValueError(response, response.json())
55
+ return data
56
+
57
+ def _subscribe(self, resource, minutes, change_type):
58
+ data = {
59
+ "changeType": change_type,
60
+ "notificationUrl": self.notification_url,
61
+ "resource": resource,
62
+ "clientState": "secretClientValue",
63
+ "latestSupportedTlsVersion": "v1_2",
64
+ }
65
+ if minutes:
66
+ # maximum time of subscription 4230 minutes (under 3 days)
67
+ date = timezone.now() + timedelta(minutes=minutes)
68
+ date = date.strftime("%Y-%m-%dT%H:%M:%SZ")
69
+ data["expirationDateTime"] = date
70
+
71
+ url = f"{self.graph_url}/subscriptions"
72
+ response = self._query(url, method="POST", data=json.dumps(data))
73
+ data = None
74
+ if response:
75
+ if response.json():
76
+ data = parse(response.json(), scalar_value=True)
77
+ if data:
78
+ data = data[0]
79
+ else:
80
+ raise ValueError(response, response.json())
81
+ return data
82
+
83
+ def _unsubscribe(self, subscription_id):
84
+ url = f"{self.graph_url}/subscriptions/{subscription_id}"
85
+ return self._query(url, method="DELETE")
86
+
87
+ def _renew_subscription(self, subscription_id, minutes=4230):
88
+ url = f"{self.graph_url}/subscriptions/{subscription_id}"
89
+ date = timezone.now() + timedelta(minutes=minutes)
90
+ date = date.strftime("%Y-%m-%dT%H:%M:%SZ")
91
+ data = {"expirationDateTime": date}
92
+ response = self._query(url, method="PATCH", data=json.dumps(data))
93
+ data = None
94
+ if response:
95
+ if response.json():
96
+ data = parse(response.json(), scalar_value=True)
97
+ if data:
98
+ data = data[0]
99
+ else:
100
+ raise ValueError(response, response.json())
101
+ return data
102
+
103
+ def subscriptions(self):
104
+ url = f"{self.graph_url}/subscriptions"
105
+ # curl -X GET -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJub25jZSI6IlVUSmxpNTVOcDV1MHVDb3dwTWxpMThIdDdQRDZwV1VkMWNyNlRRYUNlNG8iLCJhbGciOiJSUzI1NiIsIng1dCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyIsImtpZCI6Im5PbzNaRHJPRFhFSzFqS1doWHNsSFJfS1hFZyJ9.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83ZWJhNTBhMi00N2QyLTQxODUtYWIwNy0yYTNjYTE4YzliNzYvIiwiaWF0IjoxNjE4MjE5ODIwLCJuYmYiOjE2MTgyMTk4MjAsImV4cCI6MTYxODIyMzcyMCwiYWlvIjoiRTJaZ1lBaVNGN3ZUS0tGbng4YTk2ak5mNjhFVEFBPT0iLCJhcHBfZGlzcGxheW5hbWUiOiJOb3RpZmljYXRpb25DYWxsVXNlckFwcGxpY2F0aW9uIiwiYXBwaWQiOiJkYjg5ZDYxNi0xYWJlLTQzNTgtYWRmNC0zYzE2ZWY3ZjQ0ZjAiLCJhcHBpZGFjciI6IjEiLCJpZHAiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83ZWJhNTBhMi00N2QyLTQxODUtYWIwNy0yYTNjYTE4YzliNzYvIiwiaWR0eXAiOiJhcHAiLCJvaWQiOiIyNzNjODI1Yy00YTYyLTQyOWUtODdiZi0zOWYzZDY2ZTMwYWYiLCJyaCI6IjAuQVRvQW9sQzZmdEpIaFVHckJ5bzhvWXliZGhiV2lkdS1HbGhEcmZROEZ1OV9SUEE2QUFBLiIsInJvbGVzIjpbIkFjY2Vzc1Jldmlldy5SZWFkV3JpdGUuTWVtYmVyc2hpcCIsIk1haWwuUmVhZFdyaXRlIiwiVXNlci5SZWFkV3JpdGUuQWxsIiwiRGVsZWdhdGVkUGVybWlzc2lvbkdyYW50LlJlYWRXcml0ZS5BbGwiLCJDYWxlbmRhcnMuUmVhZCIsIk1haWwuUmVhZEJhc2ljLkFsbCIsIkdyb3VwLlJlYWQuQWxsIiwiQWNjZXNzUmV2aWV3LlJlYWRXcml0ZS5BbGwiLCJEaXJlY3RvcnkuUmVhZFdyaXRlLkFsbCIsIkNhbGxSZWNvcmRzLlJlYWQuQWxsIiwiSWRlbnRpdHlVc2VyRmxvdy5SZWFkLkFsbCIsIlVzZXIuSW52aXRlLkFsbCIsIkRpcmVjdG9yeS5SZWFkLkFsbCIsIlVzZXIuUmVhZC5BbGwiLCJVc2VyTm90aWZpY2F0aW9uLlJlYWRXcml0ZS5DcmVhdGVkQnlBcHAiLCJGaWxlcy5SZWFkLkFsbCIsIk1haWwuUmVhZCIsIkNoYXQuUmVhZC5BbGwiLCJVc2VyLkV4cG9ydC5BbGwiLCJJZGVudGl0eVByb3ZpZGVyLlJlYWQuQWxsIiwiQ2FsZW5kYXJzLlJlYWRXcml0ZSIsIklkZW50aXR5Umlza3lVc2VyLlJlYWQuQWxsIiwiQWNjZXNzUmV2aWV3LlJlYWQuQWxsIiwiTWFpbC5TZW5kIiwiVXNlci5NYW5hZ2VJZGVudGl0aWVzLkFsbCIsIkNvbnRhY3RzLlJlYWQiLCJJZGVudGl0eVJpc2tFdmVudC5SZWFkLkFsbCIsIk1haWwuUmVhZEJhc2ljIiwiQ2hhdC5SZWFkQmFzaWMuQWxsIiwiQ2FsbHMuQWNjZXNzTWVkaWEuQWxsIiwiQXBwbGljYXRpb24uUmVhZC5BbGwiLCJSZXBvcnRzLlJlYWQuQWxsIl0sInN1YiI6IjI3M2M4MjVjLTRhNjItNDI5ZS04N2JmLTM5ZjNkNjZlMzBhZiIsInRlbmFudF9yZWdpb25fc2NvcGUiOiJFVSIsInRpZCI6IjdlYmE1MGEyLTQ3ZDItNDE4NS1hYjA3LTJhM2NhMThjOWI3NiIsInV0aSI6Im9ONGJ1anMzaGtlOUVoUTRCTlV6QUEiLCJ2ZXIiOiIxLjAiLCJ4bXNfdGNkdCI6MTU1ODY4MjUxMX0.Jw5jVFFK2HIHXkWpR61eVGdVv-NnwsMBpHDRUur6UXrUlxtPwLAVvaklEILzALUNKAWe2Ic0k737tAq1C10DCkHF3iJ5bXQWTD0yAux1pZrHNYMMJ-bRvAdcuaLys5Amtas8gcYhj8mSpHdiSclW_17TxPDacSjvDwYEFOjgzAUYUtsoR5Q_q2H14QVR-PFd9WreqcOVy4ELNBBUhFdFjmV8Cb0lLUujW5Q0zBniOrwyRN4VYSdGEuyogoCOeICoNBdgwnLjXq6BMv6CLLSpzwEKDp-ikK7SETDd1uyDADYOUg-YuJ_D-ZIiobUNpA4UnBMNpfrUrCDzF84iox0vWA" 'https://graph.microsoft.com/v1.0/subscriptions'
106
+ response = self._query(url)
107
+ data = None
108
+ if response:
109
+ if datum := response.json():
110
+ data = parse(datum.get("value"))
111
+ url = datum.get("@odata.nextLink")
112
+ while url:
113
+ response = self._query(url)
114
+ datum = response.json()
115
+ data += parse(datum.get("value"))
116
+ url = datum.get("@odata.nextLink")
117
+
118
+ else:
119
+ raise ValueError(response, response.json())
120
+ return data
121
+
122
+ def user(self, email):
123
+ query_params = {"$select": "id, userPrincipalName, displayName"}
124
+ url = f"{self.graph_url}/users/{email}"
125
+ response = self._query(url, params=query_params)
126
+ data = None
127
+ if response:
128
+ if response.json():
129
+ data = parse(response.json(), scalar_value=True)
130
+ if data:
131
+ data = data[0]
132
+ else:
133
+ raise ValueError(response, response.json())
134
+ return data
135
+
136
+ def users(self, filter_params=True):
137
+ query_params = {
138
+ "$select": "id,displayName,businessPhones,mobilePhone, userPrincipalName, mail,email, mailNickname, givenName, surname, imAddresses"
139
+ }
140
+ url = f"{self.graph_url}/users"
141
+ if filter_params:
142
+ response = self._query(url, params=query_params)
143
+ else:
144
+ response = self._query(url)
145
+ data = None
146
+ if response:
147
+ data = parse(response.json().get("value"))
148
+ else:
149
+ raise ValueError(response, response.json())
150
+ return data
151
+
152
+ def call(self, call_id: str, raise_error: bool = False):
153
+ url = f"{self.graph_url}/communications/callRecords/{call_id}"
154
+ response = self._query(url)
155
+ data = None
156
+ if response and (json_data := response.json()):
157
+ data = parse(pd.json_normalize(json_data))
158
+ if data:
159
+ data = data[0]
160
+ elif raise_error:
161
+ raise ValueError(response, response.json())
162
+ return data
163
+
164
+ # CREATION CALENDAR
165
+ def create_calendar_group(self, user_id, data):
166
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups"
167
+ return self._query_create(url, data)
168
+
169
+ def create_calendar(self, user_id, data, id_calendar_group=None):
170
+ if id_calendar_group:
171
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}/calendars"
172
+ else:
173
+ url = f"{self.graph_url}/users/{user_id}/calendars"
174
+ return self._query_create(url, data)
175
+
176
+ def create_calendar_event(self, user_id, data, id_calendar_group=None, id_calendar=None):
177
+ if id_calendar:
178
+ # A user's calendar in the default calendarGroup.
179
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}/events"
180
+ if id_calendar_group:
181
+ # A user's calendar in a specific calendarGroup.
182
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}/calendars/{id_calendar}/events"
183
+ else:
184
+ # A user's or group's default calendar.
185
+ url = f"{self.graph_url}/users/{user_id}/events"
186
+ return self._query_create(url, data)
187
+
188
+ # LIST CALENDAR
189
+ def get_list_calendar_groups(self, user_id):
190
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups"
191
+ return self._query_get_list(url)
192
+
193
+ def get_list_calendars(self, user_id, id_calendar_group=None):
194
+ if id_calendar_group:
195
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}/calendars"
196
+ else:
197
+ url = f"{self.graph_url}/users/{user_id}/calendars"
198
+ # Configure query parameters to modify the results
199
+ # query_params = {
200
+ # "$select": "subject, organizer, createdDateTime, lastModifiedDateTime, start, end, reminderMinutesBeforeStart, isReminderOn, recurrence, hasAttachments, importance, location, webLink, attendees",
201
+ # "$orderby": "createdDateTime DESC",
202
+ # }
203
+ return self._query_get_list(url)
204
+
205
+ def get_list_calendar_events(self, user_id, id_calendar=None, id_calendar_group=None):
206
+ if id_calendar:
207
+ # A user's calendar in the default calendarGroup.
208
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}/events"
209
+ if id_calendar_group:
210
+ # A user's calendar in a specific calendarGroup.
211
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}/calendars/{id_calendar}/events"
212
+ else:
213
+ # A user's or group's default calendar.
214
+ url = f"{self.graph_url}/users/{user_id}/events"
215
+ return self._query_get_list(url)
216
+
217
+ # GET CALENDAR
218
+ def get_calendar_group(self, user_id, id_calendar_group=None):
219
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}"
220
+ return self._query_get(url)
221
+
222
+ def get_calendar(self, user_id, id_calendar, id_calendar_group=None):
223
+ if id_calendar_group:
224
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}/calendars/{id_calendar}"
225
+ else:
226
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}"
227
+ return self._query_get(url)
228
+
229
+ def get_calendar_event(self, user_id, id_event, id_calendar=None, id_calendar_group=None):
230
+ if id_calendar:
231
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}/events/{id_event}"
232
+ if id_calendar_group:
233
+ url = f"{self.graph_url}/users/{user_id}/calendargroups/{id_calendar_group}/calendars/{id_calendar}/events/{id_event}"
234
+ else:
235
+ url = f"{self.graph_url}/users/{user_id}/events/{id_event}"
236
+
237
+ return self._query_get(url)
238
+
239
+ # FORWARD CALENDAR EVENT
240
+ def forward_calendar_event(self, user_id, id_event, data, id_calendar_group=None, id_calendar=None):
241
+ if id_calendar:
242
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}/events/{id_event}/forward"
243
+ if id_calendar_group:
244
+ url = f"{self.graph_url}/users/{user_id}/calendargroups/{id_calendar_group}/calendars/{id_calendar}/events/{id_event}/forward"
245
+ else:
246
+ url = f"{self.graph_url}/users/{user_id}/events/{id_event}/forward"
247
+ return self._query_create(url, data)
248
+
249
+ def get_calendar_event_by_resource(self, resource):
250
+ url = f"{self.graph_url}/{resource}"
251
+ return self._query_get(url)
252
+
253
+ def get_calendar_from_navigation_link(self, url):
254
+ return self._query_get(url)
255
+
256
+ # UPDATE CALENDAR
257
+ def update_calendar_group(self, user_id, id_calendar_group, data):
258
+ url = f"{self.graph_url}/users/{user_id}/calendargroups/{id_calendar_group}"
259
+ return self._query_update(url, data)
260
+
261
+ def update_calendar(self, user_id, id_calendar, data, id_calendar_group=None):
262
+ if id_calendar_group:
263
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}/calendars/{id_calendar}"
264
+ else:
265
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}"
266
+ return self._query_update(url, data)
267
+
268
+ def update_calendar_event(self, user_id, id_event, data, id_calendar=None, id_calendar_group=None):
269
+ if id_calendar:
270
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}/events/{id_event}"
271
+ if id_calendar_group:
272
+ url = f"{self.graph_url}/users/{user_id}/calendargroups/{id_calendar_group}/calendars/{id_calendar}/events/{id_event}"
273
+ else:
274
+ url = f"{self.graph_url}/users/{user_id}/events/{id_event}"
275
+ return self._query_update(url, data)
276
+
277
+ # DELETE CALENDAR
278
+ def delete_calendar_group(self, user_id, id_calendar_group):
279
+ url = f"{self.graph_url}/users/{user_id}/calendargroups/{id_calendar_group}"
280
+ return self._query_delete(url)
281
+
282
+ def delete_calendar(self, user_id, id_calendar, id_calendar_group=None):
283
+ if id_calendar_group:
284
+ url = f"{self.graph_url}/users/{user_id}/calendarGroups/{id_calendar_group}/calendars/{id_calendar}"
285
+ else:
286
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}"
287
+ return self._query_delete(url)
288
+
289
+ def delete_calendar_event(self, user_id, id_event, id_calendar=None, id_calendar_group=None):
290
+ if id_calendar:
291
+ url = f"{self.graph_url}/users/{user_id}/calendars/{id_calendar}/events/{id_event}"
292
+ if id_calendar_group:
293
+ url = f"{self.graph_url}/users/{user_id}/calendargroups/{id_calendar_group}/calendars/{id_calendar}/events/{id_event}"
294
+ else:
295
+ url = f"{self.graph_url}/users/{user_id}/events/{id_event}"
296
+ return self._query_delete(url)
297
+
298
+ def delete_calendar_event_by_resource(self, resource):
299
+ url = f"{self.graph_url}/{resource}"
300
+ return self._query_delete(url)
301
+
302
+ def get_or_create_workbench_calendar(self, user_id, calendar_name):
303
+ workbench_calendar = None
304
+ datum = self.get_list_calendars(user_id=user_id)
305
+ if calendar_name:
306
+ calendars = {calendar.get("name"): calendar.get("id") for calendar in datum}
307
+ if calendar_name in calendars.keys():
308
+ workbench_calendar = self.get_calendar(user_id, calendars[calendar_name])
309
+ else:
310
+ data = {"name": calendar_name}
311
+ workbench_calendar = self.create_calendar(user_id, data)
312
+ else:
313
+ for calendar in datum:
314
+ if calendar.get("is_default_calendar") is True:
315
+ workbench_calendar = self.get_calendar(user_id, calendar.get("id"))
316
+ return workbench_calendar
317
+
318
+ def _query_get_list(self, url):
319
+ response = self._query(url)
320
+ datum = None
321
+ if response:
322
+ if response.json():
323
+ if response.json().get("value"):
324
+ datum = parse(pd.json_normalize(response.json().get("value")))
325
+ else:
326
+ raise ValueError(response, response.json())
327
+ return datum
328
+
329
+ def _query_get(self, url, raise_error=False):
330
+ response = self._query(url)
331
+ data = None
332
+ if response:
333
+ if response.json():
334
+ data = parse(pd.json_normalize(response.json()))
335
+ if data:
336
+ data = data[0]
337
+ elif raise_error:
338
+ raise ValueError(response, response.json())
339
+ return data
340
+
341
+ def _query_create(self, url, data):
342
+ response = self._query(url, method="POST", data=json.dumps(data))
343
+ data = None
344
+ if response.status_code < 400:
345
+ try:
346
+ if response.json():
347
+ data = parse(pd.json_normalize(response.json()))
348
+ if data:
349
+ data = data[0]
350
+ except requests.exceptions.InvalidJSONError:
351
+ pass # print(response, response.__dict__)
352
+
353
+ else:
354
+ raise ValueError(response, response.__dict__)
355
+ return data
356
+
357
+ def _query_update(self, url, data):
358
+ response = self._query(url, method="PATCH", data=json.dumps(data))
359
+ data = None
360
+ if response:
361
+ if response.json():
362
+ data = parse(pd.json_normalize(response.json()))
363
+ else:
364
+ if response.status_code not in [status.HTTP_503_SERVICE_UNAVAILABLE, status.HTTP_409_CONFLICT]:
365
+ raise ValueError(response, response.json())
366
+ else:
367
+ import time
368
+
369
+ time.sleep(60)
370
+ if response := self._query(url, method="PATCH", data=json.dumps(data)):
371
+ if response.json():
372
+ data = parse(pd.json_normalize(response.json()))
373
+ return data
374
+
375
+ def _query_delete(self, url):
376
+ response = self._query(url, method="DELETE")
377
+ if response:
378
+ return response
379
+ else:
380
+ raise ValueError(response)
381
+
382
+ def _query(self, url, method="GET", data=None, params=None, access_token=True, is_json=True):
383
+ headers = {"content-type": "application/json" if is_json else "application/x-www-form-urlencoded"}
384
+ if access_token:
385
+ global_preferences = global_preferences_registry.manager()
386
+ headers["Authorization"] = f'Bearer {global_preferences["wbintegrator_office365__access_token"]}'
387
+ if method == "POST":
388
+ response = requests.post(url, data=data, headers=headers)
389
+ elif method == "DELETE":
390
+ response = requests.delete(url, headers=headers)
391
+ elif method == "PATCH":
392
+ response = requests.patch(url, data=data, headers=headers)
393
+ else:
394
+ response = requests.get(url, headers=headers, params=params)
395
+ if response.status_code == status.HTTP_401_UNAUTHORIZED:
396
+ new_token = self._get_access_token()
397
+ if new_token != global_preferences["wbintegrator_office365__access_token"]:
398
+ global_preferences["wbintegrator_office365__access_token"] = new_token
399
+ return self._query(
400
+ url, method=method, data=data, params=params, access_token=access_token, is_json=is_json
401
+ )
402
+ else:
403
+ return response
@@ -0,0 +1,43 @@
1
+ from collections import defaultdict
2
+
3
+ from django.db.models.signals import (
4
+ post_delete,
5
+ post_init,
6
+ post_migrate,
7
+ post_save,
8
+ pre_delete,
9
+ pre_init,
10
+ pre_migrate,
11
+ pre_save,
12
+ )
13
+
14
+
15
+ class DisableSignals(object):
16
+ def __init__(self, disabled_signals=None):
17
+ self.stashed_signals = defaultdict(list)
18
+ self.disabled_signals = disabled_signals or [
19
+ pre_init,
20
+ post_init,
21
+ pre_save,
22
+ post_save,
23
+ pre_delete,
24
+ post_delete,
25
+ pre_migrate,
26
+ post_migrate,
27
+ ]
28
+
29
+ def __enter__(self):
30
+ for signal in self.disabled_signals:
31
+ self.disconnect(signal)
32
+
33
+ def __exit__(self, exc_type, exc_val, exc_tb):
34
+ for signal in list(self.stashed_signals):
35
+ self.reconnect(signal)
36
+
37
+ def disconnect(self, signal):
38
+ self.stashed_signals[signal] = signal.receivers
39
+ signal.receivers = []
40
+
41
+ def reconnect(self, signal):
42
+ signal.receivers = self.stashed_signals.get(signal, [])
43
+ del self.stashed_signals[signal]
@@ -0,0 +1,135 @@
1
+ import pandas as pd
2
+
3
+
4
+ def parse(_dict, scalar_value=False):
5
+ data = None
6
+ if scalar_value:
7
+ df = pd.DataFrame(_dict, index=[0])
8
+ else:
9
+ df = pd.DataFrame(_dict)
10
+ df = df.rename(columns=_map)
11
+ data = df.to_dict("records")
12
+ return data
13
+
14
+
15
+ _map = {
16
+ "lastModifiedDateTime": "last_modified",
17
+ "startDateTime": "start",
18
+ "endDateTime": "end",
19
+ "organizer": "organizer",
20
+ "displayName": "display_name",
21
+ "businessPhones": "business_phones",
22
+ "mobilePhone": "mobile_phone",
23
+ "surname": "surname",
24
+ "givenName": "given_name",
25
+ "mailNickname": "mail_nickname",
26
+ "userPrincipalName": "user_principal_name",
27
+ "notificationUrl": "notification_url",
28
+ "changeType": "change_type",
29
+ "expirationDateTime": "expiration",
30
+ "resourceData": "resource_data",
31
+ "subscriptionExpirationDateTime": "subscription_expiration",
32
+ "clientState": "client_state",
33
+ "tenantId": "tenant_id",
34
+ "subscriptionId": "subscription_id",
35
+ "organizer.user": "organizer.user",
36
+ "organizer.user.id": "organizer.user.id",
37
+ "organizer.user.displayName": "organizer.user.display_name",
38
+ "organizer.phone": "organizer.phone",
39
+ "organizer.phone.id": "organizer.phone.id",
40
+ "organizer.phone.displayName": "organizer.phone.display_name",
41
+ "organizer.guest": "organizer.guest",
42
+ "organizer.guest.id": "organizer.guest.id",
43
+ "organizer.guest.displayName": "organizer.guest.display_name",
44
+ "organizer.spoolUser": "organizer.splool_user",
45
+ "organizer.acsUser": "organizer.acs_user",
46
+ "organizer.encrypted": "organizer.encrypted",
47
+ "organizer.onPremises": "organizer.on_premises",
48
+ "organizer.acsApplicationInstance": "organizer.acs_appllication_instance",
49
+ "organizer.spoolApplicationInstance": "organizer.spool_application_instance",
50
+ "organizer.applicationInstance": "organizer.application_instance",
51
+ "organizer.application": "organizer.application",
52
+ "organizer.device": "organizer.device",
53
+ "joinWebUrl": "join_web_url",
54
+ "preferredLanguage": "preferred_language",
55
+ "officeLocation": "office_location",
56
+ "jobTitle": "job_title",
57
+ "createdDateTime": "created",
58
+ "isReminderOn": "is_reminder_on",
59
+ "reminderMinutesBeforeStart": "reminder_minutes_before_start",
60
+ "hasAttachments": "has_attachments",
61
+ "webLink": "web_link",
62
+ "start.dateTime": "start",
63
+ "start.timeZone": "start.time_zone",
64
+ "end.dateTime": "end",
65
+ "end.timeZone": "end.time_zone",
66
+ "location.displayName": "location.display_name",
67
+ "location.locationUri": "location.location_uri",
68
+ "location.locationType": "location.location_type",
69
+ "location.uniqueId": "location.unique_id",
70
+ "location.uniqueIdType": "location.unique_id_type",
71
+ "location.address.type": "location.address.type",
72
+ "location.address.street": "location.address.street",
73
+ "location.address.city": "location.address.city",
74
+ "location.address.postalCode": "location.address.postal_code",
75
+ "location.address.countryOrRegion": "location.address.country_or_region",
76
+ "recurrence.pattern.daysOfWeek": "recurrence.pattern.days_of_week",
77
+ "recurrence.pattern.dayOfMonth": "recurrence.pattern.day_of_month",
78
+ "recurrence.pattern.firstDayOfWeek": "recurrence.pattern.first_day_of_week",
79
+ "recurrence.range.recurrenceTimeZone": "recurrence.range.recurrence_time_zone",
80
+ "recurrence.range.numberOfOccurrences": "recurrence.range.number_of_occurrences",
81
+ "recurrence.range.startDate": "recurrence.range.start_date",
82
+ "recurrence.range.endDate": "recurrence.range.end_date",
83
+ "organizer.emailAddress.name": "organizer.email_address.name",
84
+ "organizer.emailAddress.address": "organizer.email_address.address",
85
+ "responseStatus.response": "response_status.response",
86
+ "responseStatus.time": "response_status.time",
87
+ "attendees": "participants",
88
+ "@odata.context": "odata_context",
89
+ "@odata.etag": "odata_etag",
90
+ "applicationId": "application_id",
91
+ "includeResourceData": "include_resource_data",
92
+ "latestSupportedTlsVersion": "latest_supported_tls_version",
93
+ "encryptionCertificate": "encryption_certificate",
94
+ "encryptionCertificateId": "encryption_certificate_id",
95
+ "notificationQueryOptions": "notification_query_options",
96
+ "notificationContentType": "notification_content_type",
97
+ "lifecycleNotificationUrl": "lifecycle_notification_url",
98
+ "creatorId": "creator_id",
99
+ "isDefaultCalendar": "is_default_calendar",
100
+ "hexColor": "hex_color",
101
+ "changeKey": "change_key",
102
+ "canShare": "can_share",
103
+ "canViewPrivateItems": "can_view_private_items",
104
+ "isShared": "is_shared",
105
+ "isSharedWithMe": "is_shared_with_me",
106
+ "canEdit": "can_edit",
107
+ "allowedOnlineMeetingProviders": "allowed_online_meeting_providers",
108
+ "defaultOnlineMeetingProvider": "default_online_meeting_provider",
109
+ "isTallyingResponses": "is_tallying_responses",
110
+ "isRemovable": "is_removable",
111
+ "teamsForBusiness": "teams_for_business",
112
+ "transactionId": "transaction_id",
113
+ "originalStartTimeZone": "original_start_time_zone",
114
+ "originalEndTimeZone": "original_end_time_zone",
115
+ "bodyPreview": "body_preview",
116
+ "isAllDay": "is_all_day",
117
+ "isCancelled": "is_cancelled",
118
+ "isOrganizer": "is_organizer",
119
+ "responseRequested": "response_requested",
120
+ "seriesMasterId": "series_master_id",
121
+ "showAs": "show_as",
122
+ "onlineMeeting": "online_meeting",
123
+ "onlineMeetingUrl": "online_meeting_url",
124
+ "isOnlineMeeting": "is_online_meeting",
125
+ "onlineMeetingProvider": "online_meeting_provider",
126
+ "allowNewTimeProposals": "allow_new_time_proposals",
127
+ "occurrenceId": "occurrence_id",
128
+ "isDraft": "is_draft",
129
+ "hideAttendees": "hide_attendees",
130
+ "responseStatus": "response_status",
131
+ "appId": "app_id",
132
+ "passwordCredentials": "password_credentials",
133
+ "iCalUId": "uid",
134
+ "body.contentType": "body.content_type",
135
+ }
@@ -0,0 +1 @@
1
+ from .calls import NumberOfCallKPI