wbintegrator_office365 1.43.1__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.
Files changed (42) 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/templates/admin/tenant_change_list.html +12 -0
  26. wbintegrator_office365/tests/__init__.py +0 -0
  27. wbintegrator_office365/tests/conftest.py +28 -0
  28. wbintegrator_office365/tests/test_admin.py +86 -0
  29. wbintegrator_office365/tests/test_models.py +65 -0
  30. wbintegrator_office365/tests/test_tasks.py +318 -0
  31. wbintegrator_office365/tests/test_views.py +128 -0
  32. wbintegrator_office365/tests/tests.py +12 -0
  33. wbintegrator_office365/urls.py +46 -0
  34. wbintegrator_office365/viewsets/__init__.py +31 -0
  35. wbintegrator_office365/viewsets/display.py +306 -0
  36. wbintegrator_office365/viewsets/endpoints.py +52 -0
  37. wbintegrator_office365/viewsets/menu.py +65 -0
  38. wbintegrator_office365/viewsets/titles.py +49 -0
  39. wbintegrator_office365/viewsets/viewsets.py +745 -0
  40. wbintegrator_office365-1.43.1.dist-info/METADATA +10 -0
  41. wbintegrator_office365-1.43.1.dist-info/RECORD +42 -0
  42. wbintegrator_office365-1.43.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