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