From 73bdd398330b34d3517b693b1b3cc7de75aa84a6 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 7 Feb 2024 18:24:09 +0300 Subject: [PATCH 01/10] added GET request for rating of projects --- rate_projects/serializers.py | 47 ++++++++++++++++++++++++++++++++++-- rate_projects/urls.py | 5 ++-- rate_projects/validators.py | 10 ++++++++ rate_projects/views.py | 41 +++++++++++++++++++++++++++++-- 4 files changed, 97 insertions(+), 6 deletions(-) diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py index 5402f676..3c858691 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -1,8 +1,8 @@ from rest_framework import serializers -from rate_projects.constants import get_type + from rate_projects.models import ProjectScore -from rate_projects.validators import ProjectScoreValidate +from .validators import find_filled_field class ProjectScoreCreateSerializer(serializers.ModelSerializer[ProjectScore]): @@ -19,3 +19,46 @@ class Meta: ] +from rest_framework import serializers +from .models import Criteria, ProjectScore +from projects.models import Project + + +class CriteriaSerializer(serializers.ModelSerializer): + class Meta: + model = Criteria + exclude = ['partner_program'] + + +from django.forms.models import model_to_dict + + +class ProjectScoreSerializer(serializers.ModelSerializer): + filled_value = serializers.SerializerMethodField() + + class Meta: + model = ProjectScore + fields = ['criteria_id', 'project_id', 'filled_value'] + + def get_filled_value(self, obj): + filled_values = [{'project_id': score.project_id, 'criteria_id': score.criteria_id, 'filled_value': find_filled_field(model_to_dict(score))[0]} + for score in obj] + return filled_values + + +class ProjectSerializer(serializers.ModelSerializer): + criterias = serializers.SerializerMethodField() + + class Meta: + model = Project + fields = ['id', 'name', 'leader', 'description', 'image_address', 'industry', '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'][0]['filled_value']: + if criteria['id'] == score['criteria_id'] and obj.id == score['project_id']: + copied_criteria['filled_value'] = score['filled_value'] + criterias.append(copied_criteria) + return criterias diff --git a/rate_projects/urls.py b/rate_projects/urls.py index b340b2b3..99983fe4 100644 --- a/rate_projects/urls.py +++ b/rate_projects/urls.py @@ -1,7 +1,8 @@ from django.urls import path -from rate_projects.views import RateProject +from rate_projects.views import RateProject, RateProjects urlpatterns = [ - path("rate/", RateProject.as_view()) + path("rate/", RateProject.as_view()), + path("/", RateProjects.as_view()) ] \ No newline at end of file diff --git a/rate_projects/validators.py b/rate_projects/validators.py index 3f00d066..3132b3a3 100644 --- a/rate_projects/validators.py +++ b/rate_projects/validators.py @@ -1,6 +1,16 @@ from rate_projects.constants import get_type + +def find_filled_field(data): + filled_fields = [] + for field in ['value_int', 'value_bool', 'value_str', 'value_float']: + if data[field] is not None: + filled_fields.append(data[field]) + return filled_fields + + + class ProjectScoreValidate: def __init__(self, **kwargs): self.criteria_type = kwargs.get('criteria_type') diff --git a/rate_projects/views.py b/rate_projects/views.py index 496a719e..480713be 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -3,8 +3,10 @@ from rest_framework import generics, status from rest_framework.response import Response - -from rate_projects.serializers import ProjectScoreCreateSerializer +from projects.models import Project +from rate_projects.models import Criteria, ProjectScore +from rate_projects.serializers import ProjectScoreCreateSerializer, CriteriaSerializer, ProjectScoreSerializer, \ + ProjectSerializer from users.permissions import IsExpert User = get_user_model() @@ -29,5 +31,40 @@ def create(self, request, *args, **kwargs): return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) +class RateProjects(generics.ListAPIView): + permission_classes = [IsExpert] + + 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 + ) + ) + projects = ( + Project.objects + .filter(partner_program_profiles__partner_program_id=program_id) + .distinct() + ) + criteria_serializer = CriteriaSerializer(data=criterias, many=True) + scores_serializer = ProjectScoreSerializer(data=[scores], many=True)# idk why it needs [], but don't fix what ain't broken + + criteria_serializer.is_valid() + scores_serializer.is_valid() + + projects_serializer = ProjectSerializer( + data=projects, + context={'data_criterias': criteria_serializer.data, 'data_scores': scores_serializer.data}, + many=True + ) + projects_serializer.is_valid() + return Response(projects_serializer.data, status=200) \ No newline at end of file From 09bc49392213bfc37265c36a1841ce21cfc9620f Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 9 Feb 2024 12:29:08 +0300 Subject: [PATCH 02/10] used black, did naming corrections, fixed admin panel --- rate_projects/admin.py | 27 +++++++------- rate_projects/constants.py | 11 ++---- rate_projects/models.py | 68 ++++++++++-------------------------- rate_projects/serializers.py | 54 ++++++++++++++++------------ rate_projects/urls.py | 4 +-- rate_projects/validators.py | 40 +++++++++++---------- rate_projects/views.py | 50 ++++++++++++++------------ 7 files changed, 118 insertions(+), 136 deletions(-) diff --git a/rate_projects/admin.py b/rate_projects/admin.py index 9f7da922..1ecb1455 100644 --- a/rate_projects/admin.py +++ b/rate_projects/admin.py @@ -5,25 +5,26 @@ # Register your models here. @admin.register(Criteria) class CriteriaAdmin(admin.ModelAdmin): - list_display = ( - "id", - "name", - "type", - # добавить проект / программу - ) + list_display = ("id", "name", "type", "get_program_name") list_display_links = ( "id", "name", ) + def get_program_name(self, obj): + return obj.partner_program.name + @admin.register(ProjectScore) class ProjectScoreAdmin(admin.ModelAdmin): - list_display = ( - "id", - # добавить проект / программу - ) - list_display_links = ( - "id", - ) + list_display = ("id", "get_user_name", "get_project_name", "get_criteria_name") + list_display_links = ("id",) + + def get_user_name(self, obj): + return f"{obj.user.last_name} {obj.user.first_name}" + + def get_criteria_name(self, obj): + return obj.criteria.name + def get_project_name(self, obj): + return obj.project.name diff --git a/rate_projects/constants.py b/rate_projects/constants.py index ed30d627..f0c1deea 100644 --- a/rate_projects/constants.py +++ b/rate_projects/constants.py @@ -1,13 +1,8 @@ -TYPES = ( +VERBOSE_NAME_TYPES = ( ("str", "Текст"), ("int", "Целочисленное число"), ("float", "Число с плавающей точкой"), - ("bool", "Да или нет") + ("bool", "Да или нет"), ) -get_type = { - "str": str, - "int": int, - "float": float, - "bool": bool -} +GET_TYPE_FROM_STRING = {"str": str, "int": int, "float": float, "bool": bool} diff --git a/rate_projects/models.py b/rate_projects/models.py index a7795a16..281efcd4 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -3,11 +3,12 @@ from partner_programs.models import PartnerProgram from projects.models import Project -from .constants import TYPES, get_type +from .constants import VERBOSE_NAME_TYPES from .validators import ProjectScoreValidate User = get_user_model() + class Criteria(models.Model): """ Criteria model @@ -21,32 +22,22 @@ class Criteria(models.Model): partner_program: A ForeignKey connection to PartnerProgram model """ - name = models.CharField( - verbose_name="Название", - max_length=50 - ) - description = models.TextField( - verbose_name="Описание", - null=True, - blank=True - ) - type = models.CharField( - verbose_name="Тип", - max_length=8, - choices=TYPES - ) + + name = models.CharField(verbose_name="Название", max_length=50) + description = models.TextField(verbose_name="Описание", null=True, blank=True) + type = models.CharField(verbose_name="Тип", max_length=8, choices=VERBOSE_NAME_TYPES) min_value = models.FloatField( verbose_name="Минимально допустимое числовое значение", help_text="(если есть)", null=True, - blank=True + blank=True, ) max_value = models.FloatField( verbose_name="Максимально допустимое числовое значение", help_text="(если есть)", null=True, - blank=True + blank=True, ) partner_program = models.ForeignKey( PartnerProgram, @@ -62,7 +53,6 @@ class Meta: verbose_name_plural = "Критерии оценки проектов" - class ProjectScore(models.Model): """ ProjectScore model @@ -77,46 +67,26 @@ class ProjectScore(models.Model): value_str: IntegerField for value """ + criteria = models.ForeignKey( - Criteria, - on_delete=models.CASCADE, - related_name="scores" - ) - user = models.ForeignKey( - User, - on_delete=models.CASCADE, - related_name="scores" - ) - project = models.ForeignKey( - Project, - on_delete=models.CASCADE, - related_name="scores" + Criteria, on_delete=models.CASCADE, related_name="scores" ) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="scores") + project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scores") value_int = models.IntegerField( - verbose_name="Целочисленное значение", - null=True, - blank=True + verbose_name="Целочисленное значение", null=True, blank=True ) value_float = models.FloatField( - verbose_name="Значение с плавающей запятой", - max_length=50, - null=True, - blank=True + verbose_name="Значение с плавающей запятой", max_length=50, null=True, blank=True ) value_bool = models.BooleanField( - verbose_name="'Да или нет' значение", - null=True, - blank=True + verbose_name="'Да или нет' значение", null=True, blank=True ) value_str = models.FloatField( - verbose_name="Текстовое значение", - max_length=50, - null=True, - blank=True + verbose_name="Текстовое значение", max_length=50, null=True, blank=True ) - def __str__(self): return f"ProjectScore<{self.id}> - {self.criteria.name}" @@ -128,13 +98,11 @@ def save(self, *args, **kwargs): value_bool=self.value_bool, value_float=self.value_float, criteria_min_value=self.criteria.min_value, - criteria_max_value=self.criteria.max_value + criteria_max_value=self.criteria.max_value, ) super().save(*args, **kwargs) - class Meta: verbose_name = "Оценка проекта" verbose_name_plural = "Оценки проектов" - unique_together = ('criteria', 'user', 'project') - + unique_together = ("criteria", "user", "project") diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py index 3c858691..834a7425 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -1,11 +1,12 @@ -from rest_framework import serializers - +from django.forms.models import model_to_dict -from rate_projects.models import ProjectScore from .validators import find_filled_field +from rest_framework import serializers +from .models import Criteria, ProjectScore +from projects.models import Project -class ProjectScoreCreateSerializer(serializers.ModelSerializer[ProjectScore]): +class ProjectScoreCreateSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore fields = [ @@ -15,22 +16,14 @@ class Meta: "value_int", "value_float", "value_bool", - "value_str" + "value_str", ] -from rest_framework import serializers -from .models import Criteria, ProjectScore -from projects.models import Project - - class CriteriaSerializer(serializers.ModelSerializer): class Meta: model = Criteria - exclude = ['partner_program'] - - -from django.forms.models import model_to_dict + exclude = ["partner_program"] class ProjectScoreSerializer(serializers.ModelSerializer): @@ -38,11 +31,17 @@ class ProjectScoreSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore - fields = ['criteria_id', 'project_id', 'filled_value'] + fields = ["criteria_id", "project_id", "filled_value"] def get_filled_value(self, obj): - filled_values = [{'project_id': score.project_id, 'criteria_id': score.criteria_id, 'filled_value': find_filled_field(model_to_dict(score))[0]} - for score in obj] + filled_values = [ + { + "project_id": score.project_id, + "criteria_id": score.criteria_id, + "filled_value": find_filled_field(model_to_dict(score))[0], + } + for score in obj + ] return filled_values @@ -51,14 +50,25 @@ class ProjectSerializer(serializers.ModelSerializer): class Meta: model = Project - fields = ['id', 'name', 'leader', 'description', 'image_address', 'industry', 'criterias'] + fields = [ + "id", + "name", + "leader", + "description", + "image_address", + "industry", + "criterias", + ] def get_criterias(self, obj): criterias = [] - for criteria in self.context['data_criterias']: + for criteria in self.context["data_criterias"]: copied_criteria = criteria.copy() # Создать полную копию критерия - for score in self.context['data_scores'][0]['filled_value']: - if criteria['id'] == score['criteria_id'] and obj.id == score['project_id']: - copied_criteria['filled_value'] = score['filled_value'] + for score in self.context["data_scores"][0]["filled_value"]: + if ( + criteria["id"] == score["criteria_id"] + and obj.id == score["project_id"] + ): + copied_criteria["filled_value"] = score["filled_value"] criterias.append(copied_criteria) return criterias diff --git a/rate_projects/urls.py b/rate_projects/urls.py index 99983fe4..4892a932 100644 --- a/rate_projects/urls.py +++ b/rate_projects/urls.py @@ -4,5 +4,5 @@ urlpatterns = [ path("rate/", RateProject.as_view()), - path("/", RateProjects.as_view()) -] \ No newline at end of file + path("/", RateProjects.as_view()), +] diff --git a/rate_projects/validators.py b/rate_projects/validators.py index 3132b3a3..f4a85986 100644 --- a/rate_projects/validators.py +++ b/rate_projects/validators.py @@ -1,25 +1,23 @@ -from rate_projects.constants import get_type - +from rate_projects.constants import GET_TYPE_FROM_STRING def find_filled_field(data): filled_fields = [] - for field in ['value_int', 'value_bool', 'value_str', 'value_float']: + for field in ["value_int", "value_bool", "value_str", "value_float"]: if data[field] is not None: filled_fields.append(data[field]) return filled_fields - class ProjectScoreValidate: def __init__(self, **kwargs): - self.criteria_type = kwargs.get('criteria_type') - self.value_int = kwargs.get('value_int') - self.value_str = kwargs.get('value_str') - self.value_bool = kwargs.get('value_bool') - self.value_float = kwargs.get('value_float') - self.criteria_min_value = kwargs.get('criteria_min_value') - self.criteria_max_value = kwargs.get('criteria_max_value') + self.criteria_type = kwargs.get("criteria_type") + self.value_int = kwargs.get("value_int") + self.value_str = kwargs.get("value_str") + self.value_bool = kwargs.get("value_bool") + self.value_float = kwargs.get("value_float") + self.criteria_min_value = kwargs.get("criteria_min_value") + self.criteria_max_value = kwargs.get("criteria_max_value") self._validate_quantity_filled_fields() self._validate_numeric_fields() @@ -27,7 +25,7 @@ def __init__(self, **kwargs): @property def _find_filled_field(self): filled_fields = [] - for field in ['value_int', 'value_bool', 'value_str', 'value_float']: + for field in ["value_int", "value_bool", "value_str", "value_float"]: if getattr(self, field) is not None: filled_fields.append(getattr(self, field)) return filled_fields @@ -35,16 +33,22 @@ def _find_filled_field(self): def _validate_quantity_filled_fields(self): filled_fields = self._find_filled_field if len(filled_fields) > 1: - raise ValueError(f'Должно быть заполнено менее 2-ух полей!') + raise ValueError("Должно быть заполнено менее 2-ух полей!") - if not isinstance(filled_fields[0], get_type.get(self.criteria_type)): - raise ValueError(f'Тип введённых данных не совпадает с требуемым!') + if not isinstance(filled_fields[0], GET_TYPE_FROM_STRING.get(self.criteria_type)): + raise ValueError("Тип введённых данных не совпадает с требуемым!") def _validate_numeric_fields(self): added_value = self._find_filled_field[0] - if self.criteria_type != 'bool' and isinstance(added_value, (int, float)): - if self.criteria_min_value is not None and self.criteria_min_value > added_value: + if self.criteria_type != "bool" and isinstance(added_value, (int, float)): + if ( + self.criteria_min_value is not None + and self.criteria_min_value > added_value + ): raise ValueError("Оценка этого критерия принизила допустимые значения!") - elif self.criteria_max_value is not None and self.criteria_max_value < added_value: + elif ( + self.criteria_max_value is not None + and self.criteria_max_value < added_value + ): raise ValueError("Оценка этого критерия превысила допустимые значения!") diff --git a/rate_projects/views.py b/rate_projects/views.py index 480713be..838f76f5 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -5,12 +5,17 @@ from projects.models import Project from rate_projects.models import Criteria, ProjectScore -from rate_projects.serializers import ProjectScoreCreateSerializer, CriteriaSerializer, ProjectScoreSerializer, \ - ProjectSerializer +from rate_projects.serializers import ( + ProjectScoreCreateSerializer, + CriteriaSerializer, + ProjectScoreSerializer, + ProjectSerializer, +) from users.permissions import IsExpert User = get_user_model() + class RateProject(generics.CreateAPIView): serializer_class = ProjectScoreCreateSerializer permission_classes = [IsExpert] @@ -18,17 +23,17 @@ class RateProject(generics.CreateAPIView): def create(self, request, *args, **kwargs): try: data = self.request.data - data['user'] = self.request.user.id + data["user"] = self.request.user.id serializer = self.get_serializer(data=data) if serializer.is_valid(): serializer.save() self.perform_create(serializer) - return Response({'success': True}, status=status.HTTP_201_CREATED) + return Response({"success": True}, status=status.HTTP_201_CREATED) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) except Exception as e: - return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) class RateProjects(generics.ListAPIView): @@ -36,35 +41,34 @@ class RateProjects(generics.ListAPIView): def get(self, request, *args, **kwargs): user = self.request.user - program_id = self.kwargs.get('program_id') - + 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 - ) + criterias = Criteria.objects.prefetch_related("partner_program").filter( + partner_program_id=program_id ) - projects = ( - Project.objects - .filter(partner_program_profiles__partner_program_id=program_id) - .distinct() + scores = ProjectScore.objects.prefetch_related("criteria").filter( + criteria__in=criterias.values_list("id", flat=True), user=user ) + projects = Project.objects.filter( + partner_program_profiles__partner_program_id=program_id + ).distinct() criteria_serializer = CriteriaSerializer(data=criterias, many=True) - scores_serializer = ProjectScoreSerializer(data=[scores], many=True)# idk why it needs [], but don't fix what ain't broken + scores_serializer = ProjectScoreSerializer( + data=[scores], many=True + ) # idk why it needs [], but don't fix what ain't broken criteria_serializer.is_valid() scores_serializer.is_valid() projects_serializer = ProjectSerializer( data=projects, - context={'data_criterias': criteria_serializer.data, 'data_scores': scores_serializer.data}, - many=True + context={ + "data_criterias": criteria_serializer.data, + "data_scores": scores_serializer.data, + }, + many=True, ) projects_serializer.is_valid() - return Response(projects_serializer.data, status=200) \ No newline at end of file + return Response(projects_serializer.data, status=200) From afc342af98fd38db70f8df08ca3a683f8b3a19ab Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 9 Feb 2024 17:12:51 +0300 Subject: [PATCH 03/10] added serializer for GET --- rate_projects/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rate_projects/views.py b/rate_projects/views.py index 838f76f5..beb2f930 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -37,6 +37,7 @@ def create(self, request, *args, **kwargs): class RateProjects(generics.ListAPIView): + serializer_class = ProjectSerializer permission_classes = [IsExpert] def get(self, request, *args, **kwargs): @@ -60,7 +61,7 @@ def get(self, request, *args, **kwargs): criteria_serializer.is_valid() scores_serializer.is_valid() - projects_serializer = ProjectSerializer( + projects_serializer = self.get_serializer( data=projects, context={ "data_criterias": criteria_serializer.data, From 975bf2c170aa9eba7930ff9ca5b0d0ddece32e0a Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 9 Feb 2024 17:39:41 +0300 Subject: [PATCH 04/10] linter and naming update --- rate_projects/serializers.py | 2 +- rate_projects/views.py | 4 ++-- users/permissions.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py index 834a7425..43f76263 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -45,7 +45,7 @@ def get_filled_value(self, obj): return filled_values -class ProjectSerializer(serializers.ModelSerializer): +class ProjectScoreGetSerializer(serializers.ModelSerializer): criterias = serializers.SerializerMethodField() class Meta: diff --git a/rate_projects/views.py b/rate_projects/views.py index beb2f930..0daddd19 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -9,7 +9,7 @@ ProjectScoreCreateSerializer, CriteriaSerializer, ProjectScoreSerializer, - ProjectSerializer, + ProjectScoreGetSerializer, ) from users.permissions import IsExpert @@ -37,7 +37,7 @@ def create(self, request, *args, **kwargs): class RateProjects(generics.ListAPIView): - serializer_class = ProjectSerializer + serializer_class = ProjectScoreGetSerializer permission_classes = [IsExpert] def get(self, request, *args, **kwargs): diff --git a/users/permissions.py b/users/permissions.py index ce7e91ae..ef53b4de 100644 --- a/users/permissions.py +++ b/users/permissions.py @@ -12,10 +12,10 @@ def has_object_permission(self, request, view, obj): return False - class IsExpert(BasePermission): """ Allows access if user is EXPERT """ + def has_permission(self, request, view): - return request.user.user_type == 3 \ No newline at end of file + return request.user.user_type == 3 From 7e41257a8e87957e08c94de4d9f491cc6d1b0c6d Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 10 Feb 2024 05:49:04 +0300 Subject: [PATCH 05/10] updated serializer code, naming and model fixes --- ...alter_projectscore_value_float_and_more.py | 34 +++++++++++++++++++ rate_projects/models.py | 17 ++++++---- rate_projects/serializers.py | 19 ++++------- rate_projects/urls.py | 2 +- rate_projects/views.py | 5 ++- 5 files changed, 55 insertions(+), 22 deletions(-) create mode 100644 rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py diff --git a/rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py b/rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py new file mode 100644 index 00000000..5cb82ee0 --- /dev/null +++ b/rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 4.2.3 on 2024-02-10 02:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("rate_projects", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="projectscore", + name="comment", + field=models.CharField( + blank=True, max_length=100, null=True, verbose_name="Комментарий" + ), + ), + migrations.AlterField( + model_name="projectscore", + name="value_float", + field=models.FloatField( + blank=True, null=True, verbose_name="Значение с плавающей запятой" + ), + ), + migrations.AlterField( + model_name="projectscore", + name="value_str", + field=models.CharField( + blank=True, max_length=50, null=True, verbose_name="Текстовое значение" + ), + ), + ] \ No newline at end of file diff --git a/rate_projects/models.py b/rate_projects/models.py index 281efcd4..140e0399 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -61,10 +61,11 @@ class ProjectScore(models.Model): criteria: A ForeignKey connection to Criteria model user: A ForeignKey connection to User model - value_int: IntegerField for value - value_float: IntegerField for value - value_bool: IntegerField for value - value_str: IntegerField for value + value_float: FloatField for value + value_bool: BooleanField for value + value_str: CharField for value + + commentary: CharField for optional commentary """ @@ -78,15 +79,19 @@ class ProjectScore(models.Model): verbose_name="Целочисленное значение", null=True, blank=True ) value_float = models.FloatField( - verbose_name="Значение с плавающей запятой", max_length=50, null=True, blank=True + verbose_name="Значение с плавающей запятой", null=True, blank=True ) value_bool = models.BooleanField( verbose_name="'Да или нет' значение", null=True, blank=True ) - value_str = models.FloatField( + value_str = models.CharField( verbose_name="Текстовое значение", max_length=50, null=True, blank=True ) + comment = models.CharField( + verbose_name="Комментарий", null=True, blank=True, max_length=100 + ) + def __str__(self): return f"ProjectScore<{self.id}> - {self.criteria.name}" diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py index 43f76263..21f1cb21 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -17,6 +17,7 @@ class Meta: "value_float", "value_bool", "value_str", + "comment", ] @@ -31,18 +32,10 @@ class ProjectScoreSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore - fields = ["criteria_id", "project_id", "filled_value"] + fields = ["criteria_id", "project_id", "filled_value", "comment"] def get_filled_value(self, obj): - filled_values = [ - { - "project_id": score.project_id, - "criteria_id": score.criteria_id, - "filled_value": find_filled_field(model_to_dict(score))[0], - } - for score in obj - ] - return filled_values + return find_filled_field(model_to_dict(obj))[0] class ProjectScoreGetSerializer(serializers.ModelSerializer): @@ -63,12 +56,14 @@ class Meta: def get_criterias(self, obj): criterias = [] for criteria in self.context["data_criterias"]: - copied_criteria = criteria.copy() # Создать полную копию критерия - for score in self.context["data_scores"][0]["filled_value"]: + 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["filled_value"] = score["filled_value"] + copied_criteria["comment"] = score["comment"] + criterias.append(copied_criteria) return criterias diff --git a/rate_projects/urls.py b/rate_projects/urls.py index 4892a932..4f9f208e 100644 --- a/rate_projects/urls.py +++ b/rate_projects/urls.py @@ -4,5 +4,5 @@ urlpatterns = [ path("rate/", RateProject.as_view()), - path("/", RateProjects.as_view()), + path("", RateProjects.as_view()), ] diff --git a/rate_projects/views.py b/rate_projects/views.py index 0daddd19..753c1d93 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -53,10 +53,9 @@ def get(self, request, *args, **kwargs): projects = Project.objects.filter( partner_program_profiles__partner_program_id=program_id ).distinct() + criteria_serializer = CriteriaSerializer(data=criterias, many=True) - scores_serializer = ProjectScoreSerializer( - data=[scores], many=True - ) # idk why it needs [], but don't fix what ain't broken + scores_serializer = ProjectScoreSerializer(data=scores, many=True) criteria_serializer.is_valid() scores_serializer.is_valid() From bafe334542752a5dc79637f0fdfaa5eea4b98b48 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 10 Feb 2024 13:59:56 +0300 Subject: [PATCH 06/10] changed code to one value field --- rate_projects/constants.py | 2 - rate_projects/migrations/0001_initial.py | 36 +++--------- ...alter_projectscore_value_float_and_more.py | 34 ----------- rate_projects/models.py | 22 ++----- rate_projects/serializers.py | 24 ++------ rate_projects/validators.py | 58 ++++++------------- 6 files changed, 34 insertions(+), 142 deletions(-) delete mode 100644 rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py diff --git a/rate_projects/constants.py b/rate_projects/constants.py index f0c1deea..11504c22 100644 --- a/rate_projects/constants.py +++ b/rate_projects/constants.py @@ -4,5 +4,3 @@ ("float", "Число с плавающей точкой"), ("bool", "Да или нет"), ) - -GET_TYPE_FROM_STRING = {"str": str, "int": int, "float": float, "bool": bool} diff --git a/rate_projects/migrations/0001_initial.py b/rate_projects/migrations/0001_initial.py index b1bcb784..341ab849 100644 --- a/rate_projects/migrations/0001_initial.py +++ b/rate_projects/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.3 on 2024-02-07 11:02 +# Generated by Django 4.2.3 on 2024-02-10 10:49 from django.conf import settings from django.db import migrations, models @@ -10,9 +10,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ("projects", "0021_project_subscribers"), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), ("partner_programs", "0004_auto_20231230_0002"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ("projects", "0021_project_subscribers"), ] operations = [ @@ -91,33 +91,15 @@ class Migration(migrations.Migration): ), ), ( - "value_int", - models.IntegerField( - blank=True, null=True, verbose_name="Целочисленное значение" - ), - ), - ( - "value_float", - models.FloatField( - blank=True, - max_length=50, - null=True, - verbose_name="Значение с плавающей запятой", - ), - ), - ( - "value_bool", - models.BooleanField( - blank=True, null=True, verbose_name="'Да или нет' значение" + "value", + models.CharField( + blank=True, max_length=50, null=True, verbose_name="Значение" ), ), ( - "value_str", - models.FloatField( - blank=True, - max_length=50, - null=True, - verbose_name="Текстовое значение", + "comment", + models.CharField( + blank=True, max_length=100, null=True, verbose_name="Комментарий" ), ), ( diff --git a/rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py b/rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py deleted file mode 100644 index 5cb82ee0..00000000 --- a/rate_projects/migrations/0002_projectscore_comment_alter_projectscore_value_float_and_more.py +++ /dev/null @@ -1,34 +0,0 @@ -# Generated by Django 4.2.3 on 2024-02-10 02:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("rate_projects", "0001_initial"), - ] - - operations = [ - migrations.AddField( - model_name="projectscore", - name="comment", - field=models.CharField( - blank=True, max_length=100, null=True, verbose_name="Комментарий" - ), - ), - migrations.AlterField( - model_name="projectscore", - name="value_float", - field=models.FloatField( - blank=True, null=True, verbose_name="Значение с плавающей запятой" - ), - ), - migrations.AlterField( - model_name="projectscore", - name="value_str", - field=models.CharField( - blank=True, max_length=50, null=True, verbose_name="Текстовое значение" - ), - ), - ] \ No newline at end of file diff --git a/rate_projects/models.py b/rate_projects/models.py index 140e0399..3bc77742 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -61,9 +61,7 @@ class ProjectScore(models.Model): criteria: A ForeignKey connection to Criteria model user: A ForeignKey connection to User model - value_float: FloatField for value - value_bool: BooleanField for value - value_str: CharField for value + value: CharField for value commentary: CharField for optional commentary @@ -75,17 +73,8 @@ class ProjectScore(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="scores") project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="scores") - value_int = models.IntegerField( - verbose_name="Целочисленное значение", null=True, blank=True - ) - value_float = models.FloatField( - verbose_name="Значение с плавающей запятой", null=True, blank=True - ) - value_bool = models.BooleanField( - verbose_name="'Да или нет' значение", null=True, blank=True - ) - value_str = models.CharField( - verbose_name="Текстовое значение", max_length=50, null=True, blank=True + value = models.CharField( + verbose_name="Значение", max_length=50, null=True, blank=True ) comment = models.CharField( @@ -98,10 +87,7 @@ def __str__(self): def save(self, *args, **kwargs): ProjectScoreValidate( criteria_type=self.criteria.type, - value_int=self.value_int, - value_str=self.value_str, - value_bool=self.value_bool, - value_float=self.value_float, + value=self.value, criteria_min_value=self.criteria.min_value, criteria_max_value=self.criteria.max_value, ) diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py index 21f1cb21..cd73f7a7 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -1,6 +1,3 @@ -from django.forms.models import model_to_dict - -from .validators import find_filled_field from rest_framework import serializers from .models import Criteria, ProjectScore from projects.models import Project @@ -9,16 +6,7 @@ class ProjectScoreCreateSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore - fields = [ - "criteria", - "user", - "project", - "value_int", - "value_float", - "value_bool", - "value_str", - "comment", - ] + fields = ["criteria", "user", "project", "value", "comment"] class CriteriaSerializer(serializers.ModelSerializer): @@ -28,14 +16,9 @@ class Meta: class ProjectScoreSerializer(serializers.ModelSerializer): - filled_value = serializers.SerializerMethodField() - class Meta: model = ProjectScore - fields = ["criteria_id", "project_id", "filled_value", "comment"] - - def get_filled_value(self, obj): - return find_filled_field(model_to_dict(obj))[0] + fields = ["criteria_id", "project_id", "value", "comment"] class ProjectScoreGetSerializer(serializers.ModelSerializer): @@ -57,12 +40,13 @@ def get_criterias(self, obj): criterias = [] for criteria in self.context["data_criterias"]: copied_criteria = criteria.copy() + copied_criteria["comment"] = None for score in self.context["data_scores"]: if ( criteria["id"] == score["criteria_id"] and obj.id == score["project_id"] ): - copied_criteria["filled_value"] = score["filled_value"] + copied_criteria["filled_value"] = score["value"] copied_criteria["comment"] = score["comment"] criterias.append(copied_criteria) diff --git a/rate_projects/validators.py b/rate_projects/validators.py index f4a85986..5d566b33 100644 --- a/rate_projects/validators.py +++ b/rate_projects/validators.py @@ -1,54 +1,30 @@ -from rate_projects.constants import GET_TYPE_FROM_STRING - - -def find_filled_field(data): - filled_fields = [] - for field in ["value_int", "value_bool", "value_str", "value_float"]: - if data[field] is not None: - filled_fields.append(data[field]) - return filled_fields - - class ProjectScoreValidate: def __init__(self, **kwargs): self.criteria_type = kwargs.get("criteria_type") - self.value_int = kwargs.get("value_int") - self.value_str = kwargs.get("value_str") - self.value_bool = kwargs.get("value_bool") - self.value_float = kwargs.get("value_float") + self.value = kwargs.get("value") self.criteria_min_value = kwargs.get("criteria_min_value") self.criteria_max_value = kwargs.get("criteria_max_value") - self._validate_quantity_filled_fields() - self._validate_numeric_fields() - - @property - def _find_filled_field(self): - filled_fields = [] - for field in ["value_int", "value_bool", "value_str", "value_float"]: - if getattr(self, field) is not None: - filled_fields.append(getattr(self, field)) - return filled_fields - - def _validate_quantity_filled_fields(self): - filled_fields = self._find_filled_field - if len(filled_fields) > 1: - raise ValueError("Должно быть заполнено менее 2-ух полей!") + self._validate_data_type() + self._validate_numeric_limits() - if not isinstance(filled_fields[0], GET_TYPE_FROM_STRING.get(self.criteria_type)): - raise ValueError("Тип введённых данных не совпадает с требуемым!") + def _validate_data_type(self): + if self.criteria_type in ["float", "int"]: + try: + float(self.value) + except TypeError: + raise TypeError("Введённое значение не соответствует формату!") - def _validate_numeric_fields(self): - added_value = self._find_filled_field[0] + elif (self.criteria_type == "bool") and (self.value not in ["True", "False"]): + raise TypeError("Введённое значение не соответствует формату!") - if self.criteria_type != "bool" and isinstance(added_value, (int, float)): - if ( - self.criteria_min_value is not None - and self.criteria_min_value > added_value + 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 < added_value + elif self.criteria_max_value is not None and self.criteria_max_value < float( + self.value ): raise ValueError("Оценка этого критерия превысила допустимые значения!") From 1929b5404e22f1b37ad89d805c8da2b6a929a949 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 10 Feb 2024 14:02:06 +0300 Subject: [PATCH 07/10] changed code to one value field --- rate_projects/validators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rate_projects/validators.py b/rate_projects/validators.py index 5d566b33..1dfc0a65 100644 --- a/rate_projects/validators.py +++ b/rate_projects/validators.py @@ -12,8 +12,8 @@ def _validate_data_type(self): if self.criteria_type in ["float", "int"]: try: float(self.value) - except TypeError: - raise TypeError("Введённое значение не соответствует формату!") + except ValueError: + raise ValueError("Введённое значение не соответствует формату!") elif (self.criteria_type == "bool") and (self.value not in ["True", "False"]): raise TypeError("Введённое значение не соответствует формату!") From cbd156722a430189be25baa612729b894aa92bb7 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 12 Feb 2024 10:32:36 +0300 Subject: [PATCH 08/10] validation error fixed --- rate_projects/validators.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rate_projects/validators.py b/rate_projects/validators.py index 1dfc0a65..9ac6800b 100644 --- a/rate_projects/validators.py +++ b/rate_projects/validators.py @@ -14,6 +14,8 @@ def _validate_data_type(self): float(self.value) except ValueError: raise ValueError("Введённое значение не соответствует формату!") + except TypeError: + raise TypeError("Вы не ввели никакие данные!") elif (self.criteria_type == "bool") and (self.value not in ["True", "False"]): raise TypeError("Введённое значение не соответствует формату!") From b12a4c267bf8da39ee7d6db7284478566246f99e Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 21:49:08 +0300 Subject: [PATCH 09/10] corrections are made --- .../0002_remove_projectscore_comment.py | 17 +++++++++++++++++ rate_projects/models.py | 5 ----- rate_projects/serializers.py | 8 +++----- 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 rate_projects/migrations/0002_remove_projectscore_comment.py diff --git a/rate_projects/migrations/0002_remove_projectscore_comment.py b/rate_projects/migrations/0002_remove_projectscore_comment.py new file mode 100644 index 00000000..eec68ccf --- /dev/null +++ b/rate_projects/migrations/0002_remove_projectscore_comment.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.3 on 2024-02-19 17:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("rate_projects", "0001_initial"), + ] + + operations = [ + migrations.RemoveField( + model_name="projectscore", + name="comment", + ), + ] \ No newline at end of file diff --git a/rate_projects/models.py b/rate_projects/models.py index 3bc77742..2f72b7a6 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -63,7 +63,6 @@ class ProjectScore(models.Model): value: CharField for value - commentary: CharField for optional commentary """ @@ -77,10 +76,6 @@ class ProjectScore(models.Model): verbose_name="Значение", max_length=50, null=True, blank=True ) - comment = models.CharField( - verbose_name="Комментарий", null=True, blank=True, max_length=100 - ) - def __str__(self): return f"ProjectScore<{self.id}> - {self.criteria.name}" diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py index cd73f7a7..1d16d717 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -6,7 +6,7 @@ class ProjectScoreCreateSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore - fields = ["criteria", "user", "project", "value", "comment"] + fields = ["criteria", "user", "project", "value"] class CriteriaSerializer(serializers.ModelSerializer): @@ -18,7 +18,7 @@ class Meta: class ProjectScoreSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore - fields = ["criteria_id", "project_id", "value", "comment"] + fields = ["criteria_id", "project_id", "value"] class ProjectScoreGetSerializer(serializers.ModelSerializer): @@ -40,14 +40,12 @@ def get_criterias(self, obj): criterias = [] for criteria in self.context["data_criterias"]: copied_criteria = criteria.copy() - copied_criteria["comment"] = None for score in self.context["data_scores"]: if ( criteria["id"] == score["criteria_id"] and obj.id == score["project_id"] ): - copied_criteria["filled_value"] = score["value"] - copied_criteria["comment"] = score["comment"] + copied_criteria["value"] = score["value"] criterias.append(copied_criteria) return criterias From 205ec48e5319c3b59f80304b908e2860056f9e2d Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 03:02:31 +0300 Subject: [PATCH 10/10] pagination added --- rate_projects/pagination.py | 7 +++++++ rate_projects/views.py | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 rate_projects/pagination.py diff --git a/rate_projects/pagination.py b/rate_projects/pagination.py new file mode 100644 index 00000000..91a85ab6 --- /dev/null +++ b/rate_projects/pagination.py @@ -0,0 +1,7 @@ +from rest_framework.pagination import LimitOffsetPagination + + +class RateProjectsPagination(LimitOffsetPagination): + default_limit = 10 + limit_query_param = "limit" + offset_query_param = "offset" diff --git a/rate_projects/views.py b/rate_projects/views.py index 753c1d93..e7c6fa39 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -5,6 +5,7 @@ from projects.models import Project from rate_projects.models import Criteria, ProjectScore +from rate_projects.pagination import RateProjectsPagination from rate_projects.serializers import ( ProjectScoreCreateSerializer, CriteriaSerializer, @@ -39,6 +40,7 @@ def create(self, request, *args, **kwargs): class RateProjects(generics.ListAPIView): serializer_class = ProjectScoreGetSerializer permission_classes = [IsExpert] + pagination_class = RateProjectsPagination def get(self, request, *args, **kwargs): user = self.request.user @@ -50,10 +52,12 @@ def get(self, request, *args, **kwargs): scores = ProjectScore.objects.prefetch_related("criteria").filter( criteria__in=criterias.values_list("id", flat=True), user=user ) - projects = Project.objects.filter( + 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) @@ -71,4 +75,4 @@ def get(self, request, *args, **kwargs): projects_serializer.is_valid() - return Response(projects_serializer.data, status=200) + return self.get_paginated_response(projects_serializer.data)