Skip to content

Commit 9c8c6ad

Browse files
authored
Merge pull request #545 from PROCOLLAB-github/fix/project_goals_bulk_create
Поддержка bulk POST для целей проекта
2 parents 04726df + 7e61a1f commit 9c8c6ad

3 files changed

Lines changed: 70 additions & 31 deletions

File tree

projects/serializers.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@
2626
User = get_user_model()
2727

2828

29+
class EmptySerializer(serializers.Serializer):
30+
pass
31+
32+
2933
class AchievementListSerializer(serializers.ModelSerializer):
3034
class Meta:
3135
model = Achievement
@@ -115,23 +119,34 @@ class Meta:
115119
fields = ("id", "first_name", "last_name", "avatar")
116120

117121

122+
class ProjectGoalBulkListSerializer(serializers.ListSerializer):
123+
"""Bulk_create при POST запросе со списком объектов на /projects/{project_pk}/goals/."""
124+
125+
def create(self, validated_data):
126+
project = self.context["project"]
127+
objs = [ProjectGoal(project=project, **item) for item in validated_data]
128+
created = ProjectGoal.objects.bulk_create(objs)
129+
return created
130+
131+
118132
class ProjectGoalSerializer(serializers.ModelSerializer):
119-
project = serializers.PrimaryKeyRelatedField(read_only=True)
133+
project_id = serializers.IntegerField(read_only=True)
120134
responsible = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
121135
responsible_info = ResponsibleMiniSerializer(source="responsible", read_only=True)
122136

123137
class Meta:
124138
model = ProjectGoal
125139
fields = [
126140
"id",
127-
"project",
141+
"project_id",
128142
"title",
129143
"completion_date",
130144
"responsible",
131145
"responsible_info",
132146
"is_done",
133147
]
134-
read_only_fields = ["id", "project", "responsible_info"]
148+
read_only_fields = ["id", "project_id", "responsible_info"]
149+
list_serializer_class = ProjectGoalBulkListSerializer
135150

136151

137152
class ProjectDetailSerializer(serializers.ModelSerializer):

projects/urls.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@
2222
)
2323

2424
app_name = "projects"
25-
project_goal_list = GoalViewSet.as_view({"get": "list", "post": "create"})
25+
project_goal_list = GoalViewSet.as_view(
26+
{
27+
"get": "list",
28+
"post": "create",
29+
}
30+
)
31+
2632
project_goal_detail = GoalViewSet.as_view(
2733
{
2834
"get": "retrieve",
@@ -55,9 +61,7 @@
5561
),
5662
path("<int:pk>/", ProjectDetail.as_view()),
5763
path("<int:pk>/recommended_users", ProjectRecommendedUsers.as_view()),
58-
path(
59-
"assign-to-program/", DuplicateProjectView.as_view(), name="duplicate-project"
60-
),
64+
path("assign-to-program/", DuplicateProjectView.as_view(), name="duplicate-project"),
6165
path(
6266
"<int:project_id>/program-fields/",
6367
PartnerProgramFieldValueBulkUpdateView.as_view(),

projects/views.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
from projects.serializers import (
4545
AchievementDetailSerializer,
4646
AchievementListSerializer,
47+
EmptySerializer,
4748
ProjectCollaboratorSerializer,
4849
ProjectDetailSerializer,
4950
ProjectDuplicateRequestSerializer,
@@ -90,9 +91,7 @@ def create(self, request, *args, **kwargs):
9091

9192
try:
9293
partner_program_id = request.data.get("partner_program_id")
93-
update_partner_program(
94-
partner_program_id, request.user, serializer.instance
95-
)
94+
update_partner_program(partner_program_id, request.user, serializer.instance)
9695
except PartnerProgram.DoesNotExist:
9796
return Response(
9897
{"detail": "Partner program with this id does not exist"},
@@ -105,9 +104,7 @@ def create(self, request, *args, **kwargs):
105104
)
106105

107106
headers = self.get_success_headers(serializer.data)
108-
return Response(
109-
serializer.data, status=status.HTTP_201_CREATED, headers=headers
110-
)
107+
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
111108

112109
def post(self, request, *args, **kwargs):
113110
"""
@@ -241,9 +238,7 @@ def get(self, request):
241238
{
242239
"all": self.get_queryset().filter(draft=False).count(),
243240
"my": self.get_queryset()
244-
.filter(
245-
Q(leader_id=request.user.id) | Q(collaborator__user=request.user)
246-
)
241+
.filter(Q(leader_id=request.user.id) | Q(collaborator__user=request.user))
247242
.distinct()
248243
.count(),
249244
},
@@ -313,9 +308,7 @@ def _project_data(
313308
return project.id, project.leader.id
314309

315310
@staticmethod
316-
def _collabs_queryset(
317-
project_id: int, requested_id: int, leader_id: int
318-
) -> QuerySet:
311+
def _collabs_queryset(project_id: int, requested_id: int, leader_id: int) -> QuerySet:
319312
return Collaborator.objects.exclude(
320313
user__id=leader_id
321314
).get( # чтоб случайно лидер сам себя не удалил
@@ -586,9 +579,7 @@ def _project_data(
586579
return project.id, project.leader.id
587580

588581
@staticmethod
589-
def _collabs_queryset(
590-
project_id: int, requested_id: int, leader_id: int
591-
) -> QuerySet:
582+
def _collabs_queryset(project_id: int, requested_id: int, leader_id: int) -> QuerySet:
592583
return Collaborator.objects.exclude(
593584
user__id=leader_id
594585
).get( # чтоб случайно лидер сам себя не удалил
@@ -626,6 +617,7 @@ def delete(self, request, project_pk: int) -> Response:
626617
class SwitchLeaderRole(generics.GenericAPIView):
627618
permission_classes = [IsProjectLeader]
628619
queryset = Project.objects.all().select_related("leader")
620+
serializer_class = EmptySerializer
629621

630622
@staticmethod
631623
def _get_new_leader(user_id: int, project: Project) -> Collaborator:
@@ -677,9 +669,7 @@ def post(self, request):
677669
data = serializer.validated_data
678670

679671
original_project = get_object_or_404(Project, id=data["project_id"])
680-
partner_program = get_object_or_404(
681-
PartnerProgram, id=data["partner_program_id"]
682-
)
672+
partner_program = get_object_or_404(PartnerProgram, id=data["partner_program_id"])
683673

684674
with transaction.atomic():
685675
new_project = Project.objects.create(
@@ -721,16 +711,46 @@ class GoalViewSet(viewsets.ModelViewSet):
721711
permission_classes = [IsProjectLeaderOrReadOnly]
722712

723713
def get_queryset(self):
724-
qs = super().get_queryset()
725714
project_pk = self.kwargs.get("project_pk")
715+
qs = super().get_queryset()
726716
return qs.filter(project_id=project_pk) if project_pk is not None else qs
727717

728-
def perform_create(self, serializer):
718+
def get_serializer_context(self):
719+
ctx = super().get_serializer_context()
729720
project_pk = self.kwargs.get("project_pk")
730-
if project_pk is None:
731-
serializer.save()
732-
else:
733-
serializer.save(project_id=project_pk)
721+
if project_pk and "project" not in ctx:
722+
ctx["project"] = get_object_or_404(Project, pk=project_pk)
723+
return ctx
724+
725+
@swagger_auto_schema(
726+
request_body=ProjectGoalSerializer(many=True),
727+
responses={201: ProjectGoalSerializer(many=True)},
728+
)
729+
def create(self, request, *args, **kwargs):
730+
if not isinstance(request.data, list):
731+
return Response(
732+
{"detail": "В теле запроса должен быть массив целей."}, status=400
733+
)
734+
serializer = self.get_serializer(data=request.data, many=True)
735+
serializer.is_valid(raise_exception=True)
736+
created = serializer.save()
737+
out = self.get_serializer(created, many=True)
738+
return Response(out.data, status=status.HTTP_201_CREATED)
739+
740+
def update(self, request, *args, **kwargs):
741+
if isinstance(request.data, list):
742+
return Response(
743+
{"detail": "Обновление выполняется для одной цели по её ID."}, status=400
744+
)
745+
return super().update(request, *args, **kwargs)
746+
747+
def partial_update(self, request, *args, **kwargs):
748+
if isinstance(request.data, list):
749+
return Response(
750+
{"detail": "Частичное обновление выполняется для одной цели по её ID."},
751+
status=400,
752+
)
753+
return super().partial_update(request, *args, **kwargs)
734754

735755
def perform_update(self, serializer):
736756
serializer.save(project=self.get_object().project)

0 commit comments

Comments
 (0)