Skip to content

Commit

Permalink
✨(service_providers) add API endpoints
Browse files Browse the repository at this point in the history
This allow to display service providers in the frontend.
Not used yet, but will allow to manage organization and
teams related service providers.
  • Loading branch information
qbey committed Nov 15, 2024
1 parent 32a3772 commit 1b3e7e1
Show file tree
Hide file tree
Showing 7 changed files with 252 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/backend/core/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,12 @@ def validate(self, attrs):
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"]
47 changes: 47 additions & 0 deletions src/backend/core/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,3 +540,50 @@ def get(self, request):
dict_settings[setting] = getattr(settings, setting)

return response.Response(dict_settings)


class ServiceProviderFilter(filters.BaseFilterBackend):
"""
Filter service providers by audience.
"""

def filter_queryset(self, request, queryset, view):
"""
Filter service providers by audience.
"""
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
15 changes: 15 additions & 0 deletions src/backend/core/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
3 changes: 3 additions & 0 deletions src/backend/core/tests/service_providers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
Test for the service providers viewset.
"""
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"""
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
of because of their teams (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."}
3 changes: 3 additions & 0 deletions src/backend/people/api_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
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()
Expand Down

0 comments on commit 1b3e7e1

Please sign in to comment.