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,170 @@
1
+ from collections import OrderedDict
2
+ from unittest import TestCase
3
+
4
+ import pytest
5
+ from django.core.exceptions import ValidationError as DjangoValidationError
6
+ from rest_framework.exceptions import ValidationError
7
+ from rest_framework.test import APIRequestFactory
8
+ from wbcore.contrib.authentication.factories import UserFactory
9
+ from wbcore.contrib.directory.factories import PersonFactory
10
+ from wbcore.contrib.directory.models import Entry
11
+ from wbcrm.factories import ActivityFactory, GroupFactory, ProductFactory
12
+ from wbcrm.serializers import ActivityModelSerializer, ProductModelSerializer
13
+ from wbcrm.serializers.activities import handle_representation
14
+
15
+
16
+ @pytest.mark.django_db
17
+ class TestActivitySerializers(TestCase):
18
+ @pytest.mark.unit
19
+ def test_remove_group_member_from_instance(self):
20
+ group_member_a = PersonFactory()
21
+ group_member_a_entry = Entry.objects.get(id=group_member_a.id)
22
+ group_member_b = PersonFactory()
23
+ group_member_b_entry = Entry.objects.get(id=group_member_b.id)
24
+ group = GroupFactory(members=(group_member_a_entry, group_member_b_entry))
25
+ activity = ActivityFactory(groups=(group,), participants=(group_member_a, group_member_b))
26
+
27
+ request = APIRequestFactory().get("")
28
+ request.user = UserFactory()
29
+ request.parser_context = {}
30
+ serializer = ActivityModelSerializer(activity, context={"request": request})
31
+ new_data = {
32
+ "participants": [group_member_b],
33
+ }
34
+ assert serializer.data
35
+ assert group_member_a.id in serializer.data["participants"]
36
+ assert group_member_b.id in serializer.data["participants"]
37
+ with self.assertRaises(ValidationError):
38
+ serializer.validate(new_data)
39
+
40
+ @pytest.mark.unit
41
+ def test_remove_all_group_members_from_instance(self):
42
+ group_member_a = PersonFactory()
43
+ group_member_a_entry = Entry.objects.get(id=group_member_a.id)
44
+ group_member_b = PersonFactory()
45
+ group_member_b_entry = Entry.objects.get(id=group_member_b.id)
46
+ group = GroupFactory(members=(group_member_a_entry, group_member_b_entry))
47
+ activity = ActivityFactory(groups=(group,), participants=(group_member_a, group_member_b))
48
+
49
+ request = APIRequestFactory().get("")
50
+ request.user = UserFactory()
51
+ request.parser_context = {}
52
+ serializer = ActivityModelSerializer(activity, context={"request": request})
53
+ new_data = {
54
+ "participants": [],
55
+ }
56
+ assert serializer.data
57
+ assert set(serializer.data["participants"]) == {group_member_a.id, group_member_b.id}
58
+ with self.assertRaises(ValidationError):
59
+ serializer.validate(new_data)
60
+
61
+ @pytest.mark.unit
62
+ def test_remove_all_group_members_and_groups_from_instance(self):
63
+ group_member_a = PersonFactory()
64
+ group_member_a_entry = Entry.objects.get(id=group_member_a.id)
65
+ group_member_b = PersonFactory()
66
+ group_member_b_entry = Entry.objects.get(id=group_member_b.id)
67
+ group = GroupFactory(members=(group_member_a_entry, group_member_b_entry))
68
+ activity = ActivityFactory(groups=(group,), participants=(group_member_a, group_member_b))
69
+
70
+ request = APIRequestFactory().get("")
71
+ request.user = UserFactory()
72
+ request.parser_context = {}
73
+ serializer = ActivityModelSerializer(activity, context={"request": request})
74
+ new_data = {
75
+ "participants": [],
76
+ "groups": [],
77
+ }
78
+ assert serializer.data
79
+ try:
80
+ validated_data = serializer.validate(new_data)
81
+ except DjangoValidationError:
82
+ self.fail("Activity threw error in validation method!")
83
+ else:
84
+ self.assertEqual(validated_data, new_data)
85
+
86
+ @pytest.mark.unit
87
+ def test_remove_not_group_membersfrom_instance(self):
88
+ group_member_a = PersonFactory()
89
+ group_member_a_entry = Entry.objects.get(id=group_member_a.id)
90
+ not_group_member = PersonFactory()
91
+ not_group_member_b_entry = Entry.objects.get(id=not_group_member.id)
92
+ group = GroupFactory(members=(group_member_a_entry, not_group_member_b_entry))
93
+ activity = ActivityFactory(groups=(group,), participants=(group_member_a, not_group_member))
94
+
95
+ request = APIRequestFactory().get("")
96
+ request.user = UserFactory()
97
+ request.parser_context = {}
98
+ serializer = ActivityModelSerializer(activity, context={"request": request})
99
+ new_data = {
100
+ "participants": [
101
+ group_member_a.id,
102
+ ],
103
+ "groups": [],
104
+ }
105
+ assert serializer.data
106
+ try:
107
+ validated_data = serializer.validate(new_data)
108
+ except DjangoValidationError:
109
+ self.fail("Activity threw error in validation method!")
110
+ else:
111
+ self.assertEqual(validated_data, new_data)
112
+
113
+
114
+ @pytest.mark.django_db
115
+ class TestActivitySerializersHelperFunction:
116
+ @pytest.mark.parametrize("is_private, is_confidential", [(True, False), (True, False)])
117
+ def test_handle_representaion(self, is_private, is_confidential):
118
+ test_representation = OrderedDict(
119
+ [
120
+ ("id", "Test-ID"),
121
+ ("title", "Test-Title"),
122
+ ("is_private", is_private),
123
+ ("is_confidential", is_confidential),
124
+ ("foo", "Foo"),
125
+ ("bar", "Bar"),
126
+ ]
127
+ )
128
+ test_representation = handle_representation(test_representation)
129
+ assert test_representation["id"] == "Test-ID"
130
+ if is_private:
131
+ assert test_representation["title"] == "Private Activity"
132
+ assert test_representation["foo"] is None
133
+ assert test_representation["bar"] is None
134
+ elif not is_private and is_confidential:
135
+ assert test_representation["title"] == "Confidential Activity"
136
+ assert test_representation["foo"] is None
137
+ assert test_representation["bar"] is None
138
+ elif not is_private and not is_confidential:
139
+ assert test_representation["title"] == "Test-Title"
140
+ assert test_representation["foo"] == "Foo"
141
+ assert test_representation["bar"] == "Bar"
142
+
143
+
144
+ @pytest.mark.django_db
145
+ class TestUtilsSerializers:
146
+ def test_duplicate_company_product(self):
147
+ product = ProductFactory(is_competitor=False)
148
+ serializer = ProductModelSerializer()
149
+ data = {"title": product.title, "is_competitor": product.is_competitor}
150
+ with pytest.raises(DjangoValidationError):
151
+ serializer.validate(data)
152
+
153
+ def test_non_duplicate_competitors_product(self):
154
+ product = ProductFactory(is_competitor=False)
155
+ serializer = ProductModelSerializer()
156
+ data = {"title": product.title, "is_competitor": not product.is_competitor}
157
+ assert serializer.validate(data)
158
+
159
+ def test_non_duplicate_company_product(self):
160
+ product = ProductFactory(is_competitor=True)
161
+ serializer = ProductModelSerializer()
162
+ data = {"title": product.title, "is_competitor": not product.is_competitor}
163
+ assert serializer.validate(data)
164
+
165
+ def test_duplicate_competitors_product(self):
166
+ product = ProductFactory(is_competitor=True)
167
+ serializer = ProductModelSerializer()
168
+ data = {"title": product.title, "is_competitor": product.is_competitor}
169
+ with pytest.raises(DjangoValidationError):
170
+ serializer.validate(data)
@@ -0,0 +1,94 @@
1
+ from datetime import timedelta
2
+
3
+ import pytest
4
+ from django.utils import timezone
5
+ from faker import Faker
6
+ from wbcore.contrib.directory.models import Company
7
+ from wbcrm.factories import ActivityTypeFactory
8
+ from wbcrm.models import Activity, ActivityType
9
+ from wbcrm.tasks import default_activity_heat_calculation
10
+
11
+ fake = Faker()
12
+
13
+
14
+ @pytest.mark.django_db
15
+ class TestSpecificTasks:
16
+ pass
17
+
18
+
19
+ @pytest.mark.django_db
20
+ class TestActivityHeatCalculationTasks:
21
+ @pytest.mark.parametrize("end", [fake.date_time()])
22
+ def test_high_activity_heat(self, company_factory, activity_factory, end):
23
+ end = timezone.make_aware(end)
24
+
25
+ high_activity_company_a = company_factory(name="Company A", activity_heat=0.0)
26
+ high_activity_company_b = company_factory(name="Company B", activity_heat=0.0)
27
+ high_activity_company_c = company_factory(name="Company C", activity_heat=0.0)
28
+ activity_factory(
29
+ status=Activity.Status.REVIEWED,
30
+ companies=[high_activity_company_a.id, high_activity_company_b.id],
31
+ type=ActivityTypeFactory(score=ActivityType.Score.HIGH),
32
+ start=end - timedelta(hours=2),
33
+ end=end - timedelta(hours=1),
34
+ )
35
+
36
+ activity_factory(
37
+ status=Activity.Status.REVIEWED,
38
+ companies=[high_activity_company_b.id],
39
+ type=ActivityTypeFactory(score=ActivityType.Score.LOW),
40
+ start=end - timedelta(hours=2),
41
+ end=end - timedelta(hours=1),
42
+ )
43
+
44
+ activity_factory(
45
+ status=Activity.Status.REVIEWED,
46
+ companies=[
47
+ high_activity_company_c.id,
48
+ ],
49
+ type=ActivityTypeFactory(score=ActivityType.Score.HIGH),
50
+ start=end - timedelta(days=1, hours=2),
51
+ end=end - timedelta(days=1, hours=1),
52
+ )
53
+
54
+ default_activity_heat_calculation(end)
55
+ high_activity_company_a: Company = Company.objects.get(id=high_activity_company_a.id)
56
+ high_activity_company_b: Company = Company.objects.get(id=high_activity_company_b.id)
57
+ high_activity_company_c: Company = Company.objects.get(id=high_activity_company_c.id)
58
+ date_score = 365 / 365
59
+ target_score_a = (float(ActivityType.Score.HIGH) * date_score) / float(ActivityType.Score.MAX)
60
+ target_score_b = (
61
+ (float(ActivityType.Score.HIGH) * date_score) + (float(ActivityType.Score.LOW) * date_score)
62
+ ) / float(ActivityType.Score.MAX)
63
+ target_score_c = (float(ActivityType.Score.HIGH) * (364 / 365)) / float(ActivityType.Score.MAX)
64
+ assert round(high_activity_company_a.activity_heat, 1) == round(target_score_a, 1)
65
+ assert round(high_activity_company_b.activity_heat, 1) == round(target_score_b, 1)
66
+ assert round(high_activity_company_c.activity_heat, 1) == round(target_score_c, 1)
67
+
68
+ @pytest.mark.parametrize("end", [fake.date_time()])
69
+ def test_low_activity_heat(self, company_factory, activity_factory, end):
70
+ end = timezone.make_aware(end)
71
+
72
+ low_activity_company_a = company_factory(name="Company A", activity_heat=0.0)
73
+ low_activity_company_b = company_factory(name="Company B", activity_heat=0.0)
74
+ activity_factory(
75
+ status=Activity.Status.REVIEWED,
76
+ companies=[low_activity_company_a.id],
77
+ type=ActivityTypeFactory(score=ActivityType.Score.LOW),
78
+ start=end - timedelta(hours=2),
79
+ end=end - timedelta(hours=1),
80
+ )
81
+ activity_factory(
82
+ status=Activity.Status.REVIEWED,
83
+ companies=[low_activity_company_b.id],
84
+ type=ActivityTypeFactory(score=ActivityType.Score.HIGH),
85
+ start=end - timedelta(hours=2),
86
+ end=end - timedelta(hours=1),
87
+ )
88
+
89
+ default_activity_heat_calculation(end)
90
+ low_activity_company_a = Company.objects.get(id=low_activity_company_a.id)
91
+ date_score = (365 - 100) / 365
92
+ target_score_a = (float(ActivityType.Score.LOW) * date_score) / float(ActivityType.Score.MAX)
93
+ assert round(low_activity_company_a.activity_heat, 1) == round(target_score_a, 1)
94
+ assert low_activity_company_b.activity_heat == 0.0