(api) support TeamAccess ordering on user-based fields

Important ordering fields for TeamAccess depend on user's
identities data. User and identities has a one-to-many relationship,
which forced us to prefetch the user-related data when listing
team's accesses.

Prefetch get data from the database using two SQL queries, and
join data in Python. User's data were not available in the first
SQL query.

Without annotating the query set with user main identities data,
we could not use default OrderingFilter backend code, which order_by()
the queryset.
This commit is contained in:
Lebaud Antoine
2024-03-09 18:08:03 +01:00
committed by aleb_the_flash
parent 6de0d013c3
commit 99cee241f9
2 changed files with 56 additions and 10 deletions

View File

@@ -327,8 +327,8 @@ class TeamAccessViewSet(
detail_serializer_class = serializers.TeamAccessSerializer
filter_backends = [filters.OrderingFilter]
ordering = ['role']
ordering_fields = ['role']
ordering = ["role"]
ordering_fields = ["role", "email", "name"]
def get_permissions(self):
"""User only needs to be authenticated to list team accesses"""
@@ -361,6 +361,10 @@ class TeamAccessViewSet(
user=self.request.user, team=self.kwargs["team_id"]
).values("role")[:1]
user_main_identity_query = models.Identity.objects.filter(
user=OuterRef("user_id"), is_main=True
)
queryset = (
# The logged-in user should be part of a team to see its accesses
queryset.filter(
@@ -375,7 +379,11 @@ class TeamAccessViewSet(
)
# 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))
.annotate(
user_role=Subquery(user_role_query),
email=Subquery(user_main_identity_query.values("email")[:1]),
name=Subquery(user_main_identity_query.values("name")[:1]),
)
.distinct()
)
return queryset

View File

@@ -3,6 +3,7 @@ Test for team accesses API endpoints in People's core app : list
"""
import pytest
from rest_framework.status import HTTP_200_OK
from rest_framework.test import APIClient
from core import factories, models
@@ -225,23 +226,60 @@ def test_api_team_accesses_list_authenticated_ordering():
response = client.get(
f"/api/v1.0/teams/{team.id!s}/accesses/?ordering=role",
)
assert response.status_code == 200
assert response.status_code == HTTP_200_OK
assert response.json()["count"] == 21
results = [
team_access["role"]
for team_access in response.json()["results"]
]
results = [team_access["role"] for team_access in response.json()["results"]]
assert sorted(results) == results
response = client.get(
f"/api/v1.0/teams/{team.id!s}/accesses/?ordering=-role",
)
assert response.status_code == 200
assert response.status_code == HTTP_200_OK
assert response.json()["count"] == 21
results = [team_access["role"] for team_access in response.json()["results"]]
assert sorted(results, reverse=True) == results
@pytest.mark.parametrize("ordering_fields", ["name", "email"])
def test_api_team_accesses_list_authenticated_ordering_user(ordering_fields):
"""Team accesses can be ordered by user's fields "email" or "name"."""
user = factories.UserFactory()
factories.IdentityFactory(user=user, is_main=True)
team = factories.TeamFactory()
models.TeamAccess.objects.create(team=team, user=user)
# create 20 new team members
for _ in range(20):
extra_user = factories.IdentityFactory(is_main=True).user
factories.TeamAccessFactory(team=team, user=extra_user)
client = APIClient()
client.force_login(user)
response = client.get(
f"/api/v1.0/teams/{team.id!s}/accesses/?ordering={ordering_fields}",
)
assert response.status_code == HTTP_200_OK
assert response.json()["count"] == 21
results = [
team_access["role"]
team_access["user"][ordering_fields]
for team_access in response.json()["results"]
]
assert sorted(results) == results
response = client.get(
f"/api/v1.0/teams/{team.id!s}/accesses/?ordering=-{ordering_fields}",
)
assert response.status_code == HTTP_200_OK
assert response.json()["count"] == 21
results = [
team_access["user"][ordering_fields]
for team_access in response.json()["results"]
]
assert sorted(results, reverse=True) == results