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
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:
@@ -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],
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user