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.
- 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"))
|