feat(user): users/me and admin user-management contract fields
This commit is contained in:
@@ -1,3 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from rest_framework import serializers
|
||||
from rest_framework.validators import UniqueValidator
|
||||
@@ -89,7 +93,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
class CurrentUserProfileSerializer(serializers.ModelSerializer):
|
||||
"""Профиль текущего пользователя в контракте `/users/me/`."""
|
||||
|
||||
middle_name = serializers.CharField(source="mid_name", allow_null=True)
|
||||
middle_name = serializers.SerializerMethodField()
|
||||
full_name = serializers.ReadOnlyField()
|
||||
|
||||
class Meta:
|
||||
@@ -101,6 +105,10 @@ class CurrentUserProfileSerializer(serializers.ModelSerializer):
|
||||
"full_name",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_middle_name(obj: Profile) -> str:
|
||||
return obj.mid_name or ""
|
||||
|
||||
|
||||
class CurrentUserCapabilitiesSerializer(serializers.Serializer):
|
||||
"""Возможности пользователя для UI-контрактов."""
|
||||
@@ -153,6 +161,88 @@ class CurrentUserSerializer(serializers.ModelSerializer):
|
||||
)
|
||||
|
||||
|
||||
class UserManagementSerializer(serializers.Serializer):
|
||||
"""Serializer for users listed in admin management endpoint."""
|
||||
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
username = serializers.CharField(read_only=True)
|
||||
email = serializers.EmailField(read_only=True)
|
||||
phone = serializers.CharField(read_only=True, allow_null=True)
|
||||
is_active = serializers.BooleanField(read_only=True)
|
||||
first_name = serializers.SerializerMethodField()
|
||||
middle_name = serializers.SerializerMethodField()
|
||||
last_name = serializers.SerializerMethodField()
|
||||
progress_message = serializers.SerializerMethodField()
|
||||
result = serializers.SerializerMethodField()
|
||||
error = serializers.SerializerMethodField()
|
||||
started_at = serializers.SerializerMethodField()
|
||||
completed_at = serializers.SerializerMethodField()
|
||||
duration = serializers.SerializerMethodField()
|
||||
is_successful = serializers.SerializerMethodField()
|
||||
|
||||
@staticmethod
|
||||
def _get_profile_value(profile: Profile | None, field_name: str) -> str:
|
||||
if profile is None:
|
||||
return ""
|
||||
value = getattr(profile, field_name, "")
|
||||
return value or ""
|
||||
|
||||
def get_first_name(self, obj: User) -> str:
|
||||
return self._get_profile_value(getattr(obj, "profile", None), "first_name")
|
||||
|
||||
def get_middle_name(self, obj: User) -> str:
|
||||
return self._get_profile_value(getattr(obj, "profile", None), "mid_name")
|
||||
|
||||
def get_last_name(self, obj: User) -> str:
|
||||
return self._get_profile_value(getattr(obj, "profile", None), "last_name")
|
||||
|
||||
@staticmethod
|
||||
def _get_latest_job(obj: User) -> Any | None:
|
||||
return getattr(obj, "latest_job", None)
|
||||
|
||||
def get_progress_message(self, obj: User) -> str | None:
|
||||
job = self._get_latest_job(obj)
|
||||
if job is None:
|
||||
return None
|
||||
return job.progress_message or None
|
||||
|
||||
def get_result(self, obj: User):
|
||||
job = self._get_latest_job(obj)
|
||||
if job is None:
|
||||
return None
|
||||
return job.result
|
||||
|
||||
def get_error(self, obj: User) -> str | None:
|
||||
job = self._get_latest_job(obj)
|
||||
if job is None:
|
||||
return None
|
||||
return job.error or None
|
||||
|
||||
def get_started_at(self, obj: User):
|
||||
job = self._get_latest_job(obj)
|
||||
if job is None:
|
||||
return None
|
||||
return job.started_at
|
||||
|
||||
def get_completed_at(self, obj: User):
|
||||
job = self._get_latest_job(obj)
|
||||
if job is None:
|
||||
return None
|
||||
return job.completed_at
|
||||
|
||||
def get_duration(self, obj: User) -> float | None:
|
||||
job = self._get_latest_job(obj)
|
||||
if job is None:
|
||||
return None
|
||||
return job.duration
|
||||
|
||||
def get_is_successful(self, obj: User) -> bool | None:
|
||||
job = self._get_latest_job(obj)
|
||||
if job is None:
|
||||
return None
|
||||
return job.is_successful
|
||||
|
||||
|
||||
class UserUpdateSerializer(serializers.ModelSerializer):
|
||||
"""Сериализатор для обновления данных пользователя"""
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ urlpatterns = [
|
||||
path("token/verify/", TokenVerifyView.as_view(), name="token_verify"),
|
||||
# Пользовательские данные
|
||||
path("me/", views.CurrentUserView.as_view(), name="current_user"),
|
||||
path("admin/users/", views.AdminUsersManagementView.as_view(), name="admin_users"),
|
||||
path("me/update/", views.UserUpdateView.as_view(), name="user_update"),
|
||||
path("profile/", views.ProfileDetailView.as_view(), name="profile_detail"),
|
||||
path("profile/full/", views.user_profile_detail, name="profile_full"),
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
from django.contrib.auth import authenticate
|
||||
from apps.core.models import BackgroundJob
|
||||
from apps.core.services import BackgroundJobService
|
||||
from django.contrib.auth import authenticate, get_user_model
|
||||
from django.contrib.auth.hashers import check_password
|
||||
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.permissions import AllowAny, IsAuthenticated
|
||||
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
@@ -15,12 +17,15 @@ from .serializers import (
|
||||
PasswordChangeSerializer,
|
||||
ProfileUpdateSerializer,
|
||||
TokenSerializer,
|
||||
UserManagementSerializer,
|
||||
UserRegistrationSerializer,
|
||||
UserSerializer,
|
||||
UserUpdateSerializer,
|
||||
)
|
||||
from .services import ProfileService, UserService
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
# Swagger теги для группировки
|
||||
AUTH_TAG = "Аутентификация"
|
||||
USER_TAG = "Пользователь"
|
||||
@@ -230,6 +235,45 @@ class PasswordChangeView(APIView):
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
class AdminUsersManagementView(APIView):
|
||||
"""Список пользователей для административной страницы управления."""
|
||||
|
||||
permission_classes = [IsAdminUser]
|
||||
|
||||
@staticmethod
|
||||
def _attach_latest_jobs(users: list[User]):
|
||||
user_ids = [user.id for user in users]
|
||||
if not user_ids:
|
||||
return
|
||||
|
||||
latest_jobs: dict[int, BackgroundJob] = {}
|
||||
jobs = BackgroundJobService.get_queryset().filter(
|
||||
user_id__in=user_ids
|
||||
).order_by("user_id", "-created_at")
|
||||
|
||||
for job in jobs:
|
||||
if job.user_id not in latest_jobs:
|
||||
latest_jobs[job.user_id] = job
|
||||
|
||||
for user in users:
|
||||
user.latest_job = latest_jobs.get(user.id)
|
||||
|
||||
@swagger_auto_schema(
|
||||
tags=[USER_TAG],
|
||||
operation_summary="Список пользователей (admin)",
|
||||
operation_description=(
|
||||
"Возвращает пользователей для административной панели управления. "
|
||||
"Включает метрики последней фоновой задачи пользователя."
|
||||
),
|
||||
responses={200: UserManagementSerializer(many=True)},
|
||||
)
|
||||
def get(self, request):
|
||||
users = User.objects.all().select_related("profile").order_by("id")
|
||||
self._attach_latest_jobs(list(users))
|
||||
serializer = UserManagementSerializer(users, many=True)
|
||||
return Response({"results": serializer.data})
|
||||
|
||||
|
||||
@swagger_auto_schema(
|
||||
method="get",
|
||||
tags=[USER_TAG],
|
||||
|
||||
Reference in New Issue
Block a user