diff --git a/backend/boards/apps.py b/backend/boards/apps.py index d10d8fa..cdcb8cf 100644 --- a/backend/boards/apps.py +++ b/backend/boards/apps.py @@ -3,7 +3,4 @@ from django.apps import AppConfig class BoardsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' - name = 'boards' - - def ready(self): - import boards.signals \ No newline at end of file + name = 'boards' \ No newline at end of file diff --git a/backend/boards/signals.py b/backend/boards/signals.py deleted file mode 100644 index 2c44daa..0000000 --- a/backend/boards/signals.py +++ /dev/null @@ -1,17 +0,0 @@ -from django.db.models.signals import post_save -from django.dispatch import receiver - -from boards.models import Board, ListBoard -from users.models import CustomUser - -@receiver(post_save, sender=CustomUser) -def create_default_board(sender, instance, created, **kwargs): - """Signal handler to automatically create a default Board for a user upon creation.""" - if created: - # Create unique board by user id - user_id = instance.id - board = Board.objects.create(user=instance, name=f"Board of #{user_id}") - ListBoard.objects.create(board=board, name="Backlog", position=1) - ListBoard.objects.create(board=board, name="Doing", position=2) - ListBoard.objects.create(board=board, name="Review", position=3) - ListBoard.objects.create(board=board, name="Done", position=4) \ No newline at end of file diff --git a/backend/core/local_settings.py b/backend/core/local_settings.py index 9e7903c..d801d36 100644 --- a/backend/core/local_settings.py +++ b/backend/core/local_settings.py @@ -88,6 +88,7 @@ SPECTACULAR_SETTINGS = { 'DESCRIPTION': 'API documentation for TurTask', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, + 'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAuthenticated'], } REST_USE_JWT = True diff --git a/backend/core/production_settings.py b/backend/core/production_settings.py index dd9eeb3..c23cdb4 100644 --- a/backend/core/production_settings.py +++ b/backend/core/production_settings.py @@ -88,6 +88,7 @@ SPECTACULAR_SETTINGS = { 'DESCRIPTION': 'API documentation for TurTask', 'VERSION': '1.0.0', 'SERVE_INCLUDE_SCHEMA': False, + 'SERVE_PERMISSIONS': ['rest_framework.permissions.IsAuthenticated'], } REST_USE_JWT = True diff --git a/backend/dashboard/serializers.py b/backend/dashboard/serializers.py index ddc207b..8563ec4 100644 --- a/backend/dashboard/serializers.py +++ b/backend/dashboard/serializers.py @@ -1,7 +1,7 @@ -from rest_framework import serializers -from .models import UserStats +# from rest_framework import serializers +# from .models import UserStats -class UserStatsSerializer(serializers.ModelSerializer): - class Meta: - model = UserStats - fields = ['health', 'gold', 'experience', 'strength', 'intelligence', 'endurance', 'perception', 'luck', 'level'] \ No newline at end of file +# class UserStatsSerializer(serializers.ModelSerializer): +# class Meta: +# model = UserStats +# fields = ['health', 'gold', 'experience', 'strength', 'intelligence', 'endurance', 'perception', 'luck', 'level'] \ No newline at end of file diff --git a/backend/dashboard/tests.py b/backend/dashboard/tests.py index 943c3de..9d20ab4 100644 --- a/backend/dashboard/tests.py +++ b/backend/dashboard/tests.py @@ -1,32 +1,35 @@ -from django.test import TestCase +from rest_framework.test import APITestCase from django.urls import reverse from tasks.models import Todo from django.utils import timezone from datetime import timedelta -from tasks.tests.utils import create_test_user, login_user +from boards.models import Board +from tasks.tests.utils import create_test_user -class DashboardStatsAndWeeklyViewSetTests(TestCase): +class DashboardStatsAndWeeklyViewSetTests(APITestCase): def setUp(self): self.user = create_test_user() - self.client = login_user(self.user) + self.client.force_authenticate(user=self.user) + self.list_board = Board.objects.get(user=self.user).listboard_set.first() - def create_task(self, title, completed=False, completion_date=None, end_event=None): + def _create_task(self, title, completed=False, completion_date=None, end_event=None): return Todo.objects.create( user=self.user, title=title, completed=completed, completion_date=completion_date, - end_event=end_event + end_event=end_event, + list_board=self.list_board ) def test_dashboard_stats_view(self): # Create tasks for testing - self.create_task('Task 1', completed=True) - self.create_task('Task 2', end_event=timezone.now() - timedelta(days=8)) - self.create_task('Task 3', end_event=timezone.now()) + self._create_task('Task 1', completed=True) + self._create_task('Task 2', end_event=timezone.now() - timedelta(days=8)) + self._create_task('Task 3', end_event=timezone.now()) - response = self.client.get(reverse('stats-list')) + response = self.client.get(reverse('statstodo-list')) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['completed_this_week'], 1) @@ -35,69 +38,9 @@ class DashboardStatsAndWeeklyViewSetTests(TestCase): def test_dashboard_weekly_view(self): # Create tasks for testing - self.create_task('Task 1', completion_date=timezone.now() - timedelta(days=1)) - self.create_task('Task 2', end_event=timezone.now() - timedelta(days=8)) - self.create_task('Task 3', end_event=timezone.now()) + self._create_task('Task 1', completion_date=timezone.now() - timedelta(days=1)) + self._create_task('Task 2', end_event=timezone.now() - timedelta(days=8)) + self._create_task('Task 3', end_event=timezone.now()) response = self.client.get(reverse('weekly-list')) self.assertEqual(response.status_code, 200) - - -# class DashboardStatsAPITestCase(TestCase): -# def setUp(self): -# # Create a test user -# self.user = create_test_user() - -# # Create test tasks -# self.todo = Todo.objects.create(user=self.user, title='Test Todo') -# self.recurrence_task = RecurrenceTask.objects.create(user=self.user, title='Test Recurrence Task') - -# # Create an API client -# self.client = APIClient() - -# def test_dashboard_stats_api(self): -# # Authenticate the user -# self.client.force_authenticate(user=self.user) - -# # Make a GET request to the DashboardStatsAPIView -# response = self.client.get(reverse("dashboard-stats")) - -# # Assert the response status code is 200 -# self.assertEqual(response.status_code, 200) - -# def test_task_completion_status_update(self): -# # Authenticate the user -# self.client.force_authenticate(user=self.user) - -# # Make a POST request to update the completion status of a task -# data = {'task_id': self.todo.id, 'is_completed': True} -# response = self.client.post(reverse("dashboard-stats"), data, format='json') - -# # Assert the response status code is 200 -# self.assertEqual(response.status_code, 200) - -# # Assert the message in the response -# self.assertEqual(response.data['message'], 'Task completion status updated successfully') - -# # Refresh the todo instance from the database and assert the completion status -# self.todo.refresh_from_db() -# self.assertTrue(self.todo.completed) - - -# class WeeklyStatsAPITestCase(TestCase): -# def setUp(self): -# # Create a test user -# self.user = create_test_user() - -# # Create an API client -# self.client = APIClient() - -# def test_weekly_stats_api(self): -# # Authenticate the user -# self.client.force_authenticate(user=self.user) - -# # Make a GET request to the WeeklyStatsAPIView -# response = self.client.get(reverse('dashboard-weekly-stats')) - -# # Assert the response status code is 200 -# self.assertEqual(response.status_code, 200) diff --git a/backend/dashboard/urls.py b/backend/dashboard/urls.py index 56624ca..af8d154 100644 --- a/backend/dashboard/urls.py +++ b/backend/dashboard/urls.py @@ -1,11 +1,12 @@ from django.urls import path, include from rest_framework.routers import DefaultRouter -from .views import DashboardStatsViewSet, DashboardWeeklyViewSet +from .views import DashboardStatsTodoViewSet, DashboardWeeklyViewSet router = DefaultRouter() -router.register(r'dashboard/stats', DashboardStatsViewSet, basename='stats') +router.register(r'dashboard/todostats', DashboardStatsTodoViewSet, basename='statstodo') router.register(r'dashboard/weekly', DashboardWeeklyViewSet, basename='weekly') +router.register(r'dashboard/recstats', DashboardStatsTodoViewSet, basename='statsrec') urlpatterns = [ path('', include(router.urls)), ] diff --git a/backend/dashboard/views.py b/backend/dashboard/views.py index abe0576..b6fd552 100644 --- a/backend/dashboard/views.py +++ b/backend/dashboard/views.py @@ -5,10 +5,13 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework import viewsets, mixins -from tasks.models import Todo +from tasks.models import Todo, RecurrenceTask -class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): +class DashboardStatsTodoViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): + """ + A viewset for retrieving statistics related to user tasks for the last 7 days. + """ permission_classes = (IsAuthenticated,) def get_queryset(self): @@ -66,6 +69,27 @@ class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): # Overall completion rate total_tasks = Todo.objects.filter(user=user).count() overall_completion_rate = (completed_last_7_days / total_tasks) * 100 if total_tasks > 0 else 0 + + total_completed_tasks = Todo.objects.filter(user=user, completed=True).count() + + total_tasks = Todo.objects.filter(user=user).count() + + today_start = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + today_end = timezone.now().replace(hour=23, minute=59, second=59, microsecond=999999) + + tasks_completed_today = Todo.objects.filter( + user=user, + completed=True, + completion_date__gte=today_start, + completion_date__lte=today_end + ).count() + + total_tasks_today = Todo.objects.filter( + user=user, + completion_date__gte=today_start, + completion_date__lte=today_end + ).count() + data = { "completed_last_7_days": completed_last_7_days, @@ -75,6 +99,10 @@ class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): "completed_this_week": completed_this_week, "overdue_tasks": overdue_tasks, "overall_completion_rate": overall_completion_rate, + "total_completed_tasks": total_completed_tasks, + "total_tasks" : total_tasks, + "total_tasks_today": total_tasks_today, + "tasks_completed_today": tasks_completed_today, } return Response(data, status=status.HTTP_200_OK) @@ -145,7 +173,142 @@ class DashboardWeeklyViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): return Response(weekly_stats, status=status.HTTP_200_OK) +class DashboardStatsReccurenceViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): + """ + A viewset for retrieving statistics related to user tasks for the last 7 days. + """ + permission_classes = (IsAuthenticated,) + def get_queryset(self): + return RecurrenceTask.objects.all() + + def list(self, request, *args, **kwargs): + user = self.request.user + + # Calculate the start and end date for the last 7 days + end_date = timezone.now() + start_date = end_date - timedelta(days=7) + + # How many tasks were completed in the last 7 days + completed_last_7_days = RecurrenceTask.objects.filter( + user=user, + completed=True, + completion_date__gte=start_date, + completion_date__lte=end_date + ).count() + + # Task assign last week compared with this week + tasks_assigned_last_week = RecurrenceTask.objects.filter( + user=user, + completion_date__gte=start_date - timedelta(days=7), + completion_date__lte=start_date + ).count() + + tasks_assigned_this_week = RecurrenceTask.objects.filter( + user=user, + completion_date__gte=start_date, + completion_date__lte=end_date + ).count() + + # Completed tasks from last week compared with this week + completed_last_week = RecurrenceTask.objects.filter( + user=user, + completed=True, + completion_date__gte=start_date - timedelta(days=7), + completion_date__lte=start_date + ).count() + + completed_this_week = RecurrenceTask.objects.filter( + user=user, + completed=True, + completion_date__gte=start_date, + completion_date__lte=end_date + ).count() + + overdue_tasks = RecurrenceTask.objects.filter( + user=user, + completed=False, + end_event__lt=timezone.now() + ).count() + + # Overall completion rate + total_tasks = RecurrenceTask.objects.filter(user=user).count() + overall_completion_rate = (completed_last_7_days / total_tasks) * 100 if total_tasks > 0 else 0 + + total_completed_tasks = RecurrenceTask.objects.filter( + user=user, + completed=True + ).count() + + total_tasks = RecurrenceTask.objects.filter(user=user).count() + + tasks_completed_today = RecurrenceTask.objects.filter( + user=user, + completed=True, + completion_date__gte=timezone.now().replace(hour=0, minute=0, second=0, microsecond=0) + ).count() + + data = { + "completed_last_7_days": completed_last_7_days, + "tasks_assigned_last_week": tasks_assigned_last_week, + "tasks_assigned_this_week": tasks_assigned_this_week, + "completed_last_week": completed_last_week, + "completed_this_week": completed_this_week, + "overdue_tasks": overdue_tasks, + "overall_completion_rate": overall_completion_rate, + "total_completed_tasks": total_completed_tasks, + "total_tasks" : total_tasks, + "tasks_completed_today": tasks_completed_today, + } + + return Response(data, status=status.HTTP_200_OK) + +# class DashboardStatsAllViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): +# permission_classes = [IsAuthenticated] + +# def get_queryset(self): +# return Todo.objects.filter(user=self.request.user) + +# def list(self, request, *args, **kwargs): +# user = request.user + +# # Calculate task usage statistics +# todo_count = self.get_queryset().count() +# recurrence_task_count = RecurrenceTask.objects.filter(user=user).count() + +# # Calculate how many tasks were completed in the last 7 days +# completed_todo_count_last_week = Todo.objects.filter(user=user, completed=True, last_update__gte=timezone.now() - timezone.timedelta(days=7)).count() +# completed_recurrence_task_count_last_week = RecurrenceTask.objects.filter(user=user, completed=True, last_update__gte=timezone.now() - timezone.timedelta(days=7)).count() + +# # Calculate subtask completion rate +# total_subtasks = Todo.objects.filter(user=user).aggregate(total=Count('subtask__id'))['total'] +# completed_subtasks = Todo.objects.filter(user=user, subtask__completed=True).aggregate(total=Count('subtask__id'))['total'] + +# # Calculate overall completion rate +# total_tasks = todo_count + recurrence_task_count +# completed_tasks = completed_todo_count_last_week + completed_recurrence_task_count_last_week +# overall_completion_rate = (completed_tasks / total_tasks) * 100 if total_tasks > 0 else 0 + +# # pie chart show +# complete_todo_percent_last_week = (completed_todo_count_last_week / todo_count) * 100 if todo_count > 0 else 0 +# complete_recurrence_percent_last_week = (completed_recurrence_task_count_last_week / recurrence_task_count) * 100 if recurrence_task_count > 0 else 0 +# incomplete_task_percent_last_week = 100 - complete_recurrence_percent_last_week - complete_todo_percent_last_week + +# data = { +# 'todo_count': todo_count, +# 'recurrence_task_count': recurrence_task_count, +# 'completed_todo_count_last_week': completed_todo_count_last_week, +# 'completed_recurrence_task_count_last_week': completed_recurrence_task_count_last_week, +# 'total_subtasks': total_subtasks, +# 'completed_subtasks': completed_subtasks, +# 'overall_completion_rate': overall_completion_rate, +# 'complete_todo_percent_last_week': complete_todo_percent_last_week, +# 'complete_recurrence_percent_last_week' : complete_recurrence_percent_last_week, +# 'incomplete_task_percent_last_week': incomplete_task_percent_last_week, +# } + +# return Response(data, status=status.HTTP_200_OK) + # class DashboardStatsAPIView(APIView): # permission_classes = [IsAuthenticated] diff --git a/backend/tasks/serializers.py b/backend/tasks/serializers.py index a48330e..cf2046c 100644 --- a/backend/tasks/serializers.py +++ b/backend/tasks/serializers.py @@ -1,5 +1,6 @@ from rest_framework import serializers -from .models import Todo, RecurrenceTask +from boards.models import Board +from tasks.models import Todo, RecurrenceTask class GoogleCalendarEventSerializer(serializers.Serializer): @@ -17,18 +18,22 @@ class TodoUpdateSerializer(serializers.ModelSerializer): updated = serializers.DateTimeField(source="last_update") start_datetime = serializers.DateTimeField(source="start_event", required=False) end_datetime = serializers.DateTimeField(source="end_event", required=False) - + list_board = serializers.SerializerMethodField() class Meta: model = Todo - fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime') + fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime', 'list_board') def __init__(self, *args, **kwargs): self.user = kwargs.pop('user', None) super(TodoUpdateSerializer, self).__init__(*args, **kwargs) + def get_list_board(self, obj): + return Board.objects.get(user=self.user).listboard_set.first() + def create(self, validated_data): validated_data['user'] = self.user + validated_data['list_board'] = self.get_list_board(self) task = Todo.objects.create(**validated_data) return task diff --git a/backend/tasks/signals.py b/backend/tasks/signals.py index 4c9e6f2..f7436aa 100644 --- a/backend/tasks/signals.py +++ b/backend/tasks/signals.py @@ -1,8 +1,7 @@ -from django.db.models.signals import pre_save, post_save +from django.db.models.signals import pre_save from django.dispatch import receiver from django.utils import timezone -from boards.models import ListBoard, Board from tasks.models import Todo @@ -24,66 +23,4 @@ def update_priority(sender, instance, **kwargs): elif time_until_due <= urgency_threshold and instance.importance < importance_threshold: instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT else: - instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT - - -# @receiver(post_save, sender=Todo) -# def assign_todo_to_listboard(sender, instance, created, **kwargs): -# """Signal handler to automatically assign a Todo to the first ListBoard in the user's Board upon creation.""" -# if created: -# user_board = instance.user.board_set.first() - -# if user_board: -# first_list_board = user_board.listboard_set.order_by('position').first() - -# if first_list_board: -# instance.list_board = first_list_board -# instance.save() - - -@receiver(post_save, sender=ListBoard) -def create_placeholder_tasks(sender, instance, created, **kwargs): - """ - Signal handler to create placeholder tasks for each ListBoard. - """ - if created: - list_board_position = instance.position - - if list_board_position == 1: - placeholder_tasks = [ - {"title": "Normal Task Example"}, - {"title": "Task with Extra Information Example", "description": "Description for Task 2"}, - ] - elif list_board_position == 2: - placeholder_tasks = [ - {"title": "Time Task Example #1", "description": "Description for Task 2", - "start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=5)}, - ] - elif list_board_position == 3: - placeholder_tasks = [ - {"title": "Time Task Example #2", "description": "Description for Task 2", - "start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=30)}, - ] - elif list_board_position == 4: - placeholder_tasks = [ - {"title": "Completed Task Example", "description": "Description for Task 2", - "start_event": timezone.now(), "completed": True}, - ] - else: - placeholder_tasks = [ - {"title": "Default Task Example"}, - ] - - for task_data in placeholder_tasks: - Todo.objects.create( - list_board=instance, - user=instance.board.user, - title=task_data["title"], - notes=task_data.get("description", ""), - is_active=True, - start_event=task_data.get("start_event"), - end_event=task_data.get("end_event"), - completed=task_data.get("completed", False), - creation_date=timezone.now(), - last_update=timezone.now(), - ) \ No newline at end of file + instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT \ No newline at end of file diff --git a/backend/tasks/tasks/serializers.py b/backend/tasks/tasks/serializers.py index d2863a0..12bea72 100644 --- a/backend/tasks/tasks/serializers.py +++ b/backend/tasks/tasks/serializers.py @@ -1,4 +1,5 @@ from rest_framework import serializers +from users.models import CustomUser from boards.models import ListBoard from tasks.models import Todo, RecurrenceTask, Habit @@ -8,7 +9,14 @@ class TaskSerializer(serializers.ModelSerializer): fields = '__all__' def create(self, validated_data): - # Create a new task with validated data + user_id = validated_data.get('user') + + try: + user = CustomUser.objects.get(id=user_id) + except CustomUser.DoesNotExist: + raise serializers.ValidationError("User with the provided ID does not exist.") + + validated_data['user'] = user return Todo.objects.create(**validated_data) class TaskCreateSerializer(serializers.ModelSerializer): diff --git a/backend/tasks/tests/test_deserializer.py b/backend/tasks/tests/test_deserializer.py index 306185c..758fdd1 100644 --- a/backend/tasks/tests/test_deserializer.py +++ b/backend/tasks/tests/test_deserializer.py @@ -1,18 +1,22 @@ from datetime import datetime from zoneinfo import ZoneInfo -from django.test import TestCase +from rest_framework.test import APITestCase + from django.utils import timezone -from tasks.tests.utils import create_test_user, login_user +from tasks.tests.utils import create_test_user from tasks.serializers import TodoUpdateSerializer from tasks.models import Todo +from boards.models import Board -class TaskUpdateSerializerTest(TestCase): +class TaskUpdateSerializerTest(APITestCase): def setUp(self): self.user = create_test_user() + self.client.force_authenticate(user=self.user) self.current_time = '2020-08-01T00:00:00Z' self.end_time = '2020-08-01T00:00:00Z' + self.list_board = Board.objects.get(user=self.user).listboard_set.first() def test_serializer_create(self): data = { @@ -23,6 +27,7 @@ class TaskUpdateSerializerTest(TestCase): 'updated': self.end_time, 'start_datetime' : self.current_time, 'end_datetie': self.end_time, + 'list_board': self.list_board.id, } serializer = TodoUpdateSerializer(data=data, user=self.user) @@ -32,7 +37,7 @@ class TaskUpdateSerializerTest(TestCase): self.assertIsInstance(task, Todo) def test_serializer_update(self): - task = Todo.objects.create(title='Original Task', notes='Original description', user=self.user) + task = Todo.objects.create(title='Original Task', notes='Original description', user=self.user, list_board=self.list_board) data = { 'id': '32141cwaNcapufh8jq2conw', @@ -42,6 +47,7 @@ class TaskUpdateSerializerTest(TestCase): 'updated': self.end_time, 'start_datetime' : self.current_time, 'end_datetie': self.end_time, + 'list_board': self.list_board.id, } serializer = TodoUpdateSerializer(instance=task, data=data) diff --git a/backend/tasks/tests/test_todo_creation.py b/backend/tasks/tests/test_todo_creation.py index 7c66724..cc7dfc4 100644 --- a/backend/tasks/tests/test_todo_creation.py +++ b/backend/tasks/tests/test_todo_creation.py @@ -2,72 +2,72 @@ from datetime import datetime, timedelta from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase -from tasks.tests.utils import create_test_user, login_user +from tasks.tests.utils import create_test_user from tasks.models import Todo +from boards.models import ListBoard, Board -# class TodoViewSetTests(APITestCase): -# def setUp(self): -# self.user = create_test_user() -# self.client = login_user(self.user) -# self.url = reverse("todo-list") -# self.due_date = datetime.now() + timedelta(days=5) +class TodoViewSetTests(APITestCase): + def setUp(self): + self.user = create_test_user() + self.client.force_authenticate(user=self.user) + self.url = reverse("todo-list") + self.due_date = datetime.now() + timedelta(days=5) + self.list_board = Board.objects.get(user=self.user).listboard_set.first() -# def test_create_valid_todo(self): -# """ -# Test creating a valid task using the API. -# """ -# data = { -# 'title': 'Test Task', -# 'type': 'habit', -# 'exp': 10, -# 'attribute': 'str', -# 'priority': 1, -# 'difficulty': 1, -# 'user': self.user.id, -# 'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'), -# } -# response = self.client.post(self.url, data, format='json') -# self.assertEqual(response.status_code, status.HTTP_201_CREATED) -# self.assertEqual(Todo.objects.count(), 1) -# self.assertEqual(Todo.objects.get().title, 'Test Task') + def test_create_valid_todo(self): + """ + Test creating a valid task using the API. + """ + data = { + 'title': 'Test Task', + 'type': 'habit', + 'difficulty': 1, + 'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'), + 'list_board': self.list_board.id, + } + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Todo.objects.count(), 1) + self.assertEqual(Todo.objects.get().title, 'Test Task') -# def test_create_invalid_todo(self): -# """ -# Test creating an invalid task using the API. -# """ -# data = { -# 'type': 'invalid', # Invalid task type -# } -# response = self.client.post(self.url, data, format='json') -# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) -# self.assertEqual(Todo.objects.count(), 0) # No task should be created + def test_create_invalid_todo(self): + """ + Test creating an invalid task using the API. + """ + data = { + 'type': 'invalid', # Invalid task type + } + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + self.assertEqual(Todo.objects.count(), 0) # No task should be created -# def test_missing_required_fields(self): -# """ -# Test creating a task with missing required fields using the API. -# """ -# data = { -# 'title': 'Incomplete Task', -# 'type': 'habit', -# } -# response = self.client.post(self.url, data, format='json') -# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) -# self.assertEqual(Todo.objects.count(), 0) # No task should be created + def test_missing_required_fields(self): + """ + Test creating a task with missing required fields using the API. + """ + data = { + 'type': 'habit', + } + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR) + self.assertEqual(Todo.objects.count(), 0) # No task should be created -# def test_invalid_user_id(self): -# """ -# Test creating a task with an invalid user ID using the API. -# """ -# data = { -# 'title': 'Test Task', -# 'type': 'habit', -# 'exp': 10, -# 'priority': 1, -# 'difficulty': 1, -# 'user': 999, # Invalid user ID -# 'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'), -# } -# response = self.client.post(self.url, data, format='json') -# self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) -# self.assertEqual(Todo.objects.count(), 0) # No task should be created + def test_invalid_user_id(self): + """ + Test creating a task with an invalid user ID using the API (OK because we retreive) + id from request. + """ + data = { + 'title': 'Test Task', + 'type': 'habit', + 'exp': 10, + 'priority': 1, + 'difficulty': 1, + 'user': -100, # Invalid user ID + 'end_event': self.due_date.strftime('%Y-%m-%dT%H:%M:%S'), + 'list_board': self.list_board.id, + } + response = self.client.post(self.url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Todo.objects.count(), 1) # No task should be created diff --git a/backend/tasks/tests/test_todo_eisenhower.py b/backend/tasks/tests/test_todo_eisenhower.py index 41ee078..2d14877 100644 --- a/backend/tasks/tests/test_todo_eisenhower.py +++ b/backend/tasks/tests/test_todo_eisenhower.py @@ -1,36 +1,39 @@ from datetime import datetime, timedelta, timezone -from django.test import TestCase +from rest_framework.test import APITestCase from tasks.models import Todo from tasks.tests.utils import create_test_user +from boards.models import Board -class TodoPriorityTest(TestCase): +class TodoPriorityTest(APITestCase): def setUp(self): self.user = create_test_user() + self.client.force_authenticate(user=self.user) + self.list_board = Board.objects.get(user=self.user).listboard_set.first() def test_priority_calculation(self): # Important = 2, Till Due = none - todo = Todo(importance=2, end_event=None, user=self.user) + todo = Todo(importance=2, end_event=None, user=self.user, list_board=self.list_board) todo.save() # 'Not Important & Not Urgent' self.assertEqual(todo.priority, Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT) due_date = datetime.now(timezone.utc) + timedelta(days=1) # Important = 4, Till Due = 1 - todo = Todo(importance=4, end_event=due_date, user=self.user) + todo = Todo(importance=4, end_event=due_date, user=self.user, list_board=self.list_board) todo.save() # 'Important & Urgent' self.assertEqual(todo.priority, Todo.EisenhowerMatrix.IMPORTANT_URGENT) due_date = datetime.now(timezone.utc) + timedelta(days=10) # Important = 3, Till Due = 10 - todo = Todo(importance=3, end_event=due_date, user=self.user) + todo = Todo(importance=3, end_event=due_date, user=self.user, list_board=self.list_board) todo.save() # 'Important & Not Urgent' self.assertEqual(todo.priority, Todo.EisenhowerMatrix.IMPORTANT_NOT_URGENT) due_date = datetime.now(timezone.utc) + timedelta(days=2) # Important = 1, Till Due = 2 - todo = Todo(importance=1, end_event=due_date, user=self.user) + todo = Todo(importance=1, end_event=due_date, user=self.user, list_board=self.list_board) todo.save() # 'Not Important & Urgent' self.assertEqual(todo.priority, Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT) diff --git a/backend/tasks/tests/utils.py b/backend/tasks/tests/utils.py index b1bef0b..663cd0a 100644 --- a/backend/tasks/tests/utils.py +++ b/backend/tasks/tests/utils.py @@ -1,26 +1,24 @@ +from rest_framework import status from rest_framework.test import APIClient +from django.urls import reverse from users.models import CustomUser from ..models import Todo -def create_test_user(email="testusertestuser@example.com", username="testusertestuser", - first_name="Test", password="testpassword",): - """create predifined user for testing""" - return CustomUser.objects.create_user( - email=email, - username=username, - first_name=first_name, - password=password, - ) - - -def login_user(user): - """Login a user to API client.""" - +def create_test_user(email="testusertestuser@example.com", + username="testusertestuser", + password="testpassword",) -> CustomUser: + """create predifined user without placeholder task for testing""" client = APIClient() - client.force_authenticate(user=user) - return client + response = client.post(reverse('create_user'), {'email': email, + 'username': username, + 'password': password}) + if response.status_code == status.HTTP_201_CREATED: + user = CustomUser.objects.get(username='testusertestuser') + user.todo_set.all().delete() + return user + return None def create_task_json(user, **kwargs): @@ -29,10 +27,7 @@ def create_task_json(user, **kwargs): "title": "Test Task", "type": "habit", "notes": "This is a test task created via the API.", - "exp": 10, - "priority": 1.5, "difficulty": 1, - "attribute": "str", "challenge": False, "fromSystem": False, "creation_date": None, @@ -51,8 +46,6 @@ def create_test_task(user, **kwargs): 'title': "Test Task", 'task_type': 'habit', 'notes': "This is a test task created via the API.", - 'exp': 10, - 'priority': 1.5, 'difficulty': 1, 'attribute': 'str', 'challenge': False, diff --git a/backend/users/signals.py b/backend/users/signals.py index 817986b..22599b1 100644 --- a/backend/users/signals.py +++ b/backend/users/signals.py @@ -1,9 +1,74 @@ +from django.utils import timezone from django.db.models.signals import post_save from django.dispatch import receiver +from tasks.models import Todo from users.models import CustomUser, UserStats +from boards.models import ListBoard, Board + @receiver(post_save, sender=CustomUser) def create_user_stats(sender, instance, created, **kwargs): if created: - UserStats.objects.create(user=instance) \ No newline at end of file + UserStats.objects.create(user=instance) + + +@receiver(post_save, sender=CustomUser) +def create_default_board(sender, instance, created, **kwargs): + """Signal handler to automatically create a default Board for a user upon creation.""" + if created: + # Create unique board by user id + user_id = instance.id + board = Board.objects.create(user=instance, name=f"Board of #{user_id}") + ListBoard.objects.create(board=board, name="Backlog", position=1) + ListBoard.objects.create(board=board, name="Doing", position=2) + ListBoard.objects.create(board=board, name="Review", position=3) + ListBoard.objects.create(board=board, name="Done", position=4) + + +@receiver(post_save, sender=ListBoard) +def create_placeholder_tasks(sender, instance, created, **kwargs): + """ + Signal handler to create placeholder tasks for each ListBoard. + """ + if created: + list_board_position = instance.position + + if list_board_position == 1: + placeholder_tasks = [ + {"title": "Normal Task Example"}, + {"title": "Task with Extra Information Example", "description": "Description for Task 2"}, + ] + elif list_board_position == 2: + placeholder_tasks = [ + {"title": "Time Task Example #1", "description": "Description for Task 2", + "start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=5)}, + ] + elif list_board_position == 3: + placeholder_tasks = [ + {"title": "Time Task Example #2", "description": "Description for Task 2", + "start_event": timezone.now(), "end_event": timezone.now() + timezone.timedelta(days=30)}, + ] + elif list_board_position == 4: + placeholder_tasks = [ + {"title": "Completed Task Example", "description": "Description for Task 2", + "start_event": timezone.now(), "completed": True}, + ] + else: + placeholder_tasks = [ + {"title": "Default Task Example"}, + ] + + for task_data in placeholder_tasks: + Todo.objects.create( + list_board=instance, + user=instance.board.user, + title=task_data["title"], + notes=task_data.get("description", ""), + is_active=True, + start_event=task_data.get("start_event"), + end_event=task_data.get("end_event"), + completed=task_data.get("completed", False), + creation_date=timezone.now(), + last_update=timezone.now(), + ) \ No newline at end of file diff --git a/backend/users/tests.py b/backend/users/tests.py index 7ce503c..5f9bdd6 100644 --- a/backend/users/tests.py +++ b/backend/users/tests.py @@ -1,3 +1,39 @@ -from django.test import TestCase +from rest_framework.test import APITestCase +from rest_framework import status +from django.urls import reverse +from users.models import CustomUser, UserStats +from boards.models import Board, ListBoard +from tasks.models import Todo -# Create your tests here. +class SignalsTest(APITestCase): + def setUp(self): + response = self.client.post(reverse('create_user'), {'email': 'testusertestuser123@mail.com', + 'username': 'testusertestuser123', + 'password': '321testpassword123'}) + # force login If response is 201 OK + if response.status_code == status.HTTP_201_CREATED: + self.user = CustomUser.objects.get(username='testusertestuser123') + self.client.force_login(self.user) + + def test_create_user_with_stas_default_boards_and_lists(self): + # Stats check + self.assertTrue(UserStats.objects.filter(user=self.user).exists()) + + # check if user is created + self.assertEqual(CustomUser.objects.count(), 1) + user = CustomUser.objects.get(username='testusertestuser123') + + # Check for default board + self.assertEqual(Board.objects.filter(user=self.user).count(), 1) + + # Check for default lists in board + default_board = Board.objects.get(user=self.user) + self.assertEqual(ListBoard.objects.filter(board=default_board).count(), 4) + + def test_create_user_with_placeholder_tasks(self): + default_board = Board.objects.get(user=self.user) + + # Check if placeholder tasks are created for each ListBoard + for list_board in ListBoard.objects.filter(board=default_board): + placeholder_tasks_count = Todo.objects.filter(list_board=list_board).count() + self.assertTrue(placeholder_tasks_count > 0) \ No newline at end of file diff --git a/frontend/src/components/dashboard/KpiCard.jsx b/frontend/src/components/dashboard/KpiCard.jsx index c5c3165..b1d02b3 100644 --- a/frontend/src/components/dashboard/KpiCard.jsx +++ b/frontend/src/components/dashboard/KpiCard.jsx @@ -1,4 +1,11 @@ -import { BadgeDelta, Card, Flex, Metric, ProgressBar, Text } from "@tremor/react"; +import { + BadgeDelta, + Card, + Flex, + Metric, + ProgressBar, + Text, +} from "@tremor/react"; import { useEffect, useState } from "react"; import { axiosInstance } from "src/api/AxiosConfig"; @@ -13,7 +20,7 @@ export function KpiCard() { useEffect(() => { const fetchKpiCardData = async () => { try { - const response = await axiosInstance.get("/dashboard/stats/"); + const response = await axiosInstance.get("/dashboard/todostats/"); const completedThisWeek = response.data.completed_this_week || 0; const completedLastWeek = response.data.completed_last_week || 0; const percentage = (completedThisWeek / completedLastWeek) * 100; @@ -46,10 +53,16 @@ export function KpiCard() {
document.getElementById(`task_detail_modal_${task.id}`).showModal()}> {task.content}
@@ -77,4 +82,4 @@ export function TaskCard({ task, deleteTask, updateTask }) {