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.
Files changed (41) hide show
  1. wbintegrator_office365/__init__.py +1 -0
  2. wbintegrator_office365/admin.py +209 -0
  3. wbintegrator_office365/apps.py +5 -0
  4. wbintegrator_office365/configurations/__init__.py +0 -0
  5. wbintegrator_office365/configurations/configurations/__init__.py +23 -0
  6. wbintegrator_office365/dynamic_preferences_registry.py +15 -0
  7. wbintegrator_office365/factories.py +102 -0
  8. wbintegrator_office365/filters.py +237 -0
  9. wbintegrator_office365/importer/__init__.py +3 -0
  10. wbintegrator_office365/importer/api.py +403 -0
  11. wbintegrator_office365/importer/disable_signals.py +43 -0
  12. wbintegrator_office365/importer/parser.py +135 -0
  13. wbintegrator_office365/kpi_handlers/__init__.py +1 -0
  14. wbintegrator_office365/kpi_handlers/calls.py +114 -0
  15. wbintegrator_office365/migrations/0001_initial_squashed_squashed_0003_alter_calendar_owner_alter_calendarevent_organizer_and_more.py +677 -0
  16. wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendarevent_activity_and_more.py +85 -0
  17. wbintegrator_office365/migrations/0003_alter_event_options.py +20 -0
  18. wbintegrator_office365/migrations/__init__.py +0 -0
  19. wbintegrator_office365/models/__init__.py +3 -0
  20. wbintegrator_office365/models/event.py +623 -0
  21. wbintegrator_office365/models/subscription.py +144 -0
  22. wbintegrator_office365/models/tenant.py +62 -0
  23. wbintegrator_office365/serializers.py +266 -0
  24. wbintegrator_office365/tasks.py +108 -0
  25. wbintegrator_office365/tests/__init__.py +0 -0
  26. wbintegrator_office365/tests/conftest.py +28 -0
  27. wbintegrator_office365/tests/test_admin.py +86 -0
  28. wbintegrator_office365/tests/test_models.py +65 -0
  29. wbintegrator_office365/tests/test_tasks.py +318 -0
  30. wbintegrator_office365/tests/test_views.py +128 -0
  31. wbintegrator_office365/tests/tests.py +12 -0
  32. wbintegrator_office365/urls.py +46 -0
  33. wbintegrator_office365/viewsets/__init__.py +31 -0
  34. wbintegrator_office365/viewsets/display.py +306 -0
  35. wbintegrator_office365/viewsets/endpoints.py +52 -0
  36. wbintegrator_office365/viewsets/menu.py +65 -0
  37. wbintegrator_office365/viewsets/titles.py +49 -0
  38. wbintegrator_office365/viewsets/viewsets.py +745 -0
  39. wbintegrator_office365-2.2.1.dist-info/METADATA +10 -0
  40. wbintegrator_office365-2.2.1.dist-info/RECORD +41 -0
  41. wbintegrator_office365-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,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"))