wbintegrator_office365 1.55.9__tar.gz → 1.56.0__tar.gz

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 (42) hide show
  1. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/PKG-INFO +2 -1
  2. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/pyproject.toml +1 -0
  3. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/importer/api.py +5 -5
  4. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/models/event.py +5 -3
  5. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/models/subscription.py +1 -1
  6. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tasks.py +77 -0
  7. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tests/test_admin.py +1 -0
  8. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tests/test_models.py +1 -0
  9. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tests/test_tasks.py +1 -0
  10. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tests/test_views.py +2 -1
  11. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/viewsets/viewsets.py +2 -2
  12. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/.gitignore +0 -0
  13. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/__init__.py +0 -0
  14. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/admin.py +0 -0
  15. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/apps.py +0 -0
  16. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/configurations/__init__.py +0 -0
  17. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/configurations/configurations/__init__.py +0 -0
  18. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/dynamic_preferences_registry.py +0 -0
  19. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/factories.py +0 -0
  20. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/filters.py +0 -0
  21. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/importer/__init__.py +0 -0
  22. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/importer/disable_signals.py +0 -0
  23. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/importer/parser.py +0 -0
  24. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/kpi_handlers/__init__.py +0 -0
  25. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/kpi_handlers/calls.py +0 -0
  26. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/migrations/0001_initial_squashed_squashed_0003_alter_calendar_owner_alter_calendarevent_organizer_and_more.py +0 -0
  27. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendarevent_activity_and_more.py +0 -0
  28. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/migrations/0003_alter_event_options.py +0 -0
  29. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/migrations/__init__.py +0 -0
  30. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/models/__init__.py +0 -0
  31. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/models/tenant.py +0 -0
  32. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/serializers.py +0 -0
  33. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/templates/admin/tenant_change_list.html +0 -0
  34. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tests/__init__.py +0 -0
  35. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tests/conftest.py +0 -0
  36. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/tests/tests.py +0 -0
  37. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/urls.py +0 -0
  38. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/viewsets/__init__.py +0 -0
  39. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/viewsets/display.py +0 -0
  40. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/viewsets/endpoints.py +0 -0
  41. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/viewsets/menu.py +0 -0
  42. {wbintegrator_office365-1.55.9 → wbintegrator_office365-1.56.0}/wbintegrator_office365/viewsets/titles.py +0 -0
@@ -1,7 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wbintegrator_office365
3
- Version: 1.55.9
3
+ Version: 1.56.0
4
4
  Author-email: Christopher Wittlinger <c.wittlinger@stainly.com>
5
+ Requires-Dist: boto3>=1.35.99
5
6
  Requires-Dist: bs4==0.0.*
6
7
  Requires-Dist: oauthlib==3.1.*
7
8
  Requires-Dist: requests-oauthlib==1.3.*
@@ -11,6 +11,7 @@ dependencies = [
11
11
  "oauthlib == 3.1.*",
12
12
  "requests-oauthlib == 1.3.*",
13
13
  "bs4 == 0.0.*",
14
+ "boto3>=1.35.99",
14
15
  ]
15
16
 
16
17
  [tool.uv.sources]
@@ -22,7 +22,7 @@ class MicrosoftGraphAPI:
22
22
  self.graph_url = getattr(settings, "WBINTEGRATOR_OFFICE365_GRAPH_URL", "")
23
23
 
24
24
  global_preferences = global_preferences_registry.manager()
25
- if global_preferences["wbintegrator_office365__access_token"] == "0":
25
+ if global_preferences["wbintegrator_office365__access_token"] == "0": # noqa
26
26
  global_preferences["wbintegrator_office365__access_token"] = self._get_access_token()
27
27
 
28
28
  def _get_administrator_consent(self):
@@ -385,13 +385,13 @@ class MicrosoftGraphAPI:
385
385
  global_preferences = global_preferences_registry.manager()
386
386
  headers["Authorization"] = f'Bearer {global_preferences["wbintegrator_office365__access_token"]}'
387
387
  if method == "POST":
388
- response = requests.post(url, data=data, headers=headers)
388
+ response = requests.post(url, data=data, headers=headers, timeout=10)
389
389
  elif method == "DELETE":
390
- response = requests.delete(url, headers=headers)
390
+ response = requests.delete(url, headers=headers, timeout=10)
391
391
  elif method == "PATCH":
392
- response = requests.patch(url, data=data, headers=headers)
392
+ response = requests.patch(url, data=data, headers=headers, timeout=10)
393
393
  else:
394
- response = requests.get(url, headers=headers, params=params)
394
+ response = requests.get(url, headers=headers, params=params, timeout=10)
395
395
  if response.status_code == status.HTTP_401_UNAUTHORIZED:
396
396
  new_token = self._get_access_token()
397
397
  if new_token != global_preferences["wbintegrator_office365__access_token"]:
@@ -94,7 +94,7 @@ class Event(WBModel):
94
94
  )
95
95
 
96
96
  @classmethod
97
- def get_endpoint_basename(self):
97
+ def get_endpoint_basename(cls):
98
98
  return "wbintegrator_office365:event"
99
99
 
100
100
  @classmethod
@@ -254,7 +254,7 @@ class EventLog(WBModel):
254
254
  data = models.JSONField(default=dict, null=True, blank=True)
255
255
 
256
256
  @classmethod
257
- def get_endpoint_basename(self):
257
+ def get_endpoint_basename(cls):
258
258
  return "wbintegrator_office365:eventlog"
259
259
 
260
260
  @classmethod
@@ -301,8 +301,10 @@ class CallUser(WBModel):
301
301
  contacts = TelephoneContact.objects.filter(number=self.tenant_user.tenant_id, entry__isnull=False)
302
302
  if contacts.exists():
303
303
  return str(contacts.first().entry)
304
- elif self.tenant_user.tenant_id[0] == "+":
304
+ elif self.tenant_user.tenant_id and self.tenant_user.tenant_id[0] == "+":
305
305
  return self.tenant_user.tenant_id
306
+ else:
307
+ return self.tenant_user.display_name
306
308
 
307
309
  def __str__(self):
308
310
  if repr := self.get_humanized_repr():
@@ -59,7 +59,7 @@ class Subscription(WBModel):
59
59
  )
60
60
 
61
61
  @classmethod
62
- def get_endpoint_basename(self):
62
+ def get_endpoint_basename(cls):
63
63
  return "wbintegrator_office365:subscription"
64
64
 
65
65
  @classmethod
@@ -1,5 +1,8 @@
1
+ import os
2
+ from collections import defaultdict
1
3
  from datetime import date, timedelta
2
4
 
5
+ import boto3
3
6
  import humanize
4
7
  from celery import shared_task
5
8
  from django.contrib.auth import get_user_model
@@ -23,6 +26,65 @@ def format_td(td: timedelta) -> str:
23
26
  return humanize.precisedelta(td, suppress=["hours"], minimum_unit="seconds", format="%0.0f")
24
27
 
25
28
 
29
+ #################################################
30
+ ################# TEMPORARY #####################
31
+ #################################################
32
+
33
+
34
+ def convert_user(user):
35
+ email = user["Attributes"][0]["Value"]
36
+ first = user["Attributes"][3]["Value"]
37
+ last = user["Attributes"][2]["Value"]
38
+
39
+ return f"{email} ({first} {last})"
40
+
41
+
42
+ def get_fundy_users():
43
+ access_key = os.environ["FUNDY_BOTO_ACCESS_KEY"]
44
+ secret_access_key = os.environ["FUNDY_BOTO_SECRET_ACCESS_KEY"]
45
+
46
+ client = boto3.client(
47
+ "cognito-idp", region_name="eu-north-1", aws_access_key_id=access_key, aws_secret_access_key=secret_access_key
48
+ )
49
+
50
+ pagination_token = None
51
+ users = []
52
+ while True:
53
+ if not pagination_token:
54
+ response = client.list_users(
55
+ UserPoolId="eu-north-1_IFRQriJAf",
56
+ )
57
+ else:
58
+ response = client.list_users(
59
+ UserPoolId="eu-north-1_IFRQriJAf",
60
+ PaginationToken=pagination_token,
61
+ )
62
+
63
+ users.extend(response["Users"])
64
+ if response.get("PaginationToken", None) is None:
65
+ break
66
+ else:
67
+ pagination_token = response["PaginationToken"]
68
+
69
+ users_date = defaultdict(list)
70
+ for user in sorted(users, key=lambda x: x["UserCreateDate"]):
71
+ users_date[user["UserCreateDate"].date()].append(convert_user(user))
72
+ return users_date, len(users)
73
+
74
+
75
+ def get_fundy_user_statistics():
76
+ today = date.today()
77
+ users, total_users = get_fundy_users()
78
+
79
+ yesterday_users = users.get(today - timedelta(days=1), [])
80
+ return yesterday_users, len(yesterday_users), total_users
81
+
82
+
83
+ #################################################
84
+ ################ TEMPORARY END ##################
85
+ #################################################
86
+
87
+
26
88
  @shared_task
27
89
  def send_call_summary(
28
90
  to_emails: list,
@@ -109,6 +171,21 @@ def send_call_summary(
109
171
  message += "</table><br/>"
110
172
 
111
173
  message += "</div>"
174
+
175
+ ######## TEMPORARY START ########
176
+ yesterday_users, yesterday_users_count, total_users_count = get_fundy_user_statistics()
177
+ message += f"""
178
+ <div>
179
+ <h3>FUNDY USER STATISTICS</h3>
180
+ <strong>Yesterday Users:</strong> {yesterday_users_count}
181
+ <strong>Total Users:</strong> {total_users_count}
182
+ <ul>
183
+ """
184
+ for user in yesterday_users:
185
+ message += f"<li>{user}</li>"
186
+ message += "</ul></div>"
187
+ ######## TEMPORARY END ########
188
+
112
189
  title = f"Call summary - {date_repr}"
113
190
  if include_detail:
114
191
  title = "Detailed " + title
@@ -6,6 +6,7 @@ from django.contrib.messages import get_messages, storage
6
6
  from rest_framework import status
7
7
  from rest_framework.test import APIRequestFactory
8
8
  from wbcore.test.utils import get_or_create_superuser
9
+
9
10
  from wbintegrator_office365.admin import SubscriptionAdmin, TenantUserAdmin
10
11
  from wbintegrator_office365.factories import TenantUserFactory
11
12
  from wbintegrator_office365.models import Subscription, TenantUser
@@ -1,6 +1,7 @@
1
1
  from unittest.mock import patch
2
2
 
3
3
  import pytest
4
+
4
5
  from wbintegrator_office365.factories import EventFactory, SubscriptionFactory
5
6
  from wbintegrator_office365.importer import DisableSignals
6
7
  from wbintegrator_office365.models import (
@@ -6,6 +6,7 @@ import phonenumbers
6
6
  import pytest
7
7
  from rest_framework import status
8
8
  from wbcore.contrib.directory.factories import TelephoneContactFactory
9
+
9
10
  from wbintegrator_office365.factories import SubscriptionFactory, TenantUserFactory
10
11
  from wbintegrator_office365.importer import parse
11
12
  from wbintegrator_office365.models import CallEvent, Subscription
@@ -7,6 +7,7 @@ from rest_framework.test import APIRequestFactory
7
7
  from wbcore.contrib.authentication.factories import InternalUserFactory
8
8
  from wbcore.test.utils import get_or_create_superuser
9
9
  from wbhuman_resources.factories import EmployeeHumanResourceFactory
10
+
10
11
  from wbintegrator_office365.factories import CallEventFactory, CallUserFactory
11
12
  from wbintegrator_office365.viewsets.viewsets import (
12
13
  CallEventReceptionTime,
@@ -63,7 +64,7 @@ class TestView:
63
64
  )
64
65
  @patch("wbintegrator_office365.importer.MicrosoftGraphAPI._get_access_token")
65
66
  @patch("wbintegrator_office365.importer.MicrosoftGraphAPI.users")
66
- def test_get_plotly_CallEventSummaryGraph(self, mock_users, mock_acess, mvs, factory, empty_compare_employee):
67
+ def test_get_plotly_calleventsummarygraph(self, mock_users, mock_acess, mvs, factory, empty_compare_employee):
67
68
  request = APIRequestFactory().get("")
68
69
  request.user = get_or_create_superuser()
69
70
 
@@ -445,7 +445,7 @@ class CallEventSummaryGraph(viewsets.ChartViewSet):
445
445
  df = df.sort_values(by=["day"]).reset_index()
446
446
  return df
447
447
 
448
- def get_plotly(self, queryset):
448
+ def get_plotly(self, queryset): # noqa: C901
449
449
  fig = go.Figure()
450
450
  if queryset.exists():
451
451
  df = pd.DataFrame(queryset.annotate(day=F("created__date")).values("start", "end", "day"))
@@ -740,5 +740,5 @@ def listen(request):
740
740
  for notification in notifications:
741
741
  # print(colored(f"{timezone.now():%Y-%m-%d %H:%M:%S.%f} {notification}", 'blue'))
742
742
  if (resource_data := notification.get("resource_data")) and (id_event := resource_data.get("id")):
743
- transaction.on_commit(lambda: handle_event_from_webhook.delay(id_event, notification))
743
+ transaction.on_commit(lambda obj=notification: handle_event_from_webhook.delay(id_event, obj))
744
744
  return HttpResponse(notifications, content_type="text/plain")