wbcrm 1.56.8__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.
- wbcrm/__init__.py +1 -0
- wbcrm/admin/__init__.py +5 -0
- wbcrm/admin/accounts.py +60 -0
- wbcrm/admin/activities.py +104 -0
- wbcrm/admin/events.py +43 -0
- wbcrm/admin/groups.py +8 -0
- wbcrm/admin/products.py +9 -0
- wbcrm/apps.py +5 -0
- wbcrm/configurations/__init__.py +1 -0
- wbcrm/configurations/base.py +16 -0
- wbcrm/dynamic_preferences_registry.py +38 -0
- wbcrm/factories/__init__.py +14 -0
- wbcrm/factories/accounts.py +57 -0
- wbcrm/factories/activities.py +124 -0
- wbcrm/factories/groups.py +24 -0
- wbcrm/factories/products.py +11 -0
- wbcrm/filters/__init__.py +10 -0
- wbcrm/filters/accounts.py +80 -0
- wbcrm/filters/activities.py +204 -0
- wbcrm/filters/groups.py +21 -0
- wbcrm/filters/products.py +38 -0
- wbcrm/filters/signals.py +95 -0
- wbcrm/fixtures/wbcrm.json +1215 -0
- wbcrm/kpi_handlers/activities.py +171 -0
- wbcrm/locale/de/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/de/LC_MESSAGES/django.po +1557 -0
- wbcrm/locale/de/LC_MESSAGES/django.po.translated +1630 -0
- wbcrm/locale/en/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/en/LC_MESSAGES/django.po +1466 -0
- wbcrm/locale/fr/LC_MESSAGES/django.mo +0 -0
- wbcrm/locale/fr/LC_MESSAGES/django.po +1467 -0
- wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
- wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
- wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
- wbcrm/migrations/0004_alter_activity_status.py +28 -0
- wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
- wbcrm/migrations/0006_alter_activity_location.py +17 -0
- wbcrm/migrations/0007_alter_account_status.py +23 -0
- wbcrm/migrations/0008_alter_activity_options.py +16 -0
- wbcrm/migrations/0009_alter_account_is_public.py +19 -0
- wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
- wbcrm/migrations/0011_activity_summary.py +22 -0
- wbcrm/migrations/0012_alter_activity_summary.py +17 -0
- wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
- wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
- wbcrm/migrations/0015_alter_activity_type.py +23 -0
- wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
- wbcrm/migrations/0017_event.py +40 -0
- wbcrm/migrations/0018_activity_search_vector.py +24 -0
- wbcrm/migrations/__init__.py +0 -0
- wbcrm/models/__init__.py +5 -0
- wbcrm/models/accounts.py +648 -0
- wbcrm/models/activities.py +1419 -0
- wbcrm/models/events.py +15 -0
- wbcrm/models/groups.py +119 -0
- wbcrm/models/llm/activity_summaries.py +41 -0
- wbcrm/models/llm/analyze_relationship.py +50 -0
- wbcrm/models/products.py +86 -0
- wbcrm/models/recurrence.py +280 -0
- wbcrm/preferences.py +13 -0
- wbcrm/report/activity_report.py +110 -0
- wbcrm/serializers/__init__.py +23 -0
- wbcrm/serializers/accounts.py +141 -0
- wbcrm/serializers/activities.py +525 -0
- wbcrm/serializers/groups.py +30 -0
- wbcrm/serializers/products.py +58 -0
- wbcrm/serializers/recurrence.py +91 -0
- wbcrm/serializers/signals.py +71 -0
- wbcrm/static/wbcrm/markdown/documentation/activity.md +86 -0
- wbcrm/static/wbcrm/markdown/documentation/activitytype.md +20 -0
- wbcrm/static/wbcrm/markdown/documentation/group.md +2 -0
- wbcrm/static/wbcrm/markdown/documentation/product.md +11 -0
- wbcrm/synchronization/__init__.py +0 -0
- wbcrm/synchronization/activity/__init__.py +0 -0
- wbcrm/synchronization/activity/admin.py +73 -0
- wbcrm/synchronization/activity/backend.py +214 -0
- wbcrm/synchronization/activity/backends/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
- wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +406 -0
- wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/create.py +75 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/delete.py +78 -0
- wbcrm/synchronization/activity/backends/google/request_utils/external_to_internal/update.py +155 -0
- wbcrm/synchronization/activity/backends/google/request_utils/internal_to_external/update.py +181 -0
- wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
- wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
- wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
- wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
- wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
- wbcrm/synchronization/activity/backends/google/utils.py +217 -0
- wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/backend.py +593 -0
- wbcrm/synchronization/activity/backends/outlook/msgraph.py +436 -0
- wbcrm/synchronization/activity/backends/outlook/parser.py +432 -0
- wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
- wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
- wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +118 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +274 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +249 -0
- wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +174 -0
- wbcrm/synchronization/activity/controller.py +627 -0
- wbcrm/synchronization/activity/dynamic_preferences_registry.py +119 -0
- wbcrm/synchronization/activity/preferences.py +27 -0
- wbcrm/synchronization/activity/shortcuts.py +16 -0
- wbcrm/synchronization/activity/tasks.py +21 -0
- wbcrm/synchronization/activity/urls.py +7 -0
- wbcrm/synchronization/activity/utils.py +46 -0
- wbcrm/synchronization/activity/views.py +41 -0
- wbcrm/synchronization/admin.py +1 -0
- wbcrm/synchronization/apps.py +14 -0
- wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
- wbcrm/synchronization/management.py +36 -0
- wbcrm/synchronization/tasks.py +1 -0
- wbcrm/synchronization/urls.py +5 -0
- wbcrm/tasks.py +264 -0
- wbcrm/templates/email/activity.html +98 -0
- wbcrm/templates/email/activity_report.html +6 -0
- wbcrm/templates/email/daily_summary.html +72 -0
- wbcrm/templates/email/global_daily_summary.html +85 -0
- wbcrm/tests/__init__.py +0 -0
- wbcrm/tests/accounts/__init__.py +0 -0
- wbcrm/tests/accounts/test_models.py +393 -0
- wbcrm/tests/accounts/test_viewsets.py +88 -0
- wbcrm/tests/conftest.py +76 -0
- wbcrm/tests/disable_signals.py +62 -0
- wbcrm/tests/e2e/__init__.py +1 -0
- wbcrm/tests/e2e/e2e_wbcrm_utility.py +83 -0
- wbcrm/tests/e2e/test_e2e.py +370 -0
- wbcrm/tests/test_assignee_methods.py +40 -0
- wbcrm/tests/test_chartviewsets.py +112 -0
- wbcrm/tests/test_dto.py +64 -0
- wbcrm/tests/test_filters.py +52 -0
- wbcrm/tests/test_models.py +217 -0
- wbcrm/tests/test_recurrence.py +292 -0
- wbcrm/tests/test_report.py +21 -0
- wbcrm/tests/test_serializers.py +171 -0
- wbcrm/tests/test_tasks.py +95 -0
- wbcrm/tests/test_viewsets.py +967 -0
- wbcrm/tests/tests.py +121 -0
- wbcrm/typings.py +109 -0
- wbcrm/urls.py +67 -0
- wbcrm/viewsets/__init__.py +22 -0
- wbcrm/viewsets/accounts.py +122 -0
- wbcrm/viewsets/activities.py +341 -0
- wbcrm/viewsets/buttons/__init__.py +7 -0
- wbcrm/viewsets/buttons/accounts.py +27 -0
- wbcrm/viewsets/buttons/activities.py +89 -0
- wbcrm/viewsets/buttons/signals.py +17 -0
- wbcrm/viewsets/display/__init__.py +12 -0
- wbcrm/viewsets/display/accounts.py +110 -0
- wbcrm/viewsets/display/activities.py +444 -0
- wbcrm/viewsets/display/groups.py +22 -0
- wbcrm/viewsets/display/products.py +105 -0
- wbcrm/viewsets/endpoints/__init__.py +8 -0
- wbcrm/viewsets/endpoints/accounts.py +25 -0
- wbcrm/viewsets/endpoints/activities.py +30 -0
- wbcrm/viewsets/endpoints/groups.py +7 -0
- wbcrm/viewsets/endpoints/products.py +9 -0
- wbcrm/viewsets/groups.py +38 -0
- wbcrm/viewsets/menu/__init__.py +8 -0
- wbcrm/viewsets/menu/accounts.py +18 -0
- wbcrm/viewsets/menu/activities.py +49 -0
- wbcrm/viewsets/menu/groups.py +16 -0
- wbcrm/viewsets/menu/products.py +20 -0
- wbcrm/viewsets/mixins.py +35 -0
- wbcrm/viewsets/previews/__init__.py +1 -0
- wbcrm/viewsets/previews/activities.py +10 -0
- wbcrm/viewsets/products.py +57 -0
- wbcrm/viewsets/recurrence.py +27 -0
- wbcrm/viewsets/titles/__init__.py +13 -0
- wbcrm/viewsets/titles/accounts.py +23 -0
- wbcrm/viewsets/titles/activities.py +61 -0
- wbcrm/viewsets/titles/products.py +13 -0
- wbcrm/viewsets/titles/utils.py +46 -0
- wbcrm/workflows/__init__.py +1 -0
- wbcrm/workflows/assignee_methods.py +25 -0
- wbcrm-1.56.8.dist-info/METADATA +11 -0
- wbcrm-1.56.8.dist-info/RECORD +182 -0
- wbcrm-1.56.8.dist-info/WHEEL +5 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from typing import Dict, List, TypedDict
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _GoogleEntityInfo(TypedDict):
|
|
5
|
+
id: str
|
|
6
|
+
email: str
|
|
7
|
+
displayName: str
|
|
8
|
+
self: bool
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TimeInfo(TypedDict):
|
|
12
|
+
date: str
|
|
13
|
+
dateTime: str
|
|
14
|
+
timeZone: str
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _AttendeeInfo(_GoogleEntityInfo):
|
|
18
|
+
organizer: bool
|
|
19
|
+
resource: bool
|
|
20
|
+
optional: bool
|
|
21
|
+
responseStatus: str
|
|
22
|
+
comment: str
|
|
23
|
+
additionalGuests: int
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class _ExtendedPropertiesInfo(TypedDict):
|
|
27
|
+
private: Dict[any, str]
|
|
28
|
+
shared: Dict[any, str]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class _CreateRequestInfo(TypedDict):
|
|
32
|
+
requestId: str
|
|
33
|
+
conferenceSolutionKey: Dict[str, str]
|
|
34
|
+
status: Dict[str, str]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _ConferenceSolutionInfo(TypedDict):
|
|
38
|
+
key: Dict[any, str]
|
|
39
|
+
name: str
|
|
40
|
+
iconUri: str
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _EntryPointInfo(TypedDict):
|
|
44
|
+
entryPointType: str
|
|
45
|
+
uri: str
|
|
46
|
+
label: str
|
|
47
|
+
pin: str
|
|
48
|
+
accessCode: str
|
|
49
|
+
meetingCode: str
|
|
50
|
+
passcode: str
|
|
51
|
+
password: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _ConferenceDataInfo(TypedDict):
|
|
55
|
+
createRequest: _CreateRequestInfo
|
|
56
|
+
entryPoints: List[_EntryPointInfo]
|
|
57
|
+
conferenceSolution: _ConferenceSolutionInfo
|
|
58
|
+
conferenceId: str
|
|
59
|
+
signature: str
|
|
60
|
+
notes: str
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _GadgetInfo(TypedDict):
|
|
64
|
+
type: str
|
|
65
|
+
title: str
|
|
66
|
+
link: str
|
|
67
|
+
iconLink: str
|
|
68
|
+
width: int
|
|
69
|
+
height: int
|
|
70
|
+
display: str
|
|
71
|
+
preferences: Dict[any, str]
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _OverridesInfo(TypedDict):
|
|
75
|
+
method: str
|
|
76
|
+
minutes: int
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class _RemindersInfo(TypedDict):
|
|
80
|
+
useDefault: bool
|
|
81
|
+
overrides: List[_OverridesInfo]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class _URLInfo(TypedDict):
|
|
85
|
+
url: str
|
|
86
|
+
title: str
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class _AttachmentInfo(TypedDict):
|
|
90
|
+
fileUrl: str
|
|
91
|
+
title: str
|
|
92
|
+
mimeType: str
|
|
93
|
+
iconLink: str
|
|
94
|
+
fileId: str
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class GoogleEventType(TypedDict):
|
|
98
|
+
"""
|
|
99
|
+
The typing information for the Google event dictionary
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
kind: str
|
|
103
|
+
id: str
|
|
104
|
+
status: str
|
|
105
|
+
htmlLink: str
|
|
106
|
+
created: str
|
|
107
|
+
updated: str
|
|
108
|
+
summary: str
|
|
109
|
+
description: str
|
|
110
|
+
location: str
|
|
111
|
+
colorId: str
|
|
112
|
+
creator: _GoogleEntityInfo
|
|
113
|
+
organizer: _GoogleEntityInfo
|
|
114
|
+
start: TimeInfo
|
|
115
|
+
end: TimeInfo
|
|
116
|
+
endTimeUnspecified: bool
|
|
117
|
+
recurrence: List[str]
|
|
118
|
+
recurringEventId: str
|
|
119
|
+
originalStartTime: TimeInfo
|
|
120
|
+
transparency: str
|
|
121
|
+
visibility: str
|
|
122
|
+
iCalUID: str
|
|
123
|
+
sequence: int
|
|
124
|
+
attendees: List[_AttendeeInfo]
|
|
125
|
+
attendeesOmitted: bool
|
|
126
|
+
extendedProperties: _ExtendedPropertiesInfo
|
|
127
|
+
hangoutLink: str
|
|
128
|
+
conferenceData: _ConferenceDataInfo
|
|
129
|
+
gadget: _GadgetInfo
|
|
130
|
+
anyoneCanAddSelf: bool
|
|
131
|
+
guestsCanInviteOthers: bool
|
|
132
|
+
guestsCanModify: bool
|
|
133
|
+
guestsCanSeeOtherGuests: bool
|
|
134
|
+
privateCopy: bool
|
|
135
|
+
locked: bool
|
|
136
|
+
reminders: _RemindersInfo
|
|
137
|
+
source: _URLInfo
|
|
138
|
+
attachments: List[_AttachmentInfo]
|
|
139
|
+
eventType: str
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
from zoneinfo import ZoneInfo
|
|
4
|
+
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
from django.db import models
|
|
7
|
+
from dynamic_preferences.registries import global_preferences_registry
|
|
8
|
+
from wbcore.contrib.agenda.models import CalendarItem
|
|
9
|
+
from wbcore.contrib.directory.models import EmailContact, Person
|
|
10
|
+
|
|
11
|
+
from wbcrm.models import Activity, ActivityParticipant
|
|
12
|
+
from wbcrm.synchronization.activity.preferences import (
|
|
13
|
+
can_synchronize_activity_description,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from .typing_informations import GoogleEventType, TimeInfo
|
|
17
|
+
|
|
18
|
+
global_preferences = global_preferences_registry.manager()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class GoogleSyncUtils:
|
|
22
|
+
@classmethod
|
|
23
|
+
def convert_event_visibility_to_activity_visibility(cls, event_visiblity: str):
|
|
24
|
+
if event_visiblity == "public":
|
|
25
|
+
return CalendarItem.Visibility.PUBLIC
|
|
26
|
+
return CalendarItem.Visibility.PRIVATE
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def convert_activity_participants_to_attendees(cls, activity: Activity, event: GoogleEventType | None = None):
|
|
30
|
+
"""
|
|
31
|
+
A method for converting the participants of a "Workbench" activity to a list of dictionaries, that can be used to create "Google Event" attendees.
|
|
32
|
+
|
|
33
|
+
:param activity: The activity from which the participants needs to be converted.
|
|
34
|
+
:return: The list with the dictionaries of attendees. Can be empty if no participant is in the original activity.
|
|
35
|
+
"""
|
|
36
|
+
attendees_list = []
|
|
37
|
+
event_attendees = cls.get_or_create_participants(event, activity.creator) if event else []
|
|
38
|
+
|
|
39
|
+
event_attendees = [Person.all_objects.get(id=person_dict["person_id"]) for person_dict in event_attendees]
|
|
40
|
+
can_sync_external_participants: bool = global_preferences["wbactivity_sync__sync_external_participants"]
|
|
41
|
+
allowed_participants: models.QuerySet["ActivityParticipant"] = activity.activity_participants.filter(
|
|
42
|
+
participant__in=event_attendees,
|
|
43
|
+
).union(activity.activity_participants.filter(participant__id__in=Person.objects.filter_only_internal()))
|
|
44
|
+
activity_participants: models.QuerySet["ActivityParticipant"] = (
|
|
45
|
+
activity.activity_participants.all() if can_sync_external_participants else allowed_participants
|
|
46
|
+
)
|
|
47
|
+
for activity_participant in activity_participants:
|
|
48
|
+
participant = activity_participant.participant
|
|
49
|
+
status = cls.convert_participant_status_to_attendee_status(activity_participant.participation_status)
|
|
50
|
+
attendees_list.append(
|
|
51
|
+
{
|
|
52
|
+
"displayName": participant.computed_str,
|
|
53
|
+
"email": str(participant.primary_email_contact()),
|
|
54
|
+
"responseStatus": status,
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
return attendees_list
|
|
58
|
+
|
|
59
|
+
@classmethod
|
|
60
|
+
def convert_activity_to_event(cls, activity: Activity, created=False):
|
|
61
|
+
"""
|
|
62
|
+
Converts a "Workbench" activity into a dict, that can be used to create a "Google Event" instance.
|
|
63
|
+
|
|
64
|
+
:param activity: A "Workbench" activity. The activity that is converted to a dict.
|
|
65
|
+
:param created: A boolean. This should be True if the activity is created, in every other case it should be False. Per default it is False.
|
|
66
|
+
:returns: A dictionary which can be used to create a google event.
|
|
67
|
+
"""
|
|
68
|
+
# If the activity instance is to be created (e.g. pre_save when creating the activity) we cannot interact with the activity_participants.
|
|
69
|
+
participants_list = cls.convert_activity_participants_to_attendees(activity=activity)
|
|
70
|
+
timezone = ZoneInfo(settings.TIME_ZONE)
|
|
71
|
+
recurrence = []
|
|
72
|
+
if not created:
|
|
73
|
+
if (google_backend := activity.metadata.get("google_backend")) and (event := google_backend.get("event")):
|
|
74
|
+
recurrence = event.get("recurrence", [])
|
|
75
|
+
elif wb_recurrence := activity.metadata.get("recurrence"):
|
|
76
|
+
recurrence = [wb_recurrence]
|
|
77
|
+
else:
|
|
78
|
+
if wb_recurrence := activity.metadata.get("recurrence"):
|
|
79
|
+
recurrence = [wb_recurrence]
|
|
80
|
+
else:
|
|
81
|
+
recurrence = []
|
|
82
|
+
event_body = {
|
|
83
|
+
"summary": activity.title,
|
|
84
|
+
"creator": str(activity.creator.primary_email_contact()) if activity.creator else "",
|
|
85
|
+
"organizer": str(activity.assigned_to.primary_email_contact()) if activity.assigned_to else "",
|
|
86
|
+
"attendees": participants_list,
|
|
87
|
+
"description": activity.description if can_synchronize_activity_description else "",
|
|
88
|
+
"start": {
|
|
89
|
+
"dateTime": activity.period.lower.astimezone(timezone).isoformat(), # type: ignore
|
|
90
|
+
"timeZone": settings.TIME_ZONE,
|
|
91
|
+
},
|
|
92
|
+
"end": {
|
|
93
|
+
"dateTime": activity.period.upper.astimezone(timezone).isoformat(), # type: ignore
|
|
94
|
+
"timeZone": settings.TIME_ZONE,
|
|
95
|
+
},
|
|
96
|
+
"recurrence": recurrence,
|
|
97
|
+
"location": activity.location,
|
|
98
|
+
"visibility": cls.convert_activity_visibility_to_event_visibility(activity.visibility),
|
|
99
|
+
"reminders": {
|
|
100
|
+
"useDefault": False,
|
|
101
|
+
"overrides": [
|
|
102
|
+
{
|
|
103
|
+
"method": "email",
|
|
104
|
+
"minutes": Activity.ReminderChoice.get_minutes_correspondance(activity.reminder_choice),
|
|
105
|
+
}
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
}
|
|
109
|
+
return event_body
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def get_start_and_end(cls, event: GoogleEventType):
|
|
113
|
+
"""
|
|
114
|
+
Converts the google start & end times to a datetime format.
|
|
115
|
+
|
|
116
|
+
A google event dict contains either a dateTime-key or a date-key. If a date-key is present, this indicates that the event is an all day event.
|
|
117
|
+
|
|
118
|
+
:param event: A google event dictionary from which the start & and end time will be extracted and converted.
|
|
119
|
+
|
|
120
|
+
:note: If a google event takes place all day, the start date and end date will not be the same date.
|
|
121
|
+
For example, an event that will take place on the 01.06.2020 will have "2020-06-01" as the start date value and "2020-06-02" as the end date value.
|
|
122
|
+
A workbench activity will work with "datetime" values, even when the activity is an all-day activity.
|
|
123
|
+
Therefor the start date will be converted in a "datetime" object like "2020-06-01-00-00-00" and the end date will be converted to "2020-06-01-23-59-59"
|
|
124
|
+
"""
|
|
125
|
+
event_start, event_end = None, None
|
|
126
|
+
start, end = event["start"], event["end"]
|
|
127
|
+
if start_datetime := start.get("dateTime"):
|
|
128
|
+
event_start = datetime.strptime(start_datetime, "%Y-%m-%dT%H:%M:%S%z")
|
|
129
|
+
else:
|
|
130
|
+
event_start = datetime.strptime(start["date"], "%Y-%m-%d")
|
|
131
|
+
if end_datetime := end.get("dateTime"):
|
|
132
|
+
event_end = datetime.strptime(end_datetime, "%Y-%m-%dT%H:%M:%S%z")
|
|
133
|
+
else:
|
|
134
|
+
event_end = datetime.strptime(end["date"], "%Y-%m-%d") - timedelta(seconds=1)
|
|
135
|
+
return event_start, event_end
|
|
136
|
+
|
|
137
|
+
@classmethod
|
|
138
|
+
def add_instance_metadata(
|
|
139
|
+
cls, parent_occurrence: Activity, google_event_items: List[GoogleEventType], new_metadata: Dict, created=False
|
|
140
|
+
) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Adds the information of google event instances to the corresponding activities metadata field.
|
|
143
|
+
|
|
144
|
+
:param parent_occurrence: The "Workbench" parent activity. It is used to retrieve the child activities.
|
|
145
|
+
:google_event_items: The corresponding google event instances.
|
|
146
|
+
:new_metadata: The newly created metadata for the parent_occurrence
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def compare_instances_and_activity(activity: Activity, is_parent=False):
|
|
150
|
+
for google_child in google_event_items:
|
|
151
|
+
if created:
|
|
152
|
+
google_start, _ = cls.get_start_and_end(google_child)
|
|
153
|
+
activity_start = activity.period.lower # type: ignore
|
|
154
|
+
else:
|
|
155
|
+
original_start_dict: TimeInfo = google_child.get("originalStartTime", {})
|
|
156
|
+
activity_start_dict: Dict = (
|
|
157
|
+
activity.metadata.get("google_backend", {}).get("instance", {}).get("originalStartTime", {})
|
|
158
|
+
)
|
|
159
|
+
google_start = original_start_dict.get("dateTime", original_start_dict.get("date"))
|
|
160
|
+
activity_start = activity_start_dict.get("dateTime", original_start_dict.get("date"))
|
|
161
|
+
if google_start == activity_start:
|
|
162
|
+
if is_parent:
|
|
163
|
+
new_metadata["google_backend"] |= {"instance": google_child}
|
|
164
|
+
external_id = new_metadata["google_backend"]["event"].get("id")
|
|
165
|
+
Activity.objects.filter(id=activity.id).update(external_id=external_id, metadata=new_metadata)
|
|
166
|
+
else:
|
|
167
|
+
metadata = activity.metadata | {"google_backend": {"instance": google_child}}
|
|
168
|
+
Activity.objects.filter(id=activity.id).update(
|
|
169
|
+
external_id=google_child["id"], metadata=metadata
|
|
170
|
+
)
|
|
171
|
+
google_event_items.remove(google_child)
|
|
172
|
+
break
|
|
173
|
+
|
|
174
|
+
compare_instances_and_activity(parent_occurrence, True)
|
|
175
|
+
child_activities = Activity.objects.filter(parent_occurrence=parent_occurrence)
|
|
176
|
+
for wb_child in child_activities:
|
|
177
|
+
compare_instances_and_activity(wb_child)
|
|
178
|
+
|
|
179
|
+
@classmethod
|
|
180
|
+
def get_or_create_participants(cls, event: GoogleEventType, creator: Person | None):
|
|
181
|
+
"""
|
|
182
|
+
Converts the participants of an event into person objects by using the email address of the participants to search for the corresponding person entries in the "Workbench" database.
|
|
183
|
+
If no person with the right email address is found, a new person entry will automatically be created with the email address or, if possible, with the displayed name as last name.
|
|
184
|
+
|
|
185
|
+
:param event: A google event body.
|
|
186
|
+
:param creator: The creator of the event.
|
|
187
|
+
|
|
188
|
+
:return: A list of dicts with the following structure: {"person_id": int, "status":str}. Can be empty if the event has no attendees.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
participants = (
|
|
192
|
+
[{"person_id": creator.id, "status": ActivityParticipant.ParticipationStatus.ATTENDS}] if creator else []
|
|
193
|
+
)
|
|
194
|
+
if attendees := event.get("attendees"):
|
|
195
|
+
for attendee in attendees:
|
|
196
|
+
mail = attendee.get("email")
|
|
197
|
+
mail_query = Person.objects.filter(
|
|
198
|
+
id__in=EmailContact.objects.filter(address__iexact=mail, primary=True).values_list(
|
|
199
|
+
"entry_id", flat=True
|
|
200
|
+
)
|
|
201
|
+
)
|
|
202
|
+
if mail and mail_query.exists():
|
|
203
|
+
person = mail_query.first()
|
|
204
|
+
else:
|
|
205
|
+
if display_name := attendee.get("displayName"):
|
|
206
|
+
person = Person.objects.create(last_name=display_name, is_draft_entry=True)
|
|
207
|
+
else:
|
|
208
|
+
person = Person.objects.create(last_name=mail, is_draft_entry=True)
|
|
209
|
+
EmailContact.objects.create(entry=person, address=mail, primary=True)
|
|
210
|
+
if not creator or person.id != creator.id:
|
|
211
|
+
person_obj = {
|
|
212
|
+
"person_id": person.id,
|
|
213
|
+
"status": cls.convert_attendee_status_to_participant_status(attendee.get("responseStatus")),
|
|
214
|
+
}
|
|
215
|
+
participants.append(person_obj)
|
|
216
|
+
|
|
217
|
+
return participants
|
|
File without changes
|