From b6112c1ec1fee42931e58202ea7b5fe9dd09f7d2 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Thu, 29 Feb 2024 22:51:05 +0300 Subject: [PATCH 1/5] refactored POST in RateProjects --- project_rates/serializers.py | 22 ++++++++++++++++- project_rates/views.py | 47 ++++++++++++++++++++++-------------- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/project_rates/serializers.py b/project_rates/serializers.py index d07f20c1..6ad4376b 100644 --- a/project_rates/serializers.py +++ b/project_rates/serializers.py @@ -7,9 +7,29 @@ class ProjectScoreCreateSerializer(serializers.ModelSerializer): + def __init__(self, *args, **kwargs): + self.criteria_to_get = kwargs.pop("criteria_to_get", None) + super(ProjectScoreCreateSerializer, self).__init__(*args, **kwargs) + class Meta: model = ProjectScore - fields = ["criteria", "user", "project", "value"] + fields = ("criteria", "user", "project", "value") + validators = [] + + def get_queryset(self): + return self.Meta.model.objects.filter( + criteria__id__in=self.criteria_to_get + ).select_related("criteria", "project", "user") + + def validate(self, data): + criteria = data["criteria"] + ProjectScoreValidate( + criteria_type=criteria.type, + value=data.get("value"), + criteria_min_value=criteria.min_value, + criteria_max_value=criteria.max_value, + ) + return data class CriteriaSerializer(serializers.ModelSerializer): diff --git a/project_rates/views.py b/project_rates/views.py index 41fe5b69..78046842 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -13,7 +13,6 @@ CriteriaSerializer, ProjectScoreSerializer, ProjectScoreGetSerializer, - serialize_data_func, ) from users.permissions import IsExpert, IsExpertPost @@ -24,28 +23,40 @@ class RateProject(generics.CreateAPIView): serializer_class = ProjectScoreCreateSerializer permission_classes = [IsExpertPost] - def create(self, request, *args, **kwargs): - # try: + def get_needed_data(self) -> tuple[dict, list[int]]: data = self.request.data - - user = self.request.user.id + user_id = self.request.user.id project_id = self.kwargs.get("project_id") - criteria_to_get = [] + criteria_to_get = [ + criterion["criterion_id"] for criterion in data + ] # needed for validation later for criterion in data: - criterion["user_id"] = user - criterion["project_id"] = project_id - criteria_to_get.append(criterion["criterion_id"]) - - serialize_data_func(criteria_to_get, data) - ProjectScore.objects.bulk_create( - [ProjectScore(**score) for score in data], - update_conflicts=True, - update_fields=["value"], - unique_fields=["criteria", "user", "project"], - ) + criterion["user"] = user_id + criterion["project"] = project_id + criterion["criteria"] = criterion.pop("criterion_id") + + return data, criteria_to_get + + def create(self, request, *args, **kwargs) -> Response: + try: + data, criteria_to_get = self.get_needed_data() + + serializer = ProjectScoreCreateSerializer( + data=data, criteria_to_get=criteria_to_get, many=True + ) + serializer.is_valid(raise_exception=True) + + ProjectScore.objects.bulk_create( + [ProjectScore(**item) for item in serializer.validated_data], + update_conflicts=True, + update_fields=["value"], + unique_fields=["criteria", "user", "project"], + ) - return Response({"success": True}, status=status.HTTP_201_CREATED) + return Response({"success": True}, status=status.HTTP_201_CREATED) + except Exception as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class RateProjects(generics.ListAPIView): From a5ae5e02141f81c5136ce25eac8d625d5ca8bd3e Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 1 Mar 2024 18:19:40 +0300 Subject: [PATCH 2/5] finished refactoring project_rates code --- project_rates/services.py | 77 ++++++++++++++ project_rates/urls.py | 2 - project_rates/views.py | 216 +++++++++----------------------------- 3 files changed, 128 insertions(+), 167 deletions(-) create mode 100644 project_rates/services.py diff --git a/project_rates/services.py b/project_rates/services.py new file mode 100644 index 00000000..b1bfb22f --- /dev/null +++ b/project_rates/services.py @@ -0,0 +1,77 @@ +from django.db.models import QuerySet, Count, Q + +from project_rates.models import Criteria, ProjectScore +from project_rates.serializers import ( + CriteriaSerializer, + ProjectScoreSerializer, + ProjectScoreGetSerializer, +) +from projects.models import Project + + +def get_querysets(**kwargs) -> dict[str, QuerySet]: + """ + Kwargs arguments input: + + program_id: int, + user: CustomUser, + view: ListAPIView, + scored: bool = False, + project_id: int | None = None, + + """ + program_id = kwargs.get("program_id") + user = kwargs.get("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 + ) + + unpaginated_projects = Project.objects.filter( + partner_program_profiles__partner_program_id=program_id + ).distinct() + + if kwargs.get("scored"): + quantity_criterias = len(criterias) + unpaginated_projects = unpaginated_projects.annotate( + user_scores_count=Count("scores", filter=Q(scores__user=user)) + ).filter(user_scores_count=quantity_criterias) + elif kwargs.get("project_id"): + unpaginated_projects = unpaginated_projects.filter(id=kwargs.get("project_id")) + + paginated_projects = kwargs.get("view").paginate_queryset(unpaginated_projects) + + return { + "criterias_queryset": criterias, + "scores_queryset": scores, + "paginated_projects_queryset": paginated_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["paginated_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) -> None: + 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/urls.py b/project_rates/urls.py index 8b74b862..47889ba7 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -4,12 +4,10 @@ RateProject, RateProjects, RateProjectsDetails, - ScoredProjects, ) urlpatterns = [ path("rate/", RateProject.as_view()), path("", RateProjects.as_view()), - path("scored/", ScoredProjects.as_view()), path("details", RateProjectsDetails.as_view()), ] diff --git a/project_rates/views.py b/project_rates/views.py index 78046842..ede1f0c2 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -1,17 +1,18 @@ from django.contrib.auth import get_user_model -from django.db.models import Count, Q +from django.db.models import QuerySet from rest_framework import generics, status from rest_framework.response import Response -from core.services import get_views_count -from projects.models import Project -from project_rates.models import Criteria, ProjectScore +from project_rates.services import ( + get_querysets, + serialize_project_criterias, + count_scored_criterias, +) +from project_rates.models import ProjectScore from project_rates.pagination import RateProjectsPagination from project_rates.serializers import ( ProjectScoreCreateSerializer, - CriteriaSerializer, - ProjectScoreSerializer, ProjectScoreGetSerializer, ) from users.permissions import IsExpert, IsExpertPost @@ -30,7 +31,7 @@ def get_needed_data(self) -> tuple[dict, list[int]]: criteria_to_get = [ criterion["criterion_id"] for criterion in data - ] # needed for validation later + ] # is needed for validation later for criterion in data: criterion["user"] = user_id criterion["project"] = project_id @@ -39,189 +40,74 @@ def get_needed_data(self) -> tuple[dict, list[int]]: return data, criteria_to_get def create(self, request, *args, **kwargs) -> Response: - try: - data, criteria_to_get = self.get_needed_data() - - serializer = ProjectScoreCreateSerializer( - data=data, criteria_to_get=criteria_to_get, many=True - ) - serializer.is_valid(raise_exception=True) - - ProjectScore.objects.bulk_create( - [ProjectScore(**item) for item in serializer.validated_data], - update_conflicts=True, - update_fields=["value"], - unique_fields=["criteria", "user", "project"], - ) + data, criteria_to_get = self.get_needed_data() - return Response({"success": True}, status=status.HTTP_201_CREATED) - except Exception as e: - return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) - - -class RateProjects(generics.ListAPIView): - serializer_class = ProjectScoreGetSerializer - permission_classes = [IsExpert] - pagination_class = RateProjectsPagination - - def get(self, request, *args, **kwargs): - user = self.request.user - program_id = self.kwargs.get("program_id") - - 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 + serializer = ProjectScoreCreateSerializer( + data=data, criteria_to_get=criteria_to_get, many=True ) - unpaginated_projects = Project.objects.filter( - partner_program_profiles__partner_program_id=program_id - ).distinct() - - projects = self.paginate_queryset(unpaginated_projects) - - criteria_serializer = CriteriaSerializer(data=criterias, many=True) - scores_serializer = ProjectScoreSerializer(data=scores, many=True) - - criteria_serializer.is_valid() - scores_serializer.is_valid() + serializer.is_valid(raise_exception=True) - projects_serializer = self.get_serializer( - data=projects, - context={ - "data_criterias": criteria_serializer.data, - "data_scores": scores_serializer.data, - }, - many=True, + ProjectScore.objects.bulk_create( + [ProjectScore(**item) for item in serializer.validated_data], + update_conflicts=True, + update_fields=["value"], + unique_fields=["criteria", "user", "project"], ) - projects_serializer.is_valid() + return Response({"success": True}, status=status.HTTP_201_CREATED) - for project in projects_serializer.data: - filled_values = 0 - for criteria in project["criterias"]: - if criteria["name"] == "Комментарий" or criteria.get("value", None): - filled_values += 1 - if filled_values == len(project["criterias"]): - project["is_scored"] = True - - return self.get_paginated_response(projects_serializer.data) - - -class ScoredProjects(generics.ListAPIView): +class RateProjects(generics.ListAPIView): serializer_class = ProjectScoreGetSerializer permission_classes = [IsExpert] pagination_class = RateProjectsPagination - def get(self, request, *args, **kwargs): + def get_request_data(self) -> dict: user = self.request.user program_id = self.kwargs.get("program_id") + scored = True if self.request.query_params.get("scored") == "true" else False - criterias = Criteria.objects.prefetch_related("partner_program").filter( - partner_program_id=program_id - ) - quantity_criterias = criterias.count() + return {"program_id": program_id, "user": user, "view": self, "scored": scored} - scores = ProjectScore.objects.prefetch_related("criteria").filter( - criteria__in=criterias.values_list("id", flat=True), user=user - ) - unpaginated_projects = ( - Project.objects.filter( - partner_program_profiles__partner_program_id=program_id - ) - .annotate(user_scores_count=Count("scores", filter=Q(scores__user=user))) - .filter(user_scores_count=quantity_criterias) - .distinct() - ) + def get_querysets_dict(self) -> dict[str, QuerySet]: + return get_querysets(**self.get_request_data()) - projects = self.paginate_queryset(unpaginated_projects) + def serialize_querysets(self) -> list[dict]: + return serialize_project_criterias(self.get_querysets_dict()) - criteria_serializer = CriteriaSerializer(data=criterias, many=True) - scores_serializer = ProjectScoreSerializer(data=scores, many=True) + def get(self, request, *args, **kwargs): + serialized_data = self.serialize_querysets() - criteria_serializer.is_valid() - scores_serializer.is_valid() + 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] - projects_serializer = self.get_serializer( - data=projects, - context={ - "data_criterias": criteria_serializer.data, - "data_scores": scores_serializer.data, - }, - many=True, - ) + return self.get_paginated_response(serialized_data) - projects_serializer.is_valid() - for project in projects_serializer.data: - project["is_scored"] = True +class RateProjectsDetails(RateProjects): + permission_classes = [IsExpertPost] # потом решить проблему с этим - return self.get_paginated_response(projects_serializer.data) + def get_request_data(self) -> dict: + kwargs = super().get_request_data() + project_id = self.request.query_params.get("project_id") + program_id = self.request.query_params.get("program_id") -class RateProjectsDetails(generics.ListAPIView): - serializer_class = ProjectScoreGetSerializer - permission_classes = [IsExpert] + kwargs["project_id"] = int(project_id) if project_id else None + kwargs["program_id"] = int(program_id) if program_id else None + return kwargs def get(self, request, *args, **kwargs): - user = self.request.user - project_id = self.request.query_params.get("project_id") + try: + serialized_data = self.serialize_querysets()[0] - criterias = Criteria.objects.prefetch_related("partner_program").filter( - partner_program_id=int(self.request.query_params.get("program_id")) - ) - project = Project.objects.filter(id=int(project_id)).first() - scores = ProjectScore.objects.prefetch_related("criteria").filter( - criteria__in=criterias.values_list("id", flat=True), - user=user, - project=project, - ) + count_scored_criterias(serialized_data) - criterias_data = [] - for criteria in criterias: - criteria_data = { - "id": criteria.id, - "name": criteria.name, - "description": criteria.description, - "type": criteria.type, - "min_value": criteria.min_value, - "max_value": criteria.max_value, - } - criterias_data.append(criteria_data) - - project_scores_data = [] - for project_score in scores: - project_score_data = { - "criteria_id": project_score.criteria.id, - "value": project_score.value, - } - project_scores_data.append(project_score_data) - - for score in project_scores_data: - for criteria in criterias_data: - if criteria["id"] == score["criteria_id"]: - criteria["value"] = score["value"] - - response = { - "id": project.id, - "name": project.name, - "leader": project.leader.id, - "description": project.description, - "image_address": project.image_address, - "presentation_address": project.presentation_address, - "industry": project.industry.id, - "region": project.region, - "criterias": criterias_data, - "views_count": get_views_count(project), - } - - filled_values = 0 - for criteria in response["criterias"]: - if criteria.get("value", None): - filled_values += 1 - - if filled_values == len(response["criterias"]): - response["is_scored"] = True - - return Response(response, status=200) + return Response(serialized_data, status=status.HTTP_200_OK) + except IndexError: + return Response( + {"error": "Нужный проект или программа не найдены"}, + status=status.HTTP_404_NOT_FOUND, + ) From db9ae2c77b6c89814979fc1fd51f38a4d10e54f4 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 4 Mar 2024 23:50:49 +0300 Subject: [PATCH 3/5] project_rates refactoring additions --- project_rates/constants.py | 7 ++++++ project_rates/models.py | 15 +++++------ project_rates/serializers.py | 19 +++++++------- project_rates/services.py | 38 +++++++++++++++------------- project_rates/validators.py | 48 +++++++++++++++++++----------------- project_rates/views.py | 47 +++++++++++++++++++---------------- 6 files changed, 98 insertions(+), 76 deletions(-) diff --git a/project_rates/constants.py b/project_rates/constants.py index 3d9ae97b..337b010c 100644 --- a/project_rates/constants.py +++ b/project_rates/constants.py @@ -1,6 +1,13 @@ +from typing import Literal + VERBOSE_TYPES = ( ("str", "Текст"), ("int", "Целочисленное число"), ("float", "Число с плавающей точкой"), ("bool", "Да или нет"), ) + +validatable_types_names = Literal["bool", "str", "int", "float"] + + +NUMERIC_TYPES = ["int", "float"] diff --git a/project_rates/models.py b/project_rates/models.py index 3616e422..d9c684f1 100644 --- a/project_rates/models.py +++ b/project_rates/models.py @@ -4,7 +4,7 @@ from partner_programs.models import PartnerProgram from projects.models import Project from .constants import VERBOSE_TYPES -from .validators import ProjectScoreValidate +from .validators import ProjectScoreValidator User = get_user_model() @@ -80,12 +80,13 @@ def __str__(self): return f"ProjectScore<{self.id}> - {self.criteria.name}" def save(self, *args, **kwargs): - ProjectScoreValidate( - criteria_type=self.criteria.type, - value=self.value, - criteria_min_value=self.criteria.min_value, - criteria_max_value=self.criteria.max_value, - ) + data_to_validate = { + "criteria_type": self.criteria.type, + "value": self.value, + "criteria_min_value": self.criteria.min_value, + "criteria_max_value": self.criteria.max_value, + } + ProjectScoreValidator.validate(**data_to_validate) super().save(*args, **kwargs) class Meta: diff --git a/project_rates/serializers.py b/project_rates/serializers.py index 6ad4376b..f124c528 100644 --- a/project_rates/serializers.py +++ b/project_rates/serializers.py @@ -3,7 +3,7 @@ from core.services import get_views_count from .models import Criteria, ProjectScore from projects.models import Project -from .validators import ProjectScoreValidate +from .validators import ProjectScoreValidator class ProjectScoreCreateSerializer(serializers.ModelSerializer): @@ -13,7 +13,7 @@ def __init__(self, *args, **kwargs): class Meta: model = ProjectScore - fields = ("criteria", "user", "project", "value") + fields = ["criteria", "user", "project", "value"] validators = [] def get_queryset(self): @@ -23,12 +23,13 @@ def get_queryset(self): def validate(self, data): criteria = data["criteria"] - ProjectScoreValidate( - criteria_type=criteria.type, - value=data.get("value"), - criteria_min_value=criteria.min_value, - criteria_max_value=criteria.max_value, - ) + data_to_validate = { + "criteria_type": criteria.type, + "value": data.get("value"), + "criteria_min_value": criteria.min_value, + "criteria_max_value": criteria.max_value, + } + ProjectScoreValidator.validate(**data_to_validate) return data @@ -95,7 +96,7 @@ def serialize_data_func(criteria_to_get: list, data: dict): for criterion in data: needed_criteria = criteria.get(int(criterion["criterion_id"])) - ProjectScoreValidate( + ProjectScoreValidator( criteria_type=needed_criteria.type, value=criterion["value"], criteria_min_value=needed_criteria.min_value, diff --git a/project_rates/services.py b/project_rates/services.py index b1bfb22f..bee33518 100644 --- a/project_rates/services.py +++ b/project_rates/services.py @@ -13,14 +13,16 @@ def get_querysets(**kwargs) -> dict[str, QuerySet]: """ Kwargs arguments input: - program_id: int, - user: CustomUser, - view: ListAPIView, - scored: bool = False, - project_id: int | None = None, + Args: + program_id (int): The ID of the program. + user (CustomUser): An instance of the CustomUser class. + view (ListAPIView): An instance of the ListAPIView class. + scored (bool, optional): A boolean indicating if the item is scored. Defaults to False. + project_id (int | None, optional): The ID of the project, or None if not applicable. Defaults to None. """ program_id = kwargs.get("program_id") + project_id = kwargs.get("project_id") user = kwargs.get("user") criterias = Criteria.objects.prefetch_related("partner_program").filter( @@ -30,24 +32,26 @@ def get_querysets(**kwargs) -> dict[str, QuerySet]: criteria__in=criterias.values_list("id", flat=True), user=user ) - unpaginated_projects = Project.objects.filter( - partner_program_profiles__partner_program_id=program_id - ).distinct() + if project_id: + projects = [Project.objects.get(id=kwargs.get("project_id"))] + else: + projects = Project.objects.filter( + partner_program_profiles__partner_program_id=program_id + ).distinct() if kwargs.get("scored"): - quantity_criterias = len(criterias) - unpaginated_projects = unpaginated_projects.annotate( + criterias_quantity = len(criterias) + projects = projects.annotate( user_scores_count=Count("scores", filter=Q(scores__user=user)) - ).filter(user_scores_count=quantity_criterias) - elif kwargs.get("project_id"): - unpaginated_projects = unpaginated_projects.filter(id=kwargs.get("project_id")) + ).filter(user_scores_count=criterias_quantity) - paginated_projects = kwargs.get("view").paginate_queryset(unpaginated_projects) + if not project_id: + projects = kwargs.get("view").paginate_queryset(projects) return { "criterias_queryset": criterias, "scores_queryset": scores, - "paginated_projects_queryset": paginated_projects, + "projects_queryset": projects, } @@ -56,7 +60,7 @@ def serialize_project_criterias(querysets: dict[str, QuerySet]) -> list[dict]: scores_serializer = ProjectScoreSerializer(querysets["scores_queryset"], many=True) projects_serializer = ProjectScoreGetSerializer( - querysets["paginated_projects_queryset"], + querysets["projects_queryset"], context={ "data_criterias": criteria_serializer.data, "data_scores": scores_serializer.data, @@ -66,7 +70,7 @@ def serialize_project_criterias(querysets: dict[str, QuerySet]) -> list[dict]: return projects_serializer.data -def count_scored_criterias(project_data: dict) -> None: +def count_scored_criterias(project_data: dict): filled_values = sum( 1 for criteria in project_data["criterias"] diff --git a/project_rates/validators.py b/project_rates/validators.py index 9ac6800b..86484895 100644 --- a/project_rates/validators.py +++ b/project_rates/validators.py @@ -1,32 +1,36 @@ -class ProjectScoreValidate: - def __init__(self, **kwargs): - self.criteria_type = kwargs.get("criteria_type") - self.value = kwargs.get("value") - self.criteria_min_value = kwargs.get("criteria_min_value") - self.criteria_max_value = kwargs.get("criteria_max_value") +from project_rates.constants import validatable_types_names, NUMERIC_TYPES - self._validate_data_type() - self._validate_numeric_limits() - def _validate_data_type(self): - if self.criteria_type in ["float", "int"]: +class ProjectScoreValidator: + @classmethod + def validate(cls, **kwargs): + criteria_type: validatable_types_names = kwargs.get("criteria_type") + value: str = kwargs.get("value") + criteria_min_value: float | None = kwargs.get("criteria_min_value") + criteria_max_value: float | None = kwargs.get("criteria_max_value") + + cls._validate_data_type(criteria_type, value) + if criteria_type in NUMERIC_TYPES: + cls._validate_numeric_limits(criteria_min_value, criteria_max_value, value) + + @staticmethod + def _validate_data_type(criteria_type: str, value: str): + if criteria_type in NUMERIC_TYPES: try: - float(self.value) + float(value) except ValueError: raise ValueError("Введённое значение не соответствует формату!") except TypeError: raise TypeError("Вы не ввели никакие данные!") - elif (self.criteria_type == "bool") and (self.value not in ["True", "False"]): + elif (criteria_type == "bool") and (value not in ["True", "False"]): raise TypeError("Введённое значение не соответствует формату!") - def _validate_numeric_limits(self): - if self.criteria_type in ["int", "float"]: - if self.criteria_min_value is not None and self.criteria_min_value > float( - self.value - ): - raise ValueError("Оценка этого критерия принизила допустимые значения!") - elif self.criteria_max_value is not None and self.criteria_max_value < float( - self.value - ): - raise ValueError("Оценка этого критерия превысила допустимые значения!") + @staticmethod + def _validate_numeric_limits( + min_value: float | None, max_value: float | None, value: str + ): + if min_value is not None and min_value > float(value): + raise ValueError("Оценка этого критерия принизила допустимые значения!") + elif max_value is not None and max_value < float(value): + raise ValueError("Оценка этого критерия превысила допустимые значения!") diff --git a/project_rates/views.py b/project_rates/views.py index ede1f0c2..9c7c7374 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -40,21 +40,25 @@ def get_needed_data(self) -> tuple[dict, list[int]]: return data, criteria_to_get def create(self, request, *args, **kwargs) -> Response: - data, criteria_to_get = self.get_needed_data() + try: + data, criteria_to_get = self.get_needed_data() + + serializer = ProjectScoreCreateSerializer( + data=data, criteria_to_get=criteria_to_get, many=True + ) + serializer.is_valid(raise_exception=True) - serializer = ProjectScoreCreateSerializer( - data=data, criteria_to_get=criteria_to_get, many=True - ) - serializer.is_valid(raise_exception=True) + ProjectScore.objects.bulk_create( + [ProjectScore(**item) for item in serializer.validated_data], + update_conflicts=True, + update_fields=["value"], + unique_fields=["criteria", "user", "project"], + ) - ProjectScore.objects.bulk_create( - [ProjectScore(**item) for item in serializer.validated_data], - update_conflicts=True, - update_fields=["value"], - unique_fields=["criteria", "user", "project"], - ) + return Response({"success": True}, status=status.HTTP_201_CREATED) - return Response({"success": True}, status=status.HTTP_201_CREATED) + except Exception as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class RateProjects(generics.ListAPIView): @@ -100,14 +104,15 @@ def get_request_data(self) -> dict: return kwargs def get(self, request, *args, **kwargs): - try: - serialized_data = self.serialize_querysets()[0] + # try: + serialized_data = self.serialize_querysets()[0] - count_scored_criterias(serialized_data) + count_scored_criterias(serialized_data) - return Response(serialized_data, status=status.HTTP_200_OK) - except IndexError: - return Response( - {"error": "Нужный проект или программа не найдены"}, - status=status.HTTP_404_NOT_FOUND, - ) + return Response(serialized_data, status=status.HTTP_200_OK) + + # except Exception as e: + # return Response( + # {"error": str(e)}, + # status=status.HTTP_404_NOT_FOUND, + # ) From 6b5cac7f0698bc4c58f017d76aa0882137cf6991 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 11 Mar 2024 11:02:31 +0300 Subject: [PATCH 4/5] corrections made again --- project_rates/constants.py | 27 +++++++++++++++++++++-- project_rates/services.py | 44 ++++++++++++++----------------------- project_rates/validators.py | 22 ++++++++++--------- project_rates/views.py | 43 ++++++++++++++++++------------------ 4 files changed, 74 insertions(+), 62 deletions(-) diff --git a/project_rates/constants.py b/project_rates/constants.py index 337b010c..7204c042 100644 --- a/project_rates/constants.py +++ b/project_rates/constants.py @@ -1,4 +1,12 @@ from typing import Literal +from dataclasses import dataclass + +from django.db.models import QuerySet +from rest_framework.generics import ListAPIView + +from project_rates.models import Criteria, ProjectScore +from projects.models import Project +from users.models import CustomUser VERBOSE_TYPES = ( ("str", "Текст"), @@ -7,7 +15,22 @@ ("bool", "Да или нет"), ) -validatable_types_names = Literal["bool", "str", "int", "float"] +NumericTypes: list[str] = ["int", "float"] + +ValidatableTypesNames = Literal[*NumericTypes, "bool", "str"] + + +@dataclass(frozen=True) +class RatesQuerySets: + criterias_queryset: QuerySet[Criteria] + scores_queryset: QuerySet[ProjectScore] + projects_queryset: QuerySet[Project] -NUMERIC_TYPES = ["int", "float"] +@dataclass +class RatesRequestData: + program_id: int + user: CustomUser + view: ListAPIView + scored: bool | None = None + project_id: int | None = None diff --git a/project_rates/services.py b/project_rates/services.py index bee33518..cddf5294 100644 --- a/project_rates/services.py +++ b/project_rates/services.py @@ -1,5 +1,6 @@ -from django.db.models import QuerySet, Count, Q +from django.db.models import Count, Q +from project_rates.constants import RatesQuerySets from project_rates.models import Criteria, ProjectScore from project_rates.serializers import ( CriteriaSerializer, @@ -9,21 +10,10 @@ from projects.models import Project -def get_querysets(**kwargs) -> dict[str, QuerySet]: - """ - Kwargs arguments input: - - Args: - program_id (int): The ID of the program. - user (CustomUser): An instance of the CustomUser class. - view (ListAPIView): An instance of the ListAPIView class. - scored (bool, optional): A boolean indicating if the item is scored. Defaults to False. - project_id (int | None, optional): The ID of the project, or None if not applicable. Defaults to None. - - """ - program_id = kwargs.get("program_id") - project_id = kwargs.get("project_id") - user = kwargs.get("user") +def get_querysets(RatesRequestData) -> RatesQuerySets: + 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 @@ -33,34 +23,32 @@ def get_querysets(**kwargs) -> dict[str, QuerySet]: ) if project_id: - projects = [Project.objects.get(id=kwargs.get("project_id"))] + projects = [Project.objects.get(id=project_id)] else: projects = Project.objects.filter( partner_program_profiles__partner_program_id=program_id ).distinct() - if kwargs.get("scored"): + 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 = kwargs.get("view").paginate_queryset(projects) + projects = RatesRequestData.view.paginate_queryset(projects) - return { - "criterias_queryset": criterias, - "scores_queryset": scores, - "projects_queryset": projects, - } + return RatesQuerySets( + 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) +def serialize_project_criterias(querysets: RatesQuerySets) -> list[dict]: + criteria_serializer = CriteriaSerializer(querysets.criterias_queryset, many=True) + scores_serializer = ProjectScoreSerializer(querysets.scores_queryset, many=True) projects_serializer = ProjectScoreGetSerializer( - querysets["projects_queryset"], + querysets.projects_queryset, context={ "data_criterias": criteria_serializer.data, "data_scores": scores_serializer.data, diff --git a/project_rates/validators.py b/project_rates/validators.py index 86484895..c8c9d871 100644 --- a/project_rates/validators.py +++ b/project_rates/validators.py @@ -1,21 +1,23 @@ -from project_rates.constants import validatable_types_names, NUMERIC_TYPES +from project_rates.constants import ValidatableTypesNames, NumericTypes class ProjectScoreValidator: @classmethod def validate(cls, **kwargs): - criteria_type: validatable_types_names = kwargs.get("criteria_type") + criteria_type: ValidatableTypesNames = kwargs.get("criteria_type") value: str = kwargs.get("value") criteria_min_value: float | None = kwargs.get("criteria_min_value") criteria_max_value: float | None = kwargs.get("criteria_max_value") cls._validate_data_type(criteria_type, value) - if criteria_type in NUMERIC_TYPES: - cls._validate_numeric_limits(criteria_min_value, criteria_max_value, value) + if criteria_type in NumericTypes: + cls._validate_numeric_limits( + criteria_min_value, criteria_max_value, float(value) + ) @staticmethod def _validate_data_type(criteria_type: str, value: str): - if criteria_type in NUMERIC_TYPES: + if criteria_type in NumericTypes: try: float(value) except ValueError: @@ -28,9 +30,9 @@ def _validate_data_type(criteria_type: str, value: str): @staticmethod def _validate_numeric_limits( - min_value: float | None, max_value: float | None, value: str + min_value: float | None, max_value: float | None, value: float ): - if min_value is not None and min_value > float(value): - raise ValueError("Оценка этого критерия принизила допустимые значения!") - elif max_value is not None and max_value < float(value): - raise ValueError("Оценка этого критерия превысила допустимые значения!") + if min_value is not None and min_value > value: + raise ValueError("Оценка этого критерия ниже допустимого значения!") + elif max_value is not None and max_value < value: + raise ValueError("Оценка этого критерия превысила допустимое значение!") diff --git a/project_rates/views.py b/project_rates/views.py index 9c7c7374..a76a42f6 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -1,9 +1,9 @@ from django.contrib.auth import get_user_model -from django.db.models import QuerySet from rest_framework import generics, status from rest_framework.response import Response +from project_rates.constants import RatesQuerySets, RatesRequestData from project_rates.services import ( get_querysets, serialize_project_criterias, @@ -66,15 +66,18 @@ class RateProjects(generics.ListAPIView): permission_classes = [IsExpert] pagination_class = RateProjectsPagination - def get_request_data(self) -> dict: - user = self.request.user - program_id = self.kwargs.get("program_id") + def get_request_data(self) -> RatesRequestData: scored = True if self.request.query_params.get("scored") == "true" else False - return {"program_id": program_id, "user": user, "view": self, "scored": scored} + return RatesRequestData( + program_id=self.kwargs.get("program_id"), + user=self.request.user, + view=self, + scored=scored, + ) - def get_querysets_dict(self) -> dict[str, QuerySet]: - return get_querysets(**self.get_request_data()) + def get_querysets_dict(self) -> RatesQuerySets: + return get_querysets(self.get_request_data()) def serialize_querysets(self) -> list[dict]: return serialize_project_criterias(self.get_querysets_dict()) @@ -93,26 +96,22 @@ def get(self, request, *args, **kwargs): class RateProjectsDetails(RateProjects): permission_classes = [IsExpertPost] # потом решить проблему с этим - def get_request_data(self) -> dict: - kwargs = super().get_request_data() + 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") - kwargs["project_id"] = int(project_id) if project_id else None - kwargs["program_id"] = int(program_id) if program_id else None - return kwargs + 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) + try: + serialized_data = self.serialize_querysets()[0] - return Response(serialized_data, status=status.HTTP_200_OK) + count_scored_criterias(serialized_data) - # except Exception as e: - # return Response( - # {"error": str(e)}, - # status=status.HTTP_404_NOT_FOUND, - # ) + return Response(serialized_data, status=status.HTTP_200_OK) + except Exception as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) From 1928176d6e619d2ce387650d757de62f0ccfebc0 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 11 Mar 2024 11:43:51 +0300 Subject: [PATCH 5/5] can't add dataclass cuz of importerrors(((( --- project_rates/constants.py | 18 ++++-------------- project_rates/models.py | 2 +- project_rates/services.py | 21 +++++++++++---------- project_rates/views.py | 5 +++-- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/project_rates/constants.py b/project_rates/constants.py index 7204c042..c47e83e3 100644 --- a/project_rates/constants.py +++ b/project_rates/constants.py @@ -1,13 +1,14 @@ from typing import Literal from dataclasses import dataclass -from django.db.models import QuerySet from rest_framework.generics import ListAPIView -from project_rates.models import Criteria, ProjectScore -from projects.models import Project from users.models import CustomUser +NumericTypes: list[str] = ["int", "float"] + +ValidatableTypesNames = Literal[*NumericTypes, "bool", "str"] + VERBOSE_TYPES = ( ("str", "Текст"), ("int", "Целочисленное число"), @@ -15,17 +16,6 @@ ("bool", "Да или нет"), ) -NumericTypes: list[str] = ["int", "float"] - -ValidatableTypesNames = Literal[*NumericTypes, "bool", "str"] - - -@dataclass(frozen=True) -class RatesQuerySets: - criterias_queryset: QuerySet[Criteria] - scores_queryset: QuerySet[ProjectScore] - projects_queryset: QuerySet[Project] - @dataclass class RatesRequestData: diff --git a/project_rates/models.py b/project_rates/models.py index d9c684f1..f2743779 100644 --- a/project_rates/models.py +++ b/project_rates/models.py @@ -1,9 +1,9 @@ from django.contrib.auth import get_user_model from django.db import models +from .constants import VERBOSE_TYPES from partner_programs.models import PartnerProgram from projects.models import Project -from .constants import VERBOSE_TYPES from .validators import ProjectScoreValidator User = get_user_model() diff --git a/project_rates/services.py b/project_rates/services.py index cddf5294..b7ee7b18 100644 --- a/project_rates/services.py +++ b/project_rates/services.py @@ -1,6 +1,5 @@ -from django.db.models import Count, Q +from django.db.models import Count, Q, QuerySet -from project_rates.constants import RatesQuerySets from project_rates.models import Criteria, ProjectScore from project_rates.serializers import ( CriteriaSerializer, @@ -10,7 +9,7 @@ from projects.models import Project -def get_querysets(RatesRequestData) -> RatesQuerySets: +def get_querysets(RatesRequestData) -> dict[str, QuerySet]: program_id = RatesRequestData.program_id project_id = RatesRequestData.project_id user = RatesRequestData.user @@ -38,17 +37,19 @@ def get_querysets(RatesRequestData) -> RatesQuerySets: if not project_id: projects = RatesRequestData.view.paginate_queryset(projects) - return RatesQuerySets( - criterias_queryset=criterias, scores_queryset=scores, projects_queryset=projects - ) + return { + "criterias_queryset": criterias, + "scores_queryset": scores, + "projects_queryset": projects, + } -def serialize_project_criterias(querysets: RatesQuerySets) -> list[dict]: - criteria_serializer = CriteriaSerializer(querysets.criterias_queryset, many=True) - scores_serializer = ProjectScoreSerializer(querysets.scores_queryset, many=True) +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, + querysets["projects_queryset"], context={ "data_criterias": criteria_serializer.data, "data_scores": scores_serializer.data, diff --git a/project_rates/views.py b/project_rates/views.py index a76a42f6..00ea77d2 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -1,9 +1,10 @@ from django.contrib.auth import get_user_model +from django.db.models import QuerySet from rest_framework import generics, status from rest_framework.response import Response -from project_rates.constants import RatesQuerySets, RatesRequestData +from project_rates.constants import RatesRequestData from project_rates.services import ( get_querysets, serialize_project_criterias, @@ -76,7 +77,7 @@ def get_request_data(self) -> RatesRequestData: scored=scored, ) - def get_querysets_dict(self) -> RatesQuerySets: + def get_querysets_dict(self) -> dict[str, QuerySet]: return get_querysets(self.get_request_data()) def serialize_querysets(self) -> list[dict]: