From 12d7e7af35ddf79795fe0af179d365c17e451a86 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 7 Feb 2024 17:46:04 +0300 Subject: [PATCH 01/55] added POST request --- procollab/urls.py | 1 + rate_projects/admin.py | 29 ++++++++++++++++++++++++++ rate_projects/models.py | 13 ++++++++++++ rate_projects/serializers.py | 21 +++++++++++++++++++ rate_projects/urls.py | 7 +++++++ rate_projects/validators.py | 40 ++++++++++++++++++++++++++++++++++++ rate_projects/views.py | 33 +++++++++++++++++++++++++++++ users/permissions.py | 9 ++++++++ 8 files changed, 153 insertions(+) create mode 100644 rate_projects/admin.py create mode 100644 rate_projects/serializers.py create mode 100644 rate_projects/urls.py create mode 100644 rate_projects/validators.py create mode 100644 rate_projects/views.py diff --git a/procollab/urls.py b/procollab/urls.py index 4596fe57..33031d02 100644 --- a/procollab/urls.py +++ b/procollab/urls.py @@ -47,6 +47,7 @@ path("chats/", include("chats.urls", namespace="chats")), path("events/", include("events.urls", namespace="events")), path("programs/", include("partner_programs.urls", namespace="partner_programs")), + path("rate-project/", include(("rate_projects.urls", "rate_projects"))), path("feed/", include("feed.urls", namespace="feed")), path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), diff --git a/rate_projects/admin.py b/rate_projects/admin.py new file mode 100644 index 00000000..9f7da922 --- /dev/null +++ b/rate_projects/admin.py @@ -0,0 +1,29 @@ +from django.contrib import admin +from .models import Criteria, ProjectScore + + +# Register your models here. +@admin.register(Criteria) +class CriteriaAdmin(admin.ModelAdmin): + list_display = ( + "id", + "name", + "type", + # добавить проект / программу + ) + list_display_links = ( + "id", + "name", + ) + + +@admin.register(ProjectScore) +class ProjectScoreAdmin(admin.ModelAdmin): + list_display = ( + "id", + # добавить проект / программу + ) + list_display_links = ( + "id", + ) + diff --git a/rate_projects/models.py b/rate_projects/models.py index 53e6b32b..a7795a16 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -4,6 +4,7 @@ from partner_programs.models import PartnerProgram from projects.models import Project from .constants import TYPES, get_type +from .validators import ProjectScoreValidate User = get_user_model() @@ -119,6 +120,18 @@ class ProjectScore(models.Model): def __str__(self): return f"ProjectScore<{self.id}> - {self.criteria.name}" + 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, + criteria_min_value=self.criteria.min_value, + criteria_max_value=self.criteria.max_value + ) + super().save(*args, **kwargs) + class Meta: verbose_name = "Оценка проекта" diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py new file mode 100644 index 00000000..5402f676 --- /dev/null +++ b/rate_projects/serializers.py @@ -0,0 +1,21 @@ +from rest_framework import serializers + +from rate_projects.constants import get_type +from rate_projects.models import ProjectScore +from rate_projects.validators import ProjectScoreValidate + + +class ProjectScoreCreateSerializer(serializers.ModelSerializer[ProjectScore]): + class Meta: + model = ProjectScore + fields = [ + "criteria", + "user", + "project", + "value_int", + "value_float", + "value_bool", + "value_str" + ] + + diff --git a/rate_projects/urls.py b/rate_projects/urls.py new file mode 100644 index 00000000..b340b2b3 --- /dev/null +++ b/rate_projects/urls.py @@ -0,0 +1,7 @@ +from django.urls import path + +from rate_projects.views import RateProject + +urlpatterns = [ + path("rate/", RateProject.as_view()) +] \ No newline at end of file diff --git a/rate_projects/validators.py b/rate_projects/validators.py new file mode 100644 index 00000000..3f00d066 --- /dev/null +++ b/rate_projects/validators.py @@ -0,0 +1,40 @@ +from rate_projects.constants import get_type + + +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._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(f'Должно быть заполнено менее 2-ух полей!') + + if not isinstance(filled_fields[0], get_type.get(self.criteria_type)): + raise ValueError(f'Тип введённых данных не совпадает с требуемым!') + + 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: + raise ValueError("Оценка этого критерия принизила допустимые значения!") + 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 new file mode 100644 index 00000000..496a719e --- /dev/null +++ b/rate_projects/views.py @@ -0,0 +1,33 @@ +from django.contrib.auth import get_user_model + +from rest_framework import generics, status +from rest_framework.response import Response + + +from rate_projects.serializers import ProjectScoreCreateSerializer +from users.permissions import IsExpert + +User = get_user_model() + +class RateProject(generics.CreateAPIView): + serializer_class = ProjectScoreCreateSerializer + permission_classes = [IsExpert] + + def create(self, request, *args, **kwargs): + try: + data = self.request.data + 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) + 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) + + + + diff --git a/users/permissions.py b/users/permissions.py index df7eb8f9..ce7e91ae 100644 --- a/users/permissions.py +++ b/users/permissions.py @@ -10,3 +10,12 @@ def has_object_permission(self, request, view, obj): if request.method in SAFE_METHODS or (obj.user == request.user): return True 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 From 73bdd398330b34d3517b693b1b3cc7de75aa84a6 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 7 Feb 2024 18:24:09 +0300 Subject: [PATCH 02/55] 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 03/55] 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 04/55] 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 6c4171c5546694dd6f7fcd200fcadcc35bc9336c Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 9 Feb 2024 17:33:38 +0300 Subject: [PATCH 05/55] linter update --- rate_projects/admin.py | 5 +-- rate_projects/constants.py | 11 ++---- rate_projects/models.py | 68 ++++++++++-------------------------- rate_projects/serializers.py | 7 ++-- rate_projects/urls.py | 4 +-- rate_projects/validators.py | 36 +++++++++++-------- rate_projects/views.py | 11 +++--- 7 files changed, 50 insertions(+), 92 deletions(-) diff --git a/rate_projects/admin.py b/rate_projects/admin.py index 9f7da922..c5ee8df2 100644 --- a/rate_projects/admin.py +++ b/rate_projects/admin.py @@ -23,7 +23,4 @@ class ProjectScoreAdmin(admin.ModelAdmin): "id", # добавить проект / программу ) - list_display_links = ( - "id", - ) - + list_display_links = ("id",) 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 5402f676..83ec7a94 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -1,8 +1,7 @@ from rest_framework import serializers -from rate_projects.constants import get_type + from rate_projects.models import ProjectScore -from rate_projects.validators import ProjectScoreValidate class ProjectScoreCreateSerializer(serializers.ModelSerializer[ProjectScore]): @@ -15,7 +14,5 @@ class Meta: "value_int", "value_float", "value_bool", - "value_str" + "value_str", ] - - diff --git a/rate_projects/urls.py b/rate_projects/urls.py index b340b2b3..5bcb115a 100644 --- a/rate_projects/urls.py +++ b/rate_projects/urls.py @@ -2,6 +2,4 @@ from rate_projects.views import RateProject -urlpatterns = [ - path("rate/", RateProject.as_view()) -] \ No newline at end of file +urlpatterns = [path("rate/", RateProject.as_view())] diff --git a/rate_projects/validators.py b/rate_projects/validators.py index 3f00d066..dc72d9c2 100644 --- a/rate_projects/validators.py +++ b/rate_projects/validators.py @@ -1,15 +1,15 @@ -from rate_projects.constants import get_type +from rate_projects.constants import GET_TYPE_FROM_STRING 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() @@ -17,7 +17,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 @@ -25,16 +25,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 496a719e..23d5805c 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -9,6 +9,7 @@ User = get_user_model() + class RateProject(generics.CreateAPIView): serializer_class = ProjectScoreCreateSerializer permission_classes = [IsExpert] @@ -16,18 +17,14 @@ 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) From d1b9a686a50ed191678290077611fb4d4d15ece5 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 9 Feb 2024 17:35:28 +0300 Subject: [PATCH 06/55] linter fix --- users/permissions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 975bf2c170aa9eba7930ff9ca5b0d0ddece32e0a Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Fri, 9 Feb 2024 17:39:41 +0300 Subject: [PATCH 07/55] 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 751c9a648cdbf101a1c00b22b368b4c5f2d80058 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 10 Feb 2024 05:29:51 +0300 Subject: [PATCH 08/55] added comment, changed namings, fixed models --- ...alter_projectscore_value_float_and_more.py | 34 +++++++++++++++++++ rate_projects/models.py | 13 +++++-- rate_projects/serializers.py | 3 +- 3 files changed, 47 insertions(+), 3 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..834d32b8 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -66,6 +66,11 @@ class ProjectScore(models.Model): 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 + """ criteria = models.ForeignKey( @@ -78,15 +83,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 83ec7a94..495fa002 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -4,7 +4,7 @@ from rate_projects.models import ProjectScore -class ProjectScoreCreateSerializer(serializers.ModelSerializer[ProjectScore]): +class ProjectScoreCreateSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore fields = [ @@ -15,4 +15,5 @@ class Meta: "value_float", "value_bool", "value_str", + "comment", ] From 7e41257a8e87957e08c94de4d9f491cc6d1b0c6d Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 10 Feb 2024 05:49:04 +0300 Subject: [PATCH 09/55] 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 10/55] 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 11/55] 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 d8fac388204ceaa0d667729ace76130ad4c3d317 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 10 Feb 2024 14:10:16 +0300 Subject: [PATCH 12/55] changed model 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 | 28 ++--------- rate_projects/serializers.py | 5 +- rate_projects/validators.py | 50 +++++++------------ 6 files changed, 32 insertions(+), 123 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 834d32b8..3bc77742 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -61,14 +61,8 @@ 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 + value: CharField for value + commentary: CharField for optional commentary """ @@ -79,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( @@ -102,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 495fa002..70745d64 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -11,9 +11,6 @@ class Meta: "criteria", "user", "project", - "value_int", - "value_float", - "value_bool", - "value_str", + "value", "comment", ] diff --git a/rate_projects/validators.py b/rate_projects/validators.py index dc72d9c2..1dfc0a65 100644 --- a/rate_projects/validators.py +++ b/rate_projects/validators.py @@ -1,46 +1,30 @@ -from rate_projects.constants import GET_TYPE_FROM_STRING - - 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 ValueError: + raise ValueError("Введённое значение не соответствует формату!") - 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 33b5f73a6b62682993020fe2651bd6feb961410c Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 12 Feb 2024 10:18:31 +0300 Subject: [PATCH 13/55] added permissions for selected experts for selected programs --- users/admin.py | 5 +++++ users/migrations/0045_expert_programs.py | 22 ++++++++++++++++++++++ users/models.py | 4 ++++ users/permissions.py | 12 +++++++++++- 4 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 users/migrations/0045_expert_programs.py diff --git a/users/admin.py b/users/admin.py index 6c8e4bc6..745d36c0 100644 --- a/users/admin.py +++ b/users/admin.py @@ -198,3 +198,8 @@ class UserAchievementAdmin(admin.ModelAdmin): class UserLinkAdmin(admin.ModelAdmin): list_display = ("id", "user", "link") list_display_links = ("id", "user", "link") + + +@admin.register(Expert) +class ExpertAdmin(admin.ModelAdmin): + list_display = ("id", "user") diff --git a/users/migrations/0045_expert_programs.py b/users/migrations/0045_expert_programs.py new file mode 100644 index 00000000..5e12a87b --- /dev/null +++ b/users/migrations/0045_expert_programs.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.3 on 2024-02-12 06:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("partner_programs", "0004_auto_20231230_0002"), + ("users", "0044_auto_20240128_2236"), + ] + + operations = [ + migrations.AddField( + model_name="expert", + name="programs", + field=models.ManyToManyField( + related_name="experts", to="partner_programs.partnerprogram" + ), + ), + ] + diff --git a/users/models.py b/users/models.py index a86669d0..e054e81e 100644 --- a/users/models.py +++ b/users/models.py @@ -327,6 +327,10 @@ class Expert(AbstractUserWithRole): preferred_industries = models.CharField(max_length=4096, null=True, blank=True) useful_to_project = models.TextField(blank=True) + programs = models.ManyToManyField( + "partner_programs.PartnerProgram", related_name="experts", blank=True + ) + class Meta(TypedModelMeta): verbose_name = "Эксперт" verbose_name_plural = "Эксперты" diff --git a/users/permissions.py b/users/permissions.py index ef53b4de..e8f6ade3 100644 --- a/users/permissions.py +++ b/users/permissions.py @@ -1,5 +1,8 @@ +from rest_framework.exceptions import PermissionDenied from rest_framework.permissions import BasePermission, SAFE_METHODS +from users.models import Expert + class IsAchievementOwnerOrReadOnly(BasePermission): """ @@ -18,4 +21,11 @@ class IsExpert(BasePermission): """ def has_permission(self, request, view): - return request.user.user_type == 3 + user = request.user + program_id = view.kwargs.get("program_id") + + if not user.user_type == 3: + raise PermissionDenied("User is not an expert") + if not Expert.objects.filter(programs__id=program_id, user=user).exists(): + raise PermissionDenied("You don't have permission to rate this program") + return True From cbd156722a430189be25baa612729b894aa92bb7 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 12 Feb 2024 10:32:36 +0300 Subject: [PATCH 14/55] 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 d7a3bfe1a27209caa9ea8641e649b52d9727c853 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 17 Feb 2024 16:41:11 +0300 Subject: [PATCH 15/55] pagination added, serializers changed --- feed/constants.py | 14 ++++++++--- feed/helpers.py | 60 +++++++++++++++++++++++++-------------------- feed/serializers.py | 2 +- feed/views.py | 47 ++++++++++++++++++++++++++++++----- 4 files changed, 87 insertions(+), 36 deletions(-) diff --git a/feed/constants.py b/feed/constants.py index 6f2bc559..f6c3292a 100644 --- a/feed/constants.py +++ b/feed/constants.py @@ -1,9 +1,14 @@ import enum +from typing import Union, Dict +from django.db.models import QuerySet from rest_framework import serializers -from news.serializers import NewsListSerializer +from news.models import News +from news.serializers import NewsFeedListSerializer +from projects.models import Project from projects.serializers import ProjectListSerializer +from vacancy.models import Vacancy from vacancy.serializers import VacancyDetailSerializer @@ -13,8 +18,11 @@ class FeedItemType(enum.Enum): VACANCY = "Vacancy" -FEED_SERIALIZER_MAPPING: dict[FeedItemType, serializers.Serializer] = { +FEED_SERIALIZER_MAPPING: Dict[FeedItemType, serializers.Serializer] = { FeedItemType.PROJECT.value: ProjectListSerializer, - FeedItemType.NEWS.value: NewsListSerializer, + FeedItemType.NEWS.value: NewsFeedListSerializer, FeedItemType.VACANCY.value: VacancyDetailSerializer, } + +SupportedModel = Union[News | Project | Vacancy] +SupportedQuerySet = QuerySet[News | Project | Vacancy] diff --git a/feed/helpers.py b/feed/helpers.py index 1f65a8cd..ea767e4b 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -1,30 +1,46 @@ -import random -import typing +from typing import List, Iterable, Tuple, Dict from feed import constants +from feed.constants import SupportedModel, SupportedQuerySet from feed.serializers import FeedItemSerializer +from news.models import News from projects.models import Project +from django.core.paginator import Paginator +from django.db.models import Count -def collect_feed(models_list: typing.List, num) -> list[dict]: - get_model_data = { - model.__name__: collect_querysets(model, num) for model in models_list - } - result = [] - for model in get_model_data: - result.extend(to_feed_items(model, get_model_data[model])) - random.shuffle(result) - return result +from vacancy.models import Vacancy -def collect_querysets(model, num): - if model.__name__ == Project.__class__.__name__: - return set(get_n_random_projects(num) + get_n_latest_created_projects(num)) - else: - return list(model.objects.order_by("-datetime_created")[:num]) +def add_pagination(queryset: List[Dict], count: int) -> Dict: + return {"count": count, "results": queryset, "next": "0", "previous": "0"} -def to_feed_items(type_: constants.FeedItemType, items: typing.Iterable) -> list[dict]: +def paginate_model_items( + queryset: SupportedQuerySet, page_num: int +) -> Tuple[List[SupportedQuerySet], int]: + paginator = Paginator(queryset, 3) + page_obj = paginator.get_page(page_num) + total_pages = paginator.num_pages + return page_obj.object_list, total_pages + + +def collect_querysets(model: SupportedModel) -> SupportedQuerySet: + if model == Project: + queryset = model.objects.select_related("leader", "industry").filter(draft=False) + elif model == Vacancy: + queryset = model.objects.select_related("project") + elif model == News: + queryset = ( + model.objects.select_related("content_type") + .prefetch_related("content_object", "files") + .annotate(likes_count=Count("likes"), views_count=Count("views")) + ) + + return queryset.order_by("-datetime_created") + + +def to_feed_items(type_: constants.FeedItemType, items: Iterable) -> List[Dict]: feed_items = [] for item in items: serializer = to_feed_item(type_, item) @@ -33,14 +49,6 @@ def to_feed_items(type_: constants.FeedItemType, items: typing.Iterable) -> list return feed_items -def get_n_random_projects(num: int) -> list[Project]: - return list(Project.objects.filter(draft=False).order_by("?").distinct()[:num]) - - -def get_n_latest_created_projects(num: int) -> list[Project]: - return list(Project.objects.filter(draft=False).order_by("-datetime_created")[:num]) - - def to_feed_item(type_: constants.FeedItemType, data): serializer = constants.FEED_SERIALIZER_MAPPING[type_](data) - return FeedItemSerializer(data={"type": type_, "content": serializer.data}) + return FeedItemSerializer(data={"type_model": type_, "content": serializer.data}) diff --git a/feed/serializers.py b/feed/serializers.py index fd0966ae..52ff4d34 100644 --- a/feed/serializers.py +++ b/feed/serializers.py @@ -3,5 +3,5 @@ class FeedItemSerializer(serializers.Serializer): - type = serializers.ChoiceField(choices=constants.FeedItemType, required=True) + type_model = serializers.ChoiceField(choices=constants.FeedItemType, required=True) content = serializers.JSONField(required=True) diff --git a/feed/views.py b/feed/views.py index d798c5c2..60df5fce 100644 --- a/feed/views.py +++ b/feed/views.py @@ -1,3 +1,7 @@ +from typing import List, Tuple, Dict + +import random + from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework import status @@ -5,7 +9,13 @@ from rest_framework.response import Response from rest_framework.views import APIView -from feed.helpers import collect_feed +from feed.constants import SupportedModel +from feed.helpers import ( + add_pagination, + collect_querysets, + paginate_model_items, + to_feed_items, +) from news.models import News from projects.models import Project from vacancy.models import Vacancy @@ -31,13 +41,38 @@ class FeedList(APIView): } ) def get(self, request: Request, *args, **kwargs) -> Response: + models_to_get, page_number = self.get_request_data() + queryset_ready, total_pages = self.get_queryset(models_to_get, page_number) + return Response( + status=status.HTTP_200_OK, data=add_pagination(queryset_ready, total_pages) + ) + + def get_request_data(self) -> Tuple[List[SupportedModel], int]: models = [] - filter = request.query_params.get("type") - if "news" in filter: + page_number = int(self.request.query_params.get("page_number")) + + if not (filter_queries := self.request.query_params.get("type")): + return [], 0 + if "news" in filter_queries: models.append(News) - if "project" in filter: + if "project" in filter_queries: models.append(Project) - if "vacancy" in filter: + if "vacancy" in filter_queries: models.append(Vacancy) - return Response(status=status.HTTP_200_OK, data=collect_feed(models, 3)) + return models, page_number + + def get_queryset( + self, models: List[SupportedModel], page_number: int + ) -> Tuple[List[Dict], int]: + get_model_data = {model.__name__: collect_querysets(model) for model in models} + result = [] + sum_num_pages = 0 + for model in get_model_data: + get_model_data[model], num_pages = paginate_model_items( + get_model_data[model], page_number + ) + sum_num_pages += num_pages + result.extend(to_feed_items(model, get_model_data[model])) + random.shuffle(result) + return result, sum_num_pages From 7670a276f6845d2aa5f0fe3e4bf2816172b2db27 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 17 Feb 2024 16:47:14 +0300 Subject: [PATCH 16/55] added serializer that forgor to add previously --- news/serializers.py | 52 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/news/serializers.py b/news/serializers.py index 49776620..dbbe729b 100644 --- a/news/serializers.py +++ b/news/serializers.py @@ -1,10 +1,15 @@ from django.contrib.auth import get_user_model +from django.forms import model_to_dict from rest_framework import serializers from core.services import is_fan, get_likes_count, get_views_count from files.serializers import UserFileSerializer from news.mapping import NewsMapping from news.models import News +from projects.models import Project +from projects.serializers import ProjectListSerializer +from users.models import CustomUser +from users.serializers import UserFeedSerializer User = get_user_model() @@ -59,6 +64,53 @@ class Meta: ] +class NewsFeedListSerializer(serializers.ModelSerializer): + name = serializers.SerializerMethodField() + image_address = serializers.SerializerMethodField() + is_user_liked = serializers.SerializerMethodField() + files = UserFileSerializer(many=True) + views_count = serializers.IntegerField(default=0) + likes_count = serializers.IntegerField(default=0) + content_object = serializers.SerializerMethodField() + + def get_content_object(self, obj): + if obj.content_type.model == Project.__name__.lower(): + serialized_obj = ProjectListSerializer(instance=obj.content_object, data={}) + elif obj.content_type.model == CustomUser.__name__.lower(): + serialized_obj = UserFeedSerializer(instance=obj.content_object, data=model_to_dict(obj.content_object)) + + serialized_obj.is_valid() + return serialized_obj.data + + def get_name(self, obj): + return NewsMapping.get_name(obj.content_object) + + def get_image_address(self, obj): + return NewsMapping.get_image_address(obj.content_object) + + def get_is_user_liked(self, obj): + user = self.context.get("user") + if user: + return is_fan(obj, user) + return False + + class Meta: + model = News + fields = [ + "id", + "name", + "image_address", + "text", + "datetime_created", + "views_count", + "likes_count", + "files", + "is_user_liked", + "content_object" + ] + read_only_fields = ["views_count", "likes_count"] + + class NewsDetailSerializer(serializers.ModelSerializer): views_count = serializers.SerializerMethodField() likes_count = serializers.SerializerMethodField() From bd43735d4b931e5dedc0a20969b715c9a1dab771 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 17 Feb 2024 16:49:19 +0300 Subject: [PATCH 17/55] added serializer that forgor to add previously x2 --- users/serializers.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/users/serializers.py b/users/serializers.py index ae5a9b94..32df068f 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -354,6 +354,21 @@ class Meta: } +class UserFeedSerializer(serializers.ModelSerializer): + class Meta: + model = CustomUser + fields = [ + "id", + "email", + "user_type", + "first_name", + "last_name", + "patronymic", + "key_skills", + "speciality" + ] + + class AchievementDetailSerializer(serializers.ModelSerializer[UserAchievement]): class Meta: model = UserAchievement From 7d60f02f220b8a902ef7c367cff8e2264006a9a6 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 17 Feb 2024 17:45:06 +0300 Subject: [PATCH 18/55] typhints corrected --- feed/constants.py | 5 ++--- feed/helpers.py | 8 ++++---- feed/views.py | 8 +++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/feed/constants.py b/feed/constants.py index f6c3292a..9f174730 100644 --- a/feed/constants.py +++ b/feed/constants.py @@ -1,5 +1,4 @@ import enum -from typing import Union, Dict from django.db.models import QuerySet from rest_framework import serializers @@ -18,11 +17,11 @@ class FeedItemType(enum.Enum): VACANCY = "Vacancy" -FEED_SERIALIZER_MAPPING: Dict[FeedItemType, serializers.Serializer] = { +FEED_SERIALIZER_MAPPING: dict[FeedItemType, serializers.Serializer] = { FeedItemType.PROJECT.value: ProjectListSerializer, FeedItemType.NEWS.value: NewsFeedListSerializer, FeedItemType.VACANCY.value: VacancyDetailSerializer, } -SupportedModel = Union[News | Project | Vacancy] +SupportedModel = News | Project | Vacancy SupportedQuerySet = QuerySet[News | Project | Vacancy] diff --git a/feed/helpers.py b/feed/helpers.py index ea767e4b..43594118 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -1,4 +1,4 @@ -from typing import List, Iterable, Tuple, Dict +from typing import Iterable from feed import constants from feed.constants import SupportedModel, SupportedQuerySet @@ -12,13 +12,13 @@ from vacancy.models import Vacancy -def add_pagination(queryset: List[Dict], count: int) -> Dict: +def add_pagination(queryset: list[dict], count: int) -> dict: return {"count": count, "results": queryset, "next": "0", "previous": "0"} def paginate_model_items( queryset: SupportedQuerySet, page_num: int -) -> Tuple[List[SupportedQuerySet], int]: +) -> tuple[list[SupportedQuerySet], int]: paginator = Paginator(queryset, 3) page_obj = paginator.get_page(page_num) total_pages = paginator.num_pages @@ -40,7 +40,7 @@ def collect_querysets(model: SupportedModel) -> SupportedQuerySet: return queryset.order_by("-datetime_created") -def to_feed_items(type_: constants.FeedItemType, items: Iterable) -> List[Dict]: +def to_feed_items(type_: constants.FeedItemType, items: Iterable) -> list[dict]: feed_items = [] for item in items: serializer = to_feed_item(type_, item) diff --git a/feed/views.py b/feed/views.py index 60df5fce..f6146856 100644 --- a/feed/views.py +++ b/feed/views.py @@ -1,5 +1,3 @@ -from typing import List, Tuple, Dict - import random from drf_yasg import openapi @@ -47,7 +45,7 @@ def get(self, request: Request, *args, **kwargs) -> Response: status=status.HTTP_200_OK, data=add_pagination(queryset_ready, total_pages) ) - def get_request_data(self) -> Tuple[List[SupportedModel], int]: + def get_request_data(self) -> tuple[list[SupportedModel], int]: models = [] page_number = int(self.request.query_params.get("page_number")) @@ -63,8 +61,8 @@ def get_request_data(self) -> Tuple[List[SupportedModel], int]: return models, page_number def get_queryset( - self, models: List[SupportedModel], page_number: int - ) -> Tuple[List[Dict], int]: + self, models: list[SupportedModel], page_number: int + ) -> tuple[list[dict], int]: get_model_data = {model.__name__: collect_querysets(model) for model in models} result = [] sum_num_pages = 0 From 3be2338fbbd4022b0da9ee756d531794031e009b Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Sat, 17 Feb 2024 18:32:45 +0300 Subject: [PATCH 19/55] bug fixed --- news/serializers.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/news/serializers.py b/news/serializers.py index dbbe729b..2ff2d061 100644 --- a/news/serializers.py +++ b/news/serializers.py @@ -76,11 +76,13 @@ class NewsFeedListSerializer(serializers.ModelSerializer): def get_content_object(self, obj): if obj.content_type.model == Project.__name__.lower(): serialized_obj = ProjectListSerializer(instance=obj.content_object, data={}) + serialized_obj.is_valid() + return serialized_obj.data elif obj.content_type.model == CustomUser.__name__.lower(): serialized_obj = UserFeedSerializer(instance=obj.content_object, data=model_to_dict(obj.content_object)) + serialized_obj.is_valid() + return serialized_obj.data - serialized_obj.is_valid() - return serialized_obj.data def get_name(self, obj): return NewsMapping.get_name(obj.content_object) From 9dc37f27bfdba518fb8513ecd8b0ba5215f7a5fa Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 13:56:13 +0300 Subject: [PATCH 20/55] new_fixed --- feed/constants.py | 2 ++ feed/helpers.py | 55 ++++++++++++++++++++++++++++++++++------- feed/pagination.py | 18 ++++++++++++++ feed/serializers.py | 6 +++++ feed/views.py | 60 ++++++++++++++++++--------------------------- 5 files changed, 96 insertions(+), 45 deletions(-) create mode 100644 feed/pagination.py diff --git a/feed/constants.py b/feed/constants.py index 9f174730..7dc74ab8 100644 --- a/feed/constants.py +++ b/feed/constants.py @@ -25,3 +25,5 @@ class FeedItemType(enum.Enum): SupportedModel = News | Project | Vacancy SupportedQuerySet = QuerySet[News | Project | Vacancy] + +model_mapping = {"news": News, "project": Project, "vacancy": Vacancy} \ No newline at end of file diff --git a/feed/helpers.py b/feed/helpers.py index 43594118..8da74047 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -1,5 +1,8 @@ +import random from typing import Iterable +from rest_framework.pagination import LimitOffsetPagination + from feed import constants from feed.constants import SupportedModel, SupportedQuerySet from feed.serializers import FeedItemSerializer @@ -12,17 +15,51 @@ from vacancy.models import Vacancy -def add_pagination(queryset: list[dict], count: int) -> dict: - return {"count": count, "results": queryset, "next": "0", "previous": "0"} - +def add_pagination(results: list[SupportedQuerySet], count: int) -> dict: + return { + "count": count, + "previous": None, + "next": None, + "results": results + } -def paginate_model_items( - queryset: SupportedQuerySet, page_num: int +def paginate_feed( + model_data: dict[SupportedQuerySet], + paginator: LimitOffsetPagination, + request, + view ) -> tuple[list[SupportedQuerySet], int]: - paginator = Paginator(queryset, 3) - page_obj = paginator.get_page(page_num) - total_pages = paginator.num_pages - return page_obj.object_list, total_pages + result = [] + sum_num_pages = 0 + for model in model_data: + sum_num_pages += paginate_feed_queryset(model_data, paginator, request, model, view, sum_num_pages, result) + random.shuffle(result) + limit = request.query_params.get('limit') + return result[:int(limit)], sum_num_pages + +def paginate_feed_queryset( + model_data: dict[SupportedQuerySet], + paginator: LimitOffsetPagination, + request, + model, + view, + sum_num_pages: int, + result: list[SupportedQuerySet] +) -> int: + num_pages = paginator.get_count(model_data[model]) + paginated_data = paginator.paginate_queryset(model_data[model], request, view=view) + result.extend(to_feed_items(model, paginated_data)) + return num_pages + +# def paginate_model_items( +# queryset: SupportedQuerySet, page_number: int +# ) -> tuple[list[SupportedQuerySet], int]: +# paginator = Paginator(queryset, 3) +# +# +# page_obj = paginator.get_page(page_number) +# total_pages = paginator.num_pages +# return page_obj.object_list, total_pages def collect_querysets(model: SupportedModel) -> SupportedQuerySet: diff --git a/feed/pagination.py b/feed/pagination.py new file mode 100644 index 00000000..1e77e365 --- /dev/null +++ b/feed/pagination.py @@ -0,0 +1,18 @@ +from rest_framework import pagination + + +class FeedPagination(pagination.LimitOffsetPagination): + """ + Pagination for Users + + For example: + /projects/?limit=10&offset=10 + gets the next 10 news after the first 10 news. + """ + + default_limit = 10 + limit_query_param = "limit" + offset_query_param = "offset" + + + diff --git a/feed/serializers.py b/feed/serializers.py index 52ff4d34..238f3db2 100644 --- a/feed/serializers.py +++ b/feed/serializers.py @@ -5,3 +5,9 @@ class FeedItemSerializer(serializers.Serializer): type_model = serializers.ChoiceField(choices=constants.FeedItemType, required=True) content = serializers.JSONField(required=True) + + +class PagTestSerializer(serializers.Serializer): + def to_representation(self, instance): + return constants.FEED_SERIALIZER_MAPPING[instance.__class__.__name__](instance=instance) + diff --git a/feed/views.py b/feed/views.py index f6146856..5eae1658 100644 --- a/feed/views.py +++ b/feed/views.py @@ -1,5 +1,6 @@ import random +from django.db.models import QuerySet from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework import status @@ -7,19 +8,18 @@ from rest_framework.response import Response from rest_framework.views import APIView -from feed.constants import SupportedModel +from feed.constants import SupportedModel, model_mapping, SupportedQuerySet, FeedItemType from feed.helpers import ( - add_pagination, collect_querysets, - paginate_model_items, - to_feed_items, + paginate_feed, + add_pagination ) -from news.models import News -from projects.models import Project -from vacancy.models import Vacancy +from feed.pagination import FeedPagination class FeedList(APIView): + pagination_class = FeedPagination + @swagger_auto_schema( responses={ 200: openapi.Response( @@ -39,38 +39,26 @@ class FeedList(APIView): } ) def get(self, request: Request, *args, **kwargs) -> Response: - models_to_get, page_number = self.get_request_data() - queryset_ready, total_pages = self.get_queryset(models_to_get, page_number) + models_to_get: list[SupportedModel] = self.get_request_data() + full_queryset_data: dict[FeedItemType, SupportedQuerySet] = self.get_response_data(models_to_get) + paginated_data, sum_pages = self.paginate_data(full_queryset_data) + return Response( - status=status.HTTP_200_OK, data=add_pagination(queryset_ready, total_pages) + status=status.HTTP_200_OK, data=add_pagination(paginated_data, sum_pages) ) - def get_request_data(self) -> tuple[list[SupportedModel], int]: - models = [] - page_number = int(self.request.query_params.get("page_number")) + def get_request_data(self) -> list[SupportedModel]: + filter_queries = self.request.query_params.get("type") + filter_queries = filter_queries if filter_queries else '' # existence check - if not (filter_queries := self.request.query_params.get("type")): - return [], 0 - if "news" in filter_queries: - models.append(News) - if "project" in filter_queries: - models.append(Project) - if "vacancy" in filter_queries: - models.append(Vacancy) + models = [model_mapping[model_name] for model_name in model_mapping.keys() if model_name in filter_queries] + return models - return models, page_number + def get_response_data( + self, models: list[SupportedModel] + ) -> dict[FeedItemType, SupportedQuerySet]: + return {model.__name__: collect_querysets(model) for model in models} - def get_queryset( - self, models: list[SupportedModel], page_number: int - ) -> tuple[list[dict], int]: - get_model_data = {model.__name__: collect_querysets(model) for model in models} - result = [] - sum_num_pages = 0 - for model in get_model_data: - get_model_data[model], num_pages = paginate_model_items( - get_model_data[model], page_number - ) - sum_num_pages += num_pages - result.extend(to_feed_items(model, get_model_data[model])) - random.shuffle(result) - return result, sum_num_pages + def paginate_data(self, get_model_data: dict[FeedItemType, SupportedQuerySet]) -> tuple[list[dict], int]: + paginator = self.pagination_class() + return paginate_feed(get_model_data, paginator, self.request, self) From 5f612b210013b0be06e556b4d823e7433b766cf3 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 13:59:05 +0300 Subject: [PATCH 21/55] linter fix --- feed/constants.py | 2 +- feed/helpers.py | 26 +++++++++++--------------- feed/pagination.py | 3 --- feed/serializers.py | 5 +++-- feed/views.py | 27 ++++++++++++++------------- 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/feed/constants.py b/feed/constants.py index 7dc74ab8..689a6fe4 100644 --- a/feed/constants.py +++ b/feed/constants.py @@ -26,4 +26,4 @@ class FeedItemType(enum.Enum): SupportedModel = News | Project | Vacancy SupportedQuerySet = QuerySet[News | Project | Vacancy] -model_mapping = {"news": News, "project": Project, "vacancy": Vacancy} \ No newline at end of file +model_mapping = {"news": News, "project": Project, "vacancy": Vacancy} diff --git a/feed/helpers.py b/feed/helpers.py index 8da74047..984534d6 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -9,33 +9,28 @@ from news.models import News from projects.models import Project -from django.core.paginator import Paginator from django.db.models import Count from vacancy.models import Vacancy def add_pagination(results: list[SupportedQuerySet], count: int) -> dict: - return { - "count": count, - "previous": None, - "next": None, - "results": results - } + return {"count": count, "previous": None, "next": None, "results": results} + def paginate_feed( - model_data: dict[SupportedQuerySet], - paginator: LimitOffsetPagination, - request, - view + model_data: dict[SupportedQuerySet], paginator: LimitOffsetPagination, request, view ) -> tuple[list[SupportedQuerySet], int]: result = [] sum_num_pages = 0 for model in model_data: - sum_num_pages += paginate_feed_queryset(model_data, paginator, request, model, view, sum_num_pages, result) + sum_num_pages += paginate_feed_queryset( + model_data, paginator, request, model, view, sum_num_pages, result + ) random.shuffle(result) - limit = request.query_params.get('limit') - return result[:int(limit)], sum_num_pages + limit = request.query_params.get("limit") + return result[: int(limit)], sum_num_pages + def paginate_feed_queryset( model_data: dict[SupportedQuerySet], @@ -44,13 +39,14 @@ def paginate_feed_queryset( model, view, sum_num_pages: int, - result: list[SupportedQuerySet] + result: list[SupportedQuerySet], ) -> int: num_pages = paginator.get_count(model_data[model]) paginated_data = paginator.paginate_queryset(model_data[model], request, view=view) result.extend(to_feed_items(model, paginated_data)) return num_pages + # def paginate_model_items( # queryset: SupportedQuerySet, page_number: int # ) -> tuple[list[SupportedQuerySet], int]: diff --git a/feed/pagination.py b/feed/pagination.py index 1e77e365..98ffa6af 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -13,6 +13,3 @@ class FeedPagination(pagination.LimitOffsetPagination): default_limit = 10 limit_query_param = "limit" offset_query_param = "offset" - - - diff --git a/feed/serializers.py b/feed/serializers.py index 238f3db2..f44da568 100644 --- a/feed/serializers.py +++ b/feed/serializers.py @@ -9,5 +9,6 @@ class FeedItemSerializer(serializers.Serializer): class PagTestSerializer(serializers.Serializer): def to_representation(self, instance): - return constants.FEED_SERIALIZER_MAPPING[instance.__class__.__name__](instance=instance) - + return constants.FEED_SERIALIZER_MAPPING[instance.__class__.__name__]( + instance=instance + ) diff --git a/feed/views.py b/feed/views.py index 5eae1658..00f33137 100644 --- a/feed/views.py +++ b/feed/views.py @@ -1,6 +1,3 @@ -import random - -from django.db.models import QuerySet from drf_yasg import openapi from drf_yasg.utils import swagger_auto_schema from rest_framework import status @@ -9,11 +6,7 @@ from rest_framework.views import APIView from feed.constants import SupportedModel, model_mapping, SupportedQuerySet, FeedItemType -from feed.helpers import ( - collect_querysets, - paginate_feed, - add_pagination -) +from feed.helpers import collect_querysets, paginate_feed, add_pagination from feed.pagination import FeedPagination @@ -40,7 +33,9 @@ class FeedList(APIView): ) def get(self, request: Request, *args, **kwargs) -> Response: models_to_get: list[SupportedModel] = self.get_request_data() - full_queryset_data: dict[FeedItemType, SupportedQuerySet] = self.get_response_data(models_to_get) + full_queryset_data: dict[ + FeedItemType, SupportedQuerySet + ] = self.get_response_data(models_to_get) paginated_data, sum_pages = self.paginate_data(full_queryset_data) return Response( @@ -49,16 +44,22 @@ def get(self, request: Request, *args, **kwargs) -> Response: def get_request_data(self) -> list[SupportedModel]: filter_queries = self.request.query_params.get("type") - filter_queries = filter_queries if filter_queries else '' # existence check + filter_queries = filter_queries if filter_queries else "" # existence check - models = [model_mapping[model_name] for model_name in model_mapping.keys() if model_name in filter_queries] + models = [ + model_mapping[model_name] + for model_name in model_mapping.keys() + if model_name in filter_queries + ] return models def get_response_data( - self, models: list[SupportedModel] + self, models: list[SupportedModel] ) -> dict[FeedItemType, SupportedQuerySet]: return {model.__name__: collect_querysets(model) for model in models} - def paginate_data(self, get_model_data: dict[FeedItemType, SupportedQuerySet]) -> tuple[list[dict], int]: + def paginate_data( + self, get_model_data: dict[FeedItemType, SupportedQuerySet] + ) -> tuple[list[dict], int]: paginator = self.pagination_class() return paginate_feed(get_model_data, paginator, self.request, self) From 03ac396ad6e5b0f6394953b4742f71114822cf5d Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 14:03:12 +0300 Subject: [PATCH 22/55] minor fixes --- feed/helpers.py | 11 ----------- feed/pagination.py | 8 -------- 2 files changed, 19 deletions(-) diff --git a/feed/helpers.py b/feed/helpers.py index 984534d6..7cebab34 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -47,17 +47,6 @@ def paginate_feed_queryset( return num_pages -# def paginate_model_items( -# queryset: SupportedQuerySet, page_number: int -# ) -> tuple[list[SupportedQuerySet], int]: -# paginator = Paginator(queryset, 3) -# -# -# page_obj = paginator.get_page(page_number) -# total_pages = paginator.num_pages -# return page_obj.object_list, total_pages - - def collect_querysets(model: SupportedModel) -> SupportedQuerySet: if model == Project: queryset = model.objects.select_related("leader", "industry").filter(draft=False) diff --git a/feed/pagination.py b/feed/pagination.py index 98ffa6af..807c3fc9 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -2,14 +2,6 @@ class FeedPagination(pagination.LimitOffsetPagination): - """ - Pagination for Users - - For example: - /projects/?limit=10&offset=10 - gets the next 10 news after the first 10 news. - """ - default_limit = 10 limit_query_param = "limit" offset_query_param = "offset" From a69347354d50faece1466624c54d0c23134597e0 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 18:09:59 +0300 Subject: [PATCH 23/55] strange old bugs from prod fixed --- news/serializers.py | 7 ++++--- partner_programs/admin.py | 9 +-------- vacancy/serializers.py | 10 ++-------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/news/serializers.py b/news/serializers.py index 2ff2d061..677c9bf3 100644 --- a/news/serializers.py +++ b/news/serializers.py @@ -79,11 +79,12 @@ def get_content_object(self, obj): serialized_obj.is_valid() return serialized_obj.data elif obj.content_type.model == CustomUser.__name__.lower(): - serialized_obj = UserFeedSerializer(instance=obj.content_object, data=model_to_dict(obj.content_object)) + serialized_obj = UserFeedSerializer( + instance=obj.content_object, data=model_to_dict(obj.content_object) + ) serialized_obj.is_valid() return serialized_obj.data - def get_name(self, obj): return NewsMapping.get_name(obj.content_object) @@ -108,7 +109,7 @@ class Meta: "likes_count", "files", "is_user_liked", - "content_object" + "content_object", ] read_only_fields = ["views_count", "likes_count"] diff --git a/partner_programs/admin.py b/partner_programs/admin.py index 2bc505d5..149f62e5 100644 --- a/partner_programs/admin.py +++ b/partner_programs/admin.py @@ -75,16 +75,9 @@ def get_export_file_view(self, request, object_id): def get_export_file(self, partner_program: PartnerProgram): json_schema = partner_program.data_schema - from datetime import datetime - # Определим даты 1 ноября и 12 февраля - start_date = datetime(2023, 11, 1) - end_date = datetime(2024, 2, 12) - - # Произведем фильтрацию profiles = PartnerProgramUserProfile.objects.filter( - partner_program=partner_program, - datetime_created__range=(start_date, end_date) + partner_program=partner_program ) to_delete_from_json_scheme = [] column_names = ["Имя", "Фамилия", "Отчество", "Почта", "Дата рождения"] diff --git a/vacancy/serializers.py b/vacancy/serializers.py index c7fc8bc6..5aae3ca4 100644 --- a/vacancy/serializers.py +++ b/vacancy/serializers.py @@ -1,21 +1,15 @@ - from django.contrib.auth import get_user_model from rest_framework import serializers from projects.models import Project -from users.serializers import UserDetailSerializer +from users.serializers import UserDetailSerializer, CustomListField from vacancy.models import Vacancy, VacancyResponse User = get_user_model() class RequiredSkillsSerializerMixin(serializers.Serializer): - required_skills = serializers.SerializerMethodField() - - def get_required_skills(self, obj): - skills_string = obj.required_skills - skills = [skill.strip() for skill in skills_string.split(",") if skill.strip()] - return skills + required_skills = CustomListField(child=serializers.CharField()) class ProjectForVacancySerializer(serializers.ModelSerializer): From 3b4fa1785ec8b5a58a005c8c1a22aa7db4bb2279 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 18:39:24 +0300 Subject: [PATCH 24/55] naming corrected --- feed/helpers.py | 7 +++---- feed/views.py | 18 ++++++++---------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/feed/helpers.py b/feed/helpers.py index 7cebab34..76337387 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -18,13 +18,13 @@ def add_pagination(results: list[SupportedQuerySet], count: int) -> dict: return {"count": count, "previous": None, "next": None, "results": results} -def paginate_feed( +def paginate_serialize_feed( model_data: dict[SupportedQuerySet], paginator: LimitOffsetPagination, request, view ) -> tuple[list[SupportedQuerySet], int]: result = [] sum_num_pages = 0 for model in model_data: - sum_num_pages += paginate_feed_queryset( + sum_num_pages += paginate_serialize_feed_queryset( model_data, paginator, request, model, view, sum_num_pages, result ) random.shuffle(result) @@ -32,13 +32,12 @@ def paginate_feed( return result[: int(limit)], sum_num_pages -def paginate_feed_queryset( +def paginate_serialize_feed_queryset( model_data: dict[SupportedQuerySet], paginator: LimitOffsetPagination, request, model, view, - sum_num_pages: int, result: list[SupportedQuerySet], ) -> int: num_pages = paginator.get_count(model_data[model]) diff --git a/feed/views.py b/feed/views.py index 00f33137..cfa8a94f 100644 --- a/feed/views.py +++ b/feed/views.py @@ -6,7 +6,7 @@ from rest_framework.views import APIView from feed.constants import SupportedModel, model_mapping, SupportedQuerySet, FeedItemType -from feed.helpers import collect_querysets, paginate_feed, add_pagination +from feed.helpers import collect_querysets, paginate_serialize_feed, add_pagination from feed.pagination import FeedPagination @@ -32,14 +32,12 @@ class FeedList(APIView): } ) def get(self, request: Request, *args, **kwargs) -> Response: - models_to_get: list[SupportedModel] = self.get_request_data() - full_queryset_data: dict[ - FeedItemType, SupportedQuerySet - ] = self.get_response_data(models_to_get) - paginated_data, sum_pages = self.paginate_data(full_queryset_data) - + serialized_paginated_data, sum_pages = self.paginate_serialize_data( + self.get_response_data(self.get_request_data()) + ) return Response( - status=status.HTTP_200_OK, data=add_pagination(paginated_data, sum_pages) + status=status.HTTP_200_OK, + data=add_pagination(serialized_paginated_data, sum_pages), ) def get_request_data(self) -> list[SupportedModel]: @@ -58,8 +56,8 @@ def get_response_data( ) -> dict[FeedItemType, SupportedQuerySet]: return {model.__name__: collect_querysets(model) for model in models} - def paginate_data( + def paginate_serialize_data( self, get_model_data: dict[FeedItemType, SupportedQuerySet] ) -> tuple[list[dict], int]: paginator = self.pagination_class() - return paginate_feed(get_model_data, paginator, self.request, self) + return paginate_serialize_feed(get_model_data, paginator, self.request, self) From 1f3ce2c2f7767012d220bb2938c8812e2b03020a Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 21:01:41 +0300 Subject: [PATCH 25/55] POST project rate correction --- .../0002_remove_projectscore_comment.py | 17 ++++++++++++++++ rate_projects/models.py | 4 ---- rate_projects/serializers.py | 9 +-------- rate_projects/urls.py | 2 +- rate_projects/views.py | 20 ++++++++++++++----- 5 files changed, 34 insertions(+), 18 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..a4ffcc18 --- /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", + ), + ] diff --git a/rate_projects/models.py b/rate_projects/models.py index 3bc77742..ad5f628a 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -77,10 +77,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 70745d64..d51def34 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -1,16 +1,9 @@ from rest_framework import serializers - from rate_projects.models import ProjectScore class ProjectScoreCreateSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore - fields = [ - "criteria", - "user", - "project", - "value", - "comment", - ] + fields = ["criteria_id", "user_id", "project_id", "value"] diff --git a/rate_projects/urls.py b/rate_projects/urls.py index 5bcb115a..e461f04e 100644 --- a/rate_projects/urls.py +++ b/rate_projects/urls.py @@ -2,4 +2,4 @@ from rate_projects.views import RateProject -urlpatterns = [path("rate/", RateProject.as_view())] +urlpatterns = [path("rate/", RateProject.as_view())] diff --git a/rate_projects/views.py b/rate_projects/views.py index 23d5805c..ad48de89 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -3,7 +3,7 @@ from rest_framework import generics, status from rest_framework.response import Response - +from rate_projects.models import ProjectScore from rate_projects.serializers import ProjectScoreCreateSerializer from users.permissions import IsExpert @@ -17,12 +17,22 @@ class RateProject(generics.CreateAPIView): def create(self, request, *args, **kwargs): try: data = self.request.data - data["user"] = self.request.user.id - serializer = self.get_serializer(data=data) + user = self.request.user.id + project_id = self.kwargs.get("project_id") + for criteria in data: + criteria["user_id"] = user + criteria["project_id"] = project_id + criteria["criteria_id"] = criteria.pop("id") + + serializer = self.get_serializer(data=data, many=True) if serializer.is_valid(): - serializer.save() - self.perform_create(serializer) + serializer.is_valid() + + ProjectScore.objects.bulk_create( + [ProjectScore(**score) for score in data] + ) + return Response({"success": True}, status=status.HTTP_201_CREATED) else: return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) From f72f12068a7ce719ad7b2c44132ab4ff1c856310 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 21:02:17 +0300 Subject: [PATCH 26/55] minor description fix --- rate_projects/models.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/rate_projects/models.py b/rate_projects/models.py index ad5f628a..79cd5153 100644 --- a/rate_projects/models.py +++ b/rate_projects/models.py @@ -60,11 +60,8 @@ class ProjectScore(models.Model): Attributes: criteria: A ForeignKey connection to Criteria model user: A ForeignKey connection to User model - value: CharField for value - commentary: CharField for optional commentary - """ criteria = models.ForeignKey( From 92aa1889e04ea89acbe44be47a940f509afc2b16 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 21:12:21 +0300 Subject: [PATCH 27/55] =?UTF-8?q?minor=20grammar=20fix=20=F0=9F=92=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- rate_projects/views.py | 45 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/rate_projects/views.py b/rate_projects/views.py index ad48de89..a627a2f1 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -15,26 +15,25 @@ class RateProject(generics.CreateAPIView): permission_classes = [IsExpert] def create(self, request, *args, **kwargs): - try: - data = self.request.data - - user = self.request.user.id - project_id = self.kwargs.get("project_id") - for criteria in data: - criteria["user_id"] = user - criteria["project_id"] = project_id - criteria["criteria_id"] = criteria.pop("id") - - serializer = self.get_serializer(data=data, many=True) - if serializer.is_valid(): - serializer.is_valid() - - ProjectScore.objects.bulk_create( - [ProjectScore(**score) for score in data] - ) - - 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) + # try: + data = self.request.data + + user = self.request.user.id + project_id = self.kwargs.get("project_id") + for criterion in data: + criterion["user_id"] = user + criterion["project_id"] = project_id + criterion["criteria_id"] = criterion.pop("criterion_id") + + serializer = self.get_serializer(data=data, many=True) + if serializer.is_valid(): + serializer.is_valid() + + ProjectScore.objects.bulk_create([ProjectScore(**score) for score in data]) + + 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) From 96f17484ce8e4304cf46eaeaebca071a55051f9c Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 21:15:15 +0300 Subject: [PATCH 28/55] error processing code uncommented --- rate_projects/views.py | 46 ++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/rate_projects/views.py b/rate_projects/views.py index a627a2f1..c968db07 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -15,25 +15,27 @@ class RateProject(generics.CreateAPIView): permission_classes = [IsExpert] def create(self, request, *args, **kwargs): - # try: - data = self.request.data - - user = self.request.user.id - project_id = self.kwargs.get("project_id") - for criterion in data: - criterion["user_id"] = user - criterion["project_id"] = project_id - criterion["criteria_id"] = criterion.pop("criterion_id") - - serializer = self.get_serializer(data=data, many=True) - if serializer.is_valid(): - serializer.is_valid() - - ProjectScore.objects.bulk_create([ProjectScore(**score) for score in data]) - - 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) + try: + data = self.request.data + + user = self.request.user.id + project_id = self.kwargs.get("project_id") + for criterion in data: + criterion["user_id"] = user + criterion["project_id"] = project_id + criterion["criteria_id"] = criterion.pop("criterion_id") + + serializer = self.get_serializer(data=data, many=True) + if serializer.is_valid(): + serializer.is_valid() + + ProjectScore.objects.bulk_create( + [ProjectScore(**score) for score in data] + ) + + 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) From 32fa001c34b48b538ce701c78835c1ad6a384321 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 21:31:14 +0300 Subject: [PATCH 29/55] mapping corrected --- feed/constants.py | 6 +++++- feed/helpers.py | 10 +++++++--- feed/views.py | 4 ++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/feed/constants.py b/feed/constants.py index 689a6fe4..f3202add 100644 --- a/feed/constants.py +++ b/feed/constants.py @@ -26,4 +26,8 @@ class FeedItemType(enum.Enum): SupportedModel = News | Project | Vacancy SupportedQuerySet = QuerySet[News | Project | Vacancy] -model_mapping = {"news": News, "project": Project, "vacancy": Vacancy} +model_mapping = { + FeedItemType.NEWS.value: News, + FeedItemType.PROJECT.value: Project, + FeedItemType.VACANCY.value: Vacancy, +} diff --git a/feed/helpers.py b/feed/helpers.py index 76337387..9a1b4e71 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -25,11 +25,15 @@ def paginate_serialize_feed( sum_num_pages = 0 for model in model_data: sum_num_pages += paginate_serialize_feed_queryset( - model_data, paginator, request, model, view, sum_num_pages, result + model_data, paginator, request, model, view, result ) random.shuffle(result) - limit = request.query_params.get("limit") - return result[: int(limit)], sum_num_pages + limit = ( + int(request.query_params.get("limit")) + if request.query_params.get("limit") + else 10 + ) + return result[:limit], sum_num_pages def paginate_serialize_feed_queryset( diff --git a/feed/views.py b/feed/views.py index cfa8a94f..6c1f0a46 100644 --- a/feed/views.py +++ b/feed/views.py @@ -5,7 +5,7 @@ from rest_framework.response import Response from rest_framework.views import APIView -from feed.constants import SupportedModel, model_mapping, SupportedQuerySet, FeedItemType +from feed.constants import SupportedModel, SupportedQuerySet, FeedItemType, model_mapping from feed.helpers import collect_querysets, paginate_serialize_feed, add_pagination from feed.pagination import FeedPagination @@ -47,7 +47,7 @@ def get_request_data(self) -> list[SupportedModel]: models = [ model_mapping[model_name] for model_name in model_mapping.keys() - if model_name in filter_queries + if model_name.lower() in filter_queries ] return models From b12a4c267bf8da39ee7d6db7284478566246f99e Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 21:49:08 +0300 Subject: [PATCH 30/55] 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 90bb031fa4e9c14e8c3d8b48bace2d862146560e Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 22:31:21 +0300 Subject: [PATCH 31/55] deleted serializer, changed code in helpers.py --- feed/helpers.py | 13 +++++++------ feed/serializers.py | 7 ------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/feed/helpers.py b/feed/helpers.py index 9a1b4e71..a325c3b1 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -24,9 +24,12 @@ def paginate_serialize_feed( result = [] sum_num_pages = 0 for model in model_data: - sum_num_pages += paginate_serialize_feed_queryset( - model_data, paginator, request, model, view, result + paginated_part: tuple[list[dict], int] = paginate_serialize_feed_queryset( + model_data, paginator, request, model, view ) + result += paginated_part[0] + sum_num_pages += paginated_part[1] + random.shuffle(result) limit = ( int(request.query_params.get("limit")) @@ -42,12 +45,10 @@ def paginate_serialize_feed_queryset( request, model, view, - result: list[SupportedQuerySet], -) -> int: +) -> tuple[list[dict], int]: num_pages = paginator.get_count(model_data[model]) paginated_data = paginator.paginate_queryset(model_data[model], request, view=view) - result.extend(to_feed_items(model, paginated_data)) - return num_pages + return to_feed_items(model, paginated_data), num_pages def collect_querysets(model: SupportedModel) -> SupportedQuerySet: diff --git a/feed/serializers.py b/feed/serializers.py index f44da568..52ff4d34 100644 --- a/feed/serializers.py +++ b/feed/serializers.py @@ -5,10 +5,3 @@ class FeedItemSerializer(serializers.Serializer): type_model = serializers.ChoiceField(choices=constants.FeedItemType, required=True) content = serializers.JSONField(required=True) - - -class PagTestSerializer(serializers.Serializer): - def to_representation(self, instance): - return constants.FEED_SERIALIZER_MAPPING[instance.__class__.__name__]( - instance=instance - ) From 55ea5797f1646de2503e47ccb4133cc4bca61976 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 22:52:30 +0300 Subject: [PATCH 32/55] naming corrected again --- feed/helpers.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/feed/helpers.py b/feed/helpers.py index a325c3b1..7ded5f19 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -22,13 +22,13 @@ def paginate_serialize_feed( model_data: dict[SupportedQuerySet], paginator: LimitOffsetPagination, request, view ) -> tuple[list[SupportedQuerySet], int]: result = [] - sum_num_pages = 0 + pages_count = 0 for model in model_data: - paginated_part: tuple[list[dict], int] = paginate_serialize_feed_queryset( + paginated_part: dict = paginate_serialize_feed_queryset( model_data, paginator, request, model, view ) - result += paginated_part[0] - sum_num_pages += paginated_part[1] + result += paginated_part["paginated_data"] + pages_count += paginated_part["page_count"] random.shuffle(result) limit = ( @@ -36,7 +36,7 @@ def paginate_serialize_feed( if request.query_params.get("limit") else 10 ) - return result[:limit], sum_num_pages + return result[:limit], pages_count def paginate_serialize_feed_queryset( @@ -45,10 +45,13 @@ def paginate_serialize_feed_queryset( request, model, view, -) -> tuple[list[dict], int]: +) -> dict: num_pages = paginator.get_count(model_data[model]) paginated_data = paginator.paginate_queryset(model_data[model], request, view=view) - return to_feed_items(model, paginated_data), num_pages + return { + "paginated_data": to_feed_items(model, paginated_data), + "page_count": num_pages, + } def collect_querysets(model: SupportedModel) -> SupportedQuerySet: From d6198630085b54bdd5ceae6816c0b53701d6b500 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Mon, 19 Feb 2024 23:01:29 +0300 Subject: [PATCH 33/55] minor fixes --- feed/constants.py | 2 ++ feed/helpers.py | 17 +++++++++++------ feed/views.py | 4 ++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/feed/constants.py b/feed/constants.py index f3202add..7b527c31 100644 --- a/feed/constants.py +++ b/feed/constants.py @@ -31,3 +31,5 @@ class FeedItemType(enum.Enum): FeedItemType.PROJECT.value: Project, FeedItemType.VACANCY.value: Vacancy, } + +PAGINATION_CONSTANT = 10 diff --git a/feed/helpers.py b/feed/helpers.py index 7ded5f19..db6e82e1 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -2,9 +2,11 @@ from typing import Iterable from rest_framework.pagination import LimitOffsetPagination +from rest_framework.request import Request +from rest_framework.views import APIView from feed import constants -from feed.constants import SupportedModel, SupportedQuerySet +from feed.constants import SupportedModel, SupportedQuerySet, PAGINATION_CONSTANT from feed.serializers import FeedItemSerializer from news.models import News from projects.models import Project @@ -19,7 +21,10 @@ def add_pagination(results: list[SupportedQuerySet], count: int) -> dict: def paginate_serialize_feed( - model_data: dict[SupportedQuerySet], paginator: LimitOffsetPagination, request, view + model_data: dict[SupportedQuerySet], + paginator: LimitOffsetPagination, + request: Request, + view: APIView, ) -> tuple[list[SupportedQuerySet], int]: result = [] pages_count = 0 @@ -34,7 +39,7 @@ def paginate_serialize_feed( limit = ( int(request.query_params.get("limit")) if request.query_params.get("limit") - else 10 + else PAGINATION_CONSTANT ) return result[:limit], pages_count @@ -42,9 +47,9 @@ def paginate_serialize_feed( def paginate_serialize_feed_queryset( model_data: dict[SupportedQuerySet], paginator: LimitOffsetPagination, - request, - model, - view, + request: Request, + model: SupportedModel, + view: APIView, ) -> dict: num_pages = paginator.get_count(model_data[model]) paginated_data = paginator.paginate_queryset(model_data[model], request, view=view) diff --git a/feed/views.py b/feed/views.py index 6c1f0a46..58c50ed4 100644 --- a/feed/views.py +++ b/feed/views.py @@ -32,12 +32,12 @@ class FeedList(APIView): } ) def get(self, request: Request, *args, **kwargs) -> Response: - serialized_paginated_data, sum_pages = self.paginate_serialize_data( + prepared_data, sum_pages = self.paginate_serialize_data( self.get_response_data(self.get_request_data()) ) return Response( status=status.HTTP_200_OK, - data=add_pagination(serialized_paginated_data, sum_pages), + data=add_pagination(prepared_data, sum_pages), ) def get_request_data(self) -> list[SupportedModel]: From 151555c3e5df39b37029eb1cb7993b09f65ddf84 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 20 Feb 2024 10:10:12 +0300 Subject: [PATCH 34/55] hotfix feed --- feed/views.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/feed/views.py b/feed/views.py index 58c50ed4..b53678c2 100644 --- a/feed/views.py +++ b/feed/views.py @@ -35,6 +35,8 @@ def get(self, request: Request, *args, **kwargs) -> Response: prepared_data, sum_pages = self.paginate_serialize_data( self.get_response_data(self.get_request_data()) ) + for obj in prepared_data: + obj["type_model"] = obj["type_model"].lower() return Response( status=status.HTTP_200_OK, data=add_pagination(prepared_data, sum_pages), From 61c302909c981af64f89fb95ba6c04c0aeae4d3b Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Tue, 20 Feb 2024 22:57:47 +0300 Subject: [PATCH 35/55] feed fixed pagination offset --- feed/constants.py | 2 +- feed/helpers.py | 47 +++++++++++++++++++++++++++++++--------------- feed/pagination.py | 25 ++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/feed/constants.py b/feed/constants.py index 7b527c31..920621db 100644 --- a/feed/constants.py +++ b/feed/constants.py @@ -32,4 +32,4 @@ class FeedItemType(enum.Enum): FeedItemType.VACANCY.value: Vacancy, } -PAGINATION_CONSTANT = 10 +LIMIT_PAGINATION_CONSTANT = 10 diff --git a/feed/helpers.py b/feed/helpers.py index db6e82e1..3c58e552 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -1,12 +1,12 @@ -import random +from random import shuffle from typing import Iterable -from rest_framework.pagination import LimitOffsetPagination from rest_framework.request import Request from rest_framework.views import APIView from feed import constants -from feed.constants import SupportedModel, SupportedQuerySet, PAGINATION_CONSTANT +from feed.constants import SupportedModel, SupportedQuerySet, LIMIT_PAGINATION_CONSTANT +from feed.pagination import FeedPagination from feed.serializers import FeedItemSerializer from news.models import News from projects.models import Project @@ -22,37 +22,54 @@ def add_pagination(results: list[SupportedQuerySet], count: int) -> dict: def paginate_serialize_feed( model_data: dict[SupportedQuerySet], - paginator: LimitOffsetPagination, + paginator: FeedPagination, request: Request, view: APIView, ) -> tuple[list[SupportedQuerySet], int]: result = [] pages_count = 0 - for model in model_data: + + offset = int(request.query_params.get("offset", 0)) + request.query_params._mutable = True + request.query_params["offset"] = int(request.query_params["offset"]) + offset_numbers = randomize_offset(offset, len(model_data.keys())) + + for i in range(len(model_data.keys())): + request.query_params["offset"] = offset_numbers[i] paginated_part: dict = paginate_serialize_feed_queryset( - model_data, paginator, request, model, view + model_data, paginator, request, list(model_data.keys())[i], view ) result += paginated_part["paginated_data"] pages_count += paginated_part["page_count"] - random.shuffle(result) - limit = ( - int(request.query_params.get("limit")) - if request.query_params.get("limit") - else PAGINATION_CONSTANT - ) + shuffle(result) + + limit = int(request.query_params.get("limit", LIMIT_PAGINATION_CONSTANT)) return result[:limit], pages_count +def randomize_offset(offset: int, quantity_models: int) -> list[int]: + full_division = offset // quantity_models + extra_items = offset % quantity_models + + pagination_numbers = [full_division] * quantity_models + pagination_numbers[-1] += extra_items + + return pagination_numbers + + def paginate_serialize_feed_queryset( model_data: dict[SupportedQuerySet], - paginator: LimitOffsetPagination, + paginator: FeedPagination, request: Request, model: SupportedModel, view: APIView, ) -> dict: - num_pages = paginator.get_count(model_data[model]) - paginated_data = paginator.paginate_queryset(model_data[model], request, view=view) + paginated_info = paginator.custom_paginate_queryset( + model_data[model], request, view=view + ) + paginated_data = paginated_info["queryset_ready"] + num_pages = paginated_info["count"] return { "paginated_data": to_feed_items(model, paginated_data), "page_count": num_pages, diff --git a/feed/pagination.py b/feed/pagination.py index 807c3fc9..3335339b 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -1,7 +1,32 @@ from rest_framework import pagination +from rest_framework.request import Request + +from feed.constants import SupportedQuerySet class FeedPagination(pagination.LimitOffsetPagination): default_limit = 10 limit_query_param = "limit" offset_query_param = "offset" + + def custom_paginate_queryset( + self, queryset: SupportedQuerySet, request: Request, view=None + ) -> dict: + self.limit = self.get_limit(request) + if self.limit is None: + return None + + self.count = self.get_count(queryset) + self.offset = self.get_offset(request) + self.request = request + if self.count > self.limit and self.template is not None: + self.display_page_controls = True + + if self.count == 0 or self.offset > self.count: + return {"queryset_ready": [], "count": self.count} + + queryset_ready = queryset[self.offset: self.offset + self.limit] + return { + "queryset_ready": queryset_ready, + "count": self.count, + } From 2644543382c1b3f114c068bb10a413e64d26715b Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 00:35:10 +0300 Subject: [PATCH 36/55] fixed no limit and no offset bug --- feed/helpers.py | 13 +++++++++++-- feed/pagination.py | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/feed/helpers.py b/feed/helpers.py index 3c58e552..21785665 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -29,9 +29,16 @@ def paginate_serialize_feed( result = [] pages_count = 0 - offset = int(request.query_params.get("offset", 0)) + offset = request.query_params.get("offset", None) request.query_params._mutable = True - request.query_params["offset"] = int(request.query_params["offset"]) + + if offset == "" or offset is None: + offset = 0 + elif offset is not None: + offset = int(offset) + request.query_params["offset"] = offset + + offset_numbers = randomize_offset(offset, len(model_data.keys())) for i in range(len(model_data.keys())): @@ -45,6 +52,8 @@ def paginate_serialize_feed( shuffle(result) limit = int(request.query_params.get("limit", LIMIT_PAGINATION_CONSTANT)) + if limit == "": + limit = LIMIT_PAGINATION_CONSTANT return result[:limit], pages_count diff --git a/feed/pagination.py b/feed/pagination.py index 3335339b..9e88fc83 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -25,7 +25,7 @@ def custom_paginate_queryset( if self.count == 0 or self.offset > self.count: return {"queryset_ready": [], "count": self.count} - queryset_ready = queryset[self.offset: self.offset + self.limit] + queryset_ready = queryset[self.offset : self.offset + self.limit] return { "queryset_ready": queryset_ready, "count": self.count, From 5abf8cb166029353ce42d3a2c59bd2992ec694f5 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 02:46:58 +0300 Subject: [PATCH 37/55] fix of fix of feed --- feed/helpers.py | 75 ++++++++++++++++++++++++++++++++++------------ feed/pagination.py | 4 +-- 2 files changed, 58 insertions(+), 21 deletions(-) diff --git a/feed/helpers.py b/feed/helpers.py index 21785665..090f222e 100644 --- a/feed/helpers.py +++ b/feed/helpers.py @@ -5,7 +5,11 @@ from rest_framework.views import APIView from feed import constants -from feed.constants import SupportedModel, SupportedQuerySet, LIMIT_PAGINATION_CONSTANT +from feed.constants import ( + SupportedModel, + SupportedQuerySet, + LIMIT_PAGINATION_CONSTANT +) from feed.pagination import FeedPagination from feed.serializers import FeedItemSerializer from news.models import News @@ -29,42 +33,74 @@ def paginate_serialize_feed( result = [] pages_count = 0 - offset = request.query_params.get("offset", None) + if len(model_data) == 0: + return [], 0 + + offset = request.query_params.get("offset", 0) request.query_params._mutable = True - if offset == "" or offset is None: - offset = 0 - elif offset is not None: + if isinstance(offset, str) and offset.isdigit(): offset = int(offset) - request.query_params["offset"] = offset + else: + offset = 0 + request.query_params["offset"] = offset - offset_numbers = randomize_offset(offset, len(model_data.keys())) + models_counts = { + model_name: model_data[model_name].count() for model_name in model_data.keys() + } + offset_numbers = offset_distribution(offset, models_counts) + + for model_name in model_data.keys(): + request.query_params["offset"] = offset_numbers[model_name] - for i in range(len(model_data.keys())): - request.query_params["offset"] = offset_numbers[i] paginated_part: dict = paginate_serialize_feed_queryset( - model_data, paginator, request, list(model_data.keys())[i], view + model_data, paginator, request, model_name, models_counts[model_name], view ) + result += paginated_part["paginated_data"] pages_count += paginated_part["page_count"] - shuffle(result) + limit = request.query_params.get("limit", LIMIT_PAGINATION_CONSTANT) - limit = int(request.query_params.get("limit", LIMIT_PAGINATION_CONSTANT)) if limit == "": limit = LIMIT_PAGINATION_CONSTANT + else: + limit = int(limit) + + shuffle(result) return result[:limit], pages_count -def randomize_offset(offset: int, quantity_models: int) -> list[int]: - full_division = offset // quantity_models - extra_items = offset % quantity_models +def offset_distribution(offset: int, models_counts: dict) -> dict: + common_key_list = list(models_counts.keys()) + quantity_of_models = len(list(models_counts.keys())) + + full_division = offset // quantity_of_models + extra_items = offset % quantity_of_models + + distributed_not_ready = {model_name: full_division for model_name in common_key_list} + distributed = dict( + sorted(distributed_not_ready.items(), key=lambda item: models_counts[item[0]]) + ) + + last_key = common_key_list[-1] + distributed[last_key] += extra_items - pagination_numbers = [full_division] * quantity_models - pagination_numbers[-1] += extra_items + new_keys_list = list(distributed.keys()) + for i, key in enumerate( + new_keys_list + ): # распределяем переполненные значения от маленьких моделей к большим + offset_value = distributed[key] + model_count = models_counts[key] + if offset_value > model_count: + diff = offset_value - model_count + distributed[key] = model_count + if i + 1 < len(new_keys_list): + next_key = new_keys_list[i + 1] + distributed[next_key] += diff - return pagination_numbers + return distributed def paginate_serialize_feed_queryset( @@ -72,10 +108,11 @@ def paginate_serialize_feed_queryset( paginator: FeedPagination, request: Request, model: SupportedModel, + count: int, view: APIView, ) -> dict: paginated_info = paginator.custom_paginate_queryset( - model_data[model], request, view=view + model_data[model], request, count, view=view ) paginated_data = paginated_info["queryset_ready"] num_pages = paginated_info["count"] diff --git a/feed/pagination.py b/feed/pagination.py index 9e88fc83..eea7ff91 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -10,13 +10,13 @@ class FeedPagination(pagination.LimitOffsetPagination): offset_query_param = "offset" def custom_paginate_queryset( - self, queryset: SupportedQuerySet, request: Request, view=None + self, queryset: SupportedQuerySet, request: Request, count: int, view=None ) -> dict: self.limit = self.get_limit(request) if self.limit is None: return None - self.count = self.get_count(queryset) + self.count = count self.offset = self.get_offset(request) self.request = request if self.count > self.limit and self.template is not None: From 205ec48e5319c3b59f80304b908e2860056f9e2d Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 03:02:31 +0300 Subject: [PATCH 38/55] 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) From e2fe20fcf03e0bf0490c8f199af781067b6ab017 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 04:40:58 +0300 Subject: [PATCH 39/55] fix serializer --- rate_projects/serializers.py | 20 ++++++++++++++- rate_projects/views.py | 50 ++++++++++++++++++------------------ 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py index d51def34..e7b48649 100644 --- a/rate_projects/serializers.py +++ b/rate_projects/serializers.py @@ -1,9 +1,27 @@ from rest_framework import serializers -from rate_projects.models import ProjectScore + +from rate_projects.models import ProjectScore, Criteria +from rate_projects.validators import ProjectScoreValidate class ProjectScoreCreateSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore fields = ["criteria_id", "user_id", "project_id", "value"] + readonly_field = ["criterion_field"] + + +def serialize_data_func(criteria_to_get: list, data: dict): + criteria = Criteria.objects.in_bulk(criteria_to_get) + + for criterion in data: + needed_criteria = criteria.get(int(criterion["criterion_id"])) + + ProjectScoreValidate( + criteria_type=needed_criteria.type, + value=criterion["value"], + criteria_min_value=needed_criteria.min_value, + criteria_max_value=needed_criteria.max_value, + ) + criterion["criteria_id"] = criterion.pop("criterion_id") diff --git a/rate_projects/views.py b/rate_projects/views.py index c968db07..c088b46b 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -4,7 +4,7 @@ from rest_framework.response import Response from rate_projects.models import ProjectScore -from rate_projects.serializers import ProjectScoreCreateSerializer +from rate_projects.serializers import ProjectScoreCreateSerializer, serialize_data_func from users.permissions import IsExpert User = get_user_model() @@ -15,27 +15,27 @@ class RateProject(generics.CreateAPIView): permission_classes = [IsExpert] def create(self, request, *args, **kwargs): - try: - data = self.request.data - - user = self.request.user.id - project_id = self.kwargs.get("project_id") - for criterion in data: - criterion["user_id"] = user - criterion["project_id"] = project_id - criterion["criteria_id"] = criterion.pop("criterion_id") - - serializer = self.get_serializer(data=data, many=True) - if serializer.is_valid(): - serializer.is_valid() - - ProjectScore.objects.bulk_create( - [ProjectScore(**score) for score in data] - ) - - 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) + # try: + data = self.request.data + + user = self.request.user.id + project_id = self.kwargs.get("project_id") + + criteria_to_get = [] + for criterion in data: + criterion["user_id"] = user + criterion["project_id"] = project_id + criteria_to_get.append(criterion["criterion_id"]) + + serializer = self.get_serializer(data=data, many=True) + + if serializer.is_valid(): + serialize_data_func(criteria_to_get, data) + ProjectScore.objects.bulk_create([ProjectScore(**score) for score in data]) + + 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) From 76a5c0f00e418f0638db37fa1d8448b45ef1f0e2 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 04:42:19 +0300 Subject: [PATCH 40/55] minor fix --- rate_projects/views.py | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/rate_projects/views.py b/rate_projects/views.py index c088b46b..d0514006 100644 --- a/rate_projects/views.py +++ b/rate_projects/views.py @@ -15,27 +15,29 @@ class RateProject(generics.CreateAPIView): permission_classes = [IsExpert] def create(self, request, *args, **kwargs): - # try: - data = self.request.data + try: + data = self.request.data - user = self.request.user.id - project_id = self.kwargs.get("project_id") + user = self.request.user.id + project_id = self.kwargs.get("project_id") - criteria_to_get = [] - for criterion in data: - criterion["user_id"] = user - criterion["project_id"] = project_id - criteria_to_get.append(criterion["criterion_id"]) + criteria_to_get = [] + for criterion in data: + criterion["user_id"] = user + criterion["project_id"] = project_id + criteria_to_get.append(criterion["criterion_id"]) - serializer = self.get_serializer(data=data, many=True) + serializer = self.get_serializer(data=data, many=True) - if serializer.is_valid(): - serialize_data_func(criteria_to_get, data) - ProjectScore.objects.bulk_create([ProjectScore(**score) for score in data]) + if serializer.is_valid(): + serialize_data_func(criteria_to_get, data) + ProjectScore.objects.bulk_create( + [ProjectScore(**score) for score in data] + ) - return Response({"success": True}, status=status.HTTP_201_CREATED) - else: - return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + 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) + except Exception as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) From 6aa7dfc08a4e0ce488834fa064906795e1e231de Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 11:32:55 +0300 Subject: [PATCH 41/55] fixed vacancy skills --- users/serializers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/users/serializers.py b/users/serializers.py index 32df068f..64e7b48e 100644 --- a/users/serializers.py +++ b/users/serializers.py @@ -32,7 +32,7 @@ def to_representation(self, data): if isinstance(data, list): return data return [ - i.replace("'", "") for i in data.strip("][").split(", ") if i.replace("'", "") + i.replace("'", "") for i in data.strip("][").split(",") if i.replace("'", "") ] @@ -365,7 +365,7 @@ class Meta: "last_name", "patronymic", "key_skills", - "speciality" + "speciality", ] From a6b0475f20190a6e0a0baf5bdee8da4cae5cd55c Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 11:36:55 +0300 Subject: [PATCH 42/55] comment added --- feed/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feed/pagination.py b/feed/pagination.py index eea7ff91..36965a2f 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -25,7 +25,7 @@ def custom_paginate_queryset( if self.count == 0 or self.offset > self.count: return {"queryset_ready": [], "count": self.count} - queryset_ready = queryset[self.offset : self.offset + self.limit] + queryset_ready = queryset[self.offset : self.offset + self.limit] # TODO: поправить линтер в будущем return { "queryset_ready": queryset_ready, "count": self.count, From 6e46a8c5f5e0752860842c805ab1fd79836b3d8a Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 11:41:00 +0300 Subject: [PATCH 43/55] comment added x2 --- feed/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feed/pagination.py b/feed/pagination.py index 36965a2f..4ab8eaf1 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -25,7 +25,7 @@ def custom_paginate_queryset( if self.count == 0 or self.offset > self.count: return {"queryset_ready": [], "count": self.count} - queryset_ready = queryset[self.offset : self.offset + self.limit] # TODO: поправить линтер в будущем + queryset_ready = queryset[self.offset : self.offset + self.limit] # noqa: E203 return { "queryset_ready": queryset_ready, "count": self.count, From 743c95061fe01dcc1c852074e5e1fdf7d3d92849 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 11:41:33 +0300 Subject: [PATCH 44/55] comment added x2 --- feed/pagination.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feed/pagination.py b/feed/pagination.py index 4ab8eaf1..f7ffc1a9 100644 --- a/feed/pagination.py +++ b/feed/pagination.py @@ -25,7 +25,7 @@ def custom_paginate_queryset( if self.count == 0 or self.offset > self.count: return {"queryset_ready": [], "count": self.count} - queryset_ready = queryset[self.offset : self.offset + self.limit] # noqa: E203 + queryset_ready = queryset[self.offset : self.offset + self.limit] # noqa: E203 return { "queryset_ready": queryset_ready, "count": self.count, From e3f464608bac48d594f17acb291db5d4abef3e5b Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 16:57:13 +0300 Subject: [PATCH 45/55] naming fixed --- project_rates/serializers.py | 4 ++-- project_rates/urls.py | 2 +- project_rates/views.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/project_rates/serializers.py b/project_rates/serializers.py index e7b48649..4a8ab863 100644 --- a/project_rates/serializers.py +++ b/project_rates/serializers.py @@ -1,8 +1,8 @@ from rest_framework import serializers -from rate_projects.models import ProjectScore, Criteria -from rate_projects.validators import ProjectScoreValidate +from project_rates.models import ProjectScore, Criteria +from project_rates.validators import ProjectScoreValidate class ProjectScoreCreateSerializer(serializers.ModelSerializer): diff --git a/project_rates/urls.py b/project_rates/urls.py index e461f04e..d9d2c910 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from rate_projects.views import RateProject +from project_rates.views import RateProject urlpatterns = [path("rate/", RateProject.as_view())] diff --git a/project_rates/views.py b/project_rates/views.py index d0514006..30679939 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -3,8 +3,8 @@ from rest_framework import generics, status from rest_framework.response import Response -from rate_projects.models import ProjectScore -from rate_projects.serializers import ProjectScoreCreateSerializer, serialize_data_func +from project_rates.models import ProjectScore +from project_rates.serializers import ProjectScoreCreateSerializer, serialize_data_func from users.permissions import IsExpert User = get_user_model() From 6d9f3a3b052f492310c750724e9e1ae9115cc5e2 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 17:00:39 +0300 Subject: [PATCH 46/55] naming fixed x2 --- project_rates/migrations/0002_remove_projectscore_comment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project_rates/migrations/0002_remove_projectscore_comment.py b/project_rates/migrations/0002_remove_projectscore_comment.py index a4ffcc18..f0b57f0d 100644 --- a/project_rates/migrations/0002_remove_projectscore_comment.py +++ b/project_rates/migrations/0002_remove_projectscore_comment.py @@ -6,7 +6,7 @@ class Migration(migrations.Migration): dependencies = [ - ("rate_projects", "0001_initial"), + ("project_rates", "0001_initial"), ] operations = [ From 94ddeb9da50974ba9eb22cd4b6c655f2628fa28c Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 17:03:02 +0300 Subject: [PATCH 47/55] naming fixed in urls.py --- procollab/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/procollab/urls.py b/procollab/urls.py index 33031d02..25c9d7de 100644 --- a/procollab/urls.py +++ b/procollab/urls.py @@ -47,7 +47,7 @@ path("chats/", include("chats.urls", namespace="chats")), path("events/", include("events.urls", namespace="events")), path("programs/", include("partner_programs.urls", namespace="partner_programs")), - path("rate-project/", include(("rate_projects.urls", "rate_projects"))), + path("rate-project/", include(("project_rates.urls", "rate_projects"))), path("feed/", include("feed.urls", namespace="feed")), path("api/token/", TokenObtainPairView.as_view(), name="token_obtain_pair"), path("api/token/refresh/", TokenRefreshView.as_view(), name="token_refresh"), From 0eb7f54c9b292fa147fd1afc27079c8b4f67e238 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 17:36:30 +0300 Subject: [PATCH 48/55] wrong naming again bruh --- project_rates/models.py | 19 ++++++-- project_rates/serializers.py | 58 +++++++++++++++------- project_rates/urls.py | 7 ++- project_rates/validators.py | 2 + project_rates/views.py | 73 ++++++++++++++++++++-------- rate_projects/models.py | 94 ------------------------------------ rate_projects/serializers.py | 51 ------------------- rate_projects/urls.py | 8 --- rate_projects/validators.py | 32 ------------ rate_projects/views.py | 78 ------------------------------ 10 files changed, 116 insertions(+), 306 deletions(-) delete mode 100644 rate_projects/models.py delete mode 100644 rate_projects/serializers.py delete mode 100644 rate_projects/urls.py delete mode 100644 rate_projects/validators.py delete mode 100644 rate_projects/views.py diff --git a/project_rates/models.py b/project_rates/models.py index 2f2f2b64..2f72b7a6 100644 --- a/project_rates/models.py +++ b/project_rates/models.py @@ -3,9 +3,8 @@ from partner_programs.models import PartnerProgram from projects.models import Project - -from .constants import VERBOSE_TYPES - +from .constants import VERBOSE_NAME_TYPES +from .validators import ProjectScoreValidate User = get_user_model() @@ -26,7 +25,7 @@ class Criteria(models.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=VERBOSE_TYPES) + type = models.CharField(verbose_name="Тип", max_length=8, choices=VERBOSE_NAME_TYPES) min_value = models.FloatField( verbose_name="Минимально допустимое числовое значение", @@ -61,14 +60,15 @@ class ProjectScore(models.Model): Attributes: criteria: A ForeignKey connection to Criteria model user: A ForeignKey connection to User model + value: CharField 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") @@ -79,6 +79,15 @@ class ProjectScore(models.Model): 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, + ) + super().save(*args, **kwargs) + class Meta: verbose_name = "Оценка проекта" verbose_name_plural = "Оценки проектов" diff --git a/project_rates/serializers.py b/project_rates/serializers.py index 4a8ab863..1d16d717 100644 --- a/project_rates/serializers.py +++ b/project_rates/serializers.py @@ -1,27 +1,51 @@ from rest_framework import serializers - - -from project_rates.models import ProjectScore, Criteria -from project_rates.validators import ProjectScoreValidate +from .models import Criteria, ProjectScore +from projects.models import Project class ProjectScoreCreateSerializer(serializers.ModelSerializer): class Meta: model = ProjectScore - fields = ["criteria_id", "user_id", "project_id", "value"] - readonly_field = ["criterion_field"] + fields = ["criteria", "user", "project", "value"] -def serialize_data_func(criteria_to_get: list, data: dict): - criteria = Criteria.objects.in_bulk(criteria_to_get) +class CriteriaSerializer(serializers.ModelSerializer): + class Meta: + model = Criteria + exclude = ["partner_program"] - for criterion in data: - needed_criteria = criteria.get(int(criterion["criterion_id"])) - ProjectScoreValidate( - criteria_type=needed_criteria.type, - value=criterion["value"], - criteria_min_value=needed_criteria.min_value, - criteria_max_value=needed_criteria.max_value, - ) - criterion["criteria_id"] = criterion.pop("criterion_id") +class ProjectScoreSerializer(serializers.ModelSerializer): + class Meta: + model = ProjectScore + fields = ["criteria_id", "project_id", "value"] + + +class ProjectScoreGetSerializer(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"]: + if ( + criteria["id"] == score["criteria_id"] + and obj.id == score["project_id"] + ): + copied_criteria["value"] = score["value"] + + criterias.append(copied_criteria) + return criterias diff --git a/project_rates/urls.py b/project_rates/urls.py index d9d2c910..5d0f69e3 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -1,5 +1,8 @@ from django.urls import path -from project_rates.views import RateProject +from project_rates.views import RateProject, RateProjects -urlpatterns = [path("rate/", RateProject.as_view())] +urlpatterns = [ + path("rate/", RateProject.as_view()), + path("", RateProjects.as_view()), +] diff --git a/project_rates/validators.py b/project_rates/validators.py index 1dfc0a65..9ac6800b 100644 --- a/project_rates/validators.py +++ b/project_rates/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("Введённое значение не соответствует формату!") diff --git a/project_rates/views.py b/project_rates/views.py index 30679939..a3a8053c 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -3,8 +3,15 @@ from rest_framework import generics, status from rest_framework.response import Response -from project_rates.models import ProjectScore -from project_rates.serializers import ProjectScoreCreateSerializer, serialize_data_func +from projects.models import Project +from project_rates.models import Criteria, ProjectScore +from rate_projects.pagination import RateProjectsPagination +from project_rates.serializers import ( + ProjectScoreCreateSerializer, + CriteriaSerializer, + ProjectScoreSerializer, + ProjectScoreGetSerializer, +) from users.permissions import IsExpert User = get_user_model() @@ -17,27 +24,55 @@ class RateProject(generics.CreateAPIView): def create(self, request, *args, **kwargs): try: data = self.request.data + data["user"] = self.request.user.id - user = self.request.user.id - project_id = self.kwargs.get("project_id") - - criteria_to_get = [] - for criterion in data: - criterion["user_id"] = user - criterion["project_id"] = project_id - criteria_to_get.append(criterion["criterion_id"]) - - serializer = self.get_serializer(data=data, many=True) - + serializer = self.get_serializer(data=data) if serializer.is_valid(): - serialize_data_func(criteria_to_get, data) - ProjectScore.objects.bulk_create( - [ProjectScore(**score) for score in data] - ) - + serializer.save() + self.perform_create(serializer) 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) + + +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 + ) + 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() + + projects_serializer = self.get_serializer( + data=projects, + context={ + "data_criterias": criteria_serializer.data, + "data_scores": scores_serializer.data, + }, + many=True, + ) + + projects_serializer.is_valid() + + return self.get_paginated_response(projects_serializer.data) diff --git a/rate_projects/models.py b/rate_projects/models.py deleted file mode 100644 index 2f72b7a6..00000000 --- a/rate_projects/models.py +++ /dev/null @@ -1,94 +0,0 @@ -from django.contrib.auth import get_user_model -from django.db import models - -from partner_programs.models import PartnerProgram -from projects.models import Project -from .constants import VERBOSE_NAME_TYPES -from .validators import ProjectScoreValidate - -User = get_user_model() - - -class Criteria(models.Model): - """ - Criteria model - - Attributes: - name: A CharField name of the criteria - description: A TextField description of criteria - type: A CharField choice between "str", "int", "bool" and "float" - min_value: Optional FloatField for numeric values - max_value: Optional FloatField for numeric values - 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=VERBOSE_NAME_TYPES) - - min_value = models.FloatField( - verbose_name="Минимально допустимое числовое значение", - help_text="(если есть)", - null=True, - blank=True, - ) - max_value = models.FloatField( - verbose_name="Максимально допустимое числовое значение", - help_text="(если есть)", - null=True, - blank=True, - ) - partner_program = models.ForeignKey( - PartnerProgram, - on_delete=models.CASCADE, - related_name="criterias", - ) - - def __str__(self): - return f"Criteria<{self.id}> - {self.name} {self.partner_program.name}" - - class Meta: - verbose_name = "Критерий оценки проекта" - verbose_name_plural = "Критерии оценки проектов" - - -class ProjectScore(models.Model): - """ - ProjectScore model - - Attributes: - criteria: A ForeignKey connection to Criteria model - user: A ForeignKey connection to User model - - value: CharField 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") - - value = models.CharField( - verbose_name="Значение", max_length=50, null=True, blank=True - ) - - 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, - ) - super().save(*args, **kwargs) - - class Meta: - verbose_name = "Оценка проекта" - verbose_name_plural = "Оценки проектов" - unique_together = ("criteria", "user", "project") diff --git a/rate_projects/serializers.py b/rate_projects/serializers.py deleted file mode 100644 index 1d16d717..00000000 --- a/rate_projects/serializers.py +++ /dev/null @@ -1,51 +0,0 @@ -from rest_framework import serializers -from .models import Criteria, ProjectScore -from projects.models import Project - - -class ProjectScoreCreateSerializer(serializers.ModelSerializer): - class Meta: - model = ProjectScore - fields = ["criteria", "user", "project", "value"] - - -class CriteriaSerializer(serializers.ModelSerializer): - class Meta: - model = Criteria - exclude = ["partner_program"] - - -class ProjectScoreSerializer(serializers.ModelSerializer): - class Meta: - model = ProjectScore - fields = ["criteria_id", "project_id", "value"] - - -class ProjectScoreGetSerializer(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"]: - if ( - criteria["id"] == score["criteria_id"] - and obj.id == score["project_id"] - ): - copied_criteria["value"] = score["value"] - - criterias.append(copied_criteria) - return criterias diff --git a/rate_projects/urls.py b/rate_projects/urls.py deleted file mode 100644 index 4f9f208e..00000000 --- a/rate_projects/urls.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.urls import path - -from rate_projects.views import RateProject, RateProjects - -urlpatterns = [ - path("rate/", RateProject.as_view()), - path("", RateProjects.as_view()), -] diff --git a/rate_projects/validators.py b/rate_projects/validators.py deleted file mode 100644 index 9ac6800b..00000000 --- a/rate_projects/validators.py +++ /dev/null @@ -1,32 +0,0 @@ -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") - - self._validate_data_type() - self._validate_numeric_limits() - - def _validate_data_type(self): - if self.criteria_type in ["float", "int"]: - try: - 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("Введённое значение не соответствует формату!") - - 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("Оценка этого критерия превысила допустимые значения!") diff --git a/rate_projects/views.py b/rate_projects/views.py deleted file mode 100644 index e7c6fa39..00000000 --- a/rate_projects/views.py +++ /dev/null @@ -1,78 +0,0 @@ -from django.contrib.auth import get_user_model - -from rest_framework import generics, status -from rest_framework.response import Response - -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, - ProjectScoreSerializer, - ProjectScoreGetSerializer, -) -from users.permissions import IsExpert - -User = get_user_model() - - -class RateProject(generics.CreateAPIView): - serializer_class = ProjectScoreCreateSerializer - permission_classes = [IsExpert] - - def create(self, request, *args, **kwargs): - try: - data = self.request.data - 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) - 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) - - -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 - ) - 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() - - projects_serializer = self.get_serializer( - data=projects, - context={ - "data_criterias": criteria_serializer.data, - "data_scores": scores_serializer.data, - }, - many=True, - ) - - projects_serializer.is_valid() - - return self.get_paginated_response(projects_serializer.data) From 9f41094eb7e1a682abd1a79aecce10caf0a5f80a Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 17:38:40 +0300 Subject: [PATCH 49/55] imports fixes --- project_rates/models.py | 4 ++-- project_rates/views.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/project_rates/models.py b/project_rates/models.py index 2f72b7a6..3616e422 100644 --- a/project_rates/models.py +++ b/project_rates/models.py @@ -3,7 +3,7 @@ from partner_programs.models import PartnerProgram from projects.models import Project -from .constants import VERBOSE_NAME_TYPES +from .constants import VERBOSE_TYPES from .validators import ProjectScoreValidate User = get_user_model() @@ -25,7 +25,7 @@ class Criteria(models.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=VERBOSE_NAME_TYPES) + type = models.CharField(verbose_name="Тип", max_length=8, choices=VERBOSE_TYPES) min_value = models.FloatField( verbose_name="Минимально допустимое числовое значение", diff --git a/project_rates/views.py b/project_rates/views.py index a3a8053c..60c0baa6 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -5,7 +5,7 @@ from projects.models import Project from project_rates.models import Criteria, ProjectScore -from rate_projects.pagination import RateProjectsPagination +from project_rates.pagination import RateProjectsPagination from project_rates.serializers import ( ProjectScoreCreateSerializer, CriteriaSerializer, From f0eb0640fcc9fb8dbb34ddcdff9875fa3718947b Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 18:12:53 +0300 Subject: [PATCH 50/55] undo fix --- users/admin.py | 5 ----- users/migrations/0045_expert_programs.py | 22 ---------------------- users/models.py | 4 ---- users/permissions.py | 12 +----------- 4 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 users/migrations/0045_expert_programs.py diff --git a/users/admin.py b/users/admin.py index 49ecba7c..0e506205 100644 --- a/users/admin.py +++ b/users/admin.py @@ -198,8 +198,3 @@ class UserAchievementAdmin(admin.ModelAdmin): class UserLinkAdmin(admin.ModelAdmin): list_display = ("id", "user", "link") list_display_links = ("id", "user", "link") - - -@admin.register(Expert) -class ExpertAdmin(admin.ModelAdmin): - list_display = ("id", "user") diff --git a/users/migrations/0045_expert_programs.py b/users/migrations/0045_expert_programs.py deleted file mode 100644 index 5e12a87b..00000000 --- a/users/migrations/0045_expert_programs.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 4.2.3 on 2024-02-12 06:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ("partner_programs", "0004_auto_20231230_0002"), - ("users", "0044_auto_20240128_2236"), - ] - - operations = [ - migrations.AddField( - model_name="expert", - name="programs", - field=models.ManyToManyField( - related_name="experts", to="partner_programs.partnerprogram" - ), - ), - ] - diff --git a/users/models.py b/users/models.py index c28f950f..25006014 100644 --- a/users/models.py +++ b/users/models.py @@ -327,10 +327,6 @@ class Expert(AbstractUserWithRole): preferred_industries = models.CharField(max_length=4096, null=True, blank=True) useful_to_project = models.TextField(blank=True) - programs = models.ManyToManyField( - "partner_programs.PartnerProgram", related_name="experts", blank=True - ) - class Meta(TypedModelMeta): verbose_name = "Эксперт" verbose_name_plural = "Эксперты" diff --git a/users/permissions.py b/users/permissions.py index e8f6ade3..ef53b4de 100644 --- a/users/permissions.py +++ b/users/permissions.py @@ -1,8 +1,5 @@ -from rest_framework.exceptions import PermissionDenied from rest_framework.permissions import BasePermission, SAFE_METHODS -from users.models import Expert - class IsAchievementOwnerOrReadOnly(BasePermission): """ @@ -21,11 +18,4 @@ class IsExpert(BasePermission): """ def has_permission(self, request, view): - user = request.user - program_id = view.kwargs.get("program_id") - - if not user.user_type == 3: - raise PermissionDenied("User is not an expert") - if not Expert.objects.filter(programs__id=program_id, user=user).exists(): - raise PermissionDenied("You don't have permission to rate this program") - return True + return request.user.user_type == 3 From 88381c122bbc6d2a5e5b7c42eb0946912e13103f Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 20:10:39 +0300 Subject: [PATCH 51/55] get details added --- project_rates/urls.py | 3 ++- project_rates/views.py | 55 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/project_rates/urls.py b/project_rates/urls.py index 5d0f69e3..9caa4056 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -1,8 +1,9 @@ from django.urls import path -from project_rates.views import RateProject, RateProjects +from project_rates.views import RateProject, RateProjects, RateProjectsDetails urlpatterns = [ path("rate/", RateProject.as_view()), path("", RateProjects.as_view()), + path("details/", RateProjectsDetails.as_view()), ] diff --git a/project_rates/views.py b/project_rates/views.py index 60c0baa6..972bc2be 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -76,3 +76,58 @@ def get(self, request, *args, **kwargs): projects_serializer.is_valid() return self.get_paginated_response(projects_serializer.data) + + +class RateProjectsDetails(generics.ListAPIView): + serializer_class = ProjectScoreGetSerializer + permission_classes = [IsExpert] + + def get(self, request, *args, **kwargs): + user = self.request.user + project_id = self.kwargs.get("project_id") + + criterias = Criteria.objects.prefetch_related("partner_program").filter( + partner_program_id=int(self.request.data.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, + ) + + 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, + "industry": project.industry.id, + "criterias": criterias_data, + } + return Response(response, status=200) From bb1fe150387421d6970c1f0fb470be89e17b6982 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko <110509023+sh1nkey@users.noreply.github.com> Date: Wed, 21 Feb 2024 20:33:39 +0300 Subject: [PATCH 52/55] Revert "get details added" --- project_rates/urls.py | 3 +-- project_rates/views.py | 55 ------------------------------------------ 2 files changed, 1 insertion(+), 57 deletions(-) diff --git a/project_rates/urls.py b/project_rates/urls.py index 9caa4056..5d0f69e3 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -1,9 +1,8 @@ from django.urls import path -from project_rates.views import RateProject, RateProjects, RateProjectsDetails +from project_rates.views import RateProject, RateProjects urlpatterns = [ path("rate/", RateProject.as_view()), path("", RateProjects.as_view()), - path("details/", RateProjectsDetails.as_view()), ] diff --git a/project_rates/views.py b/project_rates/views.py index 972bc2be..60c0baa6 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -76,58 +76,3 @@ def get(self, request, *args, **kwargs): projects_serializer.is_valid() return self.get_paginated_response(projects_serializer.data) - - -class RateProjectsDetails(generics.ListAPIView): - serializer_class = ProjectScoreGetSerializer - permission_classes = [IsExpert] - - def get(self, request, *args, **kwargs): - user = self.request.user - project_id = self.kwargs.get("project_id") - - criterias = Criteria.objects.prefetch_related("partner_program").filter( - partner_program_id=int(self.request.data.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, - ) - - 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, - "industry": project.industry.id, - "criterias": criterias_data, - } - return Response(response, status=200) From 411f6417d40929f3991002618d76ae67fe2fae69 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 20:47:04 +0300 Subject: [PATCH 53/55] get details added 1 --- project_rates/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/project_rates/views.py b/project_rates/views.py index 972bc2be..998bf238 100644 --- a/project_rates/views.py +++ b/project_rates/views.py @@ -130,4 +130,5 @@ def get(self, request, *args, **kwargs): "industry": project.industry.id, "criterias": criterias_data, } + return Response(response, status=200) From 99fda6ba1b25084781b02cf1b7a63e12b5001ad4 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 20:51:36 +0300 Subject: [PATCH 54/55] get details added 2 --- project_rates/urls.py | 1 + 1 file changed, 1 insertion(+) diff --git a/project_rates/urls.py b/project_rates/urls.py index 9caa4056..86cdba78 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -2,6 +2,7 @@ from project_rates.views import RateProject, RateProjects, RateProjectsDetails + urlpatterns = [ path("rate/", RateProject.as_view()), path("", RateProjects.as_view()), From d50092b23aef235a1b158b5f4ed87e0ac6a46b38 Mon Sep 17 00:00:00 2001 From: Alexey Kudelko Date: Wed, 21 Feb 2024 20:52:37 +0300 Subject: [PATCH 55/55] get details added 2 --- project_rates/urls.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project_rates/urls.py b/project_rates/urls.py index 755794ef..9caa4056 100644 --- a/project_rates/urls.py +++ b/project_rates/urls.py @@ -1,9 +1,9 @@ from django.urls import path -from project_rates.views import RateProject, RateProjects - +from project_rates.views import RateProject, RateProjects, RateProjectsDetails urlpatterns = [ path("rate/", RateProject.as_view()), path("", RateProjects.as_view()), + path("details/", RateProjectsDetails.as_view()), ]