Skip to content

Commit 601b3d0

Browse files
authored
Merge pull request #87 from TurTaskProject/feature/kanban-board
Fix Kanban not work with api, fix style and now, calendar can only delete and view. Fix profile update page
2 parents 2733379 + 145f19f commit 601b3d0

23 files changed

Lines changed: 1004 additions & 216 deletions

backend/tasks/misc/views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from rest_framework import viewsets
2+
from rest_framework.permissions import IsAuthenticated
23
from ..models import Tag
34
from .serializers import TagSerializer
45

56

67
class TagViewSet(viewsets.ModelViewSet):
78
queryset = Tag.objects.all()
8-
serializer_class = TagSerializer
9+
serializer_class = TagSerializer
10+
permission_classes = (IsAuthenticated,)

backend/tasks/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ class EisenhowerMatrix(models.IntegerChoices):
8181
priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT)
8282

8383
def save(self, *args, **kwargs):
84+
done_list_name = "Done"
85+
if self.list_board.name == done_list_name:
86+
self.completed = True
87+
Todo.objects.filter(list_board=self.list_board).update(completed=True)
88+
8489
if self.completed and not self.completion_date:
8590
self.completion_date = timezone.now()
8691
elif not self.completed:

backend/tasks/tasks/serializers.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
from rest_framework import serializers
22
from users.models import CustomUser
33
from boards.models import ListBoard
4-
from tasks.models import Todo, RecurrenceTask, Habit
4+
from tasks.models import Todo, RecurrenceTask, Habit, Subtask
55

66
class TaskSerializer(serializers.ModelSerializer):
7+
tags = serializers.SerializerMethodField()
8+
sub_task_count = serializers.SerializerMethodField()
9+
710
class Meta:
811
model = Todo
912
fields = '__all__'
@@ -19,6 +22,12 @@ def create(self, validated_data):
1922
validated_data['user'] = user
2023
return Todo.objects.create(**validated_data)
2124

25+
def get_tags(self, instance):
26+
return [tag.name for tag in instance.tags.all()]
27+
28+
def get_sub_task_count(self, instance):
29+
return instance.subtask_set.count()
30+
2231
class TaskCreateSerializer(serializers.ModelSerializer):
2332
class Meta:
2433
model = Todo
@@ -97,4 +106,14 @@ def create(self, validated_data):
97106
class HabitTaskCreateSerializer(serializers.ModelSerializer):
98107
class Meta:
99108
model = Habit
100-
exclude = ('tags',)
109+
exclude = ('tags',)
110+
111+
112+
class SubTaskSerializer(serializers.ModelSerializer):
113+
class Meta:
114+
model = Subtask
115+
fields = '__all__'
116+
117+
def create(self, validated_data):
118+
# Create a new task with validated data
119+
return Subtask.objects.create(**validated_data)

backend/tasks/tasks/views.py

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
from rest_framework.decorators import action
55
from rest_framework.permissions import IsAuthenticated
66
from rest_framework.response import Response
7+
from rest_framework import mixins
78

8-
from .serializers import ChangeTaskListBoardSerializer, ChangeTaskOrderSerializer
9+
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter
10+
11+
from tasks.tasks.serializers import ChangeTaskListBoardSerializer, ChangeTaskOrderSerializer, SubTaskSerializer
912
from boards.models import ListBoard, KanbanTaskOrder
10-
from tasks.models import Todo, RecurrenceTask, Habit
13+
from tasks.models import Todo, RecurrenceTask, Habit, Subtask
1114
from tasks.tasks.serializers import (TaskCreateSerializer,
1215
TaskSerializer,
1316
RecurrenceTaskSerializer,
@@ -32,6 +35,18 @@ def get_serializer_class(self):
3235
return TaskCreateSerializer
3336
return TaskSerializer
3437

38+
def list(self, request, *args, **kwargs):
39+
"""
40+
list all tasks of the authenticated
41+
user and send tags if those Todo too.
42+
"""
43+
try:
44+
queryset = self.get_queryset()
45+
serializer = TaskSerializer(queryset, many=True)
46+
return Response(serializer.data)
47+
except Exception as e:
48+
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
49+
3550
def create(self, request, *args, **kwargs):
3651
try:
3752
new_task_data = request.data
@@ -117,6 +132,73 @@ def change_task_list_board(self, request):
117132
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
118133

119134

135+
@extend_schema_view(
136+
list=extend_schema(
137+
parameters=[
138+
OpenApiParameter(name='parent_task', description='Parent Task ID', type=int),
139+
]
140+
)
141+
)
142+
class SubTaskViewset(viewsets.GenericViewSet,
143+
mixins.CreateModelMixin,
144+
mixins.DestroyModelMixin,
145+
mixins.ListModelMixin,
146+
mixins.UpdateModelMixin):
147+
queryset = Subtask.objects.all()
148+
permission_classes = (IsAuthenticated,)
149+
150+
def get_serializer_class(self):
151+
return SubTaskSerializer
152+
153+
def list(self, request, *args, **kwargs):
154+
"""List only subtask of parent task."""
155+
try:
156+
parent_task = request.query_params.get('parent_task')
157+
if not parent_task:
158+
raise serializers.ValidationError('parent_task is required.')
159+
queryset = self.get_queryset().filter(parent_task_id=parent_task)
160+
serializer = self.get_serializer(queryset, many=True)
161+
return Response(serializer.data)
162+
163+
except Exception as e:
164+
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
165+
166+
def create(self, request, *args, **kwargs):
167+
"""Create a new subtask, point to some parent tasks."""
168+
try:
169+
serializer = self.get_serializer(data=request.data)
170+
serializer.is_valid(raise_exception=True)
171+
self.perform_create(serializer)
172+
return Response(serializer.data)
173+
174+
except Exception as e:
175+
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
176+
177+
178+
def destroy(self, request, *args, **kwargs):
179+
"""Delete a subtask."""
180+
try:
181+
instance = self.get_object()
182+
self.perform_destroy(instance)
183+
return Response(status=status.HTTP_204_NO_CONTENT)
184+
185+
except Exception as e:
186+
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
187+
188+
189+
def partial_update(self, request, *args, **kwargs):
190+
"""Update a subtask."""
191+
try:
192+
instance = self.get_object()
193+
serializer = self.get_serializer(instance, data=request.data, partial=True)
194+
serializer.is_valid(raise_exception=True)
195+
self.perform_update(serializer)
196+
return Response(serializer.data)
197+
198+
except Exception as e:
199+
return Response({'error': str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
200+
201+
120202
class RecurrenceTaskViewSet(viewsets.ModelViewSet):
121203
queryset = RecurrenceTask.objects.all()
122204
serializer_class = RecurrenceTaskSerializer
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from datetime import datetime, timedelta
2+
from django.urls import reverse
3+
from rest_framework import status
4+
from rest_framework.test import APITestCase
5+
from tasks.tests.utils import create_test_user
6+
from tasks.models import Todo
7+
from boards.models import ListBoard, Board
8+
9+
10+
class TodoSignalHandlersTests(APITestCase):
11+
def setUp(self):
12+
self.user = create_test_user()
13+
self.client.force_authenticate(user=self.user)
14+
self.list_board = Board.objects.get(user=self.user).listboard_set.first()
15+
16+
def test_update_priority_signal_handler(self):
17+
"""
18+
Test the behavior of the update_priority signal handler.
19+
"""
20+
due_date = datetime.now() + timedelta(days=5)
21+
data = {
22+
'title': 'Test Task',
23+
'type': 'habit',
24+
'difficulty': 1,
25+
'end_event': due_date.strftime('%Y-%m-%dT%H:%M:%S'),
26+
'list_board': self.list_board.id,
27+
}
28+
response = self.client.post(reverse("todo-list"), data, format='json')
29+
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
30+
31+
# Retrieve the created task and check if priority is updated
32+
task = Todo.objects.get(title='Test Task')
33+
self.assertIsNotNone(task.priority) # Check if priority is not None

backend/tasks/urls.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from rest_framework.routers import DefaultRouter
44

55
from tasks.api import GoogleCalendarEventViewset
6-
from tasks.tasks.views import TodoViewSet, RecurrenceTaskViewSet, HabitTaskViewSet
6+
from tasks.tasks.views import TodoViewSet, RecurrenceTaskViewSet, HabitTaskViewSet, SubTaskViewset
77
from tasks.misc.views import TagViewSet
88

99

@@ -13,6 +13,7 @@
1313
router.register(r'habit', HabitTaskViewSet)
1414
router.register(r'tags', TagViewSet)
1515
router.register(r'calendar-events', GoogleCalendarEventViewset, basename='calendar-events')
16+
router.register(r'subtasks', SubTaskViewset, basename='subtasks')
1617

1718
urlpatterns = [
1819
path('', include(router.urls)),

backend/users/serializers.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,32 @@ class UpdateProfileSerializer(serializers.ModelSerializer):
3232
Serializer for updating user profile.
3333
"""
3434
profile_pic = serializers.ImageField(required=False)
35-
first_name = serializers.CharField(max_length=255, required=False)
35+
username = serializers.CharField(max_length=255, required=False)
3636
about = serializers.CharField(required=False)
3737

3838
class Meta:
3939
model = CustomUser
40-
fields = ('profile_pic', 'first_name', 'about')
40+
fields = ('profile_pic', 'username', 'about')
41+
42+
def update(self, instance, validated_data):
43+
"""
44+
Update an existing user's profile.
45+
"""
46+
for attr, value in validated_data.items():
47+
setattr(instance, attr, value)
48+
instance.save()
49+
return instance
50+
51+
class UpdateProfileNopicSerializer(serializers.ModelSerializer):
52+
"""
53+
Serializer for updating user profile.
54+
"""
55+
username = serializers.CharField(max_length=255, required=False)
56+
about = serializers.CharField(required=False)
57+
58+
class Meta:
59+
model = CustomUser
60+
fields = ('username', 'about')
4161

4262
def update(self, instance, validated_data):
4363
"""

backend/users/views.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from rest_framework_simplejwt.tokens import RefreshToken
1111

12-
from users.serializers import CustomUserSerializer, UpdateProfileSerializer
12+
from users.serializers import CustomUserSerializer, UpdateProfileSerializer, UpdateProfileNopicSerializer
1313
from users.models import CustomUser
1414

1515
class CustomUserCreate(APIView):
@@ -57,13 +57,17 @@ def post(self, request):
5757
return Response ({
5858
'error': 'User does not exist'
5959
}, status=status.HTTP_404_NOT_FOUND)
60+
6061
serializer = UpdateProfileSerializer(request.user, data=request.data)
62+
if request.data.get('profile_pic') == "null":
63+
serializer = UpdateProfileNopicSerializer(request.user, data=request.data)
64+
6165
if serializer.is_valid():
6266
serializer.save()
6367
return Response(serializer.data)
6468
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
6569

66-
70+
6771
class UserDataRetriveViewset(viewsets.GenericViewSet, mixins.RetrieveModelMixin):
6872
queryset = CustomUser.objects.all()
6973
permission_classes = (IsAuthenticated,)
@@ -72,4 +76,4 @@ class UserDataRetriveViewset(viewsets.GenericViewSet, mixins.RetrieveModelMixin)
7276
def retrieve(self, request, *args, **kwargs):
7377
serializer = self.get_serializer(request.user)
7478
return Response(serializer.data)
75-
79+

frontend/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"@wojtekmaj/react-daterange-picker": "^5.4.4",
3535
"axios": "^1.6.1",
3636
"bootstrap": "^5.3.2",
37+
"date-fns": "^2.30.0",
3738
"dotenv": "^16.3.1",
3839
"framer-motion": "^10.16.4",
3940
"gapi-script": "^1.2.0",
@@ -42,9 +43,11 @@
4243
"react": "^18.2.0",
4344
"react-beautiful-dnd": "^13.1.1",
4445
"react-bootstrap": "^2.9.1",
46+
"react-datepicker": "^4.23.0",
4547
"react-datetime-picker": "^5.5.3",
4648
"react-dom": "^18.2.0",
4749
"react-icons": "^4.11.0",
50+
"react-ios-time-picker": "^0.2.2",
4851
"react-router-dom": "^6.18.0",
4952
"react-tsparticles": "^2.12.2",
5053
"tsparticles": "^2.12.0"

0 commit comments

Comments
 (0)