1- from django .db .models import Q
1+ from django .db .models import Q , Max
2+ from drf_yasg import openapi
23from drf_yasg .utils import swagger_auto_schema
3- from rest_framework import permissions , viewsets
4+ from rest_framework import permissions , status , viewsets
5+ from rest_framework .decorators import action
6+ from rest_framework .exceptions import ValidationError
47from rest_framework .response import Response
5- from rest_framework import status
68
79from kanban .models import Board , BoardColumn
810from kanban .serializers import BoardSerializer , BoardColumnSerializer
@@ -15,6 +17,8 @@ class BoardViewSet(viewsets.ModelViewSet):
1517
1618 def get_queryset (self ):
1719 user = self .request .user
20+ if not user .is_authenticated :
21+ return Board .objects .none ()
1822 collaborator_projects = Collaborator .objects .filter (user = user ).values (
1923 "project_id"
2024 )
@@ -55,13 +59,23 @@ def update(self, request, *args, **kwargs):
5559 def destroy (self , request , * args , ** kwargs ):
5660 return super ().destroy (request , * args , ** kwargs )
5761
62+ @swagger_auto_schema (tags = ["Kanban Boards" ])
63+ @action (detail = True , methods = ["get" ], url_path = "columns" )
64+ def columns (self , request , pk = None ):
65+ board = self .get_object ()
66+ columns = board .columns .order_by ("order" , "id" )
67+ serializer = BoardColumnSerializer (columns , many = True )
68+ return Response (serializer .data )
69+
5870
5971class BoardColumnViewSet (viewsets .ModelViewSet ):
6072 serializer_class = BoardColumnSerializer
6173 permission_classes = [permissions .IsAuthenticated ]
6274
6375 def get_queryset (self ):
6476 user = self .request .user
77+ if not user .is_authenticated :
78+ return BoardColumn .objects .none ()
6579 collaborator_projects = Collaborator .objects .filter (user = user ).values (
6680 "project_id"
6781 )
@@ -104,3 +118,92 @@ def destroy(self, request, *args, **kwargs):
104118 if isinstance (exc , ValidationError ):
105119 return Response (exc .messages , status = status .HTTP_400_BAD_REQUEST )
106120 raise
121+
122+ def perform_create (self , serializer ):
123+ board = serializer .validated_data .get ("board" )
124+ if not board :
125+ raise ValidationError ("Укажите board" )
126+
127+ user = self .request .user
128+ collaborator_projects = set (
129+ Collaborator .objects .filter (user = user ).values_list ("project_id" , flat = True )
130+ )
131+ if not (
132+ board .project .leader_id == user .id
133+ or board .project_id in collaborator_projects
134+ ):
135+ raise ValidationError ("Пользователь не является участником проекта" )
136+
137+ next_order = (
138+ BoardColumn .objects .filter (board = board ).aggregate (Max ("order" ))["order__max" ]
139+ or 0
140+ )
141+ serializer .save (board = board , order = next_order + 1 )
142+
143+ @swagger_auto_schema (
144+ methods = ["post" ],
145+ tags = ["Kanban Columns" ],
146+ operation_summary = "Переместить колонку" ,
147+ operation_description = "Изменяет порядок колонки внутри доски." ,
148+ request_body = openapi .Schema (
149+ type = openapi .TYPE_OBJECT ,
150+ properties = {"new_order" : openapi .Schema (type = openapi .TYPE_INTEGER )},
151+ required = ["new_order" ],
152+ ),
153+ )
154+ @action (detail = True , methods = ["post" ], url_path = "move" )
155+ def move (self , request , pk = None ):
156+ from django .db import transaction
157+ from channels .layers import get_channel_layer
158+ from asgiref .sync import async_to_sync
159+ from kanban .models import BoardColumn
160+
161+ try :
162+ new_order = int (request .data .get ("new_order" ))
163+ except (TypeError , ValueError ):
164+ return Response (
165+ {"detail" : "new_order должен быть числом" },
166+ status = status .HTTP_400_BAD_REQUEST ,
167+ )
168+
169+ column = (
170+ BoardColumn .objects .select_related ("board" , "board__project" )
171+ .select_for_update ()
172+ .get (pk = pk )
173+ )
174+
175+ with transaction .atomic ():
176+ columns = list (
177+ BoardColumn .objects .filter (board = column .board )
178+ .select_for_update ()
179+ .order_by ("order" , "id" )
180+ )
181+ columns = [c for c in columns if c .id != column .id ]
182+ insert_index = max (0 , min (new_order - 1 , len (columns )))
183+ columns .insert (insert_index , column )
184+
185+ # Шаг 1: временно сдвигаем порядки, чтобы не ловить UNIQUE при массовом обновлении
186+ for idx , col in enumerate (columns , start = 1 ):
187+ col .order = idx + 1000
188+ BoardColumn .objects .bulk_update (columns , ["order" ])
189+
190+ # Шаг 2: выставляем финальные порядки
191+ for idx , col in enumerate (columns , start = 1 ):
192+ col .order = idx
193+
194+ BoardColumn .objects .bulk_update (columns , ["order" ])
195+
196+ channel_layer = get_channel_layer ()
197+ payload = {
198+ "action" : "column.reordered" ,
199+ "board_id" : column .board_id ,
200+ "project_id" : column .board .project_id ,
201+ "columns" : [{"id" : c .id , "order" : c .order , "name" : c .name } for c in columns ],
202+ }
203+ async_to_sync (channel_layer .group_send )(
204+ f"kanban_{ column .board .project_id } " ,
205+ {"type" : "kanban.event" , "payload" : payload },
206+ )
207+
208+ serializer = self .get_serializer (columns , many = True )
209+ return Response (serializer .data , status = status .HTTP_200_OK )
0 commit comments