Skip to content

Commit

Permalink
Merge pull request #172 from Pet-projects-CodePET/develop
Browse files Browse the repository at this point in the history
Fixed bugs
  • Loading branch information
ArtemKAF authored Apr 18, 2024
2 parents 2e4301a + 23e1698 commit c9037cf
Show file tree
Hide file tree
Showing 19 changed files with 289 additions and 129 deletions.
10 changes: 10 additions & 0 deletions src/backend/api/v1/general/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import Dict


class ToRepresentationOnlyIdMixin:
"""Миксин с методом to_representation, возвращающим только id объекта."""

def to_representation(self, instance) -> Dict[str, int]:
"""Метод представления объекта в виде словаря с полем 'id'."""

return {"id": instance.id}
6 changes: 3 additions & 3 deletions src/backend/api/v1/general/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework import serializers

from apps.general.models import Section, Skill, Specialist
from apps.general.models import Profession, Section, Skill


class SectionSerializer(serializers.ModelSerializer):
Expand All @@ -9,11 +9,11 @@ class Meta:
fields = "__all__"


class SpecialistSerializer(serializers.ModelSerializer):
class ProfessionSerializer(serializers.ModelSerializer):
"""Сериализатор специализации."""

class Meta:
model = Specialist
model = Profession
fields = "__all__"


Expand Down
8 changes: 4 additions & 4 deletions src/backend/api/v1/general/urls.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from django.urls import include, path
from rest_framework.routers import DefaultRouter
from rest_framework.routers import SimpleRouter

from api.v1.general.views import (
CounterApiView,
ProfessionViewSet,
SectionViewSet,
SkillViewSet,
SpecialistViewSet,
)

router = DefaultRouter()
router.register("specialists", SpecialistViewSet, basename="specialists")
router = SimpleRouter()
router.register("professions", ProfessionViewSet, basename="professions")
router.register("skills", SkillViewSet, basename="skills")

urlpatterns = [
Expand Down
10 changes: 5 additions & 5 deletions src/backend/api/v1/general/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from rest_framework.response import Response

from api.v1.general.serializers import (
ProfessionSerializer,
SectionSerializer,
SkillSerializer,
SpecialistSerializer,
)
from apps.general.models import Section, Skill, Specialist
from apps.general.models import Profession, Section, Skill


class SectionViewSet(viewsets.ReadOnlyModelViewSet):
Expand Down Expand Up @@ -39,11 +39,11 @@ def get(self, request):
return Response({"projects": row[0][0], "users": row[1][0]})


class SpecialistViewSet(viewsets.ReadOnlyModelViewSet):
class ProfessionViewSet(viewsets.ReadOnlyModelViewSet):
"""Представление специальностей."""

queryset = Specialist.objects.all()
serializer_class = SpecialistSerializer
queryset = Profession.objects.all()
serializer_class = ProfessionSerializer
permission_classes = (IsAuthenticated,)


Expand Down
4 changes: 2 additions & 2 deletions src/backend/api/v1/profile/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework import serializers

from apps.profile.models import Profile, UserSkill, UserSpecialization
from apps.projects.models import Skill, Specialist
from apps.projects.models import Profession, Skill


class ProfileSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -46,7 +46,7 @@ class Meta:

class UserSpecializationSerializer(serializers.ModelSerializer):
specialization = serializers.PrimaryKeyRelatedField(
queryset=Specialist.objects.all(), many=True
queryset=Profession.objects.all(), many=True
)

class Meta:
Expand Down
32 changes: 11 additions & 21 deletions src/backend/api/v1/projects/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ def get_recruitment_status(self, obj) -> str:
"""Метод определения статуса набора в проект."""

if any(
specialist.is_required
for specialist in obj.project_specialists.all()
profession.is_required
for profession in obj.project_specialists.all()
):
return "Набор открыт"
return "Набор закрыт"
Expand Down Expand Up @@ -57,12 +57,12 @@ def _check_not_unique_project_specialists(
) -> bool:
"""
Метод проверки дублирования специалистов необходимых проекту по их
специальности и грейду.
профессии и грейду.
"""

if project_specialists_data is not None:
project_specialists_fields = [
(data["specialist"], data["level"])
(data["profession"], data["level"])
for data in project_specialists_data
]
return len(project_specialists_data) != len(
Expand Down Expand Up @@ -133,21 +133,12 @@ def validate(self, attrs) -> Dict[str, Any]:
return attrs


class ToRepresentationOnlyIdMixin:
"""Миксин с методом to_representation, возвращающим только id объекта."""

def to_representation(self, instance):
"""Метод представления объекта в виде словаря с полем 'id'."""

return {"id": instance.id}


class ProjectOrDraftCreateUpdateMixin:
"""Миксин создания и редактирования проекта или его черновика."""

def process_project_specialists(
self, project_instance, project_specialists
):
) -> None:
"""Метод обработки специалистов необходимых проекту."""
project_specialists_to_update = []
skills_data_to_process: Queue[List[Skill]] = Queue()
Expand Down Expand Up @@ -175,28 +166,27 @@ def process_project_specialists(
if skills_data:
project_specialist.skills.set(skills_data)

def create(self, validated_data):
def create(self, validated_data) -> Project:
"""Метод создания проекта или его черновика."""

project_specialists = validated_data.pop("project_specialists", None)

with transaction.atomic():
project_instance = super().create(validated_data)
project_instance = super().create(validated_data) # type: ignore
self.process_project_specialists(
project_instance, project_specialists
)

return project_instance

def update(self, instance, validated_data):
def update(self, instance, validated_data) -> Project:
"""Метод обновления проекта или его черновика."""

project_specialists = validated_data.pop("project_specialists", None)
_ = validated_data.pop("project_specialists", None)

with transaction.atomic():
project_instance = super().update(instance, validated_data)
self.process_project_specialists(
project_instance, project_specialists
project_instance = super().update( # type: ignore
instance, validated_data
)

return project_instance
14 changes: 12 additions & 2 deletions src/backend/api/v1/projects/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class IsCreatorOrOwner(IsAuthenticated):
"""

def has_object_permission(self, request, view, obj):
return bool(request.user == (obj.owner or obj.creator))
return bool(request.user in (obj.owner, obj.creator))


class IsCreatorOrOwnerOrReadOnly(IsAuthenticated):
Expand All @@ -20,5 +20,15 @@ class IsCreatorOrOwnerOrReadOnly(IsAuthenticated):
def has_object_permission(self, request, view, obj):
return bool(
request.method in SAFE_METHODS
or request.user == (obj.owner or obj.creator)
or request.user in (obj.owner, obj.creator)
)


class IsProjectCreatorOrOwner(IsAuthenticated):
"""
Класс прав доступа на чтение и редактирование только создателю или
владельцу проекта.
"""

def has_object_permission(self, request, view, obj):
return bool(request.user in (obj.project.owner, obj.project.creator))
73 changes: 54 additions & 19 deletions src/backend/api/v1/projects/serializers.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from typing import Any, Dict, List, Optional
from typing import Any, Dict

from rest_framework import serializers

from api.v1.general.serializers import SkillSerializer, SpecialistSerializer
from api.v1.general.mixins import ToRepresentationOnlyIdMixin
from api.v1.general.serializers import ProfessionSerializer, SkillSerializer
from api.v1.projects.mixins import (
ProjectOrDraftCreateUpdateMixin,
ProjectOrDraftValidateMixin,
RecruitmentStatusMixin,
ToRepresentationOnlyIdMixin,
)
from apps.projects.constants import BUSYNESS_CHOICES, STATUS_CHOICES
from apps.projects.constants import BUSYNESS_CHOICES, PROJECT_STATUS_CHOICES
from apps.projects.models import Direction, Project, ProjectSpecialist


Expand All @@ -28,7 +28,7 @@ class Meta:
model = ProjectSpecialist
fields = (
"id",
"specialist",
"profession",
"skills",
"count",
"level",
Expand All @@ -39,7 +39,7 @@ class Meta:
class ReadProjectSpecialistSerializer(BaseProjectSpecialistSerializer):
"""Сериализатор для чтения специалиста необходимого проекту."""

specialist = SpecialistSerializer()
profession = ProfessionSerializer()
skills = SkillSerializer(many=True)
level = serializers.SerializerMethodField()

Expand Down Expand Up @@ -93,7 +93,7 @@ class ReadProjectSerializer(RecruitmentStatusMixin, BaseProjectSerializer):

directions = DirectionSerializer(many=True)
status = serializers.ChoiceField(
choices=STATUS_CHOICES, source="get_status_display"
choices=PROJECT_STATUS_CHOICES, source="get_status_display"
)
busyness = serializers.ChoiceField(
choices=BUSYNESS_CHOICES, source="get_busyness_display"
Expand All @@ -116,7 +116,6 @@ class WriteProjectSerializer(
"""Сериализатор для записи проектов."""

project_specialists = BaseProjectSpecialistSerializer(many=True)
status = serializers.ChoiceField(choices=STATUS_CHOICES, write_only=True)

class Meta(BaseProjectSerializer.Meta):
extra_kwargs = {
Expand All @@ -125,6 +124,8 @@ class Meta(BaseProjectSerializer.Meta):
"ended": {"required": True},
"busyness": {"required": True},
"directions": {"required": True},
"link": {"required": False},
"status": {"required": False},
}

def validate_status(self, value) -> int:
Expand All @@ -137,19 +138,24 @@ def validate_status(self, value) -> int:
return value


class ProjectPreviewMainSerializer(serializers.ModelSerializer):
"""Сериализатор превью проектов."""
class ShortProjectSpecialistSerializer(BaseProjectSpecialistSerializer):
"""Сериализатор специалистов проектов краткий."""

profession = ProfessionSerializer()

specialists = serializers.SerializerMethodField()
directions = serializers.StringRelatedField(many=True)
class Meta(BaseProjectSpecialistSerializer.Meta):
fields = ( # type: ignore
"id",
"profession",
"is_required",
)

def get_specialists(self, obj) -> Optional[List[Dict[str, Any]]]:
"""Метод получения списка специалистов."""

return [
SpecialistSerializer(specialist.specialist).data
for specialist in obj.project_specialists.all()
]
class ProjectPreviewMainSerializer(serializers.ModelSerializer):
"""Сериализатор превью проектов."""

project_specialists = ShortProjectSpecialistSerializer(many=True)
directions = DirectionSerializer(many=True)

class Meta:
model = Project
Expand All @@ -159,7 +165,7 @@ class Meta:
"started",
"ended",
"directions",
"specialists",
"project_specialists",
)


Expand All @@ -184,3 +190,32 @@ class Meta(BaseProjectSerializer.Meta):
"status": {"required": False},
"link": {"required": False},
}


class WriteProjectSpecialistSerializer(
ToRepresentationOnlyIdMixin,
BaseProjectSpecialistSerializer,
):
"""Сериализатор для записи специалиста необходимого проекту."""

def validate(self, attrs) -> Dict[str, Any]:
"""Метод валидации специалиста проекта."""

errors: Dict = {}

queryset = ProjectSpecialist.objects.filter(
project_id=self.instance.project.id,
profession=(
attrs.get("profession", None) or self.instance.profession
),
level=(attrs.get("level", None) or self.instance.level),
).exclude(id=self.instance.id)
if queryset.exists():
errors.setdefault("unique", []).append(
"У данного проекта уже есть специалист с такой профессией и "
"грейдом."
)

if errors:
raise serializers.ValidationError(errors)
return attrs
18 changes: 14 additions & 4 deletions src/backend/api/v1/projects/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,27 @@
DirectionViewSet,
DraftViewSet,
ProjectPreviewMainViewSet,
ProjectSpecialistsViewSet,
ProjectViewSet,
)

router = SimpleRouter()

router.register(
"preview_main", ProjectPreviewMainViewSet, basename="projects-preview-main"
"projects/preview_main",
ProjectPreviewMainViewSet,
basename="projects-preview-main",
)
router.register(
r"projects/project_specialists",
ProjectSpecialistsViewSet,
basename="projects-specialists",
)
router.register(
r"projects/directions", DirectionViewSet, basename="projects-directions"
)
router.register("directions", DirectionViewSet, basename="projects-directions")
router.register("drafts", DraftViewSet, basename="projects-drafts")
router.register("", ProjectViewSet, basename="projects")
router.register(r"projects/drafts", DraftViewSet, basename="projects-drafts")
router.register(r"projects", ProjectViewSet, basename="projects")


urlpatterns = [
Expand Down
Loading

0 comments on commit c9037cf

Please sign in to comment.