Skip to content

Commit 5f6288a

Browse files
authored
Merge pull request #264 from PROCOLLAB-github/flexivanov237-pro-143
added GET request for rating of projects
2 parents b52d219 + 1a4f294 commit 5f6288a

File tree

7 files changed

+284
-10
lines changed

7 files changed

+284
-10
lines changed

project_rates/admin.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,26 @@
55
# Register your models here.
66
@admin.register(Criteria)
77
class CriteriaAdmin(admin.ModelAdmin):
8-
list_display = (
9-
"id",
10-
"name",
11-
"type",
12-
# добавить проект / программу
13-
)
8+
list_display = ("id", "name", "type", "get_program_name")
149
list_display_links = (
1510
"id",
1611
"name",
1712
)
1813

14+
def get_program_name(self, obj):
15+
return obj.partner_program.name
16+
1917

2018
@admin.register(ProjectScore)
2119
class ProjectScoreAdmin(admin.ModelAdmin):
22-
list_display = (
23-
"id",
24-
# добавить проект / программу
25-
)
20+
list_display = ("id", "get_user_name", "get_project_name", "get_criteria_name")
2621
list_display_links = ("id",)
22+
23+
def get_user_name(self, obj):
24+
return f"{obj.user.last_name} {obj.user.first_name}"
25+
26+
def get_criteria_name(self, obj):
27+
return obj.criteria.name
28+
29+
def get_project_name(self, obj):
30+
return obj.project.name

project_rates/pagination.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from rest_framework.pagination import LimitOffsetPagination
2+
3+
4+
class RateProjectsPagination(LimitOffsetPagination):
5+
default_limit = 10
6+
limit_query_param = "limit"
7+
offset_query_param = "offset"

rate_projects/models.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
from django.contrib.auth import get_user_model
2+
from django.db import models
3+
4+
from partner_programs.models import PartnerProgram
5+
from projects.models import Project
6+
from .constants import VERBOSE_NAME_TYPES
7+
from .validators import ProjectScoreValidate
8+
9+
User = get_user_model()
10+
11+
12+
class Criteria(models.Model):
13+
"""
14+
Criteria model
15+
16+
Attributes:
17+
name: A CharField name of the criteria
18+
description: A TextField description of criteria
19+
type: A CharField choice between "str", "int", "bool" and "float"
20+
min_value: Optional FloatField for numeric values
21+
max_value: Optional FloatField for numeric values
22+
partner_program: A ForeignKey connection to PartnerProgram model
23+
24+
"""
25+
26+
name = models.CharField(verbose_name="Название", max_length=50)
27+
description = models.TextField(verbose_name="Описание", null=True, blank=True)
28+
type = models.CharField(verbose_name="Тип", max_length=8, choices=VERBOSE_NAME_TYPES)
29+
30+
min_value = models.FloatField(
31+
verbose_name="Минимально допустимое числовое значение",
32+
help_text="(если есть)",
33+
null=True,
34+
blank=True,
35+
)
36+
max_value = models.FloatField(
37+
verbose_name="Максимально допустимое числовое значение",
38+
help_text="(если есть)",
39+
null=True,
40+
blank=True,
41+
)
42+
partner_program = models.ForeignKey(
43+
PartnerProgram,
44+
on_delete=models.CASCADE,
45+
related_name="criterias",
46+
)
47+
48+
def __str__(self):
49+
return f"Criteria<{self.id}> - {self.name} {self.partner_program.name}"
50+
51+
class Meta:
52+
verbose_name = "Критерий оценки проекта"
53+
verbose_name_plural = "Критерии оценки проектов"
54+
55+
56+
class ProjectScore(models.Model):
57+
"""
58+
ProjectScore model
59+
60+
Attributes:
61+
criteria: A ForeignKey connection to Criteria model
62+
user: A ForeignKey connection to User model
63+
64+
value: CharField for value
65+
66+
67+
"""
68+
69+
criteria = models.ForeignKey(
70+
Criteria, on_delete=models.CASCADE, related_name="scores"
71+
)
72+
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="scores")
73+
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scores")
74+
75+
value = models.CharField(
76+
verbose_name="Значение", max_length=50, null=True, blank=True
77+
)
78+
79+
def __str__(self):
80+
return f"ProjectScore<{self.id}> - {self.criteria.name}"
81+
82+
def save(self, *args, **kwargs):
83+
ProjectScoreValidate(
84+
criteria_type=self.criteria.type,
85+
value=self.value,
86+
criteria_min_value=self.criteria.min_value,
87+
criteria_max_value=self.criteria.max_value,
88+
)
89+
super().save(*args, **kwargs)
90+
91+
class Meta:
92+
verbose_name = "Оценка проекта"
93+
verbose_name_plural = "Оценки проектов"
94+
unique_together = ("criteria", "user", "project")

rate_projects/serializers.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from rest_framework import serializers
2+
from .models import Criteria, ProjectScore
3+
from projects.models import Project
4+
5+
6+
class ProjectScoreCreateSerializer(serializers.ModelSerializer):
7+
class Meta:
8+
model = ProjectScore
9+
fields = ["criteria", "user", "project", "value"]
10+
11+
12+
class CriteriaSerializer(serializers.ModelSerializer):
13+
class Meta:
14+
model = Criteria
15+
exclude = ["partner_program"]
16+
17+
18+
class ProjectScoreSerializer(serializers.ModelSerializer):
19+
class Meta:
20+
model = ProjectScore
21+
fields = ["criteria_id", "project_id", "value"]
22+
23+
24+
class ProjectScoreGetSerializer(serializers.ModelSerializer):
25+
criterias = serializers.SerializerMethodField()
26+
27+
class Meta:
28+
model = Project
29+
fields = [
30+
"id",
31+
"name",
32+
"leader",
33+
"description",
34+
"image_address",
35+
"industry",
36+
"criterias",
37+
]
38+
39+
def get_criterias(self, obj):
40+
criterias = []
41+
for criteria in self.context["data_criterias"]:
42+
copied_criteria = criteria.copy()
43+
for score in self.context["data_scores"]:
44+
if (
45+
criteria["id"] == score["criteria_id"]
46+
and obj.id == score["project_id"]
47+
):
48+
copied_criteria["value"] = score["value"]
49+
50+
criterias.append(copied_criteria)
51+
return criterias

rate_projects/urls.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from django.urls import path
2+
3+
from rate_projects.views import RateProject, RateProjects
4+
5+
urlpatterns = [
6+
path("rate/", RateProject.as_view()),
7+
path("<int:program_id>", RateProjects.as_view()),
8+
]

rate_projects/validators.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class ProjectScoreValidate:
2+
def __init__(self, **kwargs):
3+
self.criteria_type = kwargs.get("criteria_type")
4+
self.value = kwargs.get("value")
5+
self.criteria_min_value = kwargs.get("criteria_min_value")
6+
self.criteria_max_value = kwargs.get("criteria_max_value")
7+
8+
self._validate_data_type()
9+
self._validate_numeric_limits()
10+
11+
def _validate_data_type(self):
12+
if self.criteria_type in ["float", "int"]:
13+
try:
14+
float(self.value)
15+
except ValueError:
16+
raise ValueError("Введённое значение не соответствует формату!")
17+
except TypeError:
18+
raise TypeError("Вы не ввели никакие данные!")
19+
20+
elif (self.criteria_type == "bool") and (self.value not in ["True", "False"]):
21+
raise TypeError("Введённое значение не соответствует формату!")
22+
23+
def _validate_numeric_limits(self):
24+
if self.criteria_type in ["int", "float"]:
25+
if self.criteria_min_value is not None and self.criteria_min_value > float(
26+
self.value
27+
):
28+
raise ValueError("Оценка этого критерия принизила допустимые значения!")
29+
elif self.criteria_max_value is not None and self.criteria_max_value < float(
30+
self.value
31+
):
32+
raise ValueError("Оценка этого критерия превысила допустимые значения!")

rate_projects/views.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from django.contrib.auth import get_user_model
2+
3+
from rest_framework import generics, status
4+
from rest_framework.response import Response
5+
6+
from projects.models import Project
7+
from rate_projects.models import Criteria, ProjectScore
8+
from rate_projects.pagination import RateProjectsPagination
9+
from rate_projects.serializers import (
10+
ProjectScoreCreateSerializer,
11+
CriteriaSerializer,
12+
ProjectScoreSerializer,
13+
ProjectScoreGetSerializer,
14+
)
15+
from users.permissions import IsExpert
16+
17+
User = get_user_model()
18+
19+
20+
class RateProject(generics.CreateAPIView):
21+
serializer_class = ProjectScoreCreateSerializer
22+
permission_classes = [IsExpert]
23+
24+
def create(self, request, *args, **kwargs):
25+
try:
26+
data = self.request.data
27+
data["user"] = self.request.user.id
28+
29+
serializer = self.get_serializer(data=data)
30+
if serializer.is_valid():
31+
serializer.save()
32+
self.perform_create(serializer)
33+
return Response({"success": True}, status=status.HTTP_201_CREATED)
34+
else:
35+
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
36+
except Exception as e:
37+
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
38+
39+
40+
class RateProjects(generics.ListAPIView):
41+
serializer_class = ProjectScoreGetSerializer
42+
permission_classes = [IsExpert]
43+
pagination_class = RateProjectsPagination
44+
45+
def get(self, request, *args, **kwargs):
46+
user = self.request.user
47+
program_id = self.kwargs.get("program_id")
48+
49+
criterias = Criteria.objects.prefetch_related("partner_program").filter(
50+
partner_program_id=program_id
51+
)
52+
scores = ProjectScore.objects.prefetch_related("criteria").filter(
53+
criteria__in=criterias.values_list("id", flat=True), user=user
54+
)
55+
unpaginated_projects = Project.objects.filter(
56+
partner_program_profiles__partner_program_id=program_id
57+
).distinct()
58+
59+
projects = self.paginate_queryset(unpaginated_projects)
60+
61+
criteria_serializer = CriteriaSerializer(data=criterias, many=True)
62+
scores_serializer = ProjectScoreSerializer(data=scores, many=True)
63+
64+
criteria_serializer.is_valid()
65+
scores_serializer.is_valid()
66+
67+
projects_serializer = self.get_serializer(
68+
data=projects,
69+
context={
70+
"data_criterias": criteria_serializer.data,
71+
"data_scores": scores_serializer.data,
72+
},
73+
many=True,
74+
)
75+
76+
projects_serializer.is_valid()
77+
78+
return self.get_paginated_response(projects_serializer.data)

0 commit comments

Comments
 (0)