Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions project_rates/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
from typing import Literal
from dataclasses import dataclass

from rest_framework.generics import ListAPIView

from users.models import CustomUser

NumericTypes: list[str] = ["int", "float"]

ValidatableTypesNames = Literal[*NumericTypes, "bool", "str"]

VERBOSE_TYPES = (
("str", "Текст"),
("int", "Целочисленное число"),
("float", "Число с плавающей точкой"),
("bool", "Да или нет"),
)


@dataclass
class RatesRequestData:
program_id: int
user: CustomUser
view: ListAPIView
scored: bool | None = None
project_id: int | None = None
17 changes: 9 additions & 8 deletions project_rates/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
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 ProjectScoreValidate
from .validators import ProjectScoreValidator

User = get_user_model()

Expand Down Expand Up @@ -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:
Expand Down
25 changes: 23 additions & 2 deletions project_rates/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,34 @@
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):
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"]
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"]
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


class CriteriaSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -75,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,
Expand Down
70 changes: 70 additions & 0 deletions project_rates/services.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
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(id=project_id)]
else:
projects = Project.objects.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")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

не понял логику проверки на заполненность
во первых почему мы сравниаем имя критерия с захардкоженным словом "Комментарий" и считаем что если имя комментарий то критерий заполнен?

То есть коммент может быть и пустым, но будет считаться как заполненное поле?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

комментарий идее опционален

)

if filled_values == len(project_data["criterias"]):
project_data["is_scored"] = True
2 changes: 0 additions & 2 deletions project_rates/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@
RateProject,
RateProjects,
RateProjectsDetails,
ScoredProjects,
)

urlpatterns = [
path("rate/<int:project_id>", RateProject.as_view()),
path("<int:program_id>", RateProjects.as_view()),
path("scored/<int:program_id>", ScoredProjects.as_view()),
path("details", RateProjectsDetails.as_view()),
]
50 changes: 28 additions & 22 deletions project_rates/validators.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
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 ValidatableTypesNames, NumericTypes

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: 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 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 NumericTypes:
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: float
):
if min_value is not None and min_value > value:
raise ValueError("Оценка этого критерия ниже допустимого значения!")
elif max_value is not None and max_value < value:
raise ValueError("Оценка этого критерия превысила допустимое значение!")
Loading