wbcrm 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.

Potentially problematic release.


This version of wbcrm might be problematic. Click here for more details.

Files changed (155) hide show
  1. wbcrm/__init__.py +1 -0
  2. wbcrm/admin/__init__.py +4 -0
  3. wbcrm/admin/accounts.py +59 -0
  4. wbcrm/admin/activities.py +101 -0
  5. wbcrm/admin/groups.py +7 -0
  6. wbcrm/admin/products.py +8 -0
  7. wbcrm/apps.py +5 -0
  8. wbcrm/configurations/__init__.py +1 -0
  9. wbcrm/configurations/base.py +16 -0
  10. wbcrm/dynamic_preferences_registry.py +38 -0
  11. wbcrm/factories/__init__.py +14 -0
  12. wbcrm/factories/accounts.py +56 -0
  13. wbcrm/factories/activities.py +125 -0
  14. wbcrm/factories/groups.py +23 -0
  15. wbcrm/factories/products.py +10 -0
  16. wbcrm/filters/__init__.py +10 -0
  17. wbcrm/filters/accounts.py +67 -0
  18. wbcrm/filters/activities.py +181 -0
  19. wbcrm/filters/groups.py +20 -0
  20. wbcrm/filters/products.py +37 -0
  21. wbcrm/filters/signals.py +94 -0
  22. wbcrm/migrations/0001_initial_squashed_squashed_0032_productcompanyrelationship_alter_product_prospects_and_more.py +3948 -0
  23. wbcrm/migrations/0002_alter_activity_repeat_choice.py +32 -0
  24. wbcrm/migrations/0003_remove_activity_external_id_and_more.py +63 -0
  25. wbcrm/migrations/0004_alter_activity_status.py +28 -0
  26. wbcrm/migrations/0005_account_accountrole_accountroletype_and_more.py +182 -0
  27. wbcrm/migrations/0006_alter_activity_location.py +17 -0
  28. wbcrm/migrations/0007_alter_account_status.py +23 -0
  29. wbcrm/migrations/0008_alter_activity_options.py +16 -0
  30. wbcrm/migrations/0009_alter_account_is_public.py +19 -0
  31. wbcrm/migrations/0010_alter_account_reference_id.py +17 -0
  32. wbcrm/migrations/0011_activity_summary.py +22 -0
  33. wbcrm/migrations/0012_alter_activity_summary.py +17 -0
  34. wbcrm/migrations/0013_account_action_plan_account_relationship_status_and_more.py +34 -0
  35. wbcrm/migrations/0014_alter_account_relationship_status.py +24 -0
  36. wbcrm/migrations/0015_alter_activity_type.py +23 -0
  37. wbcrm/migrations/0016_auto_20241205_1015.py +106 -0
  38. wbcrm/migrations/__init__.py +0 -0
  39. wbcrm/models/__init__.py +4 -0
  40. wbcrm/models/accounts.py +637 -0
  41. wbcrm/models/activities.py +1335 -0
  42. wbcrm/models/groups.py +118 -0
  43. wbcrm/models/products.py +83 -0
  44. wbcrm/models/recurrence.py +279 -0
  45. wbcrm/preferences.py +14 -0
  46. wbcrm/serializers/__init__.py +23 -0
  47. wbcrm/serializers/accounts.py +126 -0
  48. wbcrm/serializers/activities.py +526 -0
  49. wbcrm/serializers/groups.py +30 -0
  50. wbcrm/serializers/products.py +57 -0
  51. wbcrm/serializers/recurrence.py +90 -0
  52. wbcrm/serializers/signals.py +70 -0
  53. wbcrm/synchronization/__init__.py +0 -0
  54. wbcrm/synchronization/activity/__init__.py +0 -0
  55. wbcrm/synchronization/activity/admin.py +72 -0
  56. wbcrm/synchronization/activity/backend.py +207 -0
  57. wbcrm/synchronization/activity/backends/__init__.py +0 -0
  58. wbcrm/synchronization/activity/backends/google/__init__.py +2 -0
  59. wbcrm/synchronization/activity/backends/google/google_calendar_backend.py +399 -0
  60. wbcrm/synchronization/activity/backends/google/request_utils/__init__.py +16 -0
  61. wbcrm/synchronization/activity/backends/google/tasks.py +21 -0
  62. wbcrm/synchronization/activity/backends/google/tests/__init__.py +0 -0
  63. wbcrm/synchronization/activity/backends/google/tests/conftest.py +1 -0
  64. wbcrm/synchronization/activity/backends/google/tests/test_data.py +81 -0
  65. wbcrm/synchronization/activity/backends/google/tests/test_google_backend.py +319 -0
  66. wbcrm/synchronization/activity/backends/google/tests/test_utils.py +274 -0
  67. wbcrm/synchronization/activity/backends/google/typing_informations.py +139 -0
  68. wbcrm/synchronization/activity/backends/google/utils.py +216 -0
  69. wbcrm/synchronization/activity/backends/outlook/__init__.py +0 -0
  70. wbcrm/synchronization/activity/backends/outlook/backend.py +576 -0
  71. wbcrm/synchronization/activity/backends/outlook/msgraph.py +438 -0
  72. wbcrm/synchronization/activity/backends/outlook/parser.py +423 -0
  73. wbcrm/synchronization/activity/backends/outlook/tests/__init__.py +0 -0
  74. wbcrm/synchronization/activity/backends/outlook/tests/conftest.py +1 -0
  75. wbcrm/synchronization/activity/backends/outlook/tests/fixtures.py +606 -0
  76. wbcrm/synchronization/activity/backends/outlook/tests/test_admin.py +117 -0
  77. wbcrm/synchronization/activity/backends/outlook/tests/test_backend.py +269 -0
  78. wbcrm/synchronization/activity/backends/outlook/tests/test_controller.py +237 -0
  79. wbcrm/synchronization/activity/backends/outlook/tests/test_parser.py +173 -0
  80. wbcrm/synchronization/activity/controller.py +545 -0
  81. wbcrm/synchronization/activity/dynamic_preferences_registry.py +107 -0
  82. wbcrm/synchronization/activity/preferences.py +21 -0
  83. wbcrm/synchronization/activity/shortcuts.py +9 -0
  84. wbcrm/synchronization/activity/signals.py +28 -0
  85. wbcrm/synchronization/activity/tasks.py +21 -0
  86. wbcrm/synchronization/activity/urls.py +6 -0
  87. wbcrm/synchronization/activity/utils.py +46 -0
  88. wbcrm/synchronization/activity/views.py +37 -0
  89. wbcrm/synchronization/admin.py +1 -0
  90. wbcrm/synchronization/apps.py +15 -0
  91. wbcrm/synchronization/dynamic_preferences_registry.py +1 -0
  92. wbcrm/synchronization/management.py +36 -0
  93. wbcrm/synchronization/tasks.py +1 -0
  94. wbcrm/synchronization/urls.py +5 -0
  95. wbcrm/tasks.py +312 -0
  96. wbcrm/tests/__init__.py +0 -0
  97. wbcrm/tests/accounts/__init__.py +0 -0
  98. wbcrm/tests/accounts/test_models.py +380 -0
  99. wbcrm/tests/accounts/test_viewsets.py +87 -0
  100. wbcrm/tests/conftest.py +76 -0
  101. wbcrm/tests/disable_signals.py +52 -0
  102. wbcrm/tests/e2e/__init__.py +1 -0
  103. wbcrm/tests/e2e/e2e_wbcrm_utility.py +82 -0
  104. wbcrm/tests/e2e/test_e2e.py +369 -0
  105. wbcrm/tests/test_assignee_methods.py +39 -0
  106. wbcrm/tests/test_chartviewsets.py +111 -0
  107. wbcrm/tests/test_dto.py +63 -0
  108. wbcrm/tests/test_filters.py +51 -0
  109. wbcrm/tests/test_models.py +216 -0
  110. wbcrm/tests/test_recurrence.py +291 -0
  111. wbcrm/tests/test_report.py +20 -0
  112. wbcrm/tests/test_serializers.py +170 -0
  113. wbcrm/tests/test_tasks.py +94 -0
  114. wbcrm/tests/test_viewsets.py +967 -0
  115. wbcrm/tests/tests.py +120 -0
  116. wbcrm/typings.py +107 -0
  117. wbcrm/urls.py +67 -0
  118. wbcrm/viewsets/__init__.py +22 -0
  119. wbcrm/viewsets/accounts.py +121 -0
  120. wbcrm/viewsets/activities.py +315 -0
  121. wbcrm/viewsets/buttons/__init__.py +7 -0
  122. wbcrm/viewsets/buttons/accounts.py +27 -0
  123. wbcrm/viewsets/buttons/activities.py +68 -0
  124. wbcrm/viewsets/buttons/signals.py +17 -0
  125. wbcrm/viewsets/display/__init__.py +12 -0
  126. wbcrm/viewsets/display/accounts.py +110 -0
  127. wbcrm/viewsets/display/activities.py +443 -0
  128. wbcrm/viewsets/display/groups.py +22 -0
  129. wbcrm/viewsets/display/products.py +105 -0
  130. wbcrm/viewsets/endpoints/__init__.py +8 -0
  131. wbcrm/viewsets/endpoints/accounts.py +32 -0
  132. wbcrm/viewsets/endpoints/activities.py +30 -0
  133. wbcrm/viewsets/endpoints/groups.py +7 -0
  134. wbcrm/viewsets/endpoints/products.py +9 -0
  135. wbcrm/viewsets/groups.py +37 -0
  136. wbcrm/viewsets/menu/__init__.py +8 -0
  137. wbcrm/viewsets/menu/accounts.py +18 -0
  138. wbcrm/viewsets/menu/activities.py +61 -0
  139. wbcrm/viewsets/menu/groups.py +16 -0
  140. wbcrm/viewsets/menu/products.py +20 -0
  141. wbcrm/viewsets/mixins.py +34 -0
  142. wbcrm/viewsets/previews/__init__.py +1 -0
  143. wbcrm/viewsets/previews/activities.py +10 -0
  144. wbcrm/viewsets/products.py +56 -0
  145. wbcrm/viewsets/recurrence.py +26 -0
  146. wbcrm/viewsets/titles/__init__.py +13 -0
  147. wbcrm/viewsets/titles/accounts.py +22 -0
  148. wbcrm/viewsets/titles/activities.py +61 -0
  149. wbcrm/viewsets/titles/products.py +13 -0
  150. wbcrm/viewsets/titles/utils.py +46 -0
  151. wbcrm/workflows/__init__.py +1 -0
  152. wbcrm/workflows/assignee_methods.py +25 -0
  153. wbcrm-2.2.1.dist-info/METADATA +11 -0
  154. wbcrm-2.2.1.dist-info/RECORD +155 -0
  155. wbcrm-2.2.1.dist-info/WHEEL +5 -0
@@ -0,0 +1,423 @@
1
+ import calendar as calendar_reference
2
+ from datetime import datetime
3
+ from typing import Any
4
+
5
+ import pandas as pd
6
+ import pytz
7
+ from dateutil import parser, rrule
8
+ from django.utils.timezone import get_default_timezone, get_default_timezone_name
9
+ from psycopg.types.range import TimestamptzRange
10
+ from wbcore.utils.rrules import (
11
+ convert_rrulestr_to_dict,
12
+ convert_weekday_rrule_to_day_name,
13
+ )
14
+ from wbcrm.typings import Activity as ActivityDTO
15
+ from wbcrm.typings import ConferenceRoom as ConferenceRoomDTO
16
+ from wbcrm.typings import ParticipantStatus as ParticipantStatusDTO
17
+ from wbcrm.typings import Person as PersonDTO
18
+
19
+
20
+ class OutlookParser:
21
+ default_visibility = "normal"
22
+ visibility_map = {
23
+ ActivityDTO.Visibility.PUBLIC.name: default_visibility,
24
+ ActivityDTO.Visibility.PRIVATE.name: "private",
25
+ ActivityDTO.Visibility.CONFIDENTIAL.name: "confidential",
26
+ }
27
+ default_response_status = "notResponded"
28
+ response_status_map = {
29
+ ParticipantStatusDTO.ParticipationStatus.NOTRESPONDED.name: default_response_status,
30
+ ParticipantStatusDTO.ParticipationStatus.ATTENDS.name: "accepted",
31
+ ParticipantStatusDTO.ParticipationStatus.CANCELLED.name: "declined",
32
+ ParticipantStatusDTO.ParticipationStatus.MAYBE.name: "tentativelyAccepted",
33
+ }
34
+ default_reminder_minutes = 15
35
+ reminder_map = {
36
+ ActivityDTO.ReminderChoice.NEVER.name: -1,
37
+ ActivityDTO.ReminderChoice.EVENT_TIME.name: 0,
38
+ ActivityDTO.ReminderChoice.MINUTES_5.name: 5,
39
+ ActivityDTO.ReminderChoice.MINUTES_15.name: default_reminder_minutes,
40
+ ActivityDTO.ReminderChoice.MINUTES_30.name: 30,
41
+ ActivityDTO.ReminderChoice.HOURS_1.name: 60,
42
+ ActivityDTO.ReminderChoice.HOURS_2.name: 120,
43
+ ActivityDTO.ReminderChoice.HOURS_12.name: 720,
44
+ ActivityDTO.ReminderChoice.WEEKS_1.name: 10080,
45
+ }
46
+
47
+ @classmethod
48
+ def convert_visibility_to_sensitivity(cls, visibility: str) -> str:
49
+ return cls.visibility_map.get(visibility, cls.default_visibility)
50
+
51
+ @classmethod
52
+ def convert_sensitivity_to_visibility(cls, sensitivity: str) -> str:
53
+ visibility_map_inv = {v: k for k, v in cls.visibility_map.items()}
54
+ return visibility_map_inv.get(sensitivity, visibility_map_inv[cls.default_visibility])
55
+
56
+ @classmethod
57
+ def convert_participant_status_to_attendee_status(cls, participant_status: str) -> str:
58
+ if participant_status == ParticipantStatusDTO.ParticipationStatus.ATTENDS_DIGITALLY.name:
59
+ return "accepted"
60
+ return cls.response_status_map.get(participant_status, cls.default_response_status)
61
+
62
+ @classmethod
63
+ def convert_attendee_status_to_participant_status(cls, attendee_status: str) -> str:
64
+ if attendee_status == "organizer":
65
+ return ParticipantStatusDTO.ParticipationStatus.ATTENDS.name
66
+ response_status_map_inv = {v: k for k, v in cls.response_status_map.items()}
67
+ return response_status_map_inv.get(attendee_status, response_status_map_inv[cls.default_response_status])
68
+
69
+ @classmethod
70
+ def convert_reminder_choice_to_minutes(cls, choice: str) -> int:
71
+ return cls.reminder_map[choice]
72
+
73
+ @classmethod
74
+ def convert_reminder_minutes_to_choice(cls, minutes: int) -> str:
75
+ reminder_map_inv = {v: k for k, v in cls.reminder_map.items()}
76
+ return reminder_map_inv.get(minutes, reminder_map_inv[cls.default_reminder_minutes])
77
+
78
+ @classmethod
79
+ def convert_string_to_datetime(cls, date_time_str: str, time_zone: str) -> datetime:
80
+ date_time = parser.parse(date_time_str)
81
+ try:
82
+ return pytz.timezone(time_zone).localize(date_time)
83
+ except pytz.exceptions.UnknownTimeZoneError:
84
+ return pytz.timezone(get_default_timezone_name()).localize(date_time)
85
+
86
+ @classmethod
87
+ def convert_to_all_day_period(cls, period: TimestamptzRange) -> TimestamptzRange:
88
+ return TimestamptzRange(
89
+ lower=datetime(
90
+ year=period.lower.year, month=period.lower.month, day=period.lower.day, tzinfo=get_default_timezone()
91
+ ),
92
+ upper=datetime(
93
+ year=period.upper.year, month=period.upper.month, day=period.upper.day, tzinfo=get_default_timezone()
94
+ ),
95
+ )
96
+
97
+ @classmethod
98
+ def deserialize_person(cls, email: str, mame: str) -> PersonDTO:
99
+ if (items := mame.split(" ")) and len(items) > 1:
100
+ first_name = items[0]
101
+ last_name = " ".join(items[1:])
102
+ else:
103
+ last_name = first_name = items[0]
104
+ return PersonDTO(first_name=first_name, last_name=last_name, email=email)
105
+
106
+ @classmethod
107
+ def deserialize_conference_room(cls, email: str, name: str) -> ConferenceRoomDTO:
108
+ return ConferenceRoomDTO(name=name, email=email)
109
+
110
+ @classmethod
111
+ def deserialize_participants(cls, event: dict) -> tuple[list[ParticipantStatusDTO], list[ConferenceRoomDTO]]:
112
+ participants = {
113
+ event["organizer.email_address.address"]: ParticipantStatusDTO(
114
+ person=OutlookParser.deserialize_person(
115
+ event["organizer.email_address.address"], event["organizer.email_address.name"]
116
+ ),
117
+ status=ParticipantStatusDTO.ParticipationStatus.ATTENDS.name,
118
+ )
119
+ }
120
+ conference_rooms = {}
121
+ for attendee in event.get("attendees", []):
122
+ if mail_info := attendee.get("emailAddress"):
123
+ email = mail_info["address"]
124
+ name = mail_info.get("name", email)
125
+ if attendee.get("type") == "resource":
126
+ if not conference_rooms.get(email):
127
+ conference_rooms[email] = cls.deserialize_conference_room(email, name)
128
+ else:
129
+ if (status_participant := attendee["status"].get("response")) and (
130
+ event["is_organizer"] or (not event["is_organizer"] and status_participant != "none")
131
+ ):
132
+ status = cls.convert_attendee_status_to_participant_status(status_participant)
133
+ else:
134
+ status = None
135
+ defaults = {"status": status} if status else {}
136
+ if not participants.get(email):
137
+ participants[email] = ParticipantStatusDTO(
138
+ person=cls.deserialize_person(email, name), **defaults
139
+ )
140
+ return list(participants.values()), list(conference_rooms.values())
141
+
142
+ @classmethod
143
+ def serialize_person(cls, person: PersonDTO) -> dict:
144
+ return {
145
+ "emailAddress": {
146
+ "address": person.email,
147
+ "name": f"{person.first_name} {person.last_name}",
148
+ }
149
+ }
150
+
151
+ @classmethod
152
+ def serialize_conference_room(cls, conference_room_dto=ConferenceRoomDTO) -> dict:
153
+ return {
154
+ "emailAddress": {"address": conference_room_dto.email, "name": conference_room_dto.name},
155
+ "type": "resource",
156
+ }
157
+
158
+ @classmethod
159
+ def serialize_participants(cls, participants_dto: list[ParticipantStatusDTO]) -> list[dict[str, Any]]:
160
+ attendees = []
161
+ for participant_dto in participants_dto:
162
+ status = cls.convert_participant_status_to_attendee_status(participant_dto.status)
163
+ _data = {
164
+ **cls.serialize_person(participant_dto.person),
165
+ "type": "optional",
166
+ }
167
+ if status != "notResponded":
168
+ _data["status"] = {"response": status}
169
+ attendees += [_data]
170
+ return attendees
171
+
172
+ @classmethod
173
+ def _convert_weekday_to_day_name(cls, weekday: rrule.weekday) -> str:
174
+ default_day_name = calendar_reference.day_name[0] # Monday
175
+ if weekday and (_day_name := convert_weekday_rrule_to_day_name(weekday)):
176
+ return _day_name
177
+ return default_day_name
178
+
179
+ @classmethod
180
+ def deserialize_recurring_activities(cls, event: dict) -> dict:
181
+ recurrence = {
182
+ "recurrence_end": None,
183
+ "recurrence_count": 0,
184
+ "repeat_choice": ActivityDTO.ReoccuranceChoice.NEVER.value,
185
+ }
186
+ if event.get("type") == "seriesMaster":
187
+ freq = event["recurrence.pattern.type"]
188
+ interval = event["recurrence.pattern.interval"]
189
+ if freq == "yearly":
190
+ recurrence["repeat_choice"] = ActivityDTO.ReoccuranceChoice.YEARLY.value
191
+ elif freq == "monthly":
192
+ if interval == 3:
193
+ recurrence["repeat_choice"] = ActivityDTO.ReoccuranceChoice.QUARTERLY.value
194
+ else:
195
+ recurrence["repeat_choice"] = ActivityDTO.ReoccuranceChoice.MONTHLY.value
196
+ elif freq == "weekly":
197
+ days_of_week = {_day.lower() for _day in event.get("recurrence.pattern.days_of_week", [])}
198
+ if interval == 2:
199
+ recurrence["repeat_choice"] = ActivityDTO.ReoccuranceChoice.BIWEEKLY.value
200
+ elif interval == 1 and days_of_week == {"monday", "tuesday", "wednesday", "thursday", "friday"}:
201
+ recurrence["repeat_choice"] = ActivityDTO.ReoccuranceChoice.BUSINESS_DAILY.value
202
+ else:
203
+ recurrence["repeat_choice"] = ActivityDTO.ReoccuranceChoice.WEEKLY.value
204
+ else:
205
+ recurrence["repeat_choice"] = ActivityDTO.ReoccuranceChoice.DAILY.value
206
+ if event["recurrence.range.type"] == "endDate":
207
+ recurrence["recurrence_end"] = datetime.strptime(event["recurrence.range.end_date"], "%Y-%m-%d")
208
+ else:
209
+ recurrence["recurrence_count"] = event["recurrence.range.number_of_occurrences"] - 1
210
+ return recurrence
211
+
212
+ @classmethod
213
+ def serialize_recurring_activities(cls, activity_dto: ActivityDTO) -> dict:
214
+ recurrence = {}
215
+ if activity_dto.is_recurrent and (rule_dict := convert_rrulestr_to_dict(activity_dto.repeat_choice)):
216
+ freq_names = {idx: freq for idx, freq in enumerate(rrule.FREQNAMES)}
217
+ if freq_name := freq_names.get(rule_dict.get("freq")):
218
+ pattern = {
219
+ "interval": rule_dict.get("interval", 1),
220
+ "firstDayOfWeek": cls._convert_weekday_to_day_name(rule_dict.get("wkst")),
221
+ }
222
+ if freq_name == "MONTHLY":
223
+ pattern.update({"type": "absoluteMonthly", "dayOfMonth": activity_dto.period.lower.day})
224
+ elif freq_name == "YEARLY":
225
+ pattern.update(
226
+ {
227
+ "type": "absoluteYearly",
228
+ "month": activity_dto.period.lower.month,
229
+ "dayOfMonth": activity_dto.period.lower.day,
230
+ }
231
+ )
232
+ else: # daily or weekly
233
+ pattern["type"] = freq_name.lower()
234
+ if weekdays := rule_dict.get("byweekday", []):
235
+ pattern.update(
236
+ {
237
+ "type": "weekly",
238
+ "daysOfWeek": [cls._convert_weekday_to_day_name(weekday) for weekday in weekdays],
239
+ }
240
+ )
241
+ elif freq_name == "WEEKLY":
242
+ pattern["daysOfWeek"] = [calendar_reference.day_name[activity_dto.period.lower.weekday()]]
243
+
244
+ range = {"startDate": activity_dto.period.lower.strftime("%Y-%m-%d")}
245
+ if activity_dto.recurrence_count:
246
+ range.update(
247
+ {
248
+ "type": "numbered",
249
+ "numberOfOccurrences": activity_dto.recurrence_count + 1,
250
+ "recurrenceTimeZone": activity_dto.period.lower.tzname(),
251
+ }
252
+ )
253
+ else:
254
+ range.update(
255
+ {
256
+ "type": "endDate",
257
+ "endDate": activity_dto.recurrence_end.strftime("%Y-%m-%d"),
258
+ }
259
+ )
260
+
261
+ recurrence = {"recurrence": {"pattern": pattern, "range": range}}
262
+ return recurrence
263
+
264
+ @classmethod
265
+ def serialize_recurrence_range_with_end_date(cls, recurrence: dict, end_date: datetime) -> dict:
266
+ if (
267
+ (recc := recurrence.get("recurrence"))
268
+ and (_range := recc.get("range"))
269
+ and (start_date := _range.get("startDate"))
270
+ ):
271
+ if start_date <= end_date.strftime("%Y-%m-%d"):
272
+ recurrence["recurrence"]["range"] = {
273
+ "type": "endDate",
274
+ "startDate": start_date,
275
+ "endDate": end_date.strftime("%Y-%m-%d"),
276
+ }
277
+ if tz := _range.get("recurrenceTimeZone"):
278
+ recurrence["recurrence"]["range"]["recurrenceTimeZone"] = tz
279
+ return recurrence
280
+
281
+ @classmethod
282
+ def serialize_event_keys(cls, event: dict) -> dict:
283
+ inv_map = {value: key for key, value in _map.items()}
284
+ return dict(map(lambda item: (inv_map.get(item[0], item[0]), item[1]), event.items()))
285
+
286
+
287
+ _map = {
288
+ "lastModifiedDateTime": "last_modified_date_time",
289
+ "startDateTime": "start_date_time",
290
+ "endDateTime": "end_date_time",
291
+ "organizer": "organizer",
292
+ "displayName": "display_name",
293
+ "businessPhones": "business_phones",
294
+ "mobilePhone": "mobile_phone",
295
+ "surname": "surname",
296
+ "givenName": "given_name",
297
+ "mailNickname": "mail_nickname",
298
+ "userPrincipalName": "user_principal_name",
299
+ "notificationUrl": "notification_url",
300
+ "changeType": "change_type",
301
+ "expirationDateTime": "expiration_date_time",
302
+ "resourceData": "resource_data",
303
+ "subscriptionExpirationDateTime": "subscription_expiration_date_time",
304
+ "clientState": "client_state",
305
+ "tenantId": "tenant_id",
306
+ "subscriptionId": "subscription_id",
307
+ "organizer.user": "organizer.user",
308
+ "organizer.user.id": "organizer.user.id",
309
+ "organizer.user.displayName": "organizer.user.display_name",
310
+ "organizer.phone": "organizer.phone",
311
+ "organizer.phone.id": "organizer.phone.id",
312
+ "organizer.phone.displayName": "organizer.phone.display_name",
313
+ "organizer.guest": "organizer.guest",
314
+ "organizer.guest.id": "organizer.guest.id",
315
+ "organizer.guest.displayName": "organizer.guest.display_name",
316
+ "organizer.spoolUser": "organizer.splool_user",
317
+ "organizer.acsUser": "organizer.acs_user",
318
+ "organizer.encrypted": "organizer.encrypted",
319
+ "organizer.onPremises": "organizer.on_premises",
320
+ "organizer.acsApplicationInstance": "organizer.acs_application_instance",
321
+ "organizer.spoolApplicationInstance": "organizer.spool_application_instance",
322
+ "organizer.applicationInstance": "organizer.application_instance",
323
+ "organizer.application": "organizer.application",
324
+ "organizer.device": "organizer.device",
325
+ "joinWebUrl": "join_web_url",
326
+ "preferredLanguage": "preferred_language",
327
+ "officeLocation": "office_location",
328
+ "jobTitle": "job_title",
329
+ "createdDateTime": "created_date_time",
330
+ "isReminderOn": "is_reminder_on",
331
+ "reminderMinutesBeforeStart": "reminder_minutes_before_start",
332
+ "hasAttachments": "has_attachments",
333
+ "webLink": "web_link",
334
+ "start.dateTime": "start.date_time",
335
+ "start.timeZone": "start.time_zone",
336
+ "end.dateTime": "end.date_time",
337
+ "end.timeZone": "end.time_zone",
338
+ "location.displayName": "location.display_name",
339
+ "location.locationUri": "location.location_uri",
340
+ "location.locationType": "location.location_type",
341
+ "location.uniqueId": "location.unique_id",
342
+ "location.uniqueIdType": "location.unique_id_type",
343
+ "location.address.type": "location.address.type",
344
+ "location.address.street": "location.address.street",
345
+ "location.address.city": "location.address.city",
346
+ "location.address.postalCode": "location.address.postal_code",
347
+ "location.address.countryOrRegion": "location.address.country_or_region",
348
+ "recurrence.pattern.daysOfWeek": "recurrence.pattern.days_of_week",
349
+ "recurrence.pattern.dayOfMonth": "recurrence.pattern.day_of_month",
350
+ "recurrence.pattern.firstDayOfWeek": "recurrence.pattern.first_day_of_week",
351
+ "recurrence.range.recurrenceTimeZone": "recurrence.range.recurrence_time_zone",
352
+ "recurrence.range.numberOfOccurrences": "recurrence.range.number_of_occurrences",
353
+ "recurrence.range.startDate": "recurrence.range.start_date",
354
+ "recurrence.range.endDate": "recurrence.range.end_date",
355
+ "organizer.emailAddress.name": "organizer.email_address.name",
356
+ "organizer.emailAddress.address": "organizer.email_address.address",
357
+ "responseStatus.response": "response_status.response",
358
+ "responseStatus.time": "response_status.time",
359
+ "attendees": "attendees",
360
+ "@odata.context": "odata_context",
361
+ "@odata.etag": "odata_etag",
362
+ "applicationId": "application_id",
363
+ "includeResourceData": "include_resource_data",
364
+ "latestSupportedTlsVersion": "latest_supported_tls_version",
365
+ "encryptionCertificate": "encryption_certificate",
366
+ "encryptionCertificateId": "encryption_certificate_id",
367
+ "notificationQueryOptions": "notification_query_options",
368
+ "notificationContentType": "notification_content_type",
369
+ "lifecycleNotificationUrl": "lifecycle_notification_url",
370
+ "creatorId": "creator_id",
371
+ "isDefaultCalendar": "is_default_calendar",
372
+ "hexColor": "hex_color",
373
+ "changeKey": "change_key",
374
+ "canShare": "can_share",
375
+ "canViewPrivateItems": "can_view_private_items",
376
+ "isShared": "is_shared",
377
+ "isSharedWithMe": "is_shared_with_me",
378
+ "canEdit": "can_edit",
379
+ "allowedOnlineMeetingProviders": "allowed_online_meeting_providers",
380
+ "defaultOnlineMeetingProvider": "default_online_meeting_provider",
381
+ "isTallyingResponses": "is_tallying_responses",
382
+ "isRemovable": "is_removable",
383
+ "teamsForBusiness": "teams_for_business",
384
+ "transactionId": "transaction_id",
385
+ "originalStartTimeZone": "original_start_time_zone",
386
+ "originalEndTimeZone": "original_end_time_zone",
387
+ "bodyPreview": "body_preview",
388
+ "isAllDay": "is_all_day",
389
+ "isCancelled": "is_cancelled",
390
+ "isOrganizer": "is_organizer",
391
+ "responseRequested": "response_requested",
392
+ "seriesMasterId": "series_master_id",
393
+ "showAs": "show_as",
394
+ "onlineMeeting": "online_meeting",
395
+ "onlineMeetingUrl": "online_meeting_url",
396
+ "isOnlineMeeting": "is_online_meeting",
397
+ "onlineMeetingProvider": "online_meeting_provider",
398
+ "allowNewTimeProposals": "allow_new_time_proposals",
399
+ "occurrenceId": "occurrence_id",
400
+ "isDraft": "is_draft",
401
+ "hideAttendees": "hide_attendees",
402
+ "responseStatus": "response_status",
403
+ "appId": "app_id",
404
+ "passwordCredentials": "password_credentials",
405
+ "iCalUId": "uid",
406
+ "id": "id",
407
+ "body.contentType": "body.content_type",
408
+ }
409
+
410
+
411
+ def parse(_dict: dict, scalar_value: bool = False):
412
+ data = None
413
+ try:
414
+ if scalar_value:
415
+ df = pd.DataFrame(_dict, index=[0])
416
+ else:
417
+ df = pd.DataFrame(_dict)
418
+ except ValueError:
419
+ _dict = pd.json_normalize(_dict)
420
+ df = pd.DataFrame(_dict)
421
+ df = df.rename(columns=_map)
422
+ data = df.to_dict("records")
423
+ return data
@@ -0,0 +1 @@
1
+ from wbcrm.tests.conftest import *