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,393 @@
|
|
|
1
|
+
from datetime import date, timedelta
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from django.contrib.auth.models import Permission
|
|
6
|
+
from faker import Faker
|
|
7
|
+
from psycopg.types.range import DateRange
|
|
8
|
+
from wbcore.contrib.directory.models import Entry
|
|
9
|
+
from wbcore.contrib.directory.signals import deactivate_profile
|
|
10
|
+
|
|
11
|
+
from wbcrm.models import Account, AccountRole, AccountRoleValidity
|
|
12
|
+
|
|
13
|
+
fake = Faker()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.django_db
|
|
17
|
+
class TestAccountModel:
|
|
18
|
+
def test_init(self, account):
|
|
19
|
+
assert account.id is not None
|
|
20
|
+
|
|
21
|
+
def test_close_account_set_inactive(self, account):
|
|
22
|
+
assert account.is_active
|
|
23
|
+
account.status = Account.Status.CLOSE
|
|
24
|
+
account.save()
|
|
25
|
+
assert not account.is_active
|
|
26
|
+
|
|
27
|
+
def test_disable_account_set_status_close(self, account):
|
|
28
|
+
assert account.status == Account.Status.OPEN
|
|
29
|
+
account.delete()
|
|
30
|
+
assert not account.is_active
|
|
31
|
+
assert account.status == Account.Status.CLOSE
|
|
32
|
+
|
|
33
|
+
def test_account_with_owner_create_account_role(self, account_factory, entry_factory):
|
|
34
|
+
customer = entry_factory.create()
|
|
35
|
+
account = account_factory.create(owner=customer)
|
|
36
|
+
assert account.roles.filter(entry=customer, role_type__key="customer").exists()
|
|
37
|
+
|
|
38
|
+
def test_leaf_account_automatically_set_to_terminal(self, account_factory):
|
|
39
|
+
parent_account = account_factory.create()
|
|
40
|
+
|
|
41
|
+
assert parent_account.is_terminal_account is True
|
|
42
|
+
child_account1 = account_factory.create(parent=parent_account, is_terminal_account=False)
|
|
43
|
+
child_account2 = account_factory.create(parent=parent_account, is_terminal_account=False)
|
|
44
|
+
|
|
45
|
+
parent_account.refresh_from_db()
|
|
46
|
+
child_account1.refresh_from_db()
|
|
47
|
+
child_account2.refresh_from_db()
|
|
48
|
+
|
|
49
|
+
assert parent_account.is_terminal_account is False
|
|
50
|
+
assert child_account1.is_terminal_account is True
|
|
51
|
+
assert child_account2.is_terminal_account is True
|
|
52
|
+
|
|
53
|
+
child_account1.delete(no_deletion=False)
|
|
54
|
+
parent_account.refresh_from_db()
|
|
55
|
+
assert parent_account.is_terminal_account is False # because there is still another child
|
|
56
|
+
|
|
57
|
+
child_account2.delete(no_deletion=False)
|
|
58
|
+
parent_account.refresh_from_db()
|
|
59
|
+
assert parent_account.is_terminal_account is True # There is no children left so we expect it to become leaf
|
|
60
|
+
|
|
61
|
+
def test_disabled_parent_account_disabled_children(self, account_factory):
|
|
62
|
+
parent_account = account_factory.create()
|
|
63
|
+
child_account = account_factory.create(parent=parent_account)
|
|
64
|
+
assert child_account.is_active
|
|
65
|
+
assert parent_account.is_active
|
|
66
|
+
|
|
67
|
+
parent_account.is_active = False
|
|
68
|
+
parent_account.save()
|
|
69
|
+
|
|
70
|
+
child_account.refresh_from_db()
|
|
71
|
+
assert not child_account.is_active
|
|
72
|
+
|
|
73
|
+
def test_private_root_account_set_children_as_private(self, account_factory):
|
|
74
|
+
parent_account = account_factory.create(is_public=True)
|
|
75
|
+
child_account = account_factory.create(parent=parent_account, is_public=True)
|
|
76
|
+
parent_account.is_public = False
|
|
77
|
+
parent_account.save()
|
|
78
|
+
child_account.refresh_from_db()
|
|
79
|
+
assert not child_account.is_public
|
|
80
|
+
|
|
81
|
+
def test_can_manage(self, account, user):
|
|
82
|
+
assert not account.can_administrate(user)
|
|
83
|
+
|
|
84
|
+
perm = Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
|
|
85
|
+
user.user_permissions.add(perm)
|
|
86
|
+
|
|
87
|
+
user = get_user_model().objects.get(
|
|
88
|
+
id=user.id
|
|
89
|
+
) # we refetch user to clear perm cache, doesn't happen with refresh_from_db
|
|
90
|
+
assert account.can_administrate(user)
|
|
91
|
+
|
|
92
|
+
def test_get_inherited_roles_for_account(self, account_factory, account_role_factory):
|
|
93
|
+
parent_account = account_factory.create()
|
|
94
|
+
parent_role = account_role_factory.create(account=parent_account)
|
|
95
|
+
child_account = account_factory.create(parent=parent_account)
|
|
96
|
+
child_role = account_role_factory.create(account=child_account)
|
|
97
|
+
assert set(child_account.get_inherited_roles_for_account()) == {parent_role}
|
|
98
|
+
assert set(child_account.get_inherited_roles_for_account(include_self=True)) == {child_role, parent_role}
|
|
99
|
+
|
|
100
|
+
@pytest.mark.parametrize("validity_date,account__is_public", [(fake.date_object(), False)])
|
|
101
|
+
def test_can_see_account(self, user, account, account_role_factory, validity_date):
|
|
102
|
+
# Check user doesn't see permission on the account by default
|
|
103
|
+
assert not account.can_see_account(user, validity_date)
|
|
104
|
+
role = account_role_factory.create(account=account, entry=Entry.objects.get(id=user.profile.id))
|
|
105
|
+
# with a valid role, they can see it
|
|
106
|
+
assert account.can_see_account(user, validity_date=validity_date)
|
|
107
|
+
|
|
108
|
+
# with an unvalid role, they cannot see it
|
|
109
|
+
role.validity_set.first().timespan = DateRange(date.min, validity_date - timedelta(days=1))
|
|
110
|
+
role.validity_set.first().save()
|
|
111
|
+
assert account.can_see_account(user, validity_date=validity_date)
|
|
112
|
+
|
|
113
|
+
@pytest.mark.parametrize("account__is_public", [True, False])
|
|
114
|
+
def test_can_see_account_with_internal_employee(self, internal_user_factory, account):
|
|
115
|
+
# Check user doesn't see permission on the account by default
|
|
116
|
+
|
|
117
|
+
# with being an internal user, they can see it
|
|
118
|
+
assert account.can_see_account(internal_user_factory.create()) == account.is_public
|
|
119
|
+
|
|
120
|
+
@pytest.mark.parametrize("account__is_public", [True])
|
|
121
|
+
def test_normaluser_cannot_see_account_public_account(self, user, account):
|
|
122
|
+
assert not account.can_see_account(user)
|
|
123
|
+
|
|
124
|
+
def test_get_accounts_for_customer(self, account_factory, company_factory, employer_employee_relationship_factory):
|
|
125
|
+
owner1 = company_factory.create()
|
|
126
|
+
employee1 = employer_employee_relationship_factory.create(employer=owner1).employee
|
|
127
|
+
owner2 = company_factory.create()
|
|
128
|
+
employee2 = employer_employee_relationship_factory.create(employer=owner2).employee
|
|
129
|
+
|
|
130
|
+
parent_account = account_factory.create(owner=owner1)
|
|
131
|
+
other_parent_account = account_factory.create(owner=owner1)
|
|
132
|
+
child_account = account_factory.create(parent=parent_account, owner=owner2)
|
|
133
|
+
# test if all child account also shows in the queryset as well as the direct related account for that owner
|
|
134
|
+
assert set(Account.get_accounts_for_customer(owner1.entry_ptr)) == {
|
|
135
|
+
parent_account,
|
|
136
|
+
other_parent_account,
|
|
137
|
+
child_account,
|
|
138
|
+
}
|
|
139
|
+
assert set(Account.get_accounts_for_customer(employee1.entry_ptr)) == {
|
|
140
|
+
parent_account,
|
|
141
|
+
other_parent_account,
|
|
142
|
+
child_account,
|
|
143
|
+
}
|
|
144
|
+
assert set(Account.get_accounts_for_customer(owner2.entry_ptr)) == {child_account}
|
|
145
|
+
assert set(Account.get_accounts_for_customer(employee2.entry_ptr)) == {child_account}
|
|
146
|
+
|
|
147
|
+
def test_get_managed_accounts_for_entry(self, entry, account_factory, account_role_factory):
|
|
148
|
+
main_role = account_role_factory.create(entry=entry)
|
|
149
|
+
child_account = account_factory.create(parent=main_role.account)
|
|
150
|
+
other_account = account_factory.create() # noqa
|
|
151
|
+
other_role = account_role_factory.create() # noqa
|
|
152
|
+
assert set(Account.get_managed_accounts_for_entry(entry)) == {main_role.account, child_account}
|
|
153
|
+
|
|
154
|
+
def test_handle_user_deactivation(self, account_role_factory, person_factory):
|
|
155
|
+
profile = person_factory.create()
|
|
156
|
+
substitute_profile = person_factory.create()
|
|
157
|
+
|
|
158
|
+
valid_role = account_role_factory.create(entry=Entry.objects.get(id=profile.id))
|
|
159
|
+
unvalid_role = account_role_factory.create(
|
|
160
|
+
entry=Entry.objects.get(id=profile.id), visibility_daterange=DateRange(date.min, date.today())
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
deactivate_profile.send(AccountRole, instance=profile, substitute_profile=substitute_profile)
|
|
164
|
+
valid_role.refresh_from_db()
|
|
165
|
+
unvalid_role.refresh_from_db()
|
|
166
|
+
assert valid_role.validity_set.first().timespan == DateRange(date.min, date.today() - timedelta(days=1))
|
|
167
|
+
assert valid_role.entry.id == profile.id
|
|
168
|
+
assert unvalid_role.entry.id == profile.id
|
|
169
|
+
|
|
170
|
+
new_role = AccountRole.objects.get(entry_id=substitute_profile.id, account=valid_role.account)
|
|
171
|
+
assert new_role.validity_set.first().timespan == DateRange(date.today() - timedelta(days=1), date.max)
|
|
172
|
+
|
|
173
|
+
@pytest.mark.parametrize(
|
|
174
|
+
"validity_date,is_internal_user, is_superuser",
|
|
175
|
+
[
|
|
176
|
+
(fake.date_object(), True, True),
|
|
177
|
+
(fake.date_object(), False, True),
|
|
178
|
+
(fake.date_object(), True, False),
|
|
179
|
+
(fake.date_object(), False, False),
|
|
180
|
+
],
|
|
181
|
+
)
|
|
182
|
+
def test_filter_for_user(
|
|
183
|
+
self,
|
|
184
|
+
user_factory,
|
|
185
|
+
internal_user_factory,
|
|
186
|
+
account_factory,
|
|
187
|
+
account_role_factory,
|
|
188
|
+
validity_date,
|
|
189
|
+
is_internal_user,
|
|
190
|
+
is_superuser,
|
|
191
|
+
):
|
|
192
|
+
# We assume role logic are tested in the AccountRole manager unit test
|
|
193
|
+
# so we test here only:
|
|
194
|
+
# - chain filtering
|
|
195
|
+
# - Return account where user has a valid role
|
|
196
|
+
# - and Return public account if user is superuser or internal
|
|
197
|
+
|
|
198
|
+
if is_internal_user:
|
|
199
|
+
user = internal_user_factory.create()
|
|
200
|
+
else:
|
|
201
|
+
user = user_factory.create()
|
|
202
|
+
if is_superuser:
|
|
203
|
+
user.user_permissions.add(
|
|
204
|
+
Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
|
|
205
|
+
)
|
|
206
|
+
profile_entry = Entry.objects.get(id=user.profile.id)
|
|
207
|
+
public_account = account_factory.create(is_public=True)
|
|
208
|
+
private_account = account_factory.create(is_public=False) # noqa
|
|
209
|
+
valid_role = account_role_factory.create(account__is_public=fake.pybool(), entry=profile_entry)
|
|
210
|
+
children_valid_role_account = account_factory.create(
|
|
211
|
+
parent=valid_role.account, is_public=True
|
|
212
|
+
) # check that entry with role on the parent account can access the public children account
|
|
213
|
+
account_factory.create(
|
|
214
|
+
parent=valid_role.account, is_public=False
|
|
215
|
+
) # check that entry with role on the parent account cannot access private children account
|
|
216
|
+
|
|
217
|
+
unvalid_role = account_role_factory.create( # noqa
|
|
218
|
+
account__is_public=False,
|
|
219
|
+
entry=profile_entry,
|
|
220
|
+
visibility_daterange=DateRange(date.min, validity_date - timedelta(days=1)),
|
|
221
|
+
)
|
|
222
|
+
user = get_user_model().objects.get(id=user.id)
|
|
223
|
+
if is_superuser:
|
|
224
|
+
assert set(Account.objects.filter_for_user(user, validity_date)) == set(Account.objects.all())
|
|
225
|
+
elif is_internal_user:
|
|
226
|
+
assert set(Account.objects.filter_for_user(user, validity_date)) == {
|
|
227
|
+
public_account,
|
|
228
|
+
valid_role.account,
|
|
229
|
+
children_valid_role_account,
|
|
230
|
+
}
|
|
231
|
+
assert set(Account.objects.filter(id=public_account.id).filter_for_user(user, validity_date)) == {
|
|
232
|
+
public_account
|
|
233
|
+
} # basic test filter chainage
|
|
234
|
+
else:
|
|
235
|
+
assert set(Account.objects.filter_for_user(user, validity_date)) == {
|
|
236
|
+
valid_role.account,
|
|
237
|
+
children_valid_role_account,
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
def test_cannot_merge_account_with_children(self, account_factory):
|
|
241
|
+
"""
|
|
242
|
+
Check that we cannot merge an account that still has children
|
|
243
|
+
"""
|
|
244
|
+
base_account = account_factory.create()
|
|
245
|
+
merged_account = account_factory.create()
|
|
246
|
+
children_merged_account = account_factory.create(parent=merged_account)
|
|
247
|
+
base_account.merge(merged_account)
|
|
248
|
+
merged_account.refresh_from_db()
|
|
249
|
+
assert merged_account
|
|
250
|
+
|
|
251
|
+
# but can merge the children into the parent account
|
|
252
|
+
merged_account.merge(children_merged_account)
|
|
253
|
+
with pytest.raises(Account.DoesNotExist):
|
|
254
|
+
children_merged_account.refresh_from_db()
|
|
255
|
+
|
|
256
|
+
def test_cannot_merge_account_on_different_trees(self, account_factory):
|
|
257
|
+
"""
|
|
258
|
+
Check that we cannot merge an account that still has children
|
|
259
|
+
"""
|
|
260
|
+
base_account = account_factory.create(parent=account_factory.create())
|
|
261
|
+
merged_account = account_factory.create(parent=account_factory.create())
|
|
262
|
+
account_factory.create(parent=merged_account)
|
|
263
|
+
base_account.merge(merged_account)
|
|
264
|
+
merged_account.refresh_from_db()
|
|
265
|
+
assert merged_account
|
|
266
|
+
|
|
267
|
+
def test_account_merging(self, account_factory, account_role_factory, user):
|
|
268
|
+
# TODO implemetns for commission
|
|
269
|
+
pivot_date = date(2023, 1, 1)
|
|
270
|
+
base_account = account_factory.create()
|
|
271
|
+
base_account_role = account_role_factory.create(
|
|
272
|
+
account=base_account, visibility_daterange=DateRange(date.min, pivot_date)
|
|
273
|
+
)
|
|
274
|
+
base_account_role_validity = base_account_role.validity_set.first()
|
|
275
|
+
|
|
276
|
+
merged_account = account_factory.create()
|
|
277
|
+
merged_account_role_overlapping = account_role_factory.create(
|
|
278
|
+
account=merged_account, entry=base_account_role.entry, visibility_daterange=DateRange(pivot_date, date.max)
|
|
279
|
+
)
|
|
280
|
+
merged_account_role_overlapping.authorized_hidden_users.add(user)
|
|
281
|
+
merged_account_role_validity = merged_account_role_overlapping.validity_set.first()
|
|
282
|
+
|
|
283
|
+
merged_account_role_different = account_role_factory.create(account=merged_account)
|
|
284
|
+
merged_account_role_different_validity = merged_account_role_different.validity_set.first()
|
|
285
|
+
|
|
286
|
+
base_account.merge(merged_account)
|
|
287
|
+
|
|
288
|
+
# test that the none overlapping role were forwarded to the base account
|
|
289
|
+
assert set(base_account.roles.all()) == {base_account_role, merged_account_role_different}
|
|
290
|
+
|
|
291
|
+
# test that the validity set for the base role got the new validity from the base merged account role
|
|
292
|
+
assert set(base_account_role.validity_set.all()) == {base_account_role_validity, merged_account_role_validity}
|
|
293
|
+
|
|
294
|
+
# test that the authorized user set on the merged account role were forwarded to the base account
|
|
295
|
+
assert set(base_account_role.authorized_hidden_users.all()) == {user}
|
|
296
|
+
|
|
297
|
+
# test that the merged account was deleted
|
|
298
|
+
with pytest.raises(Account.DoesNotExist):
|
|
299
|
+
merged_account.refresh_from_db()
|
|
300
|
+
|
|
301
|
+
# test that the non overlapping role correctly follows the role
|
|
302
|
+
role = AccountRole.objects.get(account=base_account, entry=merged_account_role_different.entry)
|
|
303
|
+
assert set(role.validity_set.all()) == {merged_account_role_different_validity}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@pytest.mark.django_db
|
|
307
|
+
class TestAccountRoleModel:
|
|
308
|
+
def test_init(self, account_role):
|
|
309
|
+
assert account_role.id is not None
|
|
310
|
+
validity = account_role.validity_set.first()
|
|
311
|
+
assert validity
|
|
312
|
+
assert validity.timespan == DateRange(date.min, date.max)
|
|
313
|
+
|
|
314
|
+
def test_hidden_role_set_account_to_private(self, account_factory, account_role_factory):
|
|
315
|
+
public_account = account_factory.create(is_public=True)
|
|
316
|
+
role = account_role_factory.create(account=public_account, is_hidden=True)
|
|
317
|
+
assert not role.account.is_public
|
|
318
|
+
|
|
319
|
+
# def test_filter_for_user(self, ):
|
|
320
|
+
# # here we want to check that the chained filtering works and
|
|
321
|
+
# # - Internal or super user can see their roles + public account roles if there are unhidden + hidden role if they have the right to see them
|
|
322
|
+
# # - Otherwise, can only see direct roles
|
|
323
|
+
# # - Check if the annotation is_currently_valid is there
|
|
324
|
+
@pytest.mark.parametrize(
|
|
325
|
+
"is_superuser,is_internal_user",
|
|
326
|
+
[
|
|
327
|
+
(True, True),
|
|
328
|
+
(True, False),
|
|
329
|
+
(False, True),
|
|
330
|
+
(False, False),
|
|
331
|
+
],
|
|
332
|
+
)
|
|
333
|
+
def test_filter_related_account_roles_for_internal_user(
|
|
334
|
+
self, user_factory, internal_user_factory, account_role_factory, is_superuser, is_internal_user
|
|
335
|
+
):
|
|
336
|
+
if is_internal_user:
|
|
337
|
+
user = internal_user_factory.create()
|
|
338
|
+
else:
|
|
339
|
+
user = user_factory.create()
|
|
340
|
+
if is_superuser:
|
|
341
|
+
user.user_permissions.add(
|
|
342
|
+
Permission.objects.get(content_type__app_label="wbcrm", codename="administrate_account")
|
|
343
|
+
)
|
|
344
|
+
role_unhidden_public_account = account_role_factory.create(is_hidden=False, account__is_public=True)
|
|
345
|
+
role_unhidden_private_account = account_role_factory.create(is_hidden=False, account__is_public=False) # noqa
|
|
346
|
+
role_hidden_public_account_without_privilege = account_role_factory.create( # noqa
|
|
347
|
+
is_hidden=True, account__is_public=True
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
role_hidden_private_account_with_privilege = account_role_factory.create(
|
|
351
|
+
is_hidden=True, account__is_public=False, authorized_hidden_users=[user]
|
|
352
|
+
)
|
|
353
|
+
role_direct = account_role_factory.create(
|
|
354
|
+
entry=Entry.objects.get(id=user.profile.id),
|
|
355
|
+
account__is_public=fake.pybool(),
|
|
356
|
+
)
|
|
357
|
+
role_indirect_private_account = account_role_factory.create(account__is_public=False) # noqa
|
|
358
|
+
|
|
359
|
+
if is_superuser:
|
|
360
|
+
assert set(AccountRole.objects.filter_for_user(user)) == set(AccountRole.objects.all())
|
|
361
|
+
elif is_internal_user:
|
|
362
|
+
assert set(AccountRole.objects.filter_for_user(user)) == {
|
|
363
|
+
role_unhidden_public_account,
|
|
364
|
+
role_hidden_private_account_with_privilege,
|
|
365
|
+
role_direct,
|
|
366
|
+
}
|
|
367
|
+
assert set(AccountRole.objects.filter(is_hidden=True).filter_for_user(user)) == {
|
|
368
|
+
role_hidden_private_account_with_privilege
|
|
369
|
+
} # Test that chain filtering works
|
|
370
|
+
else:
|
|
371
|
+
assert set(AccountRole.objects.filter_for_user(user)) == {role_direct}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@pytest.mark.django_db
|
|
375
|
+
class TestAccountRoleValidityModel:
|
|
376
|
+
@pytest.mark.parametrize("validity_date", [fake.date_object()])
|
|
377
|
+
def test_get_role_validity_subquery(self, account_role, validity_date):
|
|
378
|
+
assert AccountRole.objects.count() == 1
|
|
379
|
+
assert (
|
|
380
|
+
AccountRole.objects.annotate(is_valid=AccountRoleValidity.get_role_validity_subquery(validity_date))
|
|
381
|
+
.filter(is_valid=True)
|
|
382
|
+
.count()
|
|
383
|
+
== 1
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
validity = account_role.validity_set.first()
|
|
387
|
+
validity.timespan = DateRange(date.min, validity_date - timedelta(days=1))
|
|
388
|
+
validity.save()
|
|
389
|
+
assert (
|
|
390
|
+
not AccountRole.objects.annotate(is_valid=AccountRoleValidity.get_role_validity_subquery(validity_date))
|
|
391
|
+
.filter(is_valid=True)
|
|
392
|
+
.exists()
|
|
393
|
+
)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from faker import Faker
|
|
3
|
+
from rest_framework.exceptions import AuthenticationFailed
|
|
4
|
+
from rest_framework.test import APIRequestFactory
|
|
5
|
+
from wbcore.contrib.authentication.factories.users import UserFactory
|
|
6
|
+
from wbcore.contrib.directory.models import Entry
|
|
7
|
+
|
|
8
|
+
from wbcrm.factories.accounts import AccountFactory, AccountRoleFactory
|
|
9
|
+
from wbcrm.models import Account
|
|
10
|
+
from wbcrm.viewsets.accounts import (
|
|
11
|
+
AccountModelViewSet,
|
|
12
|
+
AccountRepresentationViewSet,
|
|
13
|
+
AccountRoleAccountModelViewSet,
|
|
14
|
+
ChildAccountAccountModelViewSet,
|
|
15
|
+
InheritedAccountRoleAccountModelViewSet,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
fake = Faker()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.django_db
|
|
22
|
+
class TestAccountViewset:
|
|
23
|
+
@pytest.fixture
|
|
24
|
+
def account_user(self):
|
|
25
|
+
# True, we create a superuser
|
|
26
|
+
|
|
27
|
+
if fake.pybool():
|
|
28
|
+
user = UserFactory.create(is_superuser=True)
|
|
29
|
+
else:
|
|
30
|
+
user = UserFactory.create(is_superuser=True)
|
|
31
|
+
entry = Entry.objects.get(id=user.profile.id)
|
|
32
|
+
|
|
33
|
+
# Create a bunch of account and roles
|
|
34
|
+
public_account = AccountFactory.create(is_public=True)
|
|
35
|
+
AccountRoleFactory.create(account=public_account, entry=entry)
|
|
36
|
+
AccountRoleFactory.create(account=public_account)
|
|
37
|
+
private_account = AccountFactory.create(is_public=False)
|
|
38
|
+
AccountRoleFactory.create(account=private_account, entry=entry)
|
|
39
|
+
return user
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize(
|
|
42
|
+
"viewset_class",
|
|
43
|
+
[
|
|
44
|
+
AccountRepresentationViewSet,
|
|
45
|
+
AccountModelViewSet,
|
|
46
|
+
],
|
|
47
|
+
)
|
|
48
|
+
def test_ensure_permission_on_account(self, account_user, viewset_class):
|
|
49
|
+
request = APIRequestFactory().get("")
|
|
50
|
+
request.user = account_user
|
|
51
|
+
viewset = viewset_class(request=request)
|
|
52
|
+
assert set(viewset.get_queryset()) == set(Account.objects.filter_for_user(account_user))
|
|
53
|
+
|
|
54
|
+
def test_ensure_permission_for_nested_view(self, account_user, account_factory):
|
|
55
|
+
request = APIRequestFactory().get("")
|
|
56
|
+
request.user = account_user
|
|
57
|
+
parent_account = Account.objects.first()
|
|
58
|
+
account_factory.create(parent=parent_account, is_public=True)
|
|
59
|
+
viewset = ChildAccountAccountModelViewSet(request=request, kwargs={"account_id": parent_account.id})
|
|
60
|
+
|
|
61
|
+
assert set(viewset.get_queryset()) == set(
|
|
62
|
+
Account.objects.filter(parent=parent_account).filter_for_user(account_user)
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def test_ensure_permission_on_account_role_account(self, user, account_factory, account_role_factory):
|
|
66
|
+
request = APIRequestFactory().get("")
|
|
67
|
+
request.user = user
|
|
68
|
+
parent_account = account_factory.create()
|
|
69
|
+
child_account = account_factory.create(parent=parent_account)
|
|
70
|
+
viewset_account_role = AccountRoleAccountModelViewSet(
|
|
71
|
+
request=request, kwargs={"account_id": parent_account.id}
|
|
72
|
+
)
|
|
73
|
+
viewset_inherited_account_role = InheritedAccountRoleAccountModelViewSet(
|
|
74
|
+
request=request, kwargs={"account_id": child_account.id}
|
|
75
|
+
)
|
|
76
|
+
with pytest.raises((AuthenticationFailed,)):
|
|
77
|
+
viewset_account_role.get_queryset()
|
|
78
|
+
with pytest.raises((AuthenticationFailed,)):
|
|
79
|
+
viewset_inherited_account_role.get_queryset()
|
|
80
|
+
|
|
81
|
+
role = account_role_factory.create(account=parent_account, entry=Entry.objects.get(id=user.profile.id))
|
|
82
|
+
account_role_factory.create(account=parent_account) # noise account role
|
|
83
|
+
assert set(viewset_account_role.get_queryset()) == {role}
|
|
84
|
+
|
|
85
|
+
account_role_factory.create(
|
|
86
|
+
account=child_account, entry=Entry.objects.get(id=user.profile.id)
|
|
87
|
+
) # noise account role
|
|
88
|
+
assert set(viewset_inherited_account_role.get_queryset()) == {role}
|
wbcrm/tests/conftest.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from wbcore.tests.conftest import * # isort:skip
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from django.db.models.signals import pre_migrate
|
|
5
|
+
from pytest_factoryboy import register
|
|
6
|
+
from wbcore.contrib.agenda.factories import CalendarItemFactory, ConferenceRoomFactory
|
|
7
|
+
from wbcore.contrib.authentication.factories import (
|
|
8
|
+
InternalUserFactory,
|
|
9
|
+
SuperUserFactory,
|
|
10
|
+
UserActivityFactory,
|
|
11
|
+
UserFactory,
|
|
12
|
+
)
|
|
13
|
+
from wbcore.contrib.directory.factories import (
|
|
14
|
+
CompanyFactory,
|
|
15
|
+
EmailContactFactory,
|
|
16
|
+
EmployerEmployeeRelationshipFactory,
|
|
17
|
+
EntryFactory,
|
|
18
|
+
PersonFactory,
|
|
19
|
+
)
|
|
20
|
+
from wbcrm.factories import (
|
|
21
|
+
AccountFactory,
|
|
22
|
+
AccountRoleFactory,
|
|
23
|
+
AccountRoleTypeFactory,
|
|
24
|
+
ActivityCompanyFactory,
|
|
25
|
+
ActivityFactory,
|
|
26
|
+
ActivityParticipantFactory,
|
|
27
|
+
ActivityPersonFactory,
|
|
28
|
+
ActivityTypeFactory,
|
|
29
|
+
GroupFactory,
|
|
30
|
+
ProductFactory,
|
|
31
|
+
RecurringActivityFactory,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
register(EntryFactory)
|
|
35
|
+
register(InternalUserFactory)
|
|
36
|
+
register(EmailContactFactory)
|
|
37
|
+
register(CompanyFactory)
|
|
38
|
+
register(PersonFactory)
|
|
39
|
+
register(EmployerEmployeeRelationshipFactory)
|
|
40
|
+
register(ActivityFactory)
|
|
41
|
+
register(ActivityTypeFactory)
|
|
42
|
+
register(RecurringActivityFactory)
|
|
43
|
+
register(ActivityCompanyFactory)
|
|
44
|
+
register(ActivityPersonFactory)
|
|
45
|
+
register(ActivityParticipantFactory)
|
|
46
|
+
register(GroupFactory)
|
|
47
|
+
register(ProductFactory)
|
|
48
|
+
register(ConferenceRoomFactory)
|
|
49
|
+
register(AccountFactory)
|
|
50
|
+
register(AccountRoleFactory)
|
|
51
|
+
register(AccountRoleTypeFactory)
|
|
52
|
+
register(CalendarItemFactory)
|
|
53
|
+
|
|
54
|
+
# Authentication
|
|
55
|
+
register(UserFactory)
|
|
56
|
+
register(SuperUserFactory, "superuser")
|
|
57
|
+
register(UserActivityFactory)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture(autouse=True, scope="session")
|
|
61
|
+
def django_test_environment(django_test_environment):
|
|
62
|
+
from django.apps import apps
|
|
63
|
+
|
|
64
|
+
get_models = apps.get_models
|
|
65
|
+
|
|
66
|
+
for m in [m for m in get_models() if not m._meta.managed]:
|
|
67
|
+
m._meta.managed = True
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.fixture
|
|
71
|
+
def chrome_options(chrome_options):
|
|
72
|
+
# chrome_options.add_argument("--headless=new")
|
|
73
|
+
return chrome_options
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
pre_migrate.connect(app_pre_migration, sender=apps.get_app_config("geography"))
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import re
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
|
|
5
|
+
from django.db.models.signals import (
|
|
6
|
+
ModelSignal,
|
|
7
|
+
post_delete,
|
|
8
|
+
post_init,
|
|
9
|
+
post_migrate,
|
|
10
|
+
post_save,
|
|
11
|
+
pre_delete,
|
|
12
|
+
pre_init,
|
|
13
|
+
pre_migrate,
|
|
14
|
+
pre_save,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class DisableSignals(object):
|
|
19
|
+
def __init__(self, disabled_signals=None):
|
|
20
|
+
self.stashed_signals = defaultdict(list)
|
|
21
|
+
self.disabled_signals = disabled_signals or [
|
|
22
|
+
pre_init,
|
|
23
|
+
post_init,
|
|
24
|
+
pre_save,
|
|
25
|
+
post_save,
|
|
26
|
+
pre_delete,
|
|
27
|
+
post_delete,
|
|
28
|
+
pre_migrate,
|
|
29
|
+
post_migrate,
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
def __enter__(self):
|
|
33
|
+
for signal in self.disabled_signals:
|
|
34
|
+
self.disconnect(signal)
|
|
35
|
+
|
|
36
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
37
|
+
for signal in list(self.stashed_signals):
|
|
38
|
+
self.reconnect(signal)
|
|
39
|
+
|
|
40
|
+
def disconnect(self, signal):
|
|
41
|
+
self.stashed_signals[signal] = signal.receivers
|
|
42
|
+
signal.receivers = []
|
|
43
|
+
|
|
44
|
+
def reconnect(self, signal):
|
|
45
|
+
signal.receivers = self.stashed_signals.get(signal, [])
|
|
46
|
+
del self.stashed_signals[signal]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# https://stackoverflow.com/questions/55578230/django-how-to-visualize-signals-and-save-overrides
|
|
50
|
+
RECEIVER_MODELS = re.compile(r"sender=(\w+)\W")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DisableSignalsNotification(DisableSignals):
|
|
54
|
+
def __enter__(self):
|
|
55
|
+
for signal in self.disabled_signals:
|
|
56
|
+
if not isinstance(signal, ModelSignal):
|
|
57
|
+
continue
|
|
58
|
+
for _, receiver in signal.receivers:
|
|
59
|
+
rcode = inspect.getsource(receiver())
|
|
60
|
+
rmodel = RECEIVER_MODELS.findall(rcode)
|
|
61
|
+
if "Notification" in rmodel:
|
|
62
|
+
self.disconnect(signal)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .e2e_wbcrm_utility import create_new_activity_instance, set_up_activities
|