(contacts) add "abilities" to API endpoint data

Returns the possible actions to the frontend using the
model's `get_abilities`.
This commit is contained in:
Quentin BEY
2024-12-06 11:57:58 +01:00
committed by BEY Quentin
parent 7154a491f4
commit 6fde76fb46
8 changed files with 37 additions and 14 deletions

View File

@@ -12,6 +12,7 @@ and this project adheres to
### Added ### Added
- ✨(contacts) add "abilities" to API endpoint data #585
- ✨(contacts) allow filter list API with email - ✨(contacts) allow filter list API with email
- ✨(contacts) return profile contact from same organization - ✨(contacts) return profile contact from same organization
- ✨(dimail) automate allows requests to dimail - ✨(dimail) automate allows requests to dimail

View File

@@ -10,10 +10,13 @@ from core.models import ServiceProvider
class ContactSerializer(serializers.ModelSerializer): class ContactSerializer(serializers.ModelSerializer):
"""Serialize contacts.""" """Serialize contacts."""
abilities = serializers.SerializerMethodField()
class Meta: class Meta:
model = models.Contact model = models.Contact
fields = [ fields = [
"id", "id",
"abilities",
"override", "override",
"data", "data",
"full_name", "full_name",
@@ -21,7 +24,7 @@ class ContactSerializer(serializers.ModelSerializer):
"owner", "owner",
"short_name", "short_name",
] ]
read_only_fields = ["id", "owner"] read_only_fields = ["id", "owner", "abilities"]
extra_kwargs = { extra_kwargs = {
"override": {"required": False}, "override": {"required": False},
} }
@@ -31,6 +34,13 @@ class ContactSerializer(serializers.ModelSerializer):
validated_data.pop("override", None) validated_data.pop("override", None)
return super().update(instance, validated_data) return super().update(instance, validated_data)
def get_abilities(self, contact) -> dict:
"""Return abilities of the logged-in user on the instance."""
request = self.context.get("request")
if request:
return contact.get_abilities(request.user)
return {}
class DynamicFieldsModelSerializer(serializers.ModelSerializer): class DynamicFieldsModelSerializer(serializers.ModelSerializer):
""" """

View File

@@ -137,7 +137,7 @@ class ContactViewSet(
"""Contact ViewSet""" """Contact ViewSet"""
permission_classes = [permissions.AccessPermission] permission_classes = [permissions.AccessPermission]
queryset = models.Contact.objects.all() queryset = models.Contact.objects.select_related("user").all()
serializer_class = serializers.ContactSerializer serializer_class = serializers.ContactSerializer
throttle_classes = [BurstRateThrottle, SustainedRateThrottle] throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
ordering_fields = ["full_name", "short_name", "created_at"] ordering_fields = ["full_name", "short_name", "created_at"]

View File

@@ -196,8 +196,8 @@ class Contact(BaseModel):
For now, we still consider here, a contact without owner is "public" For now, we still consider here, a contact without owner is "public"
so we allow access to it. so we allow access to it.
""" """
is_owner = user == self.owner is_owner = user.pk == self.owner_id
is_profile_member_or_same_organization = bool(self.user) and ( is_profile_member_or_same_organization = bool(self.user_id) and (
self.user.organization_id == user.organization_id self.user.organization_id == user.organization_id
) )

View File

@@ -86,6 +86,7 @@ def test_api_contacts_create_authenticated_missing_base():
assert response.json() == { assert response.json() == {
"override": None, "override": None,
"abilities": {"delete": True, "get": True, "patch": True, "put": True},
"data": {}, "data": {},
"full_name": "David Bowman", "full_name": "David Bowman",
"id": str(new_contact.pk), "id": str(new_contact.pk),
@@ -123,6 +124,7 @@ def test_api_contacts_create_authenticated_successful():
contact = models.Contact.objects.get(owner=user) contact = models.Contact.objects.get(owner=user)
assert response.json() == { assert response.json() == {
"id": str(contact.id), "id": str(contact.id),
"abilities": {"delete": True, "get": True, "patch": True, "put": True},
"override": str(base_contact.id), "override": str(base_contact.id),
"data": CONTACT_DATA, "data": CONTACT_DATA,
"full_name": "David Bowman", "full_name": "David Bowman",
@@ -195,6 +197,7 @@ def test_api_contacts_create_authenticated_successful_with_notes():
contact = models.Contact.objects.get(owner=user) contact = models.Contact.objects.get(owner=user)
assert response.json() == { assert response.json() == {
"id": str(contact.id), "id": str(contact.id),
"abilities": {"delete": True, "get": True, "patch": True, "put": True},
"override": None, "override": None,
"data": CONTACT_DATA, "data": CONTACT_DATA,
"full_name": "David Bowman", "full_name": "David Bowman",

View File

@@ -22,7 +22,7 @@ def test_api_contacts_list_anonymous():
} }
def test_api_contacts_list_authenticated_no_query(): def test_api_contacts_list_authenticated_no_query(django_assert_num_queries):
""" """
Authenticated users should be able to list contacts without applying a query. Authenticated users should be able to list contacts without applying a query.
Profile and overridden contacts should be excluded. Profile and overridden contacts should be excluded.
@@ -57,12 +57,14 @@ def test_api_contacts_list_authenticated_no_query():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.get("/api/v1.0/contacts/") with django_assert_num_queries(2):
response = client.get("/api/v1.0/contacts/")
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == [ assert response.json() == [
{ {
"id": str(user_profile_contact.pk), "id": str(user_profile_contact.pk),
"abilities": {"delete": False, "get": True, "patch": True, "put": True},
"override": None, "override": None,
"owner": str(user.pk), "owner": str(user.pk),
"data": user_profile_contact.data, "data": user_profile_contact.data,
@@ -72,6 +74,7 @@ def test_api_contacts_list_authenticated_no_query():
}, },
{ {
"id": str(profile_contact.pk), "id": str(profile_contact.pk),
"abilities": {"delete": False, "get": True, "patch": False, "put": False},
"override": None, "override": None,
"owner": str(profile_contact.user.pk), "owner": str(profile_contact.user.pk),
"data": profile_contact.data, "data": profile_contact.data,
@@ -81,6 +84,7 @@ def test_api_contacts_list_authenticated_no_query():
}, },
{ {
"id": str(override_contact.pk), "id": str(override_contact.pk),
"abilities": {"delete": True, "get": True, "patch": True, "put": True},
"override": str(overriden_contact.pk), "override": str(overriden_contact.pk),
"owner": str(user.pk), "owner": str(user.pk),
"data": override_contact.data, "data": override_contact.data,

View File

@@ -22,7 +22,7 @@ def test_api_contacts_retrieve_anonymous():
} }
def test_api_contacts_retrieve_authenticated_owned(): def test_api_contacts_retrieve_authenticated_owned(django_assert_num_queries):
""" """
Authenticated users should be allowed to retrieve a contact they own. Authenticated users should be allowed to retrieve a contact they own.
""" """
@@ -32,11 +32,13 @@ def test_api_contacts_retrieve_authenticated_owned():
client = APIClient() client = APIClient()
client.force_login(user) client.force_login(user)
response = client.get(f"/api/v1.0/contacts/{contact.id!s}/") with django_assert_num_queries(2):
response = client.get(f"/api/v1.0/contacts/{contact.id!s}/")
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == { assert response.json() == {
"id": str(contact.id), "id": str(contact.id),
"abilities": {"delete": True, "get": True, "patch": True, "put": True},
"override": None, "override": None,
"owner": str(contact.owner.id), "owner": str(contact.owner.id),
"data": contact.data, "data": contact.data,
@@ -60,6 +62,7 @@ def test_api_contacts_retrieve_authenticated_public():
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == { assert response.json() == {
"id": str(contact.id), "id": str(contact.id),
"abilities": {"delete": False, "get": True, "patch": False, "put": False},
"override": None, "override": None,
"owner": None, "owner": None,
"data": contact.data, "data": contact.data,

View File

@@ -35,7 +35,7 @@ def test_api_contacts_update_anonymous():
assert contact_values == old_contact_values assert contact_values == old_contact_values
def test_api_contacts_update_authenticated_owned(): def test_api_contacts_update_authenticated_owned(django_assert_num_queries):
""" """
Authenticated users should be allowed to update their own contacts. Authenticated users should be allowed to update their own contacts.
""" """
@@ -52,11 +52,13 @@ def test_api_contacts_update_authenticated_owned():
).data ).data
new_contact_values["override"] = str(factories.ContactFactory().id) new_contact_values["override"] = str(factories.ContactFactory().id)
response = client.put( with django_assert_num_queries(8):
f"/api/v1.0/contacts/{contact.id!s}/", # user, 2x contact, user, 3x check, update contact
new_contact_values, response = client.put(
format="json", f"/api/v1.0/contacts/{contact.id!s}/",
) new_contact_values,
format="json",
)
assert response.status_code == 200 assert response.status_code == 200