From 579aac264e5aece811a37d9d15a4778fa5a962e3 Mon Sep 17 00:00:00 2001 From: Quentin BEY Date: Wed, 4 Dec 2024 16:54:10 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(contacts)=20list=20profile=20contacts?= =?UTF-8?q?=20from=20same=20org?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow the contact API to list "profile" contacts for user of the same organization. --- CHANGELOG.md | 3 +- src/backend/core/api/client/viewsets.py | 14 ++-- src/backend/core/factories.py | 10 +++ .../contacts/test_core_api_contacts_list.py | 66 ++++++++++++++----- 4 files changed, 69 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a376aa..2243cac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,9 @@ and this project adheres to ### Added +- ✨(contacts) return profile contact from same organization - ✨(dimail) automate allows requests to dimail -- ✨(contacts) add notes & force full_name #565 +- ✨(contacts) add notes & force full_name #565 ### Changed diff --git a/src/backend/core/api/client/viewsets.py b/src/backend/core/api/client/viewsets.py index 81e2741..f98f9d9 100644 --- a/src/backend/core/api/client/viewsets.py +++ b/src/backend/core/api/client/viewsets.py @@ -139,19 +139,21 @@ class ContactViewSet( queryset = models.Contact.objects.all() serializer_class = serializers.ContactSerializer throttle_classes = [BurstRateThrottle, SustainedRateThrottle] + ordering_fields = ["full_name", "short_name", "created_at"] + ordering = ["full_name"] def list(self, request, *args, **kwargs): """Limit listed users by a query with throttle protection.""" user = self.request.user queryset = self.filter_queryset(self.get_queryset()) - # Exclude contacts that: + # List only contacts that: queryset = queryset.filter( - # - belong to another user (keep public and owned contacts) - Q(owner__isnull=True) | Q(owner=user), - # - are profile contacts for a user - user__isnull=True, - # - are overriden base contacts + # - are owned by the user + Q(owner=user) + # - are profile contacts for a user from the same organization + | Q(user__organization_id=user.organization_id), + # - are not overriden by another contact overridden_by__isnull=True, ) diff --git a/src/backend/core/factories.py b/src/backend/core/factories.py index 54e46f3..baa43a0 100644 --- a/src/backend/core/factories.py +++ b/src/backend/core/factories.py @@ -118,6 +118,16 @@ class ContactFactory(BaseContactFactory): owner = factory.SubFactory("core.factories.UserFactory") +class ProfileContactFactory(BaseContactFactory): + """A factory to create profile contacts for a user""" + + class Meta: + model = models.Contact + + owner = factory.SelfAttribute(".user") + user = factory.SubFactory("core.factories.UserFactory") + + class OverrideContactFactory(BaseContactFactory): """A factory to create contacts for a user""" diff --git a/src/backend/core/tests/contacts/test_core_api_contacts_list.py b/src/backend/core/tests/contacts/test_core_api_contacts_list.py index c823b94..99b614a 100644 --- a/src/backend/core/tests/contacts/test_core_api_contacts_list.py +++ b/src/backend/core/tests/contacts/test_core_api_contacts_list.py @@ -27,18 +27,32 @@ def test_api_contacts_list_authenticated_no_query(): Authenticated users should be able to list contacts without applying a query. Profile and overridden contacts should be excluded. """ - user = factories.UserFactory() - factories.ContactFactory(owner=user, user=user) + organization = factories.OrganizationFactory(with_registration_id=True) + user = factories.UserFactory(organization=organization) - # Let's have 5 contacts in database: - assert user.profile_contact is not None # Excluded because profile contact - base_contact = factories.BaseContactFactory() # Excluded because overridden - factories.ContactFactory( - override=base_contact - ) # Excluded because belongs to other user - contact2 = factories.ContactFactory( - override=base_contact, owner=user, full_name="Bernard" - ) # Included + # The user's profile contact should be listed (why not) + user_profile_contact = factories.ContactFactory( + owner=user, user=user, full_name="Dave Bowman" + ) + + # A contact that belongs to another user should not be listed + factories.ContactFactory() + # even if from the same organization + factories.ContactFactory(owner__organization=organization) + + # A profile contact should not be listed if from another organization + factories.ProfileContactFactory() + + # A profile contact for someone in the same organization should be listed + profile_contact = factories.ProfileContactFactory( + user__organization=organization, full_name="Frank Poole" + ) + + # An overridden contact should not be listed, but the override must be + overriden_contact = factories.ProfileContactFactory(user__organization=organization) + override_contact = factories.ContactFactory( + owner=user, override=overriden_contact, full_name="Nicole Foole" + ) client = APIClient() client.force_login(user) @@ -48,13 +62,31 @@ def test_api_contacts_list_authenticated_no_query(): assert response.status_code == 200 assert response.json() == [ { - "id": str(contact2.id), - "override": str(base_contact.id), - "owner": str(contact2.owner.id), - "data": contact2.data, - "full_name": contact2.full_name, + "id": str(user_profile_contact.pk), + "override": None, + "owner": str(user.pk), + "data": user_profile_contact.data, + "full_name": user_profile_contact.full_name, "notes": "", - "short_name": contact2.short_name, + "short_name": user_profile_contact.short_name, + }, + { + "id": str(profile_contact.pk), + "override": None, + "owner": str(profile_contact.user.pk), + "data": profile_contact.data, + "full_name": profile_contact.full_name, + "notes": "", + "short_name": profile_contact.short_name, + }, + { + "id": str(override_contact.pk), + "override": str(overriden_contact.pk), + "owner": str(user.pk), + "data": override_contact.data, + "full_name": override_contact.full_name, + "notes": "", + "short_name": override_contact.short_name, }, ]