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.
- wbintegrator_office365/__init__.py +1 -0
- wbintegrator_office365/admin.py +209 -0
- wbintegrator_office365/apps.py +5 -0
- wbintegrator_office365/configurations/__init__.py +0 -0
- wbintegrator_office365/configurations/configurations/__init__.py +23 -0
- wbintegrator_office365/dynamic_preferences_registry.py +15 -0
- wbintegrator_office365/factories.py +102 -0
- wbintegrator_office365/filters.py +237 -0
- wbintegrator_office365/importer/__init__.py +3 -0
- wbintegrator_office365/importer/api.py +403 -0
- wbintegrator_office365/importer/disable_signals.py +43 -0
- wbintegrator_office365/importer/parser.py +135 -0
- wbintegrator_office365/kpi_handlers/__init__.py +1 -0
- wbintegrator_office365/kpi_handlers/calls.py +114 -0
- wbintegrator_office365/migrations/0001_initial_squashed_squashed_0003_alter_calendar_owner_alter_calendarevent_organizer_and_more.py +677 -0
- wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendarevent_activity_and_more.py +85 -0
- wbintegrator_office365/migrations/0003_alter_event_options.py +20 -0
- wbintegrator_office365/migrations/__init__.py +0 -0
- wbintegrator_office365/models/__init__.py +3 -0
- wbintegrator_office365/models/event.py +623 -0
- wbintegrator_office365/models/subscription.py +144 -0
- wbintegrator_office365/models/tenant.py +62 -0
- wbintegrator_office365/serializers.py +266 -0
- wbintegrator_office365/tasks.py +108 -0
- wbintegrator_office365/templates/admin/tenant_change_list.html +12 -0
- wbintegrator_office365/tests/__init__.py +0 -0
- wbintegrator_office365/tests/conftest.py +28 -0
- wbintegrator_office365/tests/test_admin.py +86 -0
- wbintegrator_office365/tests/test_models.py +65 -0
- wbintegrator_office365/tests/test_tasks.py +318 -0
- wbintegrator_office365/tests/test_views.py +128 -0
- wbintegrator_office365/tests/tests.py +12 -0
- wbintegrator_office365/urls.py +46 -0
- wbintegrator_office365/viewsets/__init__.py +31 -0
- wbintegrator_office365/viewsets/display.py +306 -0
- wbintegrator_office365/viewsets/endpoints.py +52 -0
- wbintegrator_office365/viewsets/menu.py +65 -0
- wbintegrator_office365/viewsets/titles.py +49 -0
- wbintegrator_office365/viewsets/viewsets.py +745 -0
- wbintegrator_office365-1.43.1.dist-info/METADATA +10 -0
- wbintegrator_office365-1.43.1.dist-info/RECORD +42 -0
- wbintegrator_office365-1.43.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,623 @@
|
|
1
|
+
import phonenumbers
|
2
|
+
from celery import shared_task
|
3
|
+
from django.contrib.postgres.fields import ArrayField
|
4
|
+
from django.core.exceptions import MultipleObjectsReturned
|
5
|
+
from django.db import models, transaction
|
6
|
+
from django.db.models import Case, CharField, F, Q, When
|
7
|
+
from django.db.models.signals import m2m_changed
|
8
|
+
from django.dispatch import receiver
|
9
|
+
from wbcore.contrib.directory.models import EmailContact, Person, TelephoneContact
|
10
|
+
from wbcore.models import WBModel
|
11
|
+
from wbintegrator_office365.importer import MicrosoftGraphAPI
|
12
|
+
|
13
|
+
from .tenant import TenantUser
|
14
|
+
|
15
|
+
|
16
|
+
class Event(WBModel):
|
17
|
+
class Type(models.TextChoices):
|
18
|
+
CALLRECORD = "CALLRECORD", "Call Record"
|
19
|
+
CALENDAR = "CALENDAR", "Calendar"
|
20
|
+
|
21
|
+
class Meta:
|
22
|
+
verbose_name = "Event"
|
23
|
+
verbose_name_plural = "Events"
|
24
|
+
permissions = [("administrate_event", "Can Administrate call and calendar events")]
|
25
|
+
|
26
|
+
auto_inc_id = models.IntegerField(default=0)
|
27
|
+
nb_received = models.IntegerField(default=0)
|
28
|
+
|
29
|
+
uuid_event = models.CharField(
|
30
|
+
max_length=1000,
|
31
|
+
verbose_name="Event UID",
|
32
|
+
help_text="UUID obtained from Microsoft",
|
33
|
+
null=True,
|
34
|
+
blank=True,
|
35
|
+
unique=True,
|
36
|
+
)
|
37
|
+
id_event = models.CharField(
|
38
|
+
max_length=1000,
|
39
|
+
verbose_name="Event ID",
|
40
|
+
help_text="Event ID obtained from Microsoft",
|
41
|
+
null=True,
|
42
|
+
blank=True,
|
43
|
+
unique=True,
|
44
|
+
)
|
45
|
+
type = models.CharField(
|
46
|
+
max_length=32, choices=Type.choices, null=True, blank=True, verbose_name="Type", default=""
|
47
|
+
)
|
48
|
+
subscription_id = models.CharField(
|
49
|
+
max_length=255, null=True, blank=True, verbose_name="Subscription ID", default=""
|
50
|
+
)
|
51
|
+
change_type = models.CharField(max_length=255, null=True, blank=True, verbose_name="Change Type", default="")
|
52
|
+
resource = models.CharField(max_length=1000, null=True, blank=True, verbose_name="Resource", default="")
|
53
|
+
created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
|
54
|
+
changed = models.DateTimeField(auto_now=True, verbose_name="Changed")
|
55
|
+
is_handled = models.BooleanField(default=True)
|
56
|
+
data = models.JSONField(default=dict, null=True, blank=True)
|
57
|
+
|
58
|
+
tenant_user = models.ForeignKey(
|
59
|
+
"wbintegrator_office365.TenantUser",
|
60
|
+
related_name="tenant_events",
|
61
|
+
null=True,
|
62
|
+
blank=True,
|
63
|
+
on_delete=models.deletion.SET_NULL,
|
64
|
+
verbose_name="Tenant User",
|
65
|
+
)
|
66
|
+
|
67
|
+
def save(self, *args, **kwargs):
|
68
|
+
events = Event.objects.filter(id=self.id)
|
69
|
+
object_list = Event.objects.order_by("auto_inc_id")
|
70
|
+
if len(events) == 0:
|
71
|
+
self.nb_received = 1
|
72
|
+
if len(object_list) == 0: # if there are no objects
|
73
|
+
self.auto_inc_id = 1
|
74
|
+
else:
|
75
|
+
self.auto_inc_id = object_list.last().auto_inc_id + 1
|
76
|
+
else:
|
77
|
+
self.nb_received = events.first().nb_received + 1
|
78
|
+
self.auto_inc_id = events.first().auto_inc_id
|
79
|
+
super().save(*args, **kwargs)
|
80
|
+
|
81
|
+
def __str__(self):
|
82
|
+
return f"{self.id}"
|
83
|
+
|
84
|
+
@classmethod
|
85
|
+
def is_administrator(cls, user):
|
86
|
+
user_groups = user.groups
|
87
|
+
user_permission = user.user_permissions
|
88
|
+
return (
|
89
|
+
user_groups.filter(permissions__codename="administrate_event").exists()
|
90
|
+
or user_permission.filter(codename="administrate_event").exists()
|
91
|
+
or user.is_superuser
|
92
|
+
)
|
93
|
+
|
94
|
+
@classmethod
|
95
|
+
def get_endpoint_basename(self):
|
96
|
+
return "wbintegrator_office365:event"
|
97
|
+
|
98
|
+
@classmethod
|
99
|
+
def get_representation_endpoint(cls):
|
100
|
+
return "wbintegrator_office365:eventrepresentation-list"
|
101
|
+
|
102
|
+
@classmethod
|
103
|
+
def get_representation_value_key(cls):
|
104
|
+
return "id"
|
105
|
+
|
106
|
+
@classmethod
|
107
|
+
def get_representation_label_key(cls):
|
108
|
+
return "{{id_str}} - {{uuid_event}}"
|
109
|
+
|
110
|
+
def fetch_call(self):
|
111
|
+
if data := MicrosoftGraphAPI().call(self.uuid_event):
|
112
|
+
# call user organiser
|
113
|
+
is_user = False
|
114
|
+
is_guest = False
|
115
|
+
is_phone = False
|
116
|
+
organizer_id = None
|
117
|
+
organizer_display_name = None
|
118
|
+
organizer_organization_id = None
|
119
|
+
if data.get("organizer.user.id") or (
|
120
|
+
data.get("organizer.user.id") == "" and data.get("organizer.user.tenantId")
|
121
|
+
):
|
122
|
+
is_user = True
|
123
|
+
organizer_id = data.get("organizer.user.id")
|
124
|
+
organizer_display_name = data.get("organizer.user.display_name")
|
125
|
+
organizer_organization_id = data.get("organizer.user.tenantId")
|
126
|
+
elif data.get("organizer.guest.id") or (
|
127
|
+
data.get("organizer.guest.id") == "" and data.get("organizer.guest.tenantId")
|
128
|
+
):
|
129
|
+
is_guest = True
|
130
|
+
organizer_id = data.get("organizer.guest.id")
|
131
|
+
organizer_display_name = data.get("organizer.guest.display_name")
|
132
|
+
organizer_organization_id = data.get("organizer.guest.tenantId")
|
133
|
+
elif data.get("organizer.phone.id") or (
|
134
|
+
data.get("organizer.phone.id") == "" and data.get("organizer.phone.tenantId")
|
135
|
+
):
|
136
|
+
is_phone = True
|
137
|
+
organizer_id = data.get("organizer.phone.id")
|
138
|
+
organizer_display_name = data.get("organizer.phone.display_name")
|
139
|
+
organizer_organization_id = data.get("organizer.phone.tenantId")
|
140
|
+
|
141
|
+
call_user_organizer = create_or_update_call_user(
|
142
|
+
tenant_id=organizer_id,
|
143
|
+
display_name=organizer_display_name,
|
144
|
+
tenant_organization_id=organizer_organization_id,
|
145
|
+
is_user=is_user,
|
146
|
+
is_guest=is_guest,
|
147
|
+
is_phone=is_phone,
|
148
|
+
acs_user=data.get("organizer.acs_user"),
|
149
|
+
splool_user=data.get("organizer.splool_user"),
|
150
|
+
encrypted=data.get("organizer.encrypted"),
|
151
|
+
on_premises=data.get("organizer.on_premises"),
|
152
|
+
acs_application_instance=data.get("organizer.acs_application_instance"),
|
153
|
+
spool_application_instance=data.get("organizer.spool_application_instance"),
|
154
|
+
application_instance=data.get("organizer.application_instance"),
|
155
|
+
application=data.get("organizer.application"),
|
156
|
+
device=data.get("organizer.device"),
|
157
|
+
)
|
158
|
+
|
159
|
+
# CALL EVENT
|
160
|
+
try:
|
161
|
+
call, created = CallEvent.objects.update_or_create(
|
162
|
+
event=self,
|
163
|
+
defaults={
|
164
|
+
"version": data.get("version"),
|
165
|
+
"type": data.get("type"),
|
166
|
+
"modalities": data.get("modalities"),
|
167
|
+
"last_modified": data.get("last_modified"),
|
168
|
+
"start": data.get("start"),
|
169
|
+
"end": data.get("end"),
|
170
|
+
"join_web_url": data.get("join_web_url"),
|
171
|
+
"organizer": call_user_organizer,
|
172
|
+
"data": data,
|
173
|
+
},
|
174
|
+
)
|
175
|
+
except MultipleObjectsReturned:
|
176
|
+
call = CallEvent.objects.filter(event=self).order_by("-pk")[0]
|
177
|
+
CallEvent.objects.filter(event=self).exclude(id=call.id).delete()
|
178
|
+
CallEvent.objects.filter(event=self).update(
|
179
|
+
version=data.get("version"),
|
180
|
+
type=data.get("type"),
|
181
|
+
modalities=data.get("modalities"),
|
182
|
+
last_modified=data.get("last_modified"),
|
183
|
+
start=data.get("start"),
|
184
|
+
end=data.get("end"),
|
185
|
+
join_web_url=data.get("join_web_url"),
|
186
|
+
organizer=call_user_organizer,
|
187
|
+
data=data,
|
188
|
+
)
|
189
|
+
|
190
|
+
# call user for all participants
|
191
|
+
for participant in data.get("participants"):
|
192
|
+
is_user = False
|
193
|
+
is_guest = False
|
194
|
+
is_phone = False
|
195
|
+
participant_id = None
|
196
|
+
participant_display_name = None
|
197
|
+
participant_organization_id = None
|
198
|
+
if participant.get("user"):
|
199
|
+
is_user = True
|
200
|
+
participant_id = participant.get("user").get("id")
|
201
|
+
participant_display_name = participant.get("user").get("displayName")
|
202
|
+
participant_organization_id = participant.get("user").get("tenantId")
|
203
|
+
elif participant.get("guest"):
|
204
|
+
is_guest = True
|
205
|
+
participant_id = participant.get("guest").get("id")
|
206
|
+
participant_display_name = participant.get("guest").get("displayName")
|
207
|
+
participant_organization_id = participant.get("guest").get("tenantId")
|
208
|
+
elif participant.get("phone"):
|
209
|
+
is_phone = True
|
210
|
+
participant_id = participant.get("phone").get("id")
|
211
|
+
participant_display_name = participant.get("phone").get("displayName")
|
212
|
+
participant_organization_id = participant.get("phone").get("tenantId")
|
213
|
+
|
214
|
+
call_user = create_or_update_call_user(
|
215
|
+
tenant_id=participant_id,
|
216
|
+
display_name=participant_display_name,
|
217
|
+
tenant_organization_id=participant_organization_id,
|
218
|
+
is_user=is_user,
|
219
|
+
is_guest=is_guest,
|
220
|
+
is_phone=is_phone,
|
221
|
+
acs_user=participant.get("acsUser"),
|
222
|
+
splool_user=participant.get("spoolUser"),
|
223
|
+
encrypted=participant.get("encrypted"),
|
224
|
+
on_premises=participant.get("on_premises"),
|
225
|
+
acs_application_instance=participant.get("acsApplicationInstance"),
|
226
|
+
spool_application_instance=participant.get("spoolApplicationInstance"),
|
227
|
+
application_instance=participant.get("applicationInstance"),
|
228
|
+
application=participant.get("application"),
|
229
|
+
device=participant.get("device"),
|
230
|
+
)
|
231
|
+
call.participants.add(call_user)
|
232
|
+
|
233
|
+
|
234
|
+
class EventLog(WBModel):
|
235
|
+
class Meta:
|
236
|
+
verbose_name = "Event Log"
|
237
|
+
verbose_name_plural = "Event Logs"
|
238
|
+
|
239
|
+
last_event = models.ForeignKey(
|
240
|
+
"Event",
|
241
|
+
related_name="event_logs",
|
242
|
+
verbose_name="Last Event",
|
243
|
+
on_delete=models.deletion.CASCADE,
|
244
|
+
)
|
245
|
+
id_event = models.CharField(max_length=1000, null=True, blank=True, verbose_name="Event ID")
|
246
|
+
order_received = models.IntegerField(default=0)
|
247
|
+
change_type = models.CharField(max_length=255, null=True, blank=True, verbose_name="Change Type", default="")
|
248
|
+
created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
|
249
|
+
changed = models.DateTimeField(auto_now=True, verbose_name="Changed")
|
250
|
+
resource = models.CharField(max_length=1000, null=True, blank=True, verbose_name="Resource", default="")
|
251
|
+
is_handled = models.BooleanField(default=True)
|
252
|
+
data = models.JSONField(default=dict, null=True, blank=True)
|
253
|
+
|
254
|
+
@classmethod
|
255
|
+
def get_endpoint_basename(self):
|
256
|
+
return "wbintegrator_office365:eventlog"
|
257
|
+
|
258
|
+
@classmethod
|
259
|
+
def get_representation_endpoint(cls):
|
260
|
+
return "wbintegrator_office365:eventlogrepresentation-list"
|
261
|
+
|
262
|
+
@classmethod
|
263
|
+
def get_representation_value_key(cls):
|
264
|
+
return "id"
|
265
|
+
|
266
|
+
@classmethod
|
267
|
+
def get_representation_label_key(cls):
|
268
|
+
return "{{last_event}} ({{order_received}})"
|
269
|
+
|
270
|
+
|
271
|
+
class CallUser(WBModel):
|
272
|
+
acs_user = models.CharField(max_length=256, default="", null=True, blank=True)
|
273
|
+
splool_user = models.CharField(max_length=256, default="", null=True, blank=True)
|
274
|
+
encrypted = models.CharField(max_length=256, default="", null=True, blank=True)
|
275
|
+
on_premises = models.CharField(max_length=256, default="", null=True, blank=True)
|
276
|
+
acs_application_instance = models.CharField(max_length=256, default="", null=True, blank=True)
|
277
|
+
spool_application_instance = models.CharField(max_length=256, default="", null=True, blank=True)
|
278
|
+
application_instance = models.CharField(max_length=256, default="", null=True, blank=True)
|
279
|
+
application = models.CharField(max_length=256, default="", null=True, blank=True)
|
280
|
+
device = models.CharField(max_length=256, default="", null=True, blank=True)
|
281
|
+
is_guest = models.BooleanField(default=False)
|
282
|
+
is_phone = models.BooleanField(default=False)
|
283
|
+
tenant_user = models.ForeignKey(
|
284
|
+
"wbintegrator_office365.TenantUser",
|
285
|
+
related_name="call_users",
|
286
|
+
null=True,
|
287
|
+
blank=True,
|
288
|
+
on_delete=models.deletion.SET_NULL,
|
289
|
+
)
|
290
|
+
|
291
|
+
def __str__(self):
|
292
|
+
if self.tenant_user:
|
293
|
+
if self.tenant_user.profile:
|
294
|
+
return f"{self.tenant_user.profile.computed_str}"
|
295
|
+
elif self.tenant_user.mail or self.tenant_user.display_name:
|
296
|
+
mail = self.tenant_user.mail if self.tenant_user.mail else self.tenant_user.id
|
297
|
+
return f"{self.tenant_user.display_name}({mail})"
|
298
|
+
else:
|
299
|
+
return f"{self.tenant_user.tenant_id}"
|
300
|
+
return f"{self.id}"
|
301
|
+
|
302
|
+
@classmethod
|
303
|
+
def annotated_queryset(cls, qs):
|
304
|
+
return qs.annotate(
|
305
|
+
tenant_user_str=Case(
|
306
|
+
When(tenant_user__isnull=True, then=F("id_str")),
|
307
|
+
default=F("tenant_user__profile_str"),
|
308
|
+
output_field=CharField(),
|
309
|
+
),
|
310
|
+
)
|
311
|
+
|
312
|
+
@classmethod
|
313
|
+
def get_endpoint_basename(cls):
|
314
|
+
return "wbintegrator_office365:calluser"
|
315
|
+
|
316
|
+
@classmethod
|
317
|
+
def get_representation_endpoint(cls):
|
318
|
+
return "wbintegrator_office365:calluserrepresentation-list"
|
319
|
+
|
320
|
+
@classmethod
|
321
|
+
def get_representation_value_key(cls):
|
322
|
+
return "id"
|
323
|
+
|
324
|
+
@classmethod
|
325
|
+
def get_representation_label_key(cls):
|
326
|
+
return "{{tenant_user_str}}"
|
327
|
+
|
328
|
+
|
329
|
+
class CallEvent(WBModel):
|
330
|
+
class Meta:
|
331
|
+
verbose_name = "Call Event"
|
332
|
+
verbose_name_plural = "Calls Events"
|
333
|
+
|
334
|
+
notification_types = [
|
335
|
+
(
|
336
|
+
"wbintegrator_office365.callevent.notify",
|
337
|
+
"Call Event Notification",
|
338
|
+
"Sends a notification when something happens with a Call Event triggered from Office 365",
|
339
|
+
True,
|
340
|
+
True,
|
341
|
+
False,
|
342
|
+
),
|
343
|
+
]
|
344
|
+
|
345
|
+
event = models.OneToOneField(to="wbintegrator_office365.Event", related_name="calls", on_delete=models.CASCADE)
|
346
|
+
version = models.CharField(max_length=256, null=True, blank=True, verbose_name="Version", default="")
|
347
|
+
type = models.CharField(max_length=256, null=True, blank=True, verbose_name="Type", default="")
|
348
|
+
modalities = ArrayField(
|
349
|
+
models.CharField(max_length=256), blank=True, null=True, verbose_name="Modalities", default=list
|
350
|
+
)
|
351
|
+
last_modified = models.DateTimeField(null=True, blank=True, verbose_name="Last Modified")
|
352
|
+
start = models.DateTimeField(null=True, blank=True, verbose_name="Start")
|
353
|
+
end = models.DateTimeField(null=True, blank=True, verbose_name="End")
|
354
|
+
join_web_url = models.CharField(max_length=256, null=True, blank=True, verbose_name="Join Web Url", default="")
|
355
|
+
organizer = models.ForeignKey(
|
356
|
+
CallUser, verbose_name="Organizer", null=True, blank=True, on_delete=models.deletion.SET_NULL
|
357
|
+
)
|
358
|
+
participants = models.ManyToManyField(
|
359
|
+
CallUser, blank=True, related_name="participates", verbose_name="Call participants"
|
360
|
+
)
|
361
|
+
activity = models.OneToOneField(
|
362
|
+
"wbcrm.Activity",
|
363
|
+
related_name="call_event",
|
364
|
+
null=True,
|
365
|
+
blank=True,
|
366
|
+
on_delete=models.deletion.SET_NULL,
|
367
|
+
verbose_name="Activity",
|
368
|
+
)
|
369
|
+
created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
|
370
|
+
is_internal_call = models.BooleanField(null=True, blank=True, verbose_name="Is internal call")
|
371
|
+
data = models.JSONField(default=dict, null=True, blank=True)
|
372
|
+
|
373
|
+
def __str__(self):
|
374
|
+
return f"{self.event.id} ({self.start} - {self.end})"
|
375
|
+
|
376
|
+
def check_call_is_internal_call(self, caller_id=None):
|
377
|
+
is_internal = True
|
378
|
+
callers = self.participants.all()
|
379
|
+
|
380
|
+
if caller_id:
|
381
|
+
is_internal = self.is_internal_call if self.is_internal_call is not None else is_internal
|
382
|
+
callers = callers.filter(id=caller_id)
|
383
|
+
|
384
|
+
for caller in callers:
|
385
|
+
if (tenant_user := caller.tenant_user) and (person := tenant_user.profile) and person.is_internal:
|
386
|
+
is_internal &= True
|
387
|
+
else:
|
388
|
+
is_internal &= False
|
389
|
+
|
390
|
+
self.is_internal_call = is_internal
|
391
|
+
self.save()
|
392
|
+
|
393
|
+
@classmethod
|
394
|
+
def get_endpoint_basename(cls):
|
395
|
+
return "wbintegrator_office365:callevent"
|
396
|
+
|
397
|
+
@classmethod
|
398
|
+
def get_representation_endpoint(cls):
|
399
|
+
return "wbintegrator_office365:calleventrepresentation-list"
|
400
|
+
|
401
|
+
@classmethod
|
402
|
+
def get_representation_value_key(cls):
|
403
|
+
return "id"
|
404
|
+
|
405
|
+
@classmethod
|
406
|
+
def get_representation_label_key(cls):
|
407
|
+
return "{{event.id}} ({{start}} - {{end}})"
|
408
|
+
|
409
|
+
|
410
|
+
# search person by phone if not found
|
411
|
+
def get_person_by_phone(phone):
|
412
|
+
persons = Person.objects.none()
|
413
|
+
phone_numbers = None
|
414
|
+
try:
|
415
|
+
parser_number = phonenumbers.parse(phone, "CH")
|
416
|
+
phone_numbers = (
|
417
|
+
phonenumbers.format_number(parser_number, phonenumbers.PhoneNumberFormat.E164)
|
418
|
+
if parser_number
|
419
|
+
else "".join(phone.split())
|
420
|
+
)
|
421
|
+
if phone and phone_numbers:
|
422
|
+
phone2 = "".join(phone.replace("+41", "").split())
|
423
|
+
entries = (
|
424
|
+
TelephoneContact.objects.filter(
|
425
|
+
Q(number__contains=phone) | Q(number__contains=phone2) | Q(number__contains=phone_numbers)
|
426
|
+
)
|
427
|
+
.values("entry")
|
428
|
+
.distinct()
|
429
|
+
)
|
430
|
+
persons = Person.objects.filter(id__in=entries)
|
431
|
+
except Exception as e:
|
432
|
+
print(e, phone) # noqa: T201
|
433
|
+
return persons, phone_numbers
|
434
|
+
|
435
|
+
|
436
|
+
def fetch_tenantusers():
|
437
|
+
datum = MicrosoftGraphAPI().users()
|
438
|
+
count_added = 0
|
439
|
+
if datum:
|
440
|
+
for data in datum:
|
441
|
+
# username = user.get("email")
|
442
|
+
# if user.get("display_name") and user.get("id"):
|
443
|
+
# username = re.sub(' +', ' ', user.get("display_name").lower()).replace(",", "").replace(" ", "-").strip()
|
444
|
+
tenant_id = data.get("id")
|
445
|
+
display_name = data.get("display_name")
|
446
|
+
mail = data.get("mail") if data.get("mail") else data.get("user_principal_name")
|
447
|
+
phone = (
|
448
|
+
next(iter(data.get("business_phones") or []), None)
|
449
|
+
if data.get("business_phones")
|
450
|
+
else data.get("mobile_phone")
|
451
|
+
)
|
452
|
+
# print(display_name,"/", mail,"/", phone,"/", data.get("given_name"),"/", data.get("surname"))
|
453
|
+
# 1) search person by email
|
454
|
+
entries = EmailContact.objects.filter(address=mail).values("entry").distinct()
|
455
|
+
persons = Person.objects.filter(id__in=entries)
|
456
|
+
phone_numbers = None
|
457
|
+
# 2) search person by phone if not found
|
458
|
+
if persons.count() == 0 and phone:
|
459
|
+
persons, phone_numbers = get_person_by_phone(phone)
|
460
|
+
# 3) search person by surname and given_name if not found
|
461
|
+
if persons.count() == 0 and data.get("surname") and data.get("given_name"):
|
462
|
+
persons = Person.objects.filter(
|
463
|
+
first_name__icontains=data.get("given_name"), last_name__icontains=data.get("surname")
|
464
|
+
).distinct()
|
465
|
+
# get the first person found otherwise it will be None
|
466
|
+
person = None
|
467
|
+
if persons.count() > 0:
|
468
|
+
person = persons.first()
|
469
|
+
else:
|
470
|
+
first_name, last_name = (
|
471
|
+
display_name.split(" ") if len(display_name.split(" ")) == 2 else ["AnonymousUser", tenant_id]
|
472
|
+
)
|
473
|
+
qs_person = Person.objects.filter(first_name=first_name, last_name=last_name)
|
474
|
+
if qs_person.count() > 0:
|
475
|
+
person = qs_person.first()
|
476
|
+
# person, created = Person.objects.get_or_create(first_name=first_name, last_name=last_name)
|
477
|
+
# if mail:
|
478
|
+
# EmailContact.objects.get_or_create(
|
479
|
+
# primary=True,
|
480
|
+
# entry=person,
|
481
|
+
# address=mail,
|
482
|
+
# )
|
483
|
+
# if phone_numbers:
|
484
|
+
# TelephoneContact.objects.get_or_create(
|
485
|
+
# entry=person,
|
486
|
+
# number=phone_numbers
|
487
|
+
# )
|
488
|
+
|
489
|
+
# update or create tenant user
|
490
|
+
tenantuser, created = TenantUser.objects.update_or_create(
|
491
|
+
tenant_id=tenant_id,
|
492
|
+
defaults={"display_name": display_name, "mail": mail, "phone": phone, "profile": person},
|
493
|
+
)
|
494
|
+
if created:
|
495
|
+
count_added += 1
|
496
|
+
return datum, count_added
|
497
|
+
|
498
|
+
|
499
|
+
def create_or_update_call_user(
|
500
|
+
tenant_id=None,
|
501
|
+
display_name=None,
|
502
|
+
tenant_organization_id=None,
|
503
|
+
is_user=False,
|
504
|
+
is_guest=False,
|
505
|
+
is_phone=False,
|
506
|
+
acs_user=None,
|
507
|
+
splool_user=None,
|
508
|
+
encrypted=None,
|
509
|
+
on_premises=None,
|
510
|
+
acs_application_instance=None,
|
511
|
+
spool_application_instance=None,
|
512
|
+
application_instance=None,
|
513
|
+
application=None,
|
514
|
+
device=None,
|
515
|
+
):
|
516
|
+
tenant_user = None
|
517
|
+
# get or create Person
|
518
|
+
if tenant_id:
|
519
|
+
# search person by tenant id
|
520
|
+
qs_tenant_user = TenantUser.objects.filter(tenant_id=tenant_id)
|
521
|
+
if qs_tenant_user.count() == 0:
|
522
|
+
first_name, last_name = ["AnonymousUser", tenant_id]
|
523
|
+
phone_numbers = None
|
524
|
+
persons = Person.objects.none()
|
525
|
+
person = None
|
526
|
+
# search person by display name (also for phone anonymous) # the phone number obtained is "anonymous"
|
527
|
+
if is_user or is_guest or (is_phone and tenant_id == "anonymous"):
|
528
|
+
# first_name, last_name = display_name.split(" ") if len(display_name.split(" ")) == 2 and display_name != "Guest User" and display_name != "External user" else first_name, last_name
|
529
|
+
# Sometimes we obtained "Guest User" or "External user" for certains external user
|
530
|
+
if display_name:
|
531
|
+
if (
|
532
|
+
len(display_name.strip(" ").split(" ")) == 2
|
533
|
+
and display_name.lower() != "guest user"
|
534
|
+
and display_name.lower() != "external user"
|
535
|
+
):
|
536
|
+
first_name, last_name = display_name.strip(" ").split(" ")
|
537
|
+
else:
|
538
|
+
first_name = display_name
|
539
|
+
|
540
|
+
persons = Person.objects.filter(
|
541
|
+
first_name__icontains=first_name, last_name__icontains=last_name
|
542
|
+
).distinct()
|
543
|
+
# search person by phone
|
544
|
+
elif is_phone and tenant_id != "anonymous":
|
545
|
+
persons, phone_numbers = get_person_by_phone(tenant_id)
|
546
|
+
|
547
|
+
if persons.count():
|
548
|
+
person = persons.first()
|
549
|
+
else:
|
550
|
+
qs_person = Person.objects.filter(first_name=first_name, last_name=last_name)
|
551
|
+
if qs_person.count() > 0:
|
552
|
+
person = qs_person.first()
|
553
|
+
# person, created = Person.objects.get_or_create(first_name=first_name, last_name=last_name)
|
554
|
+
# if phone_numbers:
|
555
|
+
# TelephoneContact.objects.get_or_create(
|
556
|
+
# entry=person,
|
557
|
+
# number=phone_numbers
|
558
|
+
# )
|
559
|
+
# phone_numbers = tenant_id
|
560
|
+
|
561
|
+
tenant_user, created = TenantUser.objects.get_or_create(
|
562
|
+
tenant_id=tenant_id, defaults={"phone": phone_numbers, "display_name": display_name, "profile": person}
|
563
|
+
)
|
564
|
+
else:
|
565
|
+
tenant_user = TenantUser.objects.get(tenant_id=tenant_id)
|
566
|
+
|
567
|
+
if tenant_organization_id:
|
568
|
+
tenant_user.tenant_organization_id = tenant_organization_id
|
569
|
+
tenant_user.save()
|
570
|
+
else:
|
571
|
+
tenant_user, _ = TenantUser.objects.get_or_create(
|
572
|
+
tenant_id=tenant_id, display_name=display_name, tenant_organization_id=tenant_organization_id
|
573
|
+
)
|
574
|
+
|
575
|
+
call_user, created = CallUser.objects.update_or_create(
|
576
|
+
acs_user=acs_user,
|
577
|
+
splool_user=splool_user,
|
578
|
+
encrypted=encrypted,
|
579
|
+
on_premises=on_premises,
|
580
|
+
acs_application_instance=acs_application_instance,
|
581
|
+
spool_application_instance=spool_application_instance,
|
582
|
+
application_instance=application_instance,
|
583
|
+
application=application,
|
584
|
+
device=device,
|
585
|
+
tenant_user=tenant_user,
|
586
|
+
is_phone=is_phone,
|
587
|
+
is_guest=is_guest,
|
588
|
+
)
|
589
|
+
return call_user
|
590
|
+
|
591
|
+
|
592
|
+
@shared_task
|
593
|
+
def handle_event_from_webhook(id_event, notification):
|
594
|
+
with transaction.atomic():
|
595
|
+
from wbintegrator_office365.models.subscription import Subscription
|
596
|
+
|
597
|
+
if (
|
598
|
+
(subscription_id := notification.get("subscription_id"))
|
599
|
+
and (_subscription := Subscription.objects.filter(subscription_id=subscription_id).first())
|
600
|
+
and _subscription.type_resource == Subscription.TypeResource.CALLRECORD
|
601
|
+
):
|
602
|
+
event, _ = Event.objects.update_or_create(
|
603
|
+
uuid_event=id_event,
|
604
|
+
defaults={
|
605
|
+
"type": _subscription.type_resource,
|
606
|
+
"subscription_id": subscription_id,
|
607
|
+
"change_type": notification.get("change_type"),
|
608
|
+
"data": notification,
|
609
|
+
"resource": notification.get("resource"),
|
610
|
+
"is_handled": True,
|
611
|
+
"tenant_user": _subscription.tenant_user,
|
612
|
+
},
|
613
|
+
)
|
614
|
+
event.fetch_call()
|
615
|
+
|
616
|
+
|
617
|
+
@receiver(m2m_changed, sender=CallEvent.participants.through)
|
618
|
+
def post_save_participant_event(sender, instance, action, reverse, pk_set, *args, **kwargs):
|
619
|
+
if action == "post_add" and not reverse:
|
620
|
+
for participant_id in pk_set:
|
621
|
+
instance.check_call_is_internal_call(participant_id)
|
622
|
+
elif action == "post_remove" and not reverse:
|
623
|
+
instance.check_call_is_internal_call()
|