2024-01-03 10:09:31 +01:00
|
|
|
"""API endpoints"""
|
2024-03-07 10:57:05 +01:00
|
|
|
|
2024-08-20 10:20:32 +02:00
|
|
|
from django.conf import settings
|
2024-11-19 22:58:10 +01:00
|
|
|
from django.db.models import OuterRef, Q, Subquery
|
2024-01-03 10:09:31 +01:00
|
|
|
|
2024-01-05 09:09:20 +01:00
|
|
|
from rest_framework import (
|
|
|
|
|
decorators,
|
|
|
|
|
exceptions,
|
2024-01-31 17:03:25 +01:00
|
|
|
filters,
|
2024-01-05 09:09:20 +01:00
|
|
|
mixins,
|
|
|
|
|
pagination,
|
|
|
|
|
response,
|
2024-02-12 19:07:11 +01:00
|
|
|
throttling,
|
2024-08-20 10:20:32 +02:00
|
|
|
views,
|
2024-01-05 09:09:20 +01:00
|
|
|
viewsets,
|
|
|
|
|
)
|
2024-08-20 10:20:32 +02:00
|
|
|
from rest_framework.permissions import AllowAny
|
2024-01-03 10:09:31 +01:00
|
|
|
|
2024-01-05 09:09:20 +01:00
|
|
|
from core import models
|
2024-11-25 14:44:34 +01:00
|
|
|
from core.api import permissions
|
|
|
|
|
from core.api.client import serializers
|
2024-12-05 13:49:41 +01:00
|
|
|
from core.utils.raw_sql import gen_sql_filter_json_array
|
2024-01-03 10:09:31 +01:00
|
|
|
|
✨(backend) search user on her email and name
Compute Trigram similarity on user's name, and sum it up
with existing one based on user's email.
This approach is inspired by Contact search feature, which
computes a Trigram similarity score on first name and last
name, to sum up their scores.
With a similarity score influenced by both email and name,
API results would reflect both email and name user's attributes.
As we sum up similarities, I increased the similarity threshold.
Its value is empirical, and was finetuned to avoid breaking
existing tests. Please note, the updated value is closer to the
threshold used to search contacts.
Email or Name can be None. Summing two similarity scores with
one of them None, results in a None total score. To mitigate
this issue, I added a default empty string value, to replace
None values. Thus, the similarity score on this default empty
string value is equal to 0 and not to None anymore.
2024-03-06 23:32:25 +01:00
|
|
|
SIMILARITY_THRESHOLD = 0.04
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
class NestedGenericViewSet(viewsets.GenericViewSet):
|
|
|
|
|
"""
|
|
|
|
|
A generic Viewset aims to be used in a nested route context.
|
|
|
|
|
e.g: `/api/v1.0/resource_1/<resource_1_pk>/resource_2/<resource_2_pk>/`
|
|
|
|
|
|
|
|
|
|
It allows to define all url kwargs and lookup fields to perform the lookup.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
lookup_fields: list[str] = ["pk"]
|
|
|
|
|
lookup_url_kwargs: list[str] = []
|
|
|
|
|
|
|
|
|
|
def __getattribute__(self, item):
|
|
|
|
|
"""
|
|
|
|
|
This method is overridden to allow to get the last lookup field or lookup url kwarg
|
|
|
|
|
when accessing the `lookup_field` or `lookup_url_kwarg` attribute. This is useful
|
|
|
|
|
to keep compatibility with all methods used by the parent class `GenericViewSet`.
|
|
|
|
|
"""
|
|
|
|
|
if item in ["lookup_field", "lookup_url_kwarg"]:
|
|
|
|
|
return getattr(self, item + "s", [None])[-1]
|
|
|
|
|
|
|
|
|
|
return super().__getattribute__(item)
|
|
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
"""
|
|
|
|
|
Get the list of items for this view.
|
|
|
|
|
|
|
|
|
|
`lookup_fields` attribute is enumerated here to perform the nested lookup.
|
|
|
|
|
"""
|
|
|
|
|
queryset = super().get_queryset()
|
|
|
|
|
|
|
|
|
|
# The last lookup field is removed to perform the nested lookup as it corresponds
|
|
|
|
|
# to the object pk, it is used within get_object method.
|
|
|
|
|
lookup_url_kwargs = (
|
|
|
|
|
self.lookup_url_kwargs[:-1]
|
|
|
|
|
if self.lookup_url_kwargs
|
|
|
|
|
else self.lookup_fields[:-1]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
filter_kwargs = {}
|
|
|
|
|
for index, lookup_url_kwarg in enumerate(lookup_url_kwargs):
|
|
|
|
|
if lookup_url_kwarg not in self.kwargs:
|
|
|
|
|
raise KeyError(
|
|
|
|
|
f"Expected view {self.__class__.__name__} to be called with a URL "
|
|
|
|
|
f'keyword argument named "{lookup_url_kwarg}". Fix your URL conf, or '
|
|
|
|
|
"set the `.lookup_fields` attribute on the view correctly."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
filter_kwargs.update(
|
|
|
|
|
{self.lookup_fields[index]: self.kwargs[lookup_url_kwarg]}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return queryset.filter(**filter_kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SerializerPerActionMixin:
|
|
|
|
|
"""
|
|
|
|
|
A mixin to allow to define serializer classes for each action.
|
|
|
|
|
|
|
|
|
|
This mixin is useful to avoid to define a serializer class for each action in the
|
|
|
|
|
`get_serializer_class` method.
|
|
|
|
|
|
2024-11-06 17:30:25 +01:00
|
|
|
Example:
|
|
|
|
|
```
|
|
|
|
|
class MyViewSet(SerializerPerActionMixin, viewsets.GenericViewSet):
|
|
|
|
|
serializer_class = MySerializer
|
|
|
|
|
list_serializer_class = MyListSerializer
|
|
|
|
|
retrieve_serializer_class = MyRetrieveSerializer
|
|
|
|
|
```
|
|
|
|
|
"""
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
def get_serializer_class(self):
|
|
|
|
|
"""
|
|
|
|
|
Return the serializer class to use depending on the action.
|
|
|
|
|
"""
|
2024-11-06 17:30:25 +01:00
|
|
|
if serializer_class := getattr(self, f"{self.action}_serializer_class", None):
|
|
|
|
|
return serializer_class
|
|
|
|
|
return super().get_serializer_class()
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Pagination(pagination.PageNumberPagination):
|
|
|
|
|
"""Pagination to display no more than 100 objects per page sorted by creation date."""
|
|
|
|
|
|
|
|
|
|
max_page_size = 100
|
|
|
|
|
page_size_query_param = "page_size"
|
|
|
|
|
|
|
|
|
|
|
2024-02-12 19:07:11 +01:00
|
|
|
class BurstRateThrottle(throttling.UserRateThrottle):
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
"""
|
|
|
|
|
Throttle rate for minutes. See DRF section in settings for default value.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
scope = "burst"
|
|
|
|
|
|
|
|
|
|
|
2024-02-12 19:07:11 +01:00
|
|
|
class SustainedRateThrottle(throttling.UserRateThrottle):
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
"""
|
|
|
|
|
Throttle rate for hours. See DRF section in settings for default value.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
scope = "sustained"
|
|
|
|
|
|
|
|
|
|
|
2024-01-05 09:09:20 +01:00
|
|
|
# pylint: disable=too-many-ancestors
|
2024-01-03 10:09:31 +01:00
|
|
|
class ContactViewSet(
|
|
|
|
|
mixins.CreateModelMixin,
|
|
|
|
|
mixins.DestroyModelMixin,
|
|
|
|
|
mixins.RetrieveModelMixin,
|
|
|
|
|
mixins.UpdateModelMixin,
|
|
|
|
|
viewsets.GenericViewSet,
|
|
|
|
|
):
|
|
|
|
|
"""Contact ViewSet"""
|
|
|
|
|
|
|
|
|
|
permission_classes = [permissions.IsOwnedOrPublic]
|
|
|
|
|
queryset = models.Contact.objects.all()
|
|
|
|
|
serializer_class = serializers.ContactSerializer
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
|
2024-12-04 16:54:10 +01:00
|
|
|
ordering_fields = ["full_name", "short_name", "created_at"]
|
|
|
|
|
ordering = ["full_name"]
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
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())
|
|
|
|
|
|
2024-12-04 16:54:10 +01:00
|
|
|
# List only contacts that:
|
2024-01-03 10:09:31 +01:00
|
|
|
queryset = queryset.filter(
|
2024-12-04 16:54:10 +01:00
|
|
|
# - 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
|
2024-12-02 11:14:19 +01:00
|
|
|
overridden_by__isnull=True,
|
2024-01-03 10:09:31 +01:00
|
|
|
)
|
|
|
|
|
|
2024-12-05 13:49:41 +01:00
|
|
|
# Search by case-insensitive and accent-insensitive on:
|
|
|
|
|
# - full name
|
|
|
|
|
# - short name
|
|
|
|
|
# - email (from data `emails` field)
|
2024-01-03 10:09:31 +01:00
|
|
|
if query := self.request.GET.get("q", ""):
|
2024-11-19 22:58:10 +01:00
|
|
|
queryset = queryset.filter(
|
|
|
|
|
Q(full_name__unaccent__icontains=query)
|
|
|
|
|
| Q(short_name__unaccent__icontains=query)
|
2024-12-05 13:49:41 +01:00
|
|
|
| Q(
|
|
|
|
|
id__in=gen_sql_filter_json_array(
|
|
|
|
|
queryset.model,
|
|
|
|
|
"data->'emails'",
|
|
|
|
|
"value",
|
|
|
|
|
query,
|
|
|
|
|
)
|
|
|
|
|
)
|
2024-01-03 10:09:31 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
serializer = self.get_serializer(queryset, many=True)
|
|
|
|
|
return response.Response(serializer.data)
|
|
|
|
|
|
|
|
|
|
def perform_create(self, serializer):
|
|
|
|
|
"""Set the current user as owner of the newly created contact."""
|
|
|
|
|
user = self.request.user
|
|
|
|
|
serializer.validated_data["owner"] = user
|
|
|
|
|
return super().perform_create(serializer)
|
|
|
|
|
|
|
|
|
|
|
2024-11-21 22:26:04 +01:00
|
|
|
class OrganizationViewSet(
|
|
|
|
|
mixins.RetrieveModelMixin,
|
|
|
|
|
mixins.UpdateModelMixin,
|
|
|
|
|
viewsets.GenericViewSet,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
Organization ViewSet
|
|
|
|
|
|
|
|
|
|
GET /api/organizations/<organization_id>/
|
|
|
|
|
Return the organization details for the given id.
|
|
|
|
|
|
|
|
|
|
PUT /api/organizations/<organization_id>/
|
|
|
|
|
Update the organization details for the given id.
|
|
|
|
|
|
|
|
|
|
PATCH /api/organizations/<organization_id>/
|
|
|
|
|
Partially update the organization details for the given id.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
permission_classes = [permissions.AccessPermission]
|
|
|
|
|
queryset = models.Organization.objects.all()
|
|
|
|
|
serializer_class = serializers.OrganizationSerializer
|
|
|
|
|
throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
|
|
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
"""Limit listed organizations to the one the user belongs to."""
|
|
|
|
|
return (
|
|
|
|
|
super()
|
|
|
|
|
.get_queryset()
|
|
|
|
|
.filter(pk=self.request.user.organization_id)
|
|
|
|
|
.annotate(
|
|
|
|
|
user_role=Subquery(
|
|
|
|
|
models.OrganizationAccess.objects.filter(
|
|
|
|
|
user=self.request.user, organization=OuterRef("pk")
|
|
|
|
|
).values("role")[:1]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2024-01-03 10:09:31 +01:00
|
|
|
class UserViewSet(
|
2024-11-06 17:30:25 +01:00
|
|
|
SerializerPerActionMixin,
|
|
|
|
|
mixins.UpdateModelMixin,
|
|
|
|
|
viewsets.GenericViewSet,
|
|
|
|
|
mixins.ListModelMixin,
|
2024-01-03 10:09:31 +01:00
|
|
|
):
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
"""
|
|
|
|
|
User viewset for all interactions with user infos and teams.
|
|
|
|
|
|
|
|
|
|
GET /api/users/&q=query
|
2024-10-30 01:11:28 +01:00
|
|
|
Return a list of users whose email or name matches the query.
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
"""
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
permission_classes = [permissions.IsSelf]
|
2024-11-22 14:18:50 +01:00
|
|
|
queryset = (
|
|
|
|
|
models.User.objects.select_related("organization").all().order_by("-created_at")
|
|
|
|
|
)
|
2024-01-03 10:09:31 +01:00
|
|
|
serializer_class = serializers.UserSerializer
|
2024-11-06 17:22:01 +01:00
|
|
|
get_me_serializer_class = serializers.UserMeSerializer
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
throttle_classes = [BurstRateThrottle, SustainedRateThrottle]
|
|
|
|
|
pagination_class = Pagination
|
|
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
"""Limit listed users by a query. Pagination and throttle protection apply."""
|
|
|
|
|
queryset = self.queryset
|
|
|
|
|
|
|
|
|
|
if self.action == "list":
|
|
|
|
|
# Exclude inactive contacts
|
|
|
|
|
queryset = queryset.filter(
|
|
|
|
|
is_active=True,
|
|
|
|
|
)
|
|
|
|
|
|
2024-03-22 13:36:54 +01:00
|
|
|
# Exclude all users already in the given team
|
|
|
|
|
if team_id := self.request.GET.get("team_id", ""):
|
|
|
|
|
queryset = queryset.exclude(teams__id=team_id)
|
|
|
|
|
|
2024-10-30 01:11:28 +01:00
|
|
|
# Search by case-insensitive and accent-insensitive
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
if query := self.request.GET.get("q", ""):
|
2024-10-30 01:11:28 +01:00
|
|
|
queryset = queryset.filter(
|
|
|
|
|
Q(name__unaccent__icontains=query)
|
|
|
|
|
| Q(email__unaccent__icontains=query)
|
✨(api) search users by email (#16)
* ✨(api) search users by email
The front end should be able to search users by email.
To that goal, we added a list method to the users viewset
thus creating the /users/ endpoint.
Results are filtered based on similarity with the query,
based on what preexisted for the /contacts/ endpoint.
* ✅(api) test list users by email
Test search when complete, partial query,
accentuated and capital.
Also, lower similarity threshold for user search by email
as it was too high for some tests to pass.
* 💡(api) improve documentation and test comments
Improve user viewset documentation
and comments describing tests sections
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
* 🛂(api) set isAuthenticated as base requirements
Instead of checking permissions or adding decorators
to every viewset, isAuthenticated is set as base requirement.
* 🛂(api) define throttle limits in settings
Use of Djando Rest Framework's throttle options, now set globally
to avoid duplicate code.
* 🩹(api) add email to user serializer
email field added to serializer. Tests modified accordingly.
I added the email field as "read only" to pass tests, but we need to discuss
that point in review.
* 🧱(api) move search logic to queryset
User viewset "list" method was overridden to allow search by email.
This removed the pagination. Instead of manually re-adding pagination at
the end of this method, I moved the search/filter logic to get_queryset,
to leave DRF handle pagination.
* ✅(api) test throttle protection
Test that throttle protection succesfully blocks too many requests.
* 📝(tests) improve tests comment
Fix typos on comments and clarify which setting are tested on test_throttle test
(setting import required disabling pylint false positive error)
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
---------
Co-authored-by: aleb_the_flash <45729124+lebaudantoine@users.noreply.github.com>
Co-authored-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
2024-01-29 10:14:17 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return queryset
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
@decorators.action(
|
|
|
|
|
detail=False,
|
|
|
|
|
methods=["get"],
|
|
|
|
|
url_name="me",
|
|
|
|
|
url_path="me",
|
|
|
|
|
)
|
|
|
|
|
def get_me(self, request):
|
|
|
|
|
"""
|
|
|
|
|
Return information on currently logged user
|
|
|
|
|
"""
|
2024-02-26 18:03:31 +01:00
|
|
|
user = request.user
|
2024-11-06 17:22:01 +01:00
|
|
|
return response.Response(self.get_serializer(user).data)
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TeamViewSet(
|
|
|
|
|
mixins.CreateModelMixin,
|
|
|
|
|
mixins.DestroyModelMixin,
|
|
|
|
|
mixins.ListModelMixin,
|
|
|
|
|
mixins.RetrieveModelMixin,
|
|
|
|
|
mixins.UpdateModelMixin,
|
|
|
|
|
viewsets.GenericViewSet,
|
|
|
|
|
):
|
|
|
|
|
"""Team ViewSet"""
|
|
|
|
|
|
|
|
|
|
permission_classes = [permissions.AccessPermission]
|
|
|
|
|
serializer_class = serializers.TeamSerializer
|
2024-01-31 17:03:25 +01:00
|
|
|
filter_backends = [filters.OrderingFilter]
|
|
|
|
|
ordering_fields = ["created_at"]
|
|
|
|
|
ordering = ["-created_at"]
|
2024-01-03 10:09:31 +01:00
|
|
|
queryset = models.Team.objects.all()
|
2024-10-29 10:51:13 +01:00
|
|
|
pagination_class = None
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
"""Custom queryset to get user related teams."""
|
|
|
|
|
user_role_query = models.TeamAccess.objects.filter(
|
|
|
|
|
user=self.request.user, team=OuterRef("pk")
|
|
|
|
|
).values("role")[:1]
|
2024-11-04 11:32:41 +01:00
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
models.Team.objects.prefetch_related("accesses", "service_providers")
|
|
|
|
|
.filter(
|
|
|
|
|
accesses__user=self.request.user,
|
|
|
|
|
)
|
|
|
|
|
.annotate(user_role=Subquery(user_role_query))
|
2024-01-03 10:09:31 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def perform_create(self, serializer):
|
|
|
|
|
"""Set the current user as owner of the newly created team."""
|
|
|
|
|
team = serializer.save()
|
|
|
|
|
models.TeamAccess.objects.create(
|
|
|
|
|
team=team,
|
|
|
|
|
user=self.request.user,
|
|
|
|
|
role=models.RoleChoices.OWNER,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TeamAccessViewSet(
|
|
|
|
|
mixins.CreateModelMixin,
|
|
|
|
|
mixins.DestroyModelMixin,
|
|
|
|
|
mixins.ListModelMixin,
|
|
|
|
|
mixins.RetrieveModelMixin,
|
|
|
|
|
mixins.UpdateModelMixin,
|
|
|
|
|
viewsets.GenericViewSet,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
API ViewSet for all interactions with team accesses.
|
|
|
|
|
|
|
|
|
|
GET /api/v1.0/teams/<team_id>/accesses/:<team_access_id>
|
|
|
|
|
Return list of all team accesses related to the logged-in user or one
|
|
|
|
|
team access if an id is provided.
|
|
|
|
|
|
|
|
|
|
POST /api/v1.0/teams/<team_id>/accesses/ with expected data:
|
|
|
|
|
- user: str
|
|
|
|
|
- role: str [owner|admin|member]
|
|
|
|
|
Return newly created team access
|
|
|
|
|
|
|
|
|
|
PUT /api/v1.0/teams/<team_id>/accesses/<team_access_id>/ with expected data:
|
|
|
|
|
- role: str [owner|admin|member]
|
|
|
|
|
Return updated team access
|
|
|
|
|
|
|
|
|
|
PATCH /api/v1.0/teams/<team_id>/accesses/<team_access_id>/ with expected data:
|
|
|
|
|
- role: str [owner|admin|member]
|
|
|
|
|
Return partially updated team access
|
|
|
|
|
|
|
|
|
|
DELETE /api/v1.0/teams/<team_id>/accesses/<team_access_id>/
|
|
|
|
|
Delete targeted team access
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
lookup_field = "pk"
|
|
|
|
|
pagination_class = Pagination
|
|
|
|
|
permission_classes = [permissions.AccessPermission]
|
2024-03-05 14:54:32 +01:00
|
|
|
queryset = (
|
|
|
|
|
models.TeamAccess.objects.all().select_related("user").order_by("-created_at")
|
|
|
|
|
)
|
2024-02-26 18:03:31 +01:00
|
|
|
list_serializer_class = serializers.TeamAccessReadOnlySerializer
|
|
|
|
|
detail_serializer_class = serializers.TeamAccessSerializer
|
2024-01-03 10:09:31 +01:00
|
|
|
|
2024-03-09 16:06:14 +01:00
|
|
|
filter_backends = [filters.OrderingFilter]
|
2024-03-09 18:08:03 +01:00
|
|
|
ordering = ["role"]
|
2024-06-09 22:43:42 +02:00
|
|
|
ordering_fields = ["role", "user__email", "user__name"]
|
2024-03-09 16:06:14 +01:00
|
|
|
|
2024-01-03 10:09:31 +01:00
|
|
|
def get_permissions(self):
|
|
|
|
|
"""User only needs to be authenticated to list team accesses"""
|
|
|
|
|
if self.action == "list":
|
|
|
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
|
|
|
else:
|
|
|
|
|
return super().get_permissions()
|
|
|
|
|
|
|
|
|
|
return [permission() for permission in permission_classes]
|
|
|
|
|
|
|
|
|
|
def get_serializer_context(self):
|
|
|
|
|
"""Extra context provided to the serializer class."""
|
|
|
|
|
context = super().get_serializer_context()
|
|
|
|
|
context["team_id"] = self.kwargs["team_id"]
|
|
|
|
|
return context
|
|
|
|
|
|
2024-02-26 18:03:31 +01:00
|
|
|
def get_serializer_class(self):
|
2024-10-25 14:59:02 +02:00
|
|
|
"""Chooses list or detail serializer according to the action."""
|
2024-02-26 18:03:31 +01:00
|
|
|
if self.action in {"list", "retrieve"}:
|
|
|
|
|
return self.list_serializer_class
|
|
|
|
|
return self.detail_serializer_class
|
|
|
|
|
|
2024-01-03 10:09:31 +01:00
|
|
|
def get_queryset(self):
|
|
|
|
|
"""Return the queryset according to the action."""
|
|
|
|
|
queryset = super().get_queryset()
|
|
|
|
|
queryset = queryset.filter(team=self.kwargs["team_id"])
|
|
|
|
|
|
2024-02-26 18:03:31 +01:00
|
|
|
if self.action in {"list", "retrieve"}:
|
2024-03-29 17:26:51 +01:00
|
|
|
if query := self.request.GET.get("q", ""):
|
2024-11-19 22:58:10 +01:00
|
|
|
queryset = queryset.filter(
|
|
|
|
|
Q(user__email__unaccent__icontains=query)
|
|
|
|
|
| Q(user__name__unaccent__icontains=query)
|
2024-03-29 17:26:51 +01:00
|
|
|
)
|
|
|
|
|
|
2024-03-06 19:34:14 +01:00
|
|
|
# Determine which role the logged-in user has in the team
|
2024-01-03 10:09:31 +01:00
|
|
|
user_role_query = models.TeamAccess.objects.filter(
|
2024-03-06 19:21:10 +01:00
|
|
|
user=self.request.user, team=self.kwargs["team_id"]
|
2024-01-03 10:09:31 +01:00
|
|
|
).values("role")[:1]
|
2024-02-26 18:03:31 +01:00
|
|
|
|
2024-01-03 10:09:31 +01:00
|
|
|
queryset = (
|
2024-03-06 19:34:14 +01:00
|
|
|
# The logged-in user should be part of a team to see its accesses
|
2024-01-03 10:09:31 +01:00
|
|
|
queryset.filter(
|
|
|
|
|
team__accesses__user=self.request.user,
|
|
|
|
|
)
|
2024-03-06 19:34:14 +01:00
|
|
|
# Abilities are computed based on logged-in user's role and
|
|
|
|
|
# the user role on each team access
|
2024-03-09 18:08:03 +01:00
|
|
|
.annotate(
|
|
|
|
|
user_role=Subquery(user_role_query),
|
|
|
|
|
)
|
2024-06-09 22:43:42 +02:00
|
|
|
.select_related("user")
|
2024-01-03 10:09:31 +01:00
|
|
|
.distinct()
|
|
|
|
|
)
|
|
|
|
|
return queryset
|
|
|
|
|
|
|
|
|
|
def destroy(self, request, *args, **kwargs):
|
|
|
|
|
"""Forbid deleting the last owner access"""
|
|
|
|
|
instance = self.get_object()
|
|
|
|
|
team = instance.team
|
|
|
|
|
|
|
|
|
|
# Check if the access being deleted is the last owner access for the team
|
|
|
|
|
if instance.role == "owner" and team.accesses.filter(role="owner").count() == 1:
|
2024-01-05 09:09:20 +01:00
|
|
|
return response.Response(
|
2024-01-03 10:09:31 +01:00
|
|
|
{"detail": "Cannot delete the last owner access for the team."},
|
|
|
|
|
status=400,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return super().destroy(request, *args, **kwargs)
|
|
|
|
|
|
|
|
|
|
def perform_update(self, serializer):
|
|
|
|
|
"""Check that we don't change the role if it leads to losing the last owner."""
|
|
|
|
|
instance = serializer.instance
|
|
|
|
|
|
|
|
|
|
# Check if the role is being updated and the new role is not "owner"
|
|
|
|
|
if (
|
|
|
|
|
"role" in self.request.data
|
|
|
|
|
and self.request.data["role"] != models.RoleChoices.OWNER
|
|
|
|
|
):
|
|
|
|
|
team = instance.team
|
|
|
|
|
# Check if the access being updated is the last owner access for the team
|
|
|
|
|
if (
|
|
|
|
|
instance.role == models.RoleChoices.OWNER
|
|
|
|
|
and team.accesses.filter(role=models.RoleChoices.OWNER).count() == 1
|
|
|
|
|
):
|
2024-01-05 09:09:20 +01:00
|
|
|
message = "Cannot change the role to a non-owner role for the last owner access."
|
|
|
|
|
raise exceptions.ValidationError({"role": message})
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
serializer.save()
|
2024-02-12 19:07:11 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class InvitationViewset(
|
|
|
|
|
mixins.CreateModelMixin,
|
|
|
|
|
mixins.ListModelMixin,
|
|
|
|
|
mixins.RetrieveModelMixin,
|
|
|
|
|
mixins.DestroyModelMixin,
|
|
|
|
|
viewsets.GenericViewSet,
|
|
|
|
|
):
|
|
|
|
|
"""API ViewSet for user invitations to team.
|
|
|
|
|
|
|
|
|
|
GET /api/v1.0/teams/<team_id>/invitations/:<invitation_id>/
|
|
|
|
|
Return list of invitations related to that team or or one
|
|
|
|
|
team access if an id is provided.
|
|
|
|
|
|
|
|
|
|
POST /api/v1.0/teams/<team_id>/invitations/ with expected data:
|
|
|
|
|
- email: str
|
|
|
|
|
- role: str [owner|admin|member]
|
|
|
|
|
- issuer : User, automatically added from user making query, if allowed
|
|
|
|
|
- team : Team, automatically added from requested URI
|
|
|
|
|
Return newly created invitation
|
|
|
|
|
|
|
|
|
|
PUT / PATCH : Not permitted. Instead of updating your invitation,
|
|
|
|
|
delete and create a new one.
|
|
|
|
|
|
|
|
|
|
DELETE /api/v1.0/teams/<team_id>/invitations/<invitation_id>/
|
|
|
|
|
Delete targeted invitation
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
lookup_field = "id"
|
|
|
|
|
pagination_class = Pagination
|
|
|
|
|
permission_classes = [permissions.AccessPermission]
|
|
|
|
|
queryset = (
|
|
|
|
|
models.Invitation.objects.all().select_related("team").order_by("-created_at")
|
|
|
|
|
)
|
|
|
|
|
serializer_class = serializers.InvitationSerializer
|
|
|
|
|
|
|
|
|
|
def get_permissions(self):
|
|
|
|
|
"""User only needs to be authenticated to list invitations"""
|
|
|
|
|
if self.action == "list":
|
|
|
|
|
permission_classes = [permissions.IsAuthenticated]
|
|
|
|
|
else:
|
|
|
|
|
return super().get_permissions()
|
|
|
|
|
|
|
|
|
|
return [permission() for permission in permission_classes]
|
|
|
|
|
|
|
|
|
|
def get_serializer_context(self):
|
|
|
|
|
"""Extra context provided to the serializer class."""
|
|
|
|
|
context = super().get_serializer_context()
|
|
|
|
|
context["team_id"] = self.kwargs["team_id"]
|
|
|
|
|
return context
|
|
|
|
|
|
|
|
|
|
def get_queryset(self):
|
|
|
|
|
"""Return the queryset according to the action."""
|
|
|
|
|
queryset = super().get_queryset()
|
|
|
|
|
queryset = queryset.filter(team=self.kwargs["team_id"])
|
|
|
|
|
|
|
|
|
|
if self.action == "list":
|
|
|
|
|
# Determine which role the logged-in user has in the team
|
|
|
|
|
user_role_query = models.TeamAccess.objects.filter(
|
|
|
|
|
user=self.request.user, team=self.kwargs["team_id"]
|
|
|
|
|
).values("role")[:1]
|
|
|
|
|
|
|
|
|
|
queryset = (
|
|
|
|
|
# The logged-in user should be part of a team to see its accesses
|
|
|
|
|
queryset.filter(
|
|
|
|
|
team__accesses__user=self.request.user,
|
|
|
|
|
)
|
|
|
|
|
# Abilities are computed based on logged-in user's role and
|
|
|
|
|
# the user role on each team access
|
|
|
|
|
.annotate(user_role=Subquery(user_role_query))
|
|
|
|
|
.distinct()
|
|
|
|
|
)
|
|
|
|
|
return queryset
|
2024-08-20 10:20:32 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConfigView(views.APIView):
|
|
|
|
|
"""API ViewSet for sharing some public settings."""
|
|
|
|
|
|
|
|
|
|
permission_classes = [AllowAny]
|
|
|
|
|
|
|
|
|
|
def get(self, request):
|
|
|
|
|
"""
|
|
|
|
|
GET /api/v1.0/config/
|
|
|
|
|
Return a dictionary of public settings.
|
|
|
|
|
"""
|
2024-11-15 15:54:30 +01:00
|
|
|
array_settings = ["LANGUAGES", "FEATURES", "RELEASE", "COMMIT"]
|
2024-08-20 10:20:32 +02:00
|
|
|
dict_settings = {}
|
|
|
|
|
for setting in array_settings:
|
|
|
|
|
dict_settings[setting] = getattr(settings, setting)
|
|
|
|
|
|
|
|
|
|
return response.Response(dict_settings)
|
2024-11-15 15:25:45 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|