2024-01-03 10:09:31 +01:00
|
|
|
"""Client serializers for the People core app."""
|
|
|
|
|
from rest_framework import exceptions, serializers
|
|
|
|
|
from timezone_field.rest_framework import TimeZoneSerializerField
|
|
|
|
|
|
|
|
|
|
from core import models
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContactSerializer(serializers.ModelSerializer):
|
|
|
|
|
"""Serialize contacts."""
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = models.Contact
|
|
|
|
|
fields = [
|
|
|
|
|
"id",
|
|
|
|
|
"base",
|
|
|
|
|
"data",
|
|
|
|
|
"full_name",
|
|
|
|
|
"owner",
|
|
|
|
|
"short_name",
|
|
|
|
|
]
|
|
|
|
|
read_only_fields = ["id", "owner"]
|
|
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
|
"""Make "base" field readonly but only for update/patch."""
|
|
|
|
|
validated_data.pop("base", None)
|
|
|
|
|
return super().update(instance, validated_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class UserSerializer(serializers.ModelSerializer):
|
|
|
|
|
"""Serialize users."""
|
|
|
|
|
|
|
|
|
|
timezone = TimeZoneSerializerField(use_pytz=False, required=True)
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = models.User
|
|
|
|
|
fields = [
|
|
|
|
|
"id",
|
✨(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
|
|
|
"email",
|
2024-01-03 10:09:31 +01:00
|
|
|
"language",
|
|
|
|
|
"timezone",
|
|
|
|
|
"is_device",
|
|
|
|
|
"is_staff",
|
|
|
|
|
]
|
2024-02-19 16:16:39 +01:00
|
|
|
read_only_fields = ["id", "email", "is_device", "is_staff"]
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TeamAccessSerializer(serializers.ModelSerializer):
|
|
|
|
|
"""Serialize team accesses."""
|
|
|
|
|
|
|
|
|
|
abilities = serializers.SerializerMethodField(read_only=True)
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = models.TeamAccess
|
|
|
|
|
fields = ["id", "user", "role", "abilities"]
|
|
|
|
|
read_only_fields = ["id", "abilities"]
|
|
|
|
|
|
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
|
"""Make "user" field is readonly but only on update."""
|
|
|
|
|
validated_data.pop("user", None)
|
|
|
|
|
return super().update(instance, validated_data)
|
|
|
|
|
|
|
|
|
|
def get_abilities(self, access) -> dict:
|
|
|
|
|
"""Return abilities of the logged-in user on the instance."""
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
|
if request:
|
|
|
|
|
return access.get_abilities(request.user)
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
def validate(self, attrs):
|
|
|
|
|
"""
|
|
|
|
|
Check access rights specific to writing (create/update)
|
|
|
|
|
"""
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
|
user = getattr(request, "user", None)
|
|
|
|
|
role = attrs.get("role")
|
|
|
|
|
|
|
|
|
|
# Update
|
|
|
|
|
if self.instance:
|
|
|
|
|
can_set_role_to = self.instance.get_abilities(user)["set_role_to"]
|
|
|
|
|
|
|
|
|
|
if role and role not in can_set_role_to:
|
|
|
|
|
message = (
|
|
|
|
|
f"You are only allowed to set role to {', '.join(can_set_role_to)}"
|
|
|
|
|
if can_set_role_to
|
|
|
|
|
else "You are not allowed to set this role for this team."
|
|
|
|
|
)
|
|
|
|
|
raise exceptions.PermissionDenied(message)
|
|
|
|
|
|
|
|
|
|
# Create
|
|
|
|
|
else:
|
|
|
|
|
try:
|
|
|
|
|
team_id = self.context["team_id"]
|
|
|
|
|
except KeyError as exc:
|
|
|
|
|
raise exceptions.ValidationError(
|
|
|
|
|
"You must set a team ID in kwargs to create a new team access."
|
|
|
|
|
) from exc
|
|
|
|
|
|
|
|
|
|
if not models.TeamAccess.objects.filter(
|
|
|
|
|
team=team_id,
|
|
|
|
|
user=user,
|
|
|
|
|
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
|
|
|
|
|
).exists():
|
|
|
|
|
raise exceptions.PermissionDenied(
|
|
|
|
|
"You are not allowed to manage accesses for this team."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
role == models.RoleChoices.OWNER
|
|
|
|
|
and not models.TeamAccess.objects.filter(
|
|
|
|
|
team=team_id,
|
|
|
|
|
user=user,
|
|
|
|
|
role=models.RoleChoices.OWNER,
|
|
|
|
|
).exists()
|
|
|
|
|
):
|
|
|
|
|
raise exceptions.PermissionDenied(
|
|
|
|
|
"Only owners of a team can assign other users as owners."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
attrs["team_id"] = self.context["team_id"]
|
|
|
|
|
return attrs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TeamSerializer(serializers.ModelSerializer):
|
|
|
|
|
"""Serialize teams."""
|
|
|
|
|
|
|
|
|
|
abilities = serializers.SerializerMethodField(read_only=True)
|
|
|
|
|
accesses = TeamAccessSerializer(many=True, read_only=True)
|
2024-02-01 18:15:25 +01:00
|
|
|
slug = serializers.SerializerMethodField()
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = models.Team
|
2024-02-12 11:46:15 +01:00
|
|
|
fields = [
|
|
|
|
|
"id",
|
|
|
|
|
"name",
|
|
|
|
|
"accesses",
|
|
|
|
|
"abilities",
|
|
|
|
|
"slug",
|
|
|
|
|
"created_at",
|
|
|
|
|
"updated_at",
|
|
|
|
|
]
|
|
|
|
|
read_only_fields = [
|
|
|
|
|
"id",
|
|
|
|
|
"accesses",
|
|
|
|
|
"abilities",
|
|
|
|
|
"slug",
|
|
|
|
|
"created_at",
|
|
|
|
|
"updated_at",
|
|
|
|
|
]
|
2024-01-03 10:09:31 +01:00
|
|
|
|
|
|
|
|
def get_abilities(self, team) -> dict:
|
|
|
|
|
"""Return abilities of the logged-in user on the instance."""
|
|
|
|
|
request = self.context.get("request")
|
|
|
|
|
if request:
|
|
|
|
|
return team.get_abilities(request.user)
|
|
|
|
|
return {}
|
2024-02-01 18:15:25 +01:00
|
|
|
|
|
|
|
|
def get_slug(self, instance):
|
|
|
|
|
"""Return slug from the team's name."""
|
|
|
|
|
return instance.get_slug()
|
2024-02-07 18:03:12 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class InvitationSerializer(serializers.ModelSerializer):
|
|
|
|
|
"""Serialize invitations."""
|
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
model = models.Invitation
|
|
|
|
|
fields = ["email", "team", "role", "issuer"]
|
|
|
|
|
read_only_fields = ["team", "issuer"]
|