wbintegrator_office365 1.55.3__py2.py3-none-any.whl → 1.55.5__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wbintegrator_office365/models/event.py +11 -3
- wbintegrator_office365/tasks.py +101 -56
- {wbintegrator_office365-1.55.3.dist-info → wbintegrator_office365-1.55.5.dist-info}/METADATA +1 -1
- {wbintegrator_office365-1.55.3.dist-info → wbintegrator_office365-1.55.5.dist-info}/RECORD +5 -5
- {wbintegrator_office365-1.55.3.dist-info → wbintegrator_office365-1.55.5.dist-info}/WHEEL +0 -0
@@ -290,15 +290,23 @@ class CallUser(WBModel):
|
|
290
290
|
on_delete=models.deletion.SET_NULL,
|
291
291
|
)
|
292
292
|
|
293
|
-
def
|
293
|
+
def get_humanized_repr(self) -> str | None:
|
294
294
|
if self.tenant_user:
|
295
295
|
if self.tenant_user.profile:
|
296
|
-
return
|
296
|
+
return str(self.tenant_user.profile)
|
297
297
|
elif self.tenant_user.mail or self.tenant_user.display_name:
|
298
298
|
mail = self.tenant_user.mail if self.tenant_user.mail else self.tenant_user.id
|
299
299
|
return f"{self.tenant_user.display_name}({mail})"
|
300
300
|
else:
|
301
|
-
|
301
|
+
contacts = TelephoneContact.objects.filter(number=self.tenant_user.tenant_id, entry__isnull=False)
|
302
|
+
if contacts.exists():
|
303
|
+
return str(contacts.first().entry)
|
304
|
+
elif self.tenant_user.tenant_id[0] == "+":
|
305
|
+
return self.tenant_user.tenant_id
|
306
|
+
|
307
|
+
def __str__(self):
|
308
|
+
if repr := self.get_humanized_repr():
|
309
|
+
return repr
|
302
310
|
return f"{self.id}"
|
303
311
|
|
304
312
|
@classmethod
|
wbintegrator_office365/tasks.py
CHANGED
@@ -1,77 +1,122 @@
|
|
1
|
-
import math
|
2
1
|
from datetime import date, timedelta
|
3
2
|
|
3
|
+
import humanize
|
4
4
|
from celery import shared_task
|
5
5
|
from django.contrib.auth import get_user_model
|
6
|
-
from django.
|
6
|
+
from django.contrib.auth.models import Group
|
7
|
+
from django.db.models import DurationField, ExpressionWrapper, F, Q
|
7
8
|
from wbcore.contrib.directory.models import Person
|
8
9
|
from wbcore.contrib.notifications.dispatch import send_notification
|
10
|
+
from wbcore.permissions.shortcuts import get_internal_users
|
9
11
|
|
10
12
|
from wbintegrator_office365.importer import MicrosoftGraphAPI
|
11
13
|
from wbintegrator_office365.models.event import CallEvent
|
12
14
|
from wbintegrator_office365.models.subscription import Subscription
|
13
15
|
|
14
16
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def format_td(td: timedelta) -> str:
|
18
|
+
total_seconds = td.total_seconds()
|
19
|
+
if total_seconds == 0:
|
20
|
+
return "Missed"
|
21
|
+
elif total_seconds < 60:
|
22
|
+
return "< 1min"
|
23
|
+
return humanize.precisedelta(td, suppress=["hours"], minimum_unit="seconds", format="%0.0f")
|
22
24
|
|
23
|
-
message = "<html><head><style> #summery_table tr:nth-child(even){background-color: #f2f2f2;}</style></head><body>"
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
26
|
+
@shared_task
|
27
|
+
def send_call_summary(
|
28
|
+
to_emails: list,
|
29
|
+
profile_ids: list[int] | None = None,
|
30
|
+
group_id: int | None = None,
|
31
|
+
offset: int = 0,
|
32
|
+
include_detail: bool = True,
|
33
|
+
):
|
34
|
+
internal_users = get_internal_users().filter(is_active=True)
|
35
|
+
profiles = Person.objects.filter(user_account__in=internal_users)
|
36
|
+
if profile_ids:
|
37
|
+
profiles = profiles.filter(id__in=profile_ids)
|
38
|
+
elif group_id:
|
39
|
+
profiles = profiles.filter(user_account__in=Group.objects.get(id=group_id).user_set.all())
|
31
40
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
<th style='border: 1px solid #ddd;padding: 10px 7px;' >Organized by</th> \
|
44
|
-
<th style='border: 1px solid #ddd;padding: 10px 7px;' >Participants</th> \
|
45
|
-
</tr>"
|
46
|
-
for call in call_events:
|
47
|
-
participants = ""
|
48
|
-
count = 0
|
49
|
-
total_participants = call.participants.exclude(tenant_user__profile=profile).count()
|
50
|
-
for participant in call.participants.exclude(tenant_user__profile=profile):
|
51
|
-
count += 1
|
52
|
-
participants += f"{participant.__str__()}"
|
53
|
-
if count < total_participants:
|
54
|
-
participants += ", "
|
41
|
+
end_date = date.today()
|
42
|
+
start_date = end_date - timedelta(days=offset)
|
43
|
+
if offset == 0:
|
44
|
+
frequency_repr = "Daily"
|
45
|
+
date_repr = start_date.strftime("%Y-%m-%d")
|
46
|
+
elif offset == 7:
|
47
|
+
frequency_repr = "Weekly"
|
48
|
+
date_repr = f"From {start_date:%Y-%m-%d} To {end_date:%Y-%m-%d}"
|
49
|
+
else:
|
50
|
+
frequency_repr = f"{offset} days"
|
51
|
+
date_repr = f"From {start_date:%Y-%m-%d} To {end_date:%Y-%m-%d}"
|
55
52
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
53
|
+
date_repr = f"{date_repr} ({frequency_repr})"
|
54
|
+
calls = CallEvent.objects.filter(
|
55
|
+
start__date__gte=start_date,
|
56
|
+
end__date__lte=end_date,
|
57
|
+
).annotate(duration=ExpressionWrapper(F("end") - F("start"), output_field=DurationField()))
|
58
|
+
if profiles.exists():
|
59
|
+
message = """
|
60
|
+
<div style="background-color: white; width: 720px; margin-bottom: 50px">
|
61
|
+
"""
|
62
|
+
for profile in profiles:
|
63
|
+
call_events = calls.filter(
|
64
|
+
participants__tenant_user__profile=profile,
|
65
|
+
).order_by("start")
|
66
66
|
|
67
|
-
message += "
|
67
|
+
message += f"""
|
68
|
+
<div style="text-align: left;">
|
69
|
+
<p><b>{profile.computed_str}</b></p>
|
70
|
+
<table width="100%; table-layout: fixed; border-collapse: collapse;">
|
71
|
+
<tr>
|
72
|
+
<td style="width: 33.33%; text-align: center;">Total Calls: <b>{call_events.count()}</b></td>
|
73
|
+
<td style="width: 33.33%; text-align: center;">under 1 minute: <b>{call_events.filter(duration__lte=timedelta(seconds=60)).count()}</b></td>
|
74
|
+
<td style="width: 33.33%; text-align: center;">above 1 minute: <b>{call_events.filter(duration__gt=timedelta(seconds=60)).count()}</b></td>
|
75
|
+
</tr>
|
76
|
+
</table>
|
77
|
+
</div>
|
78
|
+
"""
|
79
|
+
if include_detail:
|
80
|
+
for call_date in call_events.dates("start", "day"):
|
81
|
+
call_day_events = call_events.filter(start__date=call_date)
|
82
|
+
if call_day_events.exists():
|
83
|
+
message += f"<p><b>{call_date:%Y-%m-%d}:</b></p>"
|
84
|
+
message += "<table style='border-collapse: collapse; width: 720px; table-layout: fixed;'> \
|
85
|
+
<tr style='color: white; background-color: #1868ae;'> \
|
86
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >Start</th> \
|
87
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 20px;' >End</th> \
|
88
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 60px;' >Duration</th> \
|
89
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 80px;' >Organized by</th> \
|
90
|
+
<th style='border: 1px solid #ddd;padding: 2px 7px; width: 150px;' >Participants</th> \
|
91
|
+
</tr>"
|
92
|
+
for call in call_day_events:
|
93
|
+
participants = ",".join(
|
94
|
+
filter(
|
95
|
+
None,
|
96
|
+
[
|
97
|
+
p.get_humanized_repr()
|
98
|
+
for p in call.participants.exclude(tenant_user__profile=profile)
|
99
|
+
],
|
100
|
+
)
|
101
|
+
)
|
102
|
+
message += f"<tr> \
|
103
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.start.astimezone():%H:%M}</td> \
|
104
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 20px;' >{call.end.astimezone():%H:%M}</td> \
|
105
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 60px;' text-align:center;><b>{format_td(call.end - call.start)}</b></td> \
|
106
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 80px;' ><b>{call.organizer}</b></td> \
|
107
|
+
<td style='border: 1px solid #ddd;padding: 2px; width: 150px;' >{participants}</td> \
|
108
|
+
</tr>"
|
109
|
+
message += "</table><br/>"
|
68
110
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
111
|
+
message += "</div>"
|
112
|
+
for to_email in to_emails:
|
113
|
+
recipient = get_user_model().objects.get(email=to_email)
|
114
|
+
send_notification(
|
115
|
+
code="wbintegrator_office365.callevent.call_summary",
|
116
|
+
title=f"Call summary - {date_repr}",
|
117
|
+
body=message,
|
118
|
+
user=recipient,
|
119
|
+
)
|
75
120
|
|
76
121
|
|
77
122
|
@shared_task
|
@@ -5,7 +5,7 @@ wbintegrator_office365/dynamic_preferences_registry.py,sha256=RIptl8WGibV_XMynXl
|
|
5
5
|
wbintegrator_office365/factories.py,sha256=pG8moGurbSElIugs_Gz3nd7-UJt3vC6mGy-EZQIeldI,3872
|
6
6
|
wbintegrator_office365/filters.py,sha256=bSX1jrP1SrqiGkysoX__AqPRUPSU-uQq_-tOHinqijU,7447
|
7
7
|
wbintegrator_office365/serializers.py,sha256=tR_HdsyxwCZo2_sOdhj2NErOEjTRyoIXA6DRR-BjVKs,8097
|
8
|
-
wbintegrator_office365/tasks.py,sha256=
|
8
|
+
wbintegrator_office365/tasks.py,sha256=fwLE3YuV-zK71oD4xRNCkAgneD_PweSfu7XfQXIlAiY,7672
|
9
9
|
wbintegrator_office365/urls.py,sha256=qfIIb3CNJw40xhGJnE7ztEMt2l-kgnRXA_P8Y3sDlPg,1922
|
10
10
|
wbintegrator_office365/configurations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
11
11
|
wbintegrator_office365/configurations/configurations/__init__.py,sha256=tIBWiG0KWrDJ1xtKe5m1W9aKX5PMNLwsYuoy7xrQ4uI,1145
|
@@ -20,7 +20,7 @@ wbintegrator_office365/migrations/0002_remove_calendar_owner_remove_calendareven
|
|
20
20
|
wbintegrator_office365/migrations/0003_alter_event_options.py,sha256=rUYPQdwEuJowo10veLOZPtV3WMqxJu8_uan600EcpN4,585
|
21
21
|
wbintegrator_office365/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
22
22
|
wbintegrator_office365/models/__init__.py,sha256=sZKutR9oNMm2IaON35doaMVPgeMfxmWDqoAdALHRLIA,126
|
23
|
-
wbintegrator_office365/models/event.py,sha256=
|
23
|
+
wbintegrator_office365/models/event.py,sha256=Uj4HhNrTR9c9IGSi3HdyaZb4IqZnPbXTU7wLcNsBecg,26994
|
24
24
|
wbintegrator_office365/models/subscription.py,sha256=EnesSa2OnuygsyfXXY8ABYOjgOaUv9Jj3c6OVQOPWyU,6424
|
25
25
|
wbintegrator_office365/models/tenant.py,sha256=-FS6jg9nt8IgUv_729D92QFvrrbKleBUGnXhwaLv5Sc,2357
|
26
26
|
wbintegrator_office365/templates/admin/tenant_change_list.html,sha256=mI4C1ZmTkl5MdN4CgoIWgCziSReqlHiK71aI7zJpVEM,358
|
@@ -37,6 +37,6 @@ wbintegrator_office365/viewsets/endpoints.py,sha256=NxEdH-tKk87mJdSNxpOZdN2z0AhY
|
|
37
37
|
wbintegrator_office365/viewsets/menu.py,sha256=hafwoiHZmN3ltf30dTfwHreDwhyfdHlGOQk8bB7jB_o,2406
|
38
38
|
wbintegrator_office365/viewsets/titles.py,sha256=9GZ_fqN9-sJzYHFvibOhNrRQQMNTh_m4eAv66VaGzDM,1214
|
39
39
|
wbintegrator_office365/viewsets/viewsets.py,sha256=T9u8vViyeHrK1xzPbQqIKXgpf4qM-5KkPzbZGdLLujA,26331
|
40
|
-
wbintegrator_office365-1.55.
|
41
|
-
wbintegrator_office365-1.55.
|
42
|
-
wbintegrator_office365-1.55.
|
40
|
+
wbintegrator_office365-1.55.5.dist-info/METADATA,sha256=qpaj5CKWjJm98pmj3Ai6R8SqBNVFla6SbDqjuKRHRsM,304
|
41
|
+
wbintegrator_office365-1.55.5.dist-info/WHEEL,sha256=tkmg4JIqwd9H8mL30xA7crRmoStyCtGp0VWshokd1Jc,105
|
42
|
+
wbintegrator_office365-1.55.5.dist-info/RECORD,,
|
File without changes
|