fix(user): restore admin users list contract
Some checks failed
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 3m18s
CI/CD Pipeline / Run Tests (pull_request) Successful in 4m9s
CI/CD Pipeline / Run API Inventory E2E Tests (pull_request) Successful in 1m30s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped

This commit is contained in:
2026-03-23 12:39:30 +01:00
parent 30eb62829f
commit d030c705ac
3 changed files with 131 additions and 7 deletions

View File

@@ -1,11 +1,15 @@
from urllib.parse import urlencode
from apps.core.openapi import CommonResponses, ErrorResponses, swagger_tag
from django.contrib.auth import authenticate
from django.contrib.auth.hashers import check_password
from django.core.paginator import Paginator
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from rest_framework import generics, status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
@@ -105,6 +109,87 @@ ADMIN_USER_VALIDATION_ERROR_RESPONSE = openapi.Response(
),
)
ADMIN_USER_LIST_RESPONSE = openapi.Response(
description="Список пользователей в формате frontend-контракта",
schema=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"count": openapi.Schema(type=openapi.TYPE_INTEGER),
"next": openapi.Schema(type=openapi.TYPE_STRING, format="uri", nullable=True),
"previous": openapi.Schema(
type=openapi.TYPE_STRING, format="uri", nullable=True
),
"results": openapi.Schema(
type=openapi.TYPE_ARRAY,
items=openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"id": openapi.Schema(type=openapi.TYPE_INTEGER),
"username": openapi.Schema(type=openapi.TYPE_STRING),
"email": openapi.Schema(type=openapi.TYPE_STRING),
"phone": openapi.Schema(
type=openapi.TYPE_STRING, nullable=True
),
"is_active": openapi.Schema(type=openapi.TYPE_BOOLEAN),
"role": openapi.Schema(type=openapi.TYPE_STRING),
"role_label": openapi.Schema(type=openapi.TYPE_STRING),
"profile": openapi.Schema(
type=openapi.TYPE_OBJECT,
properties={
"first_name": openapi.Schema(type=openapi.TYPE_STRING),
"middle_name": openapi.Schema(type=openapi.TYPE_STRING),
"last_name": openapi.Schema(type=openapi.TYPE_STRING),
"full_name": openapi.Schema(type=openapi.TYPE_STRING),
},
),
},
),
),
},
),
)
def _build_page_url(request, page_number: int) -> str:
query_params = request.query_params.copy()
query_params["page"] = page_number
encoded_query = urlencode(query_params, doseq=True)
return request.build_absolute_uri(f"{request.path}?{encoded_query}")
def _paginate_user_queryset(request, queryset):
page_size_raw = request.query_params.get("page_size", "20")
page_raw = request.query_params.get("page", "1")
try:
page_size = max(1, min(int(page_size_raw), 100))
page_number = max(1, int(page_raw))
except (TypeError, ValueError) as exc:
raise ValidationError(
{
"detail": (
"Параметры page и page_size должны быть положительными "
"целыми числами."
)
}
) from exc
paginator = Paginator(queryset, page_size)
page_obj = paginator.get_page(page_number)
return {
"count": paginator.count,
"next": (
_build_page_url(request, page_obj.next_page_number())
if page_obj.has_next()
else None
),
"previous": (
_build_page_url(request, page_obj.previous_page_number())
if page_obj.has_previous()
else None
),
"results": page_obj.object_list,
}
class RegisterView(APIView):
"""
@@ -261,9 +346,23 @@ class AdminUserListCreateView(APIView):
"middle_name, last_name, role. Для обратной сортировки используйте префикс -"
),
),
openapi.Parameter(
name="page",
in_=openapi.IN_QUERY,
type=openapi.TYPE_INTEGER,
required=False,
description="Номер страницы, начиная с 1",
),
openapi.Parameter(
name="page_size",
in_=openapi.IN_QUERY,
type=openapi.TYPE_INTEGER,
required=False,
description="Размер страницы, по умолчанию 20, максимум 100",
),
],
responses={
200: FrontendManagedUserSerializer(many=True),
200: ADMIN_USER_LIST_RESPONSE,
**ErrorResponses.ADMIN,
},
)
@@ -272,8 +371,18 @@ class AdminUserListCreateView(APIView):
search=request.query_params.get("search", ""),
ordering=request.query_params.get("ordering", ""),
)
serializer = FrontendManagedUserSerializer(queryset, many=True)
return Response(serializer.data)
paginated = _paginate_user_queryset(request, queryset)
serializer = FrontendUserWithProfileSerializer(
paginated["results"], many=True
)
return Response(
{
"count": paginated["count"],
"next": paginated["next"],
"previous": paginated["previous"],
"results": serializer.data,
}
)
@swagger_auto_schema(
tags=[USER_ADMIN_TAG],

View File

@@ -254,7 +254,11 @@ class AdminUserManagementViewTest(APITestCase):
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(
set(response.data[0].keys()),
set(response.data.keys()),
{"count", "next", "previous", "results"},
)
self.assertEqual(
set(response.data["results"][0].keys()),
{
"id",
"username",
@@ -263,9 +267,14 @@ class AdminUserManagementViewTest(APITestCase):
"is_active",
"role",
"role_label",
"profile",
},
)
usernames = {item["username"] for item in response.data}
self.assertEqual(
set(response.data["results"][0]["profile"].keys()),
{"first_name", "middle_name", "last_name", "full_name"},
)
usernames = {item["username"] for item in response.data["results"]}
self.assertIn(self.admin.username, usernames)
self.assertIn(self.user.username, usernames)
@@ -282,7 +291,7 @@ class AdminUserManagementViewTest(APITestCase):
response = self.client.get(self.list_url, {"search": "Петрович"})
self.assertEqual(response.status_code, status.HTTP_200_OK)
usernames = [item["username"] for item in response.data]
usernames = [item["username"] for item in response.data["results"]]
self.assertEqual(usernames, [self.user.username])
def test_admin_can_order_users(self):
@@ -294,7 +303,7 @@ class AdminUserManagementViewTest(APITestCase):
response = self.client.get(self.list_url, {"ordering": "first_name"})
self.assertEqual(response.status_code, status.HTTP_200_OK)
ordered_ids = [item["id"] for item in response.data]
ordered_ids = [item["id"] for item in response.data["results"]]
self.assertLess(ordered_ids.index(second.id), ordered_ids.index(first.id))
def test_admin_can_create_user_with_role(self):

View File

@@ -245,6 +245,12 @@ class UserApiInventoryE2ETest(AuthenticatedApiMixin, APITestCase):
self.authenticate(self.admin)
list_response = self.client.get(reverse("api_v1:user:admin-users"))
self.assertEqual(list_response.status_code, status.HTTP_200_OK)
self.assertEqual(
set(list_response.data.keys()),
{"count", "next", "previous", "results"},
)
self.assertTrue(list_response.data["results"])
self.assertIn("profile", list_response.data["results"][0])
create_payload = {
"email": fake.unique.email(),