(service_providers) add API endpoints

This allow to display service providers in the frontend.
Not used yet, but will allow to manage organization and
teams related service providers.
This commit is contained in:
Quentin BEY
2024-11-15 15:25:45 +01:00
committed by BEY Quentin
parent b524369add
commit 8e6b6318c9
8 changed files with 253 additions and 0 deletions

View File

@@ -285,3 +285,12 @@ class InvitationSerializer(serializers.ModelSerializer):
attrs["team_id"] = team_id
attrs["issuer"] = user
return attrs
class ServiceProviderSerializer(serializers.ModelSerializer):
"""Serialize service providers."""
class Meta:
model = models.ServiceProvider
fields = ["id", "audience_id", "name"]
read_only_fields = ["id", "audience_id"]

View File

@@ -494,3 +494,50 @@ class ConfigView(views.APIView):
dict_settings[setting] = getattr(settings, setting)
return response.Response(dict_settings)
class ServiceProviderFilter(filters.BaseFilterBackend):
"""
Filter service providers.
"""
def filter_queryset(self, request, queryset, view):
"""
Filter service providers by audience or name.
"""
if name := request.GET.get("name"):
queryset = queryset.filter(name__icontains=name)
if audience_id := request.GET.get("audience_id"):
queryset = queryset.filter(audience_id=audience_id)
return queryset
class ServiceProviderViewSet(
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
viewsets.GenericViewSet,
):
"""
API ViewSet for all interactions with service providers.
GET /api/v1.0/service-providers/
Return a list of service providers.
GET /api/v1.0/service-providers/<service_provider_id>/
Return a service provider.
"""
permission_classes = [permissions.IsAuthenticated]
queryset = models.ServiceProvider.objects.all()
serializer_class = serializers.ServiceProviderSerializer
throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
pagination_class = Pagination
filter_backends = [filters.OrderingFilter, ServiceProviderFilter]
ordering = ["name"]
ordering_fields = ["name", "created_at"]
def get_queryset(self):
"""Filter the queryset to limit results to user's organization."""
queryset = super().get_queryset()
queryset = queryset.filter(organizations__id=self.request.user.organization_id)
return queryset

View File

@@ -226,5 +226,20 @@ class ServiceProviderFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.ServiceProvider
skip_postgeneration_save = True
audience_id = factory.Faker("uuid4")
@factory.post_generation
def teams(self, create, extracted, **kwargs):
"""Add teams to service provider from a given list."""
if not create or not extracted:
return
self.teams.set(extracted)
@factory.post_generation
def organizations(self, create, extracted, **kwargs):
"""Add organization to service provider from a given list."""
if not create or not extracted:
return
self.organizations.set(extracted)

View File

@@ -0,0 +1,3 @@
"""
Test for the service providers viewset.
"""

View File

@@ -0,0 +1,91 @@
"""
Tests for Service Provider API endpoint in People's core app: list
"""
import pytest
from rest_framework.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED
from core import factories
pytestmark = pytest.mark.django_db
def test_api_service_providers_list_anonymous(client):
"""Anonymous users should not be allowed to list service providers."""
factories.ServiceProviderFactory.create_batch(2)
response = client.get("/api/v1.0/service-providers/")
assert response.status_code == HTTP_401_UNAUTHORIZED
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_service_providers_list_authenticated(client):
"""
Authenticated users should be able to list service providers
of their organization.
"""
user = factories.UserFactory(with_organization=True)
client.force_login(user)
service_provider_1 = factories.ServiceProviderFactory(
name="A", organizations=[user.organization]
)
service_provider_2 = factories.ServiceProviderFactory(
name="B", organizations=[user.organization]
)
# Generate some not fetched data
factories.ServiceProviderFactory.create_batch(
2, organizations=[factories.OrganizationFactory(with_registration_id=True)]
) # Other service providers
factories.TeamFactory(
users=[user], service_providers=[factories.ServiceProviderFactory()]
)
response = client.get(
"/api/v1.0/service-providers/",
)
assert response.status_code == HTTP_200_OK
assert response.json() == {
"count": 2,
"next": None,
"previous": None,
"results": [
{
"audience_id": str(service_provider_1.audience_id),
"id": str(service_provider_1.pk),
"name": "A",
},
{
"audience_id": str(service_provider_2.audience_id),
"id": str(service_provider_2.pk),
"name": "B",
},
],
}
def test_api_service_providers_order(client):
"""Test that the service providers are sorted as requested."""
user = factories.UserFactory(with_organization=True)
factories.ServiceProviderFactory(name="A", organizations=[user.organization])
factories.ServiceProviderFactory(name="B", organizations=[user.organization])
client.force_login(user)
# Test ordering by name descending
response = client.get("/api/v1.0/service-providers/?ordering=-name")
assert response.status_code == 200
response_data = response.json()["results"]
assert response_data[0]["name"] == "B"
assert response_data[1]["name"] == "A"
# Test ordering by creation date ascending
response = client.get("/api/v1.0/service-providers/?ordering=created_at")
response_data = response.json()["results"]
assert response_data[0]["name"] == "A"
assert response_data[1]["name"] == "B"

View File

@@ -0,0 +1,85 @@
"""
Tests for Service Provider API endpoint in People's core app: retrieve
"""
import pytest
from rest_framework.status import HTTP_200_OK, HTTP_401_UNAUTHORIZED, HTTP_404_NOT_FOUND
from core import factories
pytestmark = pytest.mark.django_db
def test_api_service_providers_retrieve_anonymous(client):
"""Anonymous users should not be allowed to retrieve service providers."""
service_provider = factories.ServiceProviderFactory()
response = client.get(f"/api/v1.0/service-providers/{service_provider.pk}/")
assert response.status_code == HTTP_401_UNAUTHORIZED
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_service_providers_retrieve_authenticated_allowed(client):
"""
Authenticated users should be able to retrieve service providers
of their organization.
"""
user = factories.UserFactory(with_organization=True)
client.force_login(user)
service_provider = factories.ServiceProviderFactory(
organizations=[user.organization]
)
response = client.get(f"/api/v1.0/service-providers/{service_provider.pk}/")
assert response.status_code == HTTP_200_OK
assert response.json() == {
"audience_id": str(service_provider.audience_id),
"id": str(service_provider.pk),
"name": service_provider.name,
}
def test_api_service_providers_retrieve_authenticated_other_organization(client):
"""
Authenticated users should not be able to retrieve service providers
of other organization.
"""
user = factories.UserFactory(with_organization=True)
client.force_login(user)
service_provider = factories.ServiceProviderFactory(
organizations=[factories.OrganizationFactory(with_registration_id=True)]
)
response = client.get(f"/api/v1.0/service-providers/{service_provider.pk}/")
assert response.status_code == HTTP_404_NOT_FOUND
assert response.json() == {"detail": "No ServiceProvider matches the given query."}
def test_api_service_providers_retrieve_authenticated_on_teams(client):
"""
Authenticated users should not be able to retrieve service providers
just because it is related to one of their teams if it is not related
to their organization (might change later if needed).
"""
user = factories.UserFactory(with_organization=True)
client.force_login(user)
other_organization = factories.OrganizationFactory(with_registration_id=True)
service_provider = factories.ServiceProviderFactory()
factories.TeamFactory(
users=[user],
organization=other_organization,
service_providers=[service_provider],
)
response = client.get(f"/api/v1.0/service-providers/{service_provider.pk}/")
assert response.status_code == HTTP_404_NOT_FOUND
assert response.json() == {"detail": "No ServiceProvider matches the given query."}

View File

@@ -14,6 +14,9 @@ router = DefaultRouter()
router.register("contacts", viewsets.ContactViewSet, basename="contacts")
router.register("teams", viewsets.TeamViewSet, basename="teams")
router.register("users", viewsets.UserViewSet, basename="users")
router.register(
"service-providers", viewsets.ServiceProviderViewSet, basename="service-providers"
)
# - Routes nested under a team
team_related_router = DefaultRouter()