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,216 @@
1
+ from datetime import timedelta
2
+
3
+ import pytest
4
+ from django.apps import apps
5
+ from django.utils import timezone
6
+ from django_fsm import TransitionNotAllowed
7
+ from dynamic_preferences.registries import global_preferences_registry
8
+ from wbcore.contrib.agenda.models import CalendarItem
9
+ from wbcore.contrib.authentication.factories import (
10
+ AuthenticatedPersonFactory,
11
+ UserFactory,
12
+ )
13
+ from wbcore.contrib.directory.factories import PersonWithEmployerFactory
14
+ from wbcore.contrib.directory.models import Entry
15
+ from wbcore.contrib.icons import WBIcon
16
+ from wbcrm.models import Activity, add_employer_to_activities
17
+
18
+
19
+ @pytest.mark.django_db
20
+ class TestSpecificModelsActivities:
21
+ def test_get_color_map(self, activity_factory):
22
+ activity1 = activity_factory()
23
+ assert activity1.Status.get_color_map()
24
+
25
+ def test_get_minutes_correspondance(self, activity_factory):
26
+ activity1 = activity_factory()
27
+ assert activity1.ReminderChoice.get_minutes_correspondance("NEVER")
28
+ assert activity1.ReminderChoice.get_minutes_correspondance("EVENT_TIME") == 0
29
+ assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_5")
30
+ assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_15")
31
+ assert activity1.ReminderChoice.get_minutes_correspondance("MINUTES_30")
32
+ assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_1")
33
+ assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_2")
34
+ assert activity1.ReminderChoice.get_minutes_correspondance("HOURS_12")
35
+ assert activity1.ReminderChoice.get_minutes_correspondance("WEEKS_1")
36
+
37
+ def test_str(self, activity_factory):
38
+ assert activity_factory(preceded_by=None).__str__()
39
+
40
+ def test_get_participants(self, activity_factory, person_factory, internal_user_factory, company_factory):
41
+ person1 = person_factory()
42
+ company1 = company_factory()
43
+ if apps.is_installed("wbhuman_resources"):
44
+ from wbhuman_resources.factories import EmployeeHumanResourceFactory
45
+
46
+ person2 = AuthenticatedPersonFactory(employers=(company1,))
47
+ EmployeeHumanResourceFactory(profile=person2)
48
+ else:
49
+ person2 = person_factory(employers=(company1,))
50
+ u = internal_user_factory.create()
51
+ u.profile = person2
52
+ u.save()
53
+ activity1 = activity_factory(participants=(person1,))
54
+ assert activity1.get_participants().count() == 1
55
+ assert activity1.get_participants()[0] == person1
56
+
57
+ def test_get_activities_for_user(self, activity_factory):
58
+ activity0 = activity_factory(preceded_by=None)
59
+ user = UserFactory(is_active=True, is_superuser=True)
60
+ qs = activity0.get_activities_for_user(user)
61
+ assert qs.count() == 1
62
+
63
+ user2 = UserFactory(is_active=True)
64
+ qs2 = activity0.get_activities_for_user(user2)
65
+ assert qs2.count() == 0
66
+
67
+ def test_add_group_to_activity(self, activity_factory, group_factory, person_factory, company_factory):
68
+ activity_participant_a = person_factory()
69
+ group_member_a = person_factory()
70
+ member_a_entry = Entry.objects.get(pk=group_member_a.pk)
71
+ group_member_b = company_factory()
72
+ member_b_entry = Entry.objects.get(pk=group_member_b.pk)
73
+ group_a = group_factory(members=(member_a_entry, member_b_entry))
74
+
75
+ activity = activity_factory(
76
+ participants=[
77
+ activity_participant_a,
78
+ ]
79
+ )
80
+ assert activity.participants.filter(pk=activity_participant_a.pk).exists()
81
+ assert not activity.participants.filter(pk=group_member_a.pk).exists()
82
+ assert not activity.companies.filter(pk=group_member_b.pk).exists()
83
+
84
+ activity.groups.set((group_a,))
85
+ activity.save()
86
+
87
+ assert activity.participants.filter(pk=activity_participant_a.pk).exists()
88
+ assert activity.participants.filter(pk=group_member_a.pk).exists()
89
+ assert activity.companies.filter(pk=group_member_b.pk).exists()
90
+
91
+ def test_remove_group_to_activity(self, activity_factory, group_factory, person_factory, company_factory):
92
+ group_member_a = person_factory()
93
+ group_member_b = person_factory()
94
+ group_member_c = person_factory()
95
+ group_member_d = company_factory()
96
+ group_a = group_factory(members=(group_member_a.entry_ptr, group_member_c.entry_ptr, group_member_d.entry_ptr))
97
+ group_b = group_factory(members=(group_member_b.entry_ptr, group_member_c.entry_ptr))
98
+
99
+ activity = activity_factory(groups=[group_a, group_b])
100
+
101
+ assert activity.participants.filter(pk=group_member_a.pk).exists()
102
+ assert activity.participants.filter(pk=group_member_b.pk).exists()
103
+ assert activity.participants.filter(pk=group_member_c.pk).exists()
104
+ assert activity.companies.filter(pk=group_member_d.pk).exists()
105
+
106
+ activity.groups.remove(group_a.pk)
107
+ activity.save()
108
+
109
+ assert not activity.participants.filter(pk=group_member_a.pk).exists()
110
+ assert activity.participants.filter(pk=group_member_b.pk).exists()
111
+ assert activity.participants.filter(pk=group_member_c.pk).exists()
112
+ assert not activity.companies.filter(pk=group_member_d.pk).exists()
113
+
114
+ def test_add_participants_employer(self, activity_factory, person_factory):
115
+ person_a = PersonWithEmployerFactory()
116
+ person_b = person_factory()
117
+ employer_a = person_a.employer.get(primary=True).employer
118
+ activity_a = activity_factory(participants=(person_a, person_b))
119
+ assert person_a in activity_a.participants.all()
120
+ assert person_b in activity_a.participants.all()
121
+ assert employer_a in activity_a.companies.all()
122
+
123
+ def test_add_participants_employer_on_update(self, activity_factory, person_factory):
124
+ person_a = PersonWithEmployerFactory()
125
+ person_b = person_factory()
126
+ employer_a = person_a.employer.get(primary=True).employer
127
+ activity_a = activity_factory(participants=(person_b,))
128
+ assert person_a not in activity_a.participants.all()
129
+ assert person_b in activity_a.participants.all()
130
+ assert employer_a not in activity_a.companies.all()
131
+ activity_a.participants.set([person_a, person_b])
132
+ assert person_a in activity_a.participants.all()
133
+ assert person_b in activity_a.participants.all()
134
+ assert employer_a in activity_a.companies.all()
135
+
136
+ def test_add_participants_employer_to_canceled_activity(self, activity_factory, person_factory):
137
+ person_a = PersonWithEmployerFactory()
138
+ person_b = person_factory()
139
+ activity_a = activity_factory(participants=(person_a, person_b), status=Activity.Status.CANCELLED)
140
+ assert set(activity_a.participants.all()) == {person_a, person_b}
141
+
142
+ def test_participant_cannot_attend_digitally(
143
+ self, activity_participant_factory, activity_factory, conference_room_factory
144
+ ):
145
+ room = conference_room_factory(is_videoconference_capable=False)
146
+ activity = activity_factory(conference_room=room)
147
+ obj = activity_participant_factory(activity=activity)
148
+ with pytest.raises(TransitionNotAllowed):
149
+ obj.attendsdigitally()
150
+
151
+ def test_participant_can_attend_digitally(self, activity_participant_factory, activity_factory):
152
+ activity = activity_factory()
153
+ obj = activity_participant_factory(activity=activity)
154
+ obj.attendsdigitally()
155
+ assert True
156
+
157
+ def test_get_casted_calendar_item(self, activity_factory):
158
+ activity = activity_factory()
159
+ calendar_item = CalendarItem.objects.get(id=activity.id)
160
+ assert calendar_item.get_casted_calendar_item() == activity
161
+
162
+ def test_add_employer_to_activities(
163
+ self, activity_factory, employer_employee_relationship_factory, person_factory
164
+ ):
165
+ employee = person_factory()
166
+ act1 = activity_factory(
167
+ status=Activity.Status.PLANNED, start=timezone.now() + timedelta(1), participants=[employee]
168
+ )
169
+ act2 = activity_factory(
170
+ status=Activity.Status.REVIEWED, start=timezone.now() + timedelta(1), participants=[employee]
171
+ )
172
+ act3 = activity_factory(
173
+ status=Activity.Status.PLANNED, start=timezone.now() - timedelta(1), participants=[employee]
174
+ )
175
+ eer = employer_employee_relationship_factory(employee=employee)
176
+ add_employer_to_activities(eer.id)
177
+ act1.refresh_from_db()
178
+ act2.refresh_from_db()
179
+ act3.refresh_from_db()
180
+ assert eer.employer in act1.companies.all()
181
+ assert eer.employer not in act2.companies.all()
182
+ assert eer.employer not in act3.companies.all()
183
+ assert eer.employer.entry_ptr in act1.entities.all()
184
+ assert eer.employer.entry_ptr not in act2.entities.all()
185
+ assert eer.employer.entry_ptr not in act3.entities.all()
186
+
187
+ def test_set_color_icons(self, activity_factory, calendar_item_factory):
188
+ activity = activity_factory(type__icon=WBIcon.EURO.icon)
189
+ assert activity.color == activity.type.color
190
+ assert activity.icon == activity.type.icon
191
+
192
+ activity_type = activity.type
193
+ activity_type.icon = WBIcon.LOCATION.icon
194
+ activity_type.color = "#42f444"
195
+ activity_type.save()
196
+ activity.refresh_from_db()
197
+ assert activity.color == activity_type.color
198
+ assert activity.icon == activity_type.icon
199
+
200
+ calendar_item = calendar_item_factory()
201
+ assert calendar_item.color
202
+ assert calendar_item.icon
203
+
204
+ def test_main_company_removed_from_m2m(self, activity, company_factory):
205
+ external_company = company_factory.create()
206
+ activity.companies.add(external_company)
207
+ assert set(activity.companies.all()) == {
208
+ external_company,
209
+ }
210
+
211
+ main_company = company_factory.create()
212
+ global_preferences_registry.manager()["directory__main_company"] = main_company.id
213
+ activity.companies.add(main_company)
214
+ assert set(activity.companies.all()) == {
215
+ external_company,
216
+ }
@@ -0,0 +1,291 @@
1
+ import pytest
2
+ from django.db.models import Q
3
+ from django.forms.models import model_to_dict
4
+ from rest_framework.test import APIRequestFactory
5
+ from wbcore.contrib.authentication.factories import UserFactory
6
+ from wbcore.messages import InMemoryMessageStorage
7
+ from wbcrm.models.activities import Activity
8
+ from wbcrm.models.recurrence import Recurrence
9
+ from wbcrm.preferences import (
10
+ get_maximum_allowed_recurrent_date,
11
+ get_recurrence_maximum_count,
12
+ )
13
+ from wbcrm.serializers import ActivityModelSerializer
14
+ from wbcrm.viewsets import ActivityViewSet
15
+
16
+
17
+ @pytest.mark.django_db
18
+ class RecurrenceFixture:
19
+ @pytest.fixture
20
+ def occurrence_fixture(self, activity_factory):
21
+ return activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None)
22
+
23
+ @pytest.fixture
24
+ def occurrences_fixture(self, occurrence_fixture):
25
+ return Activity.objects.filter(Q(id=occurrence_fixture.id) | Q(parent_occurrence=occurrence_fixture)).order_by(
26
+ "start"
27
+ )
28
+
29
+
30
+ @pytest.mark.django_db
31
+ class TestRecurrenceModel(RecurrenceFixture):
32
+ @pytest.mark.parametrize("repeat_choice", [*list(Recurrence.ReoccuranceChoice)])
33
+ def test_is_recurrent(self, repeat_choice, recurring_activity_factory):
34
+ act = recurring_activity_factory(repeat_choice=repeat_choice)
35
+ assert act.is_recurrent == (act.repeat_choice != Recurrence.ReoccuranceChoice.NEVER)
36
+
37
+ def test_recurring_activity_without_end(self, recurring_activity_factory):
38
+ act = recurring_activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=None)
39
+ occurrences = Activity.objects.filter(parent_occurrence=act)
40
+ assert occurrences.count() <= get_recurrence_maximum_count()
41
+ assert (
42
+ occurrences.order_by("period__startswith").last().period.lower.date()
43
+ <= get_maximum_allowed_recurrent_date()
44
+ )
45
+
46
+ def test_is_root(self, occurrences_fixture):
47
+ for occurrence in occurrences_fixture:
48
+ assert occurrence.is_root == (occurrence.is_recurrent and not occurrence.parent_occurrence)
49
+
50
+ def test_is_leaf(self, occurrences_fixture):
51
+ for occurrence in occurrences_fixture:
52
+ assert occurrence.is_leaf == (occurrence.is_recurrent and not occurrence.next_occurrence)
53
+
54
+ def test_next_occurrence(self, occurrences_fixture):
55
+ last_row = len(occurrences_fixture) - 1
56
+ for row, occurrence in enumerate(occurrences_fixture):
57
+ if row == last_row:
58
+ assert occurrence.next_occurrence is None
59
+ else:
60
+ assert occurrence.next_occurrence == occurrences_fixture[row + 1]
61
+
62
+ def test_previous_occurrence(self, occurrences_fixture):
63
+ for row, occurrence in enumerate(occurrences_fixture):
64
+ if row == 0:
65
+ assert occurrence.previous_occurrence is None
66
+ else:
67
+ assert occurrence.previous_occurrence == occurrences_fixture[row - 1]
68
+
69
+ # def test_get_recurrent_valid_children(self, occurrences_fixture):
70
+ # parent = occurrences_fixture[0]
71
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:])
72
+ # assert parent.get_recurrent_invalid_children().count() == 0
73
+ # occurrences_fixture[1].cancel()
74
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 1
75
+ # assert parent.get_recurrent_invalid_children().count() == 1
76
+ # occurrences_fixture[2].review()
77
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 2
78
+ # assert parent.get_recurrent_invalid_children().count() == 2
79
+ # occurrences_fixture[3].finish()
80
+ # assert parent.get_recurrent_valid_children().count() == len(occurrences_fixture[1:]) - 3
81
+ # assert parent.get_recurrent_invalid_children().count() == 3
82
+
83
+ @pytest.mark.parametrize("position, only_next_occurrence", [(0, False), (0, True), (1, False), (1, True)])
84
+ def test_forward_deletion(self, occurrences_fixture, only_next_occurrence, position):
85
+ nb_activities = Activity.objects.count()
86
+ occ = occurrences_fixture[position]
87
+ next_occurrences = occurrences_fixture.filter(period__startswith__gt=occ.period.lower)
88
+ next_occ = next_occurrences.first()
89
+ nb_next_occurrences = next_occurrences.count()
90
+ assert nb_activities == 4
91
+ if only_next_occurrence:
92
+ occ.forward_deletion([next_occ.id])
93
+ assert Activity.objects.count() == nb_activities - 1 == 3
94
+ else:
95
+ occ.forward_deletion()
96
+ assert Activity.objects.count() == nb_activities - nb_next_occurrences
97
+
98
+ def test_claim_parent_hood(self, occurrences_fixture, activity_factory):
99
+ single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
100
+ single_activity.claim_parent_hood()
101
+ assert single_activity.is_root is False
102
+
103
+ for occurrence in occurrences_fixture:
104
+ occurrence.claim_parent_hood()
105
+ if occurrence.is_leaf:
106
+ assert occurrence.is_root is False
107
+ else:
108
+ assert occurrence.is_root is True
109
+
110
+ @pytest.mark.parametrize("position, propagation", [(0, False), (0, True), (1, False), (1, True)])
111
+ def test_delete(self, position, propagation, occurrences_fixture):
112
+ occ = occurrences_fixture[position]
113
+ next_occurrences = occurrences_fixture.filter(period__startswith__gt=occ.period.lower)
114
+ next_occ = next_occurrences.first()
115
+ nb_next_occurrences = next_occurrences.count()
116
+ assert Activity.objects.count() == len(occurrences_fixture) == 4
117
+ assert nb_next_occurrences == 3 - position
118
+ Activity.objects.filter(id=occ.id).update(propagate_for_all_children=propagation)
119
+ occ.refresh_from_db()
120
+ was_root = True if occ.is_root else False
121
+ occ.delete()
122
+ if propagation:
123
+ assert Activity.objects.count() == 3 - nb_next_occurrences
124
+ else:
125
+ assert Activity.objects.count() == 3
126
+ next_occ.refresh_from_db()
127
+ next_occ.is_root == was_root
128
+
129
+ @pytest.mark.parametrize("include_self", [False, True])
130
+ def test_get_occurrence_start_datetimes(self, occurrences_fixture, include_self, activity_factory):
131
+ single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
132
+ assert not single_activity._get_occurrence_start_datetimes(include_self)
133
+ assert single_activity.is_root is False
134
+
135
+ start_dates = occurrences_fixture[0]._get_occurrence_start_datetimes(include_self)
136
+ nb_occurrences = len(occurrences_fixture) if include_self else len(occurrences_fixture[1:])
137
+ assert len(start_dates) == nb_occurrences
138
+
139
+ def test_create_recurrence_child(self, occurrence_fixture):
140
+ nb_activities = Activity.objects.count()
141
+ start_dates = occurrence_fixture._get_occurrence_start_datetimes()
142
+ new_occurrence = occurrence_fixture._create_recurrence_child(start_dates[0])
143
+ assert Activity.objects.count() == nb_activities + 1
144
+ fields = [
145
+ "assigned_to",
146
+ "all_day",
147
+ "conference_room",
148
+ "creator",
149
+ "description",
150
+ "disable_participant_check",
151
+ "importance",
152
+ "visibility",
153
+ "location",
154
+ "location_longitude",
155
+ "location_latitude",
156
+ "recurrence_end",
157
+ "recurrence_count",
158
+ "reminder_choice",
159
+ "repeat_choice",
160
+ "title",
161
+ "type",
162
+ ]
163
+ assert new_occurrence.period.lower == start_dates[0]
164
+ assert occurrence_fixture.duration == new_occurrence.duration
165
+ assert new_occurrence.parent_occurrence == occurrence_fixture
166
+ assert model_to_dict(occurrence_fixture, fields=fields) == model_to_dict(new_occurrence, fields=fields)
167
+
168
+ def test_generate_occurrences(self, occurrence_fixture, activity_factory):
169
+ single_activity = activity_factory(repeat_choice=Recurrence.ReoccuranceChoice.NEVER, preceded_by=None)
170
+ assert len(single_activity.generate_occurrences()) == 0
171
+ children = occurrence_fixture.generate_occurrences().order_by("period__startswith")
172
+ assert (
173
+ len(children)
174
+ == len(occurrence_fixture._get_occurrence_start_datetimes())
175
+ == occurrence_fixture.recurrence_count
176
+ )
177
+ assert set(children) == set(occurrence_fixture.child_activities.all())
178
+ assert children[0].is_root is False
179
+ assert len(children[0].generate_occurrences(allow_reclaiming_root=False)) == 0
180
+ assert children[0].is_root is False
181
+ assert len(children[0].generate_occurrences()) == children[0].recurrence_count > 0
182
+ assert children[0].is_root is True
183
+ assert children[0].child_activities.all().count() == children[0].recurrence_count
184
+ assert occurrence_fixture.child_activities.all().count() == 0
185
+
186
+ @pytest.mark.parametrize("position_occurrence, propagation", [(0, False), (0, True), (1, False), (1, True)])
187
+ def test_forward_change(self, occurrences_fixture, propagation, position_occurrence, activity_factory):
188
+ activity = occurrences_fixture[position_occurrence]
189
+ nb_next_occurrences = len(occurrences_fixture[position_occurrence + 1 :])
190
+ if activity.is_root:
191
+ assert len(activity.get_recurrent_valid_children()) == nb_next_occurrences
192
+ else:
193
+ assert len(activity.get_recurrent_valid_children()) == 0
194
+ was_root = activity.is_root
195
+ other_activity = activity_factory(preceded_by=None)
196
+ Activity.objects.filter(id=activity.id).update(
197
+ title=other_activity.title, propagate_for_all_children=propagation
198
+ )
199
+ activity.refresh_from_db()
200
+ activity.forward_change()
201
+ activity.refresh_from_db()
202
+ assert activity.is_root == (propagation or was_root)
203
+ assert activity.propagate_for_all_children is False
204
+ if propagation or was_root:
205
+ assert len(activity.get_recurrent_valid_children()) == nb_next_occurrences
206
+ else:
207
+ assert len(activity.get_recurrent_valid_children()) == 0
208
+
209
+
210
+ @pytest.mark.django_db
211
+ class TestRecurrenceSerializers(RecurrenceFixture):
212
+ def test_next_occurrence_no_period(self, activity_factory):
213
+ activity = activity_factory(
214
+ period=None, repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None
215
+ )
216
+ request = APIRequestFactory().get("")
217
+ user = UserFactory()
218
+ request.user = user
219
+ request.parser_context = {}
220
+ serializer = ActivityModelSerializer(activity, context={"request": request})
221
+ assert serializer.next_occurrence(activity, request, user)
222
+
223
+ def test_previous_occurrence_no_period(self, activity_factory):
224
+ activity = activity_factory(
225
+ period=None, repeat_choice=Recurrence.ReoccuranceChoice.DAILY, recurrence_count=3, preceded_by=None
226
+ )
227
+ request = APIRequestFactory().get("")
228
+ user = UserFactory()
229
+ request.user = user
230
+ request.parser_context = {}
231
+ serializer = ActivityModelSerializer(activity, context={"request": request})
232
+ assert not serializer.previous_occurrence(activity, request, user)
233
+
234
+ def test_next_occurrence(self, occurrences_fixture):
235
+ request = APIRequestFactory().get("")
236
+ user = UserFactory()
237
+ request.user = user
238
+ request.parser_context = {}
239
+ last_row = len(occurrences_fixture) - 1
240
+ for row, occurrence in enumerate(occurrences_fixture):
241
+ serializer_occurrence = ActivityModelSerializer(occurrence, context={"request": request})
242
+ if row == last_row:
243
+ assert not serializer_occurrence.next_occurrence(occurrence, request, user)
244
+ else:
245
+ assert serializer_occurrence.next_occurrence(occurrence, request, user) == {
246
+ "next_occurrence": f"http://testserver/api/crm/activity/{occurrences_fixture[row+1].id}/"
247
+ }
248
+
249
+ def test_previous_occurrence(self, occurrences_fixture):
250
+ request = APIRequestFactory().get("")
251
+ user = UserFactory()
252
+ request.user = user
253
+ request.parser_context = {}
254
+ for row, occurrence in enumerate(occurrences_fixture):
255
+ serializer_occurrence = ActivityModelSerializer(occurrence, context={"request": request})
256
+ if row == 0:
257
+ assert not serializer_occurrence.previous_occurrence(occurrence, request, user)
258
+ else:
259
+ assert serializer_occurrence.previous_occurrence(occurrence, request, user) == {
260
+ "previous_occurrence": f"http://testserver/api/crm/activity/{occurrences_fixture[row-1].id}/"
261
+ }
262
+
263
+
264
+ @pytest.mark.django_db
265
+ class TestRecurrenceViewSets(RecurrenceFixture):
266
+ def test_next_and_previous_activity_button_present_for_recurring_activity(
267
+ self, occurrences_fixture, super_user_factory
268
+ ):
269
+ request = APIRequestFactory().get("")
270
+ request.user = super_user_factory()
271
+ request._messages = InMemoryMessageStorage(request)
272
+ view = ActivityViewSet.as_view({"get": "retrieve"})
273
+ for row, occurrence in enumerate(occurrences_fixture):
274
+ response = view(request, pk=occurrence.id).render()
275
+ if occurrence.is_root:
276
+ assert response.data["instance"]["_additional_resources"]["next_occurrence"].endswith(
277
+ str(occurrences_fixture[row + 1].id) + "/"
278
+ )
279
+ assert "previous_occurrence" not in response.data["instance"]["_additional_resources"]
280
+ elif occurrence.is_leaf:
281
+ assert "next_occurrence" not in response.data["instance"]["_additional_resources"]
282
+ assert response.data["instance"]["_additional_resources"]["previous_occurrence"].endswith(
283
+ str(occurrences_fixture[row - 1].id) + "/"
284
+ )
285
+ else:
286
+ assert response.data["instance"]["_additional_resources"]["next_occurrence"].endswith(
287
+ str(occurrences_fixture[row + 1].id) + "/"
288
+ )
289
+ assert response.data["instance"]["_additional_resources"]["previous_occurrence"].endswith(
290
+ str(occurrences_fixture[row - 1].id) + "/"
291
+ )
@@ -0,0 +1,20 @@
1
+ import pytest
2
+ from rest_framework.test import APIRequestFactory
3
+ from wbcore.contrib.authentication.factories import UserFactory
4
+ from wbcrm.report.activity_report import create_report, create_report_and_send
5
+
6
+
7
+ @pytest.mark.django_db
8
+ class TestSpecificReport:
9
+ def test_create_report(self, person_factory, activity_factory):
10
+ person = person_factory()
11
+ activity = activity_factory(participants=(person,))
12
+ report = create_report(person.id, activity.start, activity.end)
13
+ assert report
14
+ assert report.getvalue()
15
+
16
+ def test_create_report_and_send(self, person_factory):
17
+ request = APIRequestFactory().get("")
18
+ request.user = UserFactory(is_active=True, is_superuser=True)
19
+ person = person_factory()
20
+ create_report_and_send(request.user.profile.id, person.id)