Skip to content

Commit 9613af9

Browse files
committed
Релизован стандартный CRUD для Колонок Доски
1 parent f635997 commit 9613af9

4 files changed

Lines changed: 145 additions & 3 deletions

File tree

kanban/serializers.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,40 @@ def create(self, validated_data):
4545
if not board.columns.exists():
4646
BoardColumn.objects.create(board=board, name="Бэклог", order=1)
4747
return board
48+
49+
50+
class BoardColumnSerializer(serializers.ModelSerializer):
51+
board = serializers.PrimaryKeyRelatedField(queryset=Board.objects.all())
52+
tasks_count = serializers.IntegerField(read_only=True)
53+
54+
class Meta:
55+
model = BoardColumn
56+
fields = (
57+
"id",
58+
"board",
59+
"name",
60+
"order",
61+
"tasks_count",
62+
"created_at",
63+
"updated_at",
64+
)
65+
read_only_fields = ("id", "tasks_count", "created_at", "updated_at")
66+
67+
def validate_board(self, board: Board):
68+
request = self.context.get("request")
69+
user = getattr(request, "user", None)
70+
if not user or not user.is_authenticated:
71+
raise serializers.ValidationError("Требуется аутентификация")
72+
73+
if board.project.leader_id == user.id:
74+
return board
75+
76+
is_member = Collaborator.objects.filter(
77+
project_id=board.project_id,
78+
user_id=user.id,
79+
).exists()
80+
if not is_member:
81+
raise serializers.ValidationError(
82+
"Пользователь не является участником проекта"
83+
)
84+
return board

kanban/tests/test_column_api.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from django.urls import reverse
2+
from rest_framework import status
3+
from rest_framework.test import APIClient
4+
5+
from kanban.models import Board, BoardColumn
6+
from kanban.tests.base import BaseKanbanTestCase
7+
from projects.models import Project
8+
from users.models import CustomUser
9+
10+
11+
class ColumnAPITests(BaseKanbanTestCase):
12+
def setUp(self):
13+
super().setUp()
14+
self.client = APIClient()
15+
self.client.force_authenticate(user=self.user)
16+
self.url = reverse("kanban:kanban-column-list")
17+
18+
def test_list_columns_returns_board_columns(self):
19+
response = self.client.get(self.url)
20+
self.assertEqual(response.status_code, status.HTTP_200_OK)
21+
self.assertGreaterEqual(len(response.data), 1)
22+
self.assertEqual(response.data[0]["board"], self.board.id)
23+
24+
def test_create_column(self):
25+
payload = {"board": self.board.id, "name": "In Progress", "order": 5}
26+
response = self.client.post(self.url, payload, format="json")
27+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
28+
column_id = response.data["id"]
29+
column = BoardColumn.objects.get(id=column_id)
30+
self.assertEqual(column.name, "In Progress")
31+
32+
def test_cannot_create_column_in_foreign_project(self):
33+
foreign_leader = CustomUser.objects.create(
34+
email="foreign@example.com",
35+
first_name="Foreign",
36+
last_name="Leader",
37+
password="pass1234",
38+
birthday=self.user.birthday,
39+
)
40+
foreign_project = Project.objects.create(name="Foreign", leader=foreign_leader)
41+
foreign_board = Board.objects.create(
42+
project=foreign_project, name="Foreign Board"
43+
)
44+
payload = {"board": foreign_board.id, "name": "Forbidden", "order": 1}
45+
response = self.client.post(self.url, payload, format="json")
46+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
47+
48+
def test_cannot_delete_last_column(self):
49+
self.board.columns.exclude(pk=self.column.pk).delete()
50+
col_url = reverse("kanban:kanban-column-detail", args=[self.column.id])
51+
response = self.client.delete(col_url)
52+
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

kanban/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from django.urls import include, path
22
from rest_framework.routers import DefaultRouter
33

4-
from kanban.views import BoardViewSet
4+
from kanban.views import BoardViewSet, BoardColumnViewSet
55

66
app_name = "kanban"
77

88
router = DefaultRouter()
99
router.register(r"boards", BoardViewSet, basename="kanban-board")
10+
router.register(r"columns", BoardColumnViewSet, basename="kanban-column")
1011

1112
urlpatterns = [path("", include(router.urls))]

kanban/views.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from django.db.models import Q
22
from drf_yasg.utils import swagger_auto_schema
33
from rest_framework import permissions, viewsets
4+
from rest_framework.response import Response
5+
from rest_framework import status
46

5-
from kanban.models import Board
6-
from kanban.serializers import BoardSerializer
7+
from kanban.models import Board, BoardColumn
8+
from kanban.serializers import BoardSerializer, BoardColumnSerializer
79
from projects.models import Collaborator
810

911

@@ -52,3 +54,53 @@ def update(self, request, *args, **kwargs):
5254
@swagger_auto_schema(tags=["Kanban Boards"])
5355
def destroy(self, request, *args, **kwargs):
5456
return super().destroy(request, *args, **kwargs)
57+
58+
59+
class BoardColumnViewSet(viewsets.ModelViewSet):
60+
serializer_class = BoardColumnSerializer
61+
permission_classes = [permissions.IsAuthenticated]
62+
63+
def get_queryset(self):
64+
user = self.request.user
65+
collaborator_projects = Collaborator.objects.filter(user=user).values(
66+
"project_id"
67+
)
68+
return (
69+
BoardColumn.objects.select_related("board", "board__project")
70+
.filter(
71+
Q(board__project__leader_id=user.id)
72+
| Q(board__project_id__in=collaborator_projects)
73+
)
74+
.distinct()
75+
)
76+
77+
@swagger_auto_schema(tags=["Kanban Columns"])
78+
def list(self, request, *args, **kwargs):
79+
return super().list(request, *args, **kwargs)
80+
81+
@swagger_auto_schema(tags=["Kanban Columns"])
82+
def create(self, request, *args, **kwargs):
83+
return super().create(request, *args, **kwargs)
84+
85+
@swagger_auto_schema(tags=["Kanban Columns"])
86+
def retrieve(self, request, *args, **kwargs):
87+
return super().retrieve(request, *args, **kwargs)
88+
89+
@swagger_auto_schema(tags=["Kanban Columns"])
90+
def partial_update(self, request, *args, **kwargs):
91+
return super().partial_update(request, *args, **kwargs)
92+
93+
@swagger_auto_schema(tags=["Kanban Columns"])
94+
def update(self, request, *args, **kwargs):
95+
return super().update(request, *args, **kwargs)
96+
97+
@swagger_auto_schema(tags=["Kanban Columns"])
98+
def destroy(self, request, *args, **kwargs):
99+
try:
100+
return super().destroy(request, *args, **kwargs)
101+
except Exception as exc:
102+
from django.core.exceptions import ValidationError
103+
104+
if isinstance(exc, ValidationError):
105+
return Response(exc.messages, status=status.HTTP_400_BAD_REQUEST)
106+
raise

0 commit comments

Comments
 (0)