diff --git a/chats/serializers.py b/chats/serializers.py index 9c6200b0..75d7df13 100644 --- a/chats/serializers.py +++ b/chats/serializers.py @@ -7,7 +7,7 @@ ProjectChatMessage, ) from files.serializers import UserFileSerializer -from users.serializers import UserListSerializer, UserDetailSerializer +from users.serializers import UserListSerializer, UserDetailSerializer, UserChatSerializer class DirectChatListSerializer(serializers.ModelSerializer): @@ -128,7 +128,8 @@ class Meta: class DirectChatMessageListSerializer(serializers.ModelSerializer): - author = UserDetailSerializer() + # author = UserDetailSerializer() + author = UserChatSerializer() reply_to = DirectChatMessageSerializer(allow_null=True) files = serializers.SerializerMethodField() diff --git a/project_rates/serializers.py b/project_rates/serializers.py index f124c528..361a8587 100644 --- a/project_rates/serializers.py +++ b/project_rates/serializers.py @@ -1,8 +1,9 @@ from rest_framework import serializers from core.services import get_views_count -from .models import Criteria, ProjectScore from projects.models import Project +from .models import Criteria, ProjectScore +from .typing import CriteriasResponse, ProjectScoresResponse from .validators import ProjectScoreValidator @@ -40,14 +41,26 @@ class Meta: class ProjectScoreSerializer(serializers.ModelSerializer): + criteria = CriteriaSerializer() + class Meta: model = ProjectScore - fields = ["criteria_id", "project_id", "value"] + fields = [ + "criteria", + "value", + ] + + def to_representation(self, instance): + """For a 'flat' structure without nesting.""" + representation = super().to_representation(instance) + criteria_data = representation.pop("criteria") + return {**criteria_data, **representation} -class ProjectScoreGetSerializer(serializers.ModelSerializer): - views_count = serializers.SerializerMethodField(method_name="count_views") +class ProjectListForRateSerializer(serializers.ModelSerializer): + views_count = serializers.SerializerMethodField() criterias = serializers.SerializerMethodField() + scored = serializers.SerializerMethodField() class Meta: model = Project @@ -61,45 +74,22 @@ class Meta: "industry", "region", "views_count", + "scored", "criterias", ] - def get_criterias(self, obj): - criterias = [] - for criteria in self.context["data_criterias"]: - copied_criteria = criteria.copy() - for score in self.context["data_scores"]: - if ( - criteria["id"] == score["criteria_id"] - and obj.id == score["project_id"] - ): - copied_criteria["value"] = score["value"] - - criterias.append(copied_criteria) - return criterias - - @classmethod - def count_views(cls, project): - return get_views_count(project) - - -class ScoredProjectsSerializer(ProjectScoreGetSerializer): - scores_count = serializers.IntegerField() - - class Meta(ProjectScoreGetSerializer.Meta): - fields = ProjectScoreGetSerializer.Meta.fields + ["scores_count"] - - -def serialize_data_func(criteria_to_get: list, data: dict): - criteria = Criteria.objects.in_bulk(criteria_to_get) - - for criterion in data: - needed_criteria = criteria.get(int(criterion["criterion_id"])) - - ProjectScoreValidator( - criteria_type=needed_criteria.type, - value=criterion["value"], - criteria_min_value=needed_criteria.min_value, - criteria_max_value=needed_criteria.max_value, - ) - criterion["criteria_id"] = criterion.pop("criterion_id") + def get_views_count(self, obj) -> int: + return get_views_count(obj) + + def get_criterias(self, obj) -> CriteriasResponse | ProjectScoresResponse: + program_id = self.context["view"].kwargs.get("program_id") + if obj.scored: + scores = ProjectScore.objects.filter(project=obj).select_related("criteria") + serializer = ProjectScoreSerializer(scores, many=True) + else: + cirterias = Criteria.objects.filter(partner_program__id=program_id) + serializer = CriteriaSerializer(cirterias, many=True) + return serializer.data + + def get_scored(self, obj) -> bool: + return bool(obj.scored) diff --git a/project_rates/services.py b/project_rates/services.py deleted file mode 100644 index 0735227b..00000000 --- a/project_rates/services.py +++ /dev/null @@ -1,70 +0,0 @@ -from django.db.models import Count, Q, QuerySet - -from project_rates.models import Criteria, ProjectScore -from project_rates.serializers import ( - CriteriaSerializer, - ProjectScoreSerializer, - ProjectScoreGetSerializer, -) -from projects.models import Project - - -def get_querysets(RatesRequestData) -> dict[str, QuerySet]: - program_id = RatesRequestData.program_id - project_id = RatesRequestData.project_id - user = RatesRequestData.user - - criterias = Criteria.objects.prefetch_related("partner_program").filter( - partner_program_id=program_id - ) - scores = ProjectScore.objects.prefetch_related("criteria").filter( - criteria__in=criterias.values_list("id", flat=True), user=user - ) - - if project_id: - projects = [Project.objects.get_projects_for_list_view().get(id=project_id)] - else: - projects = Project.objects.get_projects_for_list_view().filter( - partner_program_profiles__partner_program_id=program_id - ).distinct() - - if RatesRequestData.scored: - criterias_quantity = len(criterias) - projects = projects.annotate( - user_scores_count=Count("scores", filter=Q(scores__user=user)) - ).filter(user_scores_count=criterias_quantity) - - if not project_id: - projects = RatesRequestData.view.paginate_queryset(projects) - - return { - "criterias_queryset": criterias, - "scores_queryset": scores, - "projects_queryset": projects, - } - - -def serialize_project_criterias(querysets: dict[str, QuerySet]) -> list[dict]: - criteria_serializer = CriteriaSerializer(querysets["criterias_queryset"], many=True) - scores_serializer = ProjectScoreSerializer(querysets["scores_queryset"], many=True) - - projects_serializer = ProjectScoreGetSerializer( - querysets["projects_queryset"], - context={ - "data_criterias": criteria_serializer.data, - "data_scores": scores_serializer.data, - }, - many=True, - ) - return projects_serializer.data - - -def count_scored_criterias(project_data: dict): - filled_values = sum( - 1 - for criteria in project_data["criterias"] - if criteria["name"] == "Комментарий" or criteria.get("value") - ) - - if filled_values == len(project_data["criterias"]): - project_data["is_scored"] = True diff --git a/project_rates/typing.py b/project_rates/typing.py new file mode 100644 index 00000000..162e25af --- /dev/null +++ b/project_rates/typing.py @@ -0,0 +1,14 @@ +from typing import TypedDict + + +class CriteriasResponse(TypedDict): + id: int + name: str + description: str + type: str + min_value: int | float | None + max_value: int | float | None + + +class ProjectScoresResponse(CriteriasResponse): + value: str diff --git a/project_rates/urls.py b/project_rates/urls.py index 47889ba7..bab74313 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -2,12 +2,10 @@ from project_rates.views import ( RateProject, - RateProjects, - RateProjectsDetails, + ProjectListForRate, ) urlpatterns = [ path("rate/", RateProject.as_view()), - path("", RateProjects.as_view()), - path("details", RateProjectsDetails.as_view()), + path("", ProjectListForRate.as_view()), ] diff --git a/project_rates/views.py b/project_rates/views.py index e76fb9e4..75e1cb8e 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -1,20 +1,19 @@ from django.contrib.auth import get_user_model -from django.db.models import QuerySet +from django.db.models import QuerySet, Count from rest_framework import generics, status from rest_framework.response import Response -from project_rates.constants import RatesRequestData -from project_rates.services import ( - get_querysets, - serialize_project_criterias, - count_scored_criterias, -) +from django_filters import rest_framework as filters + +from partner_programs.models import PartnerProgramUserProfile +from projects.models import Project +from projects.filters import ProjectFilter from project_rates.models import ProjectScore from project_rates.pagination import RateProjectsPagination from project_rates.serializers import ( ProjectScoreCreateSerializer, - ProjectScoreGetSerializer, + ProjectListForRateSerializer, ) from users.models import Expert from users.permissions import IsExpert, IsExpertPost @@ -70,57 +69,21 @@ def create(self, request, *args, **kwargs) -> Response: return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) -class RateProjects(generics.ListAPIView): - serializer_class = ProjectScoreGetSerializer +class ProjectListForRate(generics.ListAPIView): permission_classes = [IsExpert] + serializer_class = ProjectListForRateSerializer + filter_backends = [filters.DjangoFilterBackend] + filterset_class = ProjectFilter pagination_class = RateProjectsPagination - def get_request_data(self) -> RatesRequestData: - scored = True if self.request.query_params.get("scored") == "true" else False - - return RatesRequestData( - program_id=self.kwargs.get("program_id"), - user=self.request.user, - view=self, - scored=scored, + def get_queryset(self) -> QuerySet[Project]: + projects_ids = PartnerProgramUserProfile.objects.filter( + project__isnull=False, + partner_program__id=self.kwargs.get("program_id") + ).values_list("project__id", flat=True) + # `Count` the easiest way to check for rate exist (0 -> does not exist). + return ( + Project.objects + .filter(draft=False, id__in=projects_ids) + .annotate(scored=Count("scores")) ) - - def get_querysets_dict(self) -> dict[str, QuerySet]: - return get_querysets(self.get_request_data()) - - def serialize_querysets(self) -> list[dict]: - return serialize_project_criterias(self.get_querysets_dict()) - - def get(self, request, *args, **kwargs) -> Response: - serialized_data = self.serialize_querysets() - - if self.request.query_params.get("scored") == "true": - [project.update({"is_scored": True}) for project in serialized_data] - else: - [count_scored_criterias(project) for project in serialized_data] - - return self.get_paginated_response(serialized_data) - - -class RateProjectsDetails(RateProjects): - permission_classes = [IsExpertPost] # потом решить проблему с этим - - def get_request_data(self) -> RatesRequestData: - request_data = super().get_request_data() - - project_id = self.request.query_params.get("project_id") - program_id = self.request.query_params.get("program_id") - - request_data.project_id = int(project_id) if project_id else None - request_data.program_id = int(program_id) if program_id else None - return request_data - - def get(self, request, *args, **kwargs): - try: - serialized_data = self.serialize_querysets()[0] - - count_scored_criterias(serialized_data) - - return Response(serialized_data, status=status.HTTP_200_OK) - except Exception as e: - return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) diff --git a/users/serializers.py b/users/serializers.py index a2487c3f..74f15260 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -502,6 +502,29 @@ def _update_user_education(self, instance: CustomUser, data: list[dict]) -> None ]) +class UserChatSerializer(serializers.ModelSerializer[CustomUser]): + is_online = serializers.SerializerMethodField() + + def get_is_online(self, user: CustomUser): + request = self.context.get("request") + if request and request.user.is_authenticated and request.user.id == user.id: + return True + cache_key = get_user_online_cache_key(user) + return cache.get(cache_key, False) + + class Meta: + model = CustomUser + fields = [ + "id", + "first_name", + "last_name", + "patronymic", + "avatar", + "is_active", + "is_online", + ] + + class UserListSerializer( serializers.ModelSerializer[CustomUser], SkillsWriteSerializerMixin ):