wbintegrator_office365 2.2.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/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-2.2.1.dist-info/METADATA +10 -0
- wbintegrator_office365-2.2.1.dist-info/RECORD +41 -0
- wbintegrator_office365-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
from celery import chain, shared_task
|
2
|
+
from django.db import models, transaction
|
3
|
+
from django.db.models import Q
|
4
|
+
from django.db.models.signals import post_delete, post_save
|
5
|
+
from django.dispatch import receiver
|
6
|
+
from wbcore.models import WBModel
|
7
|
+
from wbintegrator_office365.importer import MicrosoftGraphAPI
|
8
|
+
|
9
|
+
|
10
|
+
class Subscription(WBModel):
|
11
|
+
class Meta:
|
12
|
+
verbose_name = "Subscription"
|
13
|
+
verbose_name_plural = "Subscriptions"
|
14
|
+
|
15
|
+
class TypeResource(models.TextChoices):
|
16
|
+
CALLRECORD = "CALLRECORD", "Call Record"
|
17
|
+
CALENDAR = "CALENDAR", "Calendar"
|
18
|
+
|
19
|
+
is_enable = models.BooleanField(default=True)
|
20
|
+
subscription_id = models.CharField(max_length=255, null=True, blank=True)
|
21
|
+
change_type = models.CharField(max_length=255, null=True, blank=True)
|
22
|
+
notification_url = models.CharField(max_length=255, null=True, blank=True)
|
23
|
+
resource = models.CharField(max_length=255, null=True, blank=True)
|
24
|
+
type_resource = models.CharField(
|
25
|
+
max_length=32,
|
26
|
+
choices=TypeResource.choices,
|
27
|
+
default=TypeResource.CALLRECORD,
|
28
|
+
verbose_name="Type of the resource",
|
29
|
+
)
|
30
|
+
tenant_user = models.ForeignKey(
|
31
|
+
"wbintegrator_office365.TenantUser",
|
32
|
+
related_name="subscriptions",
|
33
|
+
null=True,
|
34
|
+
blank=True,
|
35
|
+
on_delete=models.deletion.SET_NULL,
|
36
|
+
verbose_name="Tenant User",
|
37
|
+
)
|
38
|
+
expiration_date = models.DateTimeField(null=True, blank=True, verbose_name="Expiration Date")
|
39
|
+
created = models.DateTimeField(auto_now_add=True, verbose_name="Created")
|
40
|
+
application_id = models.CharField(null=True, blank=True, max_length=255)
|
41
|
+
creator_id = models.CharField(null=True, blank=True, max_length=255)
|
42
|
+
client_state = models.CharField(null=True, blank=True, max_length=255)
|
43
|
+
latest_supported_tls_version = models.CharField(null=True, blank=True, max_length=255)
|
44
|
+
notification_content_type = models.CharField(null=True, blank=True, max_length=255)
|
45
|
+
odata_context = models.CharField(null=True, blank=True, max_length=255)
|
46
|
+
encryption_certificate_id = models.CharField(null=True, blank=True, max_length=255)
|
47
|
+
encryption_certificate = models.CharField(null=True, blank=True, max_length=255)
|
48
|
+
include_resource_data = models.CharField(null=True, blank=True, max_length=255)
|
49
|
+
notification_query_options = models.CharField(null=True, blank=True, max_length=255)
|
50
|
+
|
51
|
+
def __str__(self):
|
52
|
+
return f"{self.subscription_id}: {self.expiration_date}"
|
53
|
+
|
54
|
+
def resubscribe(self) -> None:
|
55
|
+
if self.subscription_id and (data := MicrosoftGraphAPI()._renew_subscription(self.subscription_id)):
|
56
|
+
Subscription.objects.filter(id=self.id).update(
|
57
|
+
expiration_date=data.get("expiration"), notification_url=data.get("notification_url")
|
58
|
+
)
|
59
|
+
|
60
|
+
@classmethod
|
61
|
+
def get_endpoint_basename(self):
|
62
|
+
return "wbintegrator_office365:subscription"
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
def get_representation_endpoint(cls):
|
66
|
+
return "wbintegrator_office365:subscriptionrepresentation-list"
|
67
|
+
|
68
|
+
@classmethod
|
69
|
+
def get_representation_value_key(cls):
|
70
|
+
return "id"
|
71
|
+
|
72
|
+
@classmethod
|
73
|
+
def get_representation_label_key(cls):
|
74
|
+
return "{{subscription_id}}: {{expiration_date}}"
|
75
|
+
|
76
|
+
|
77
|
+
@shared_task
|
78
|
+
def subscribe(instance_id, resource, minutes=4230, change_type="created, updated"):
|
79
|
+
with transaction.atomic():
|
80
|
+
data = MicrosoftGraphAPI()._subscribe(resource, minutes, change_type=change_type)
|
81
|
+
if data.get("id"):
|
82
|
+
instance = Subscription.objects.get(id=instance_id)
|
83
|
+
if instance.type_resource == Subscription.TypeResource.CALLRECORD:
|
84
|
+
instance.tenant_user = None
|
85
|
+
instance.subscription_id = data.get("id")
|
86
|
+
instance.change_type = data.get("change_type")
|
87
|
+
instance.notification_url = data.get("notification_url")
|
88
|
+
instance.resource = data.get("resource")
|
89
|
+
instance.expiration_date = data.get("expiration")
|
90
|
+
instance.application_id = data.get("application_id")
|
91
|
+
instance.creator_id = data.get("creator_id")
|
92
|
+
instance.client_state = data.get("client_state")
|
93
|
+
instance.latest_supported_tls_version = data.get("latest_supported_tls_version")
|
94
|
+
instance.notification_content_type = data.get("notification_content_type")
|
95
|
+
instance.odata_context = data.get("odata_context")
|
96
|
+
instance.encryption_certificate_id = data.get("encryption_certificate_id")
|
97
|
+
instance.encryption_certificate = data.get("encryption_certificate")
|
98
|
+
instance.include_resource_data = data.get("include_resource_data")
|
99
|
+
instance.notification_query_options = data.get("notification_query_options")
|
100
|
+
instance.save()
|
101
|
+
|
102
|
+
|
103
|
+
@shared_task
|
104
|
+
def unsubscribe(subscription_id):
|
105
|
+
if subscription_id:
|
106
|
+
MicrosoftGraphAPI()._unsubscribe(subscription_id)
|
107
|
+
|
108
|
+
|
109
|
+
@shared_task
|
110
|
+
def verification_subscriptions(instance_id: int):
|
111
|
+
with transaction.atomic():
|
112
|
+
ms_subscriptions = [elt.get("id") for elt in MicrosoftGraphAPI().subscriptions()]
|
113
|
+
qs_subscriptions = Subscription.objects.filter(Q(id=instance_id) & Q(subscription_id__isnull=False))
|
114
|
+
qs_subscriptions.filter(~Q(subscription_id__in=ms_subscriptions)).update(is_enable=False)
|
115
|
+
|
116
|
+
|
117
|
+
@shared_task
|
118
|
+
def resubscribe_as_task(instance_id: int):
|
119
|
+
subscription = Subscription.objects.get(id=instance_id)
|
120
|
+
subscription.resubscribe()
|
121
|
+
|
122
|
+
|
123
|
+
@receiver(post_save, sender=Subscription)
|
124
|
+
def post_save_subscription(sender, instance, created, **kwargs):
|
125
|
+
if (created and instance.is_enable) or (instance.is_enable and not instance.subscription_id):
|
126
|
+
# Subscription to Call Event
|
127
|
+
if instance.type_resource == Subscription.TypeResource.CALLRECORD and not instance.expiration_date:
|
128
|
+
transaction.on_commit(
|
129
|
+
lambda: chain(
|
130
|
+
subscribe.si(instance.id, "/communications/callRecords"),
|
131
|
+
verification_subscriptions.si(instance.id),
|
132
|
+
).apply_async()
|
133
|
+
)
|
134
|
+
elif not instance.is_enable and instance.subscription_id:
|
135
|
+
transaction.on_commit(
|
136
|
+
lambda: chain(
|
137
|
+
unsubscribe.si(instance.subscription_id), verification_subscriptions.si(instance.id)
|
138
|
+
).apply_async()
|
139
|
+
)
|
140
|
+
|
141
|
+
|
142
|
+
@receiver(post_delete, sender=Subscription)
|
143
|
+
def post_delete_subscription(sender, instance, **kwargs):
|
144
|
+
unsubscribe.delay(instance.subscription_id)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
from django.conf import settings
|
2
|
+
from django.db import models
|
3
|
+
from wbcore.models import WBModel
|
4
|
+
|
5
|
+
|
6
|
+
class TenantUser(WBModel):
|
7
|
+
class Meta:
|
8
|
+
verbose_name = "Tenant User"
|
9
|
+
verbose_name_plural = "Tenant Users"
|
10
|
+
|
11
|
+
tenant_id = models.CharField(max_length=255, null=True, blank=True, verbose_name="User tenant ID", default="")
|
12
|
+
display_name = models.CharField(max_length=255, null=True, blank=True, verbose_name="Display Name", default="")
|
13
|
+
tenant_organization_id = models.CharField(
|
14
|
+
max_length=255, null=True, blank=True, verbose_name="Organization tenant ID"
|
15
|
+
)
|
16
|
+
mail = models.CharField(max_length=255, null=True, blank=True, default="")
|
17
|
+
phone = models.CharField(max_length=255, null=True, blank=True, default="")
|
18
|
+
profile = models.ForeignKey(
|
19
|
+
"directory.Person",
|
20
|
+
related_name="tenant_user",
|
21
|
+
null=True,
|
22
|
+
blank=True,
|
23
|
+
on_delete=models.deletion.SET_NULL,
|
24
|
+
verbose_name="User",
|
25
|
+
)
|
26
|
+
is_internal_organization = models.BooleanField(null=True, blank=True)
|
27
|
+
|
28
|
+
def save(self, *args, **kwargs):
|
29
|
+
if self.tenant_organization_id and (org_id := getattr(settings, "WBINTEGRATOR_OFFICE365_TENANT_ID", None)):
|
30
|
+
self.is_internal_organization = self.tenant_organization_id == org_id
|
31
|
+
super().save(*args, **kwargs)
|
32
|
+
|
33
|
+
def __str__(self):
|
34
|
+
if self.profile:
|
35
|
+
name_tenant = f"{self.profile.computed_str}"
|
36
|
+
elif self.mail or self.display_name:
|
37
|
+
mail = self.mail if self.mail else self.id
|
38
|
+
name_tenant = f"{self.display_name}({mail})"
|
39
|
+
elif self.tenant_id:
|
40
|
+
name_tenant = f"{self.tenant_id}"
|
41
|
+
elif self.tenant_organization_id:
|
42
|
+
name_tenant = f"company-{self.tenant_organization_id}"
|
43
|
+
else:
|
44
|
+
name_tenant = f"{self.id}"
|
45
|
+
status = "Internal" if self.is_internal_organization else "External"
|
46
|
+
return f"{name_tenant} ({status})"
|
47
|
+
|
48
|
+
@classmethod
|
49
|
+
def get_endpoint_basename(cls):
|
50
|
+
return "wbintegrator_office365:tenantuser"
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
def get_representation_endpoint(cls):
|
54
|
+
return "wbintegrator_office365:tenantuserrepresentation-list"
|
55
|
+
|
56
|
+
@classmethod
|
57
|
+
def get_representation_value_key(cls):
|
58
|
+
return "id"
|
59
|
+
|
60
|
+
@classmethod
|
61
|
+
def get_representation_label_key(cls):
|
62
|
+
return "{{profile_str}}({{id_str}})"
|
@@ -0,0 +1,266 @@
|
|
1
|
+
from rest_framework.reverse import reverse
|
2
|
+
from wbcore import serializers as wb_serializers
|
3
|
+
from wbcore.contrib.directory.serializers import PersonRepresentationSerializer
|
4
|
+
from wbintegrator_office365.models import (
|
5
|
+
CallEvent,
|
6
|
+
CallUser,
|
7
|
+
Event,
|
8
|
+
EventLog,
|
9
|
+
Subscription,
|
10
|
+
TenantUser,
|
11
|
+
)
|
12
|
+
|
13
|
+
|
14
|
+
class TenantUserRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
15
|
+
_detail = wb_serializers.HyperlinkField(reverse_name="wbintegrator_office365:tenantuser-detail")
|
16
|
+
profile_str = wb_serializers.StringRelatedField(source="profile")
|
17
|
+
id_str = wb_serializers.CharField(source="id")
|
18
|
+
|
19
|
+
class Meta:
|
20
|
+
model = TenantUser
|
21
|
+
fields = ("id", "tenant_id", "profile_str", "id_str", "_detail")
|
22
|
+
|
23
|
+
|
24
|
+
class SubscriptionRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
25
|
+
_detail = wb_serializers.HyperlinkField(reverse_name="wbintegrator_office365:subscription-detail")
|
26
|
+
|
27
|
+
class Meta:
|
28
|
+
model = Subscription
|
29
|
+
fields = ("id", "subscription_id", "created", "_detail")
|
30
|
+
|
31
|
+
|
32
|
+
class EventRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
33
|
+
_detail = wb_serializers.HyperlinkField(reverse_name="wbintegrator_office365:event-detail")
|
34
|
+
id_str = wb_serializers.CharField(source="id")
|
35
|
+
|
36
|
+
class Meta:
|
37
|
+
model = Event
|
38
|
+
fields = ("id", "id_str", "uuid_event", "_detail")
|
39
|
+
|
40
|
+
|
41
|
+
class CallUserRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
42
|
+
_detail = wb_serializers.HyperlinkField(reverse_name="wbintegrator_office365:calluser-detail")
|
43
|
+
tenant_user_str = wb_serializers.StringRelatedField(source="tenant_user")
|
44
|
+
id_str = wb_serializers.CharField(source="id")
|
45
|
+
|
46
|
+
class Meta:
|
47
|
+
model = CallUser
|
48
|
+
fields = ("id", "tenant_user_str", "id_str", "_detail")
|
49
|
+
|
50
|
+
|
51
|
+
class CallEventRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
52
|
+
_detail = wb_serializers.HyperlinkField(reverse_name="wbintegrator_office365:callevent-detail")
|
53
|
+
|
54
|
+
class Meta:
|
55
|
+
model = CallEvent
|
56
|
+
fields = ("id", "_detail")
|
57
|
+
|
58
|
+
|
59
|
+
class TenantUserModelSerializer(wb_serializers.ModelSerializer):
|
60
|
+
_profile = PersonRepresentationSerializer(source="profile")
|
61
|
+
|
62
|
+
class Meta:
|
63
|
+
model = TenantUser
|
64
|
+
fields = (
|
65
|
+
"id",
|
66
|
+
"tenant_id",
|
67
|
+
"display_name",
|
68
|
+
"mail",
|
69
|
+
"phone",
|
70
|
+
"profile",
|
71
|
+
"_profile",
|
72
|
+
"tenant_organization_id",
|
73
|
+
"is_internal_organization",
|
74
|
+
)
|
75
|
+
|
76
|
+
|
77
|
+
class CallUserModelSerializer(wb_serializers.ModelSerializer):
|
78
|
+
_tenant_user = TenantUserRepresentationSerializer(source="tenant_user")
|
79
|
+
|
80
|
+
name_user = wb_serializers.CharField(read_only=True)
|
81
|
+
phone = wb_serializers.CharField(read_only=True)
|
82
|
+
mail = wb_serializers.CharField(read_only=True)
|
83
|
+
|
84
|
+
class Meta:
|
85
|
+
model = CallUser
|
86
|
+
fields = read_only_fields = (
|
87
|
+
"id",
|
88
|
+
"tenant_user",
|
89
|
+
"_tenant_user",
|
90
|
+
"is_guest",
|
91
|
+
"is_phone",
|
92
|
+
"acs_user",
|
93
|
+
"splool_user",
|
94
|
+
"encrypted",
|
95
|
+
"on_premises",
|
96
|
+
"acs_application_instance",
|
97
|
+
"spool_application_instance",
|
98
|
+
"application_instance",
|
99
|
+
"application",
|
100
|
+
"device",
|
101
|
+
"name_user",
|
102
|
+
"phone",
|
103
|
+
"mail",
|
104
|
+
)
|
105
|
+
|
106
|
+
|
107
|
+
class SubscriptionModelSerializer(wb_serializers.ModelSerializer):
|
108
|
+
_tenant_user = TenantUserRepresentationSerializer(source="tenant_user")
|
109
|
+
id_str = wb_serializers.CharField(source="id")
|
110
|
+
|
111
|
+
class Meta:
|
112
|
+
model = Subscription
|
113
|
+
fields = read_only_fields = (
|
114
|
+
"id",
|
115
|
+
"id_str",
|
116
|
+
"subscription_id",
|
117
|
+
"change_type",
|
118
|
+
"notification_url",
|
119
|
+
"resource",
|
120
|
+
"expiration_date",
|
121
|
+
"application_id",
|
122
|
+
"creator_id",
|
123
|
+
"client_state",
|
124
|
+
"created",
|
125
|
+
"latest_supported_tls_version",
|
126
|
+
"notification_content_type",
|
127
|
+
"odata_context",
|
128
|
+
"encryption_certificate_id",
|
129
|
+
"encryption_certificate",
|
130
|
+
"include_resource_data",
|
131
|
+
"notification_query_options",
|
132
|
+
"tenant_user",
|
133
|
+
"_tenant_user",
|
134
|
+
"is_enable",
|
135
|
+
"type_resource",
|
136
|
+
)
|
137
|
+
read_only_fields = (
|
138
|
+
"id",
|
139
|
+
"subscription_id",
|
140
|
+
"change_type",
|
141
|
+
"notification_url",
|
142
|
+
"resource",
|
143
|
+
"expiration_date",
|
144
|
+
"application_id",
|
145
|
+
"creator_id",
|
146
|
+
"client_state",
|
147
|
+
"created",
|
148
|
+
"latest_supported_tls_version",
|
149
|
+
"notification_content_type",
|
150
|
+
"odata_context",
|
151
|
+
"encryption_certificate_id",
|
152
|
+
"encryption_certificate",
|
153
|
+
"include_resource_data",
|
154
|
+
"notification_query_options",
|
155
|
+
"tenant_user",
|
156
|
+
"is_enable",
|
157
|
+
"type_resource",
|
158
|
+
)
|
159
|
+
|
160
|
+
|
161
|
+
class SubscriptionListModelSerializer(SubscriptionModelSerializer):
|
162
|
+
class Meta:
|
163
|
+
model = Subscription
|
164
|
+
fields = read_only_fields = (
|
165
|
+
"id",
|
166
|
+
"subscription_id",
|
167
|
+
"change_type",
|
168
|
+
"expiration_date",
|
169
|
+
"type_resource",
|
170
|
+
"resource",
|
171
|
+
"tenant_user",
|
172
|
+
"_tenant_user",
|
173
|
+
"is_enable",
|
174
|
+
"created",
|
175
|
+
)
|
176
|
+
|
177
|
+
|
178
|
+
class EventModelSerializer(wb_serializers.ModelSerializer):
|
179
|
+
@wb_serializers.register_resource()
|
180
|
+
def register_history_resource(self, instance, request, user):
|
181
|
+
resources = {
|
182
|
+
"eventlog": reverse("wbintegrator_office365:event-eventlog-list", args=[instance.id], request=request),
|
183
|
+
}
|
184
|
+
return resources
|
185
|
+
|
186
|
+
id = wb_serializers.PrimaryKeyCharField()
|
187
|
+
id_str = wb_serializers.CharField(source="id")
|
188
|
+
|
189
|
+
_tenant_user = TenantUserRepresentationSerializer(source="tenant_user")
|
190
|
+
|
191
|
+
class Meta:
|
192
|
+
model = Event
|
193
|
+
fields = read_only_fields = (
|
194
|
+
"id",
|
195
|
+
"id_str",
|
196
|
+
"auto_inc_id",
|
197
|
+
"nb_received",
|
198
|
+
"type",
|
199
|
+
"subscription_id",
|
200
|
+
"change_type",
|
201
|
+
"resource",
|
202
|
+
"created",
|
203
|
+
"changed",
|
204
|
+
"tenant_user",
|
205
|
+
"_tenant_user",
|
206
|
+
"_additional_resources",
|
207
|
+
"id_event",
|
208
|
+
"uuid_event",
|
209
|
+
"is_handled",
|
210
|
+
)
|
211
|
+
|
212
|
+
|
213
|
+
class EventLogRepresentationSerializer(wb_serializers.RepresentationSerializer):
|
214
|
+
_last_event = EventRepresentationSerializer(source="last_event")
|
215
|
+
_detail = wb_serializers.HyperlinkField(reverse_name="wbintegrator_office365:eventlog-detail")
|
216
|
+
|
217
|
+
class Meta:
|
218
|
+
model = EventLog
|
219
|
+
fields = read_only_fields = ("id", "last_event", "_last_event", "order_received", "_detail")
|
220
|
+
|
221
|
+
|
222
|
+
class EventLogModelSerializer(wb_serializers.ModelSerializer):
|
223
|
+
_last_event = EventRepresentationSerializer(source="last_event")
|
224
|
+
|
225
|
+
class Meta:
|
226
|
+
model = EventLog
|
227
|
+
fields = read_only_fields = (
|
228
|
+
"id",
|
229
|
+
"last_event",
|
230
|
+
"_last_event",
|
231
|
+
"change_type",
|
232
|
+
"order_received",
|
233
|
+
"created",
|
234
|
+
"changed",
|
235
|
+
"id_event",
|
236
|
+
"resource",
|
237
|
+
"is_handled",
|
238
|
+
)
|
239
|
+
|
240
|
+
|
241
|
+
class CallEventModelSerializer(wb_serializers.ModelSerializer):
|
242
|
+
_event = EventRepresentationSerializer(source="event")
|
243
|
+
_organizer = CallUserRepresentationSerializer(source="organizer")
|
244
|
+
_participants = CallUserRepresentationSerializer(source="participants", many=True)
|
245
|
+
change_type = wb_serializers.CharField(required=False, read_only=True)
|
246
|
+
|
247
|
+
class Meta:
|
248
|
+
model = CallEvent
|
249
|
+
fields = read_only_fields = (
|
250
|
+
"id",
|
251
|
+
"event",
|
252
|
+
"_event",
|
253
|
+
"organizer",
|
254
|
+
"_organizer",
|
255
|
+
"type",
|
256
|
+
"change_type",
|
257
|
+
"start",
|
258
|
+
"end",
|
259
|
+
"last_modified",
|
260
|
+
"created",
|
261
|
+
"participants",
|
262
|
+
"_participants",
|
263
|
+
"is_internal_call",
|
264
|
+
"version",
|
265
|
+
"join_web_url",
|
266
|
+
)
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import math
|
2
|
+
from datetime import date, timedelta
|
3
|
+
|
4
|
+
from celery import shared_task
|
5
|
+
from django.contrib.auth import get_user_model
|
6
|
+
from django.db.models import Q
|
7
|
+
from wbcore.contrib.directory.models import Person
|
8
|
+
from wbcore.contrib.notifications.dispatch import send_notification
|
9
|
+
from wbintegrator_office365.importer import MicrosoftGraphAPI
|
10
|
+
from wbintegrator_office365.models.event import CallEvent
|
11
|
+
from wbintegrator_office365.models.subscription import Subscription
|
12
|
+
|
13
|
+
|
14
|
+
@shared_task
|
15
|
+
def send_call_summary(to_emails: list, profile_ids: list, offset: int = 1):
|
16
|
+
for to_email in to_emails:
|
17
|
+
if (recipient := get_user_model().objects.filter(email=to_email).first()) and (
|
18
|
+
profiles := Person.objects.filter(id__in=profile_ids)
|
19
|
+
):
|
20
|
+
_day = date.today() - timedelta(days=offset)
|
21
|
+
|
22
|
+
message = "<html><head><style> #summery_table tr:nth-child(even){background-color: #f2f2f2;}</style></head><body>"
|
23
|
+
|
24
|
+
for profile in profiles:
|
25
|
+
call_events = CallEvent.objects.filter(
|
26
|
+
participants__tenant_user__profile__computed_str__icontains=profile.computed_str,
|
27
|
+
start__date=_day,
|
28
|
+
end__date=_day,
|
29
|
+
).order_by("start")
|
30
|
+
|
31
|
+
message += f"<p> \
|
32
|
+
<span><b> {profile.computed_str} </b></span><br/> \
|
33
|
+
<span>Date: <b>{_day}</b> </span><br/> \
|
34
|
+
<span>Total number of calls: <b>{call_events.count()}</b> </span> \
|
35
|
+
</p>"
|
36
|
+
if call_events.count():
|
37
|
+
message += "<table id='summery_table' style='border-collapse: collapse;'> \
|
38
|
+
<tr style='color: white; background-color: #1868ae;'> \
|
39
|
+
<th style='border: 1px solid #ddd;padding: 10px 7px;' >Start</th> \
|
40
|
+
<th style='border: 1px solid #ddd;padding: 10px 7px;' >End</th> \
|
41
|
+
<th style='border: 1px solid #ddd;padding: 10px 7px;' >Duration (min)</th> \
|
42
|
+
<th style='border: 1px solid #ddd;padding: 10px 7px;' >Organized by</th> \
|
43
|
+
<th style='border: 1px solid #ddd;padding: 10px 7px;' >Participants</th> \
|
44
|
+
</tr>"
|
45
|
+
for call in call_events:
|
46
|
+
participants = ""
|
47
|
+
count = 0
|
48
|
+
total_participants = call.participants.exclude(tenant_user__profile=profile).count()
|
49
|
+
for participant in call.participants.exclude(tenant_user__profile=profile):
|
50
|
+
count += 1
|
51
|
+
participants += f"{participant.__str__()}"
|
52
|
+
if count < total_participants:
|
53
|
+
participants += ", "
|
54
|
+
|
55
|
+
delta_in_minutes = divmod((call.end - call.start).total_seconds(), 60)
|
56
|
+
_duration = f"{math.floor(delta_in_minutes[0])}:{math.floor(delta_in_minutes[1])}"
|
57
|
+
message += f"<tr> \
|
58
|
+
<td style='border: 1px solid #ddd;padding: 2px;' >{call.start.astimezone().strftime('%Y-%m-%d %H:%M:%S')}</td> \
|
59
|
+
<td style='border: 1px solid #ddd;padding: 2px;' >{call.end.astimezone().strftime('%Y-%m-%d %H:%M:%S')}</td> \
|
60
|
+
<td style='border: 1px solid #ddd;padding: 0px;' text-align:center;><b>{_duration}</b></td> \
|
61
|
+
<td style='border: 1px solid #ddd;padding: 2px;' ><b>{call.organizer.__str__()}</b></td> \
|
62
|
+
<td style='border: 1px solid #ddd;padding: 2px;' >{participants}</td> \
|
63
|
+
</tr>"
|
64
|
+
message += "</table><br/>"
|
65
|
+
|
66
|
+
message += "</body></html>"
|
67
|
+
|
68
|
+
send_notification(
|
69
|
+
code="wbintegrator_office365.callevent.notify",
|
70
|
+
title=f"Call summary - {_day}",
|
71
|
+
body=message,
|
72
|
+
user=recipient,
|
73
|
+
)
|
74
|
+
|
75
|
+
|
76
|
+
@shared_task
|
77
|
+
def notify_no_active_call_record_subscription(to_email):
|
78
|
+
recipient = get_user_model().objects.filter(email=to_email)
|
79
|
+
ms_subscriptions = [elt.get("id") for elt in MicrosoftGraphAPI().subscriptions()]
|
80
|
+
qs_subscriptions = Subscription.objects.filter(
|
81
|
+
Q(is_enable=True) & Q(subscription_id__isnull=False) & Q(type_resource=Subscription.TypeResource.CALLRECORD)
|
82
|
+
)
|
83
|
+
enable_subcriptions = qs_subscriptions.filter(subscription_id__in=ms_subscriptions)
|
84
|
+
if recipient.exists() and (
|
85
|
+
len(ms_subscriptions) == 0 or (qs_subscriptions.count() > 0 and enable_subcriptions.count() == 0)
|
86
|
+
):
|
87
|
+
_day = date.today()
|
88
|
+
send_notification(
|
89
|
+
code="wbintegrator_office365.callevent.notify",
|
90
|
+
title=f"No active Call Record subscriptions in Microsoft - {_day}",
|
91
|
+
body=f"""<p>There are currently no active Call record subscriptions in Microsoft, so we are no longer receiving calls, Please check</p>
|
92
|
+
<ul>
|
93
|
+
<li>Number of subscriptions on Microsoft: <b>{len(ms_subscriptions)}</b></li>
|
94
|
+
<li>Number of Call subscriptions: <b>{qs_subscriptions.count()}</b></li>
|
95
|
+
<li>Number of enabled calling subscriptions: <b>{enable_subcriptions.count()}</b></li>
|
96
|
+
|
97
|
+
</ul>
|
98
|
+
""",
|
99
|
+
user=recipient.first(),
|
100
|
+
)
|
101
|
+
|
102
|
+
|
103
|
+
@shared_task
|
104
|
+
def periodic_resubscribe_task():
|
105
|
+
for subscription in Subscription.objects.filter(
|
106
|
+
is_enable=True, type_resource=Subscription.TypeResource.CALLRECORD, subscription_id__isnull=False
|
107
|
+
):
|
108
|
+
subscription.resubscribe()
|
File without changes
|
@@ -0,0 +1,28 @@
|
|
1
|
+
from django.apps import apps
|
2
|
+
from django.db import connection
|
3
|
+
from django.db.models.signals import pre_migrate
|
4
|
+
from pytest_factoryboy import register
|
5
|
+
from wbcore.contrib.authentication.factories import UserFactory
|
6
|
+
from wbcore.contrib.geography.tests.signals import (
|
7
|
+
app_pre_migration as app_pre_migration_geography,
|
8
|
+
)
|
9
|
+
from wbhuman_resources.tests.signals import app_pre_migration as app_pre_migration_hr
|
10
|
+
from wbintegrator_office365.factories import (
|
11
|
+
CallEventFactory,
|
12
|
+
CallUserFactory,
|
13
|
+
EventFactory,
|
14
|
+
EventLogFactory,
|
15
|
+
SubscriptionFactory,
|
16
|
+
TenantUserFactory,
|
17
|
+
)
|
18
|
+
|
19
|
+
register(EventFactory)
|
20
|
+
register(EventLogFactory)
|
21
|
+
register(SubscriptionFactory)
|
22
|
+
register(TenantUserFactory)
|
23
|
+
register(CallUserFactory)
|
24
|
+
register(CallEventFactory)
|
25
|
+
register(UserFactory)
|
26
|
+
|
27
|
+
pre_migrate.connect(app_pre_migration_hr, sender=apps.get_app_config("wbintegrator_office365"))
|
28
|
+
pre_migrate.connect(app_pre_migration_geography, sender=apps.get_app_config("wbintegrator_office365"))
|