From 14d656544b0491fe881e37adc3fe33990c2bafbe Mon Sep 17 00:00:00 2001 From: Chaiyawut Thengket Date: Mon, 20 Nov 2023 10:46:18 +0700 Subject: [PATCH 1/7] add api weeklystat --- backend/dashboard/views.py | 102 ++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/backend/dashboard/views.py b/backend/dashboard/views.py index 4475d77..844c1af 100644 --- a/backend/dashboard/views.py +++ b/backend/dashboard/views.py @@ -30,6 +30,13 @@ class DashboardStatsAPIView(APIView): 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, @@ -38,6 +45,10 @@ class DashboardStatsAPIView(APIView): '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) @@ -55,4 +66,93 @@ class DashboardStatsAPIView(APIView): task.save() return Response({'message': 'Task completion status updated successfully'}, status=status.HTTP_200_OK) except Todo.DoesNotExist: - return Response({'error': 'Task not found'}, status=status.HTTP_404_NOT_FOUND) \ No newline at end of file + return Response({'error': 'Task not found'}, status=status.HTTP_404_NOT_FOUND) + +class WeeklyStatsAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + user = request.user + today = timezone.now() + + # Calculate the start and end dates for the current week + current_week_start = today - timezone.timedelta(days=today.weekday()) + current_week_end = current_week_start + timezone.timedelta(days=6) + + # Initialize a list to store daily statistics + weekly_stats = [] + + # Loop through each day of the week + for i in range(7): + # Calculate the start and end dates for the current day + current_day_start = current_week_start + timezone.timedelta(days=i) + current_day_end = current_day_start + timezone.timedelta(days=1) + + # Calculate the start and end dates for the same day over the last 7 days + last_7_days_start = current_day_start - timezone.timedelta(days=7) + last_7_days_end = current_day_end - timezone.timedelta(days=7) + + # Calculate statistics for the current day + current_day_stats = self.calculate_stats(user, current_day_start, current_day_end) + + # Calculate statistics for the same day over the last 7 days + last_7_days_stats = self.calculate_stats(user, last_7_days_start, last_7_days_end) + + # Calculate the percentage change + percent_change = self.calculate_percent_change( + current_day_stats['overall_completion_rate'], + last_7_days_stats['overall_completion_rate'] + ) + + # Append the daily statistics to the list + weekly_stats.append({ + 'day_of_week': current_day_start.strftime('%A'), + 'current_day_stats': current_day_stats, + 'last_7_days_stats': last_7_days_stats, + 'percent_change': percent_change, + }) + + response_data = { + 'weekly_stats': weekly_stats, + } + + return Response(response_data, status=status.HTTP_200_OK) + + def calculate_stats(self, user, start_date, end_date): + # Calculate task usage statistics for the specified day + todo_count = Todo.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).count() + recurrence_task_count = RecurrenceTask.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).count() + + # Calculate how many tasks were completed on the specified day + completed_todo_count = Todo.objects.filter(user=user, completed=True, last_update__gte=start_date, last_update__lte=end_date).count() + completed_recurrence_task_count = RecurrenceTask.objects.filter(user=user, completed=True, last_update__gte=start_date, last_update__lte=end_date).count() + + # Calculate subtask completion rate for the specified day + total_subtasks = Todo.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).aggregate(total=Count('subtask__id'))['total'] + completed_subtasks = Todo.objects.filter(user=user, subtask__completed=True, created_at__gte=start_date, created_at__lte=end_date).aggregate(total=Count('subtask__id'))['total'] + + # Calculate overall completion rate for the specified day + total_tasks = todo_count + recurrence_task_count + completed_tasks = completed_todo_count + completed_recurrence_task_count + overall_completion_rate = (completed_tasks / total_tasks) * 100 if total_tasks > 0 else 0 + + return { + 'start_date': start_date.strftime('%Y-%m-%d'), + 'end_date': end_date.strftime('%Y-%m-%d'), + 'todo_count': todo_count, + 'recurrence_task_count': recurrence_task_count, + 'completed_todo_count': completed_todo_count, + 'completed_recurrence_task_count': completed_recurrence_task_count, + 'total_subtasks': total_subtasks, + 'completed_subtasks': completed_subtasks, + 'overall_completion_rate': overall_completion_rate, + } + + def calculate_percent_change(self, current_value, last_value): + # Calculate the percentage change between current and last values + if last_value != 0: + percent_change = ((current_value - last_value) / last_value) * 100 + else: + percent_change = current_value * 100 # Consider infinite change when the last value is zero + + return round(percent_change, 2) \ No newline at end of file From ae892545293d0dc5e89e481a467a6d5a0681aee4 Mon Sep 17 00:00:00 2001 From: Chaiyawut Thengket Date: Mon, 20 Nov 2023 10:47:54 +0700 Subject: [PATCH 2/7] write weekly urls --- backend/dashboard/urls.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/dashboard/urls.py b/backend/dashboard/urls.py index beb8f1b..cb88adb 100644 --- a/backend/dashboard/urls.py +++ b/backend/dashboard/urls.py @@ -1,6 +1,7 @@ from django.urls import path -from .views import DashboardStatsAPIView +from .views import DashboardStatsAPIView, WeeklyStatsAPIView urlpatterns = [ path('dashboard/stats/', DashboardStatsAPIView.as_view(), name='dashboard-stats'), + path('dashboard/weekly-stats/', WeeklyStatsAPIView.as_view(), name='dashboard-weekly-stats'), ] From f97aaa902d192459ddef4b8a11790d6581b13e86 Mon Sep 17 00:00:00 2001 From: Chaiyawut Thengket Date: Mon, 20 Nov 2023 10:54:36 +0700 Subject: [PATCH 3/7] write test --- backend/dashboard/tests.py | 71 +++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/backend/dashboard/tests.py b/backend/dashboard/tests.py index 7ce503c..359baea 100644 --- a/backend/dashboard/tests.py +++ b/backend/dashboard/tests.py @@ -1,3 +1,72 @@ +# tasks/tests.py from django.test import TestCase +from django.contrib.auth.models import User +from django.utils import timezone +from rest_framework.test import APIClient +from tasks.models import Todo, RecurrenceTask -# Create your tests here. +class DashboardStatsAPITestCase(TestCase): + def setUp(self): + # Create a test user + self.user = User.objects.create_user(username='testuser', password='testpassword') + + # Create test tasks + self.todo = Todo.objects.create(user=self.user, title='Test Todo', created_at=timezone.now()) + self.recurrence_task = RecurrenceTask.objects.create(user=self.user, title='Test Recurrence Task', created_at=timezone.now()) + + # 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('/api/dashboard-stats/') + + # Assert the response status code is 200 + self.assertEqual(response.status_code, 200) + + # Add more assertions based on your expected response data + + 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('/api/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) + + # Add more assertions based on your expected response data + +class WeeklyStatsAPITestCase(TestCase): + def setUp(self): + # Create a test user + self.user = User.objects.create_user(username='testuser', password='testpassword') + + # 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('/api/weekly-stats/') + + # Assert the response status code is 200 + self.assertEqual(response.status_code, 200) + + # Add more assertions based on your expected response data + +# Add more test cases as needed From 4c4fb02a0ea1d683e3255bcf556fe9b9d48fc8d7 Mon Sep 17 00:00:00 2001 From: Chaiyawut Thengket Date: Mon, 20 Nov 2023 11:55:52 +0700 Subject: [PATCH 4/7] fix percent todo and reccurence compare with all task in that week --- backend/dashboard/tests.py | 3 ++- backend/dashboard/views.py | 20 +++++++++++++++++--- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/backend/dashboard/tests.py b/backend/dashboard/tests.py index 359baea..24d58e3 100644 --- a/backend/dashboard/tests.py +++ b/backend/dashboard/tests.py @@ -5,10 +5,11 @@ from django.utils import timezone from rest_framework.test import APIClient from tasks.models import Todo, RecurrenceTask + class DashboardStatsAPITestCase(TestCase): def setUp(self): # Create a test user - self.user = User.objects.create_user(username='testuser', password='testpassword') + self.user = User.objects.create_user(first_name='test', email='user@mail.co.th', username='testuser', password='testpassword') # Create test tasks self.todo = Todo.objects.create(user=self.user, title='Test Todo', created_at=timezone.now()) diff --git a/backend/dashboard/views.py b/backend/dashboard/views.py index 844c1af..241c023 100644 --- a/backend/dashboard/views.py +++ b/backend/dashboard/views.py @@ -99,17 +99,31 @@ class WeeklyStatsAPIView(APIView): last_7_days_stats = self.calculate_stats(user, last_7_days_start, last_7_days_end) # Calculate the percentage change - percent_change = self.calculate_percent_change( + percent_change_over_all = self.calculate_percent_change( current_day_stats['overall_completion_rate'], last_7_days_stats['overall_completion_rate'] ) + # Calculate percentage change for completed_todo_count + percent_change_todo = self.calculate_percent_change( + current_day_stats['completed_todo_count'], + last_7_days_stats['completed_todo_count'] + ) + + # Calculate percentage change for completed_recurrence_task_count + percent_change_recurrence = self.calculate_percent_change( + current_day_stats['completed_recurrence_task_count'], + last_7_days_stats['completed_recurrence_task_count'] + ) + # Append the daily statistics to the list weekly_stats.append({ 'day_of_week': current_day_start.strftime('%A'), 'current_day_stats': current_day_stats, 'last_7_days_stats': last_7_days_stats, - 'percent_change': percent_change, + 'percent_change_over_all': percent_change_over_all, + 'percent_change_todo': percent_change_todo, + 'percent_change_recurrence': percent_change_recurrence, }) response_data = { @@ -155,4 +169,4 @@ class WeeklyStatsAPIView(APIView): else: percent_change = current_value * 100 # Consider infinite change when the last value is zero - return round(percent_change, 2) \ No newline at end of file + return round(percent_change, 2) From 298d7c2d10a2dec585755ad8e2495b878513e4b4 Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 22:29:04 +0700 Subject: [PATCH 5/7] Add completion date / update with completed field --- backend/tasks/admin.py | 5 ++-- ...0018_alter_habit_creation_date_and_more.py | 29 +++++++++++++++++++ ...0019_alter_habit_creation_date_and_more.py | 28 ++++++++++++++++++ ...sk_completion_date_todo_completion_date.py | 23 +++++++++++++++ backend/tasks/models.py | 22 ++++++++++++++ 5 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 backend/tasks/migrations/0018_alter_habit_creation_date_and_more.py create mode 100644 backend/tasks/migrations/0019_alter_habit_creation_date_and_more.py create mode 100644 backend/tasks/migrations/0020_recurrencetask_completion_date_todo_completion_date.py diff --git a/backend/tasks/admin.py b/backend/tasks/admin.py index 5c5bbb3..cabf594 100644 --- a/backend/tasks/admin.py +++ b/backend/tasks/admin.py @@ -7,8 +7,9 @@ class TagAdmin(admin.ModelAdmin): @admin.register(Todo) class TodoAdmin(admin.ModelAdmin): - list_display = ['title', 'list_board', 'is_active', 'priority'] - list_filter = ['list_board', 'is_active', 'priority'] + list_display = ['title', 'list_board', 'is_active', 'priority', 'completed', 'completion_date'] + list_filter = ['list_board', 'is_active', 'priority', 'completed'] + exclude = ['completion_date'] @admin.register(RecurrenceTask) class RecurrenceTaskAdmin(admin.ModelAdmin): diff --git a/backend/tasks/migrations/0018_alter_habit_creation_date_and_more.py b/backend/tasks/migrations/0018_alter_habit_creation_date_and_more.py new file mode 100644 index 0000000..bbb1714 --- /dev/null +++ b/backend/tasks/migrations/0018_alter_habit_creation_date_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2.6 on 2023-11-20 14:58 + +from django.db import migrations, models +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0017_alter_recurrencetask_list_board_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='habit', + name='creation_date', + field=models.DateTimeField(default=django.utils.timezone.now, editable=False), + ), + migrations.AlterField( + model_name='recurrencetask', + name='creation_date', + field=models.DateTimeField(default=django.utils.timezone.now, editable=False), + ), + migrations.AlterField( + model_name='todo', + name='creation_date', + field=models.DateTimeField(default=django.utils.timezone.now, editable=False), + ), + ] diff --git a/backend/tasks/migrations/0019_alter_habit_creation_date_and_more.py b/backend/tasks/migrations/0019_alter_habit_creation_date_and_more.py new file mode 100644 index 0000000..0ba0307 --- /dev/null +++ b/backend/tasks/migrations/0019_alter_habit_creation_date_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.6 on 2023-11-20 15:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0018_alter_habit_creation_date_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='habit', + name='creation_date', + field=models.DateTimeField(auto_now_add=True), + ), + migrations.AlterField( + model_name='recurrencetask', + name='creation_date', + field=models.DateTimeField(auto_now_add=True), + ), + migrations.AlterField( + model_name='todo', + name='creation_date', + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/backend/tasks/migrations/0020_recurrencetask_completion_date_todo_completion_date.py b/backend/tasks/migrations/0020_recurrencetask_completion_date_todo_completion_date.py new file mode 100644 index 0000000..f1ffe13 --- /dev/null +++ b/backend/tasks/migrations/0020_recurrencetask_completion_date_todo_completion_date.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2.6 on 2023-11-20 15:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0019_alter_habit_creation_date_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='recurrencetask', + name='completion_date', + field=models.DateTimeField(null=True), + ), + migrations.AddField( + model_name='todo', + name='completion_date', + field=models.DateTimeField(null=True), + ), + ] diff --git a/backend/tasks/models.py b/backend/tasks/models.py index fc29fe6..c848e00 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -1,5 +1,6 @@ from django.db import models from django.conf import settings +from django.utils import timezone from boards.models import ListBoard, Board @@ -25,6 +26,7 @@ class Task(models.Model): :param challenge: Associated challenge (optional). :param fromSystem: A boolean field indicating if the task is from System. :param creation_date: Creation date of the task. + :param last_update: Last update date of the task. """ class Difficulty(models.IntegerChoices): EASY = 1, 'Easy' @@ -59,6 +61,7 @@ class Todo(Task): :param end_event: End date and time of the task. :param google_calendar_id: The Google Calendar ID of the task. :param completed: A boolean field indicating whether the task is completed. + :param completion_date: The date and time when the task is completed. :param priority: The priority of the task (range: 1 to 4). """ class EisenhowerMatrix(models.IntegerChoices): @@ -74,8 +77,17 @@ class Todo(Task): end_event = models.DateTimeField(null=True) google_calendar_id = models.CharField(max_length=255, null=True, blank=True) completed = models.BooleanField(default=False) + completion_date = models.DateTimeField(null=True) priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT) + def save(self, *args, **kwargs): + if self.completed and not self.completion_date: + self.completion_date = timezone.now() + elif not self.completed: + self.completion_date = None + + super().save(*args, **kwargs) + def __str__(self): return self.title @@ -90,6 +102,7 @@ class RecurrenceTask(Task): :param start_event: Start date and time of the task. :param end_event: End date and time of the task. :param completed: A boolean field indicating whether the task is completed. + :param completion_date: The date and time when the task is completed. :param parent_task: The parent task of the subtask. """ list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True, default=1) @@ -99,8 +112,17 @@ class RecurrenceTask(Task): start_event = models.DateTimeField(null=True) end_event = models.DateTimeField(null=True) completed = models.BooleanField(default=False) + completion_date = models.DateTimeField(null=True) parent_task = models.ForeignKey("self", on_delete=models.CASCADE, null=True) + def save(self, *args, **kwargs): + if self.completed and not self.completion_date: + self.completion_date = timezone.now() + elif not self.completed: + self.completion_date = None + + super().save(*args, **kwargs) + def __str__(self) -> str: return f"{self.title} ({self.recurrence_rule})" From 6c31fb059f9b78a1a192c515ab6d00cddbb4e0cb Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 22:36:19 +0700 Subject: [PATCH 6/7] Recode the analytic part --- backend/dashboard/tests.py | 127 ++++++------ backend/dashboard/urls.py | 12 +- backend/dashboard/views.py | 395 +++++++++++++++++++++++++------------ 3 files changed, 331 insertions(+), 203 deletions(-) diff --git a/backend/dashboard/tests.py b/backend/dashboard/tests.py index 24d58e3..345fc13 100644 --- a/backend/dashboard/tests.py +++ b/backend/dashboard/tests.py @@ -1,73 +1,66 @@ -# tasks/tests.py -from django.test import TestCase -from django.contrib.auth.models import User -from django.utils import timezone -from rest_framework.test import APIClient -from tasks.models import Todo, RecurrenceTask +# # tasks/tests.py +# from django.test import TestCase +# from django.utils import timezone +# from rest_framework.test import APIClient +# from tasks.models import Todo, RecurrenceTask +# from tasks.tests.utils import create_test_user +# from django.urls import reverse + +# 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 DashboardStatsAPITestCase(TestCase): - def setUp(self): - # Create a test user - self.user = User.objects.create_user(first_name='test', email='user@mail.co.th', username='testuser', password='testpassword') +# class WeeklyStatsAPITestCase(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', created_at=timezone.now()) - self.recurrence_task = RecurrenceTask.objects.create(user=self.user, title='Test Recurrence Task', created_at=timezone.now()) +# # Create an API client +# self.client = APIClient() - # Create an API client - self.client = APIClient() +# def test_weekly_stats_api(self): +# # Authenticate the user +# self.client.force_authenticate(user=self.user) - def test_dashboard_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')) - # Make a GET request to the DashboardStatsAPIView - response = self.client.get('/api/dashboard-stats/') - - # Assert the response status code is 200 - self.assertEqual(response.status_code, 200) - - # Add more assertions based on your expected response data - - 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('/api/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) - - # Add more assertions based on your expected response data - -class WeeklyStatsAPITestCase(TestCase): - def setUp(self): - # Create a test user - self.user = User.objects.create_user(username='testuser', password='testpassword') - - # 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('/api/weekly-stats/') - - # Assert the response status code is 200 - self.assertEqual(response.status_code, 200) - - # Add more assertions based on your expected response data - -# Add more test cases as needed +# # 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 cb88adb..73429b5 100644 --- a/backend/dashboard/urls.py +++ b/backend/dashboard/urls.py @@ -1,7 +1,9 @@ from django.urls import path -from .views import DashboardStatsAPIView, WeeklyStatsAPIView +from rest_framework.routers import DefaultRouter -urlpatterns = [ - path('dashboard/stats/', DashboardStatsAPIView.as_view(), name='dashboard-stats'), - path('dashboard/weekly-stats/', WeeklyStatsAPIView.as_view(), name='dashboard-weekly-stats'), -] +from .views import DashboardStatsViewSet, DashboardWeeklyViewSet + +router = DefaultRouter() +router.register(r'dashboard/stats', DashboardStatsViewSet, basename='dashboard-stats') +router.register(r'dashboard/weekly', DashboardWeeklyViewSet, basename='dashboard-weekly') +urlpatterns = router.urls diff --git a/backend/dashboard/views.py b/backend/dashboard/views.py index 241c023..c91f5f9 100644 --- a/backend/dashboard/views.py +++ b/backend/dashboard/views.py @@ -1,172 +1,305 @@ -from rest_framework.views import APIView -from rest_framework.response import Response -from rest_framework import status -from rest_framework.permissions import IsAuthenticated -from django.db.models import Count +from datetime import timedelta from django.utils import timezone +from rest_framework import status +from rest_framework.response import Response +from rest_framework.permissions import IsAuthenticated +from rest_framework import viewsets, mixins -from tasks.models import Todo, RecurrenceTask +from tasks.models import Todo -class DashboardStatsAPIView(APIView): + +class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): permission_classes = [IsAuthenticated] - def get(self, request): - user = request.user + def list(self, request, *args, **kwargs): + user = self.request.user - # Calculate task usage statistics - todo_count = Todo.objects.filter(user=user).count() - recurrence_task_count = RecurrenceTask.objects.filter(user=user).count() + # Calculate the start and end date for the last 7 days + end_date = timezone.now() + start_date = end_date - timedelta(days=7) - # 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() + # How many tasks were completed in the last 7 days + completed_last_7_days = Todo.objects.filter( + user=user, + completed=True, + completion_date__gte=start_date, + completion_date__lte=end_date + ).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'] + # Task assign last week compared with this week + tasks_assigned_last_week = Todo.objects.filter( + user=user, + completion_date__gte=start_date - timedelta(days=7), + completion_date__lte=start_date + ).count() - # 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 + tasks_assigned_this_week = Todo.objects.filter( + user=user, + completion_date__gte=start_date, + completion_date__lte=end_date + ).count() - # pie chart show - complete_todo_percent_last_week = (completed_todo_count_last_week / todo_count) * 100 if todo_count > 0 else 0 + # Completed tasks from last week compared with this week + completed_last_week = Todo.objects.filter( + user=user, + completed=True, + completion_date__gte=start_date - timedelta(days=7), + completion_date__lte=start_date + ).count() - complete_recurrence_percent_last_week = (completed_recurrence_task_count_last_week / recurrence_task_count) * 100 if recurrence_task_count > 0 else 0 + completed_this_week = Todo.objects.filter( + user=user, + completed=True, + completion_date__gte=start_date, + completion_date__lte=end_date + ).count() - incomplete_task_percent_last_week = 100 - complete_recurrence_percent_last_week - complete_todo_percent_last_week + overdue_tasks = Todo.objects.filter( + user=user, + completed=False, + end_event__lt=timezone.now() + ).count() + + # 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 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, - + "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, } return Response(data, status=status.HTTP_200_OK) - - def post(self, request): - # Handle incoming data from the POST request - # Update the necessary information based on the data - task_id = request.data.get('task_id') - is_completed = request.data.get('is_completed') - try: - task = Todo.objects.get(id=task_id, user=request.user) - task.completed = is_completed - task.save() - return Response({'message': 'Task completion status updated successfully'}, status=status.HTTP_200_OK) - except Todo.DoesNotExist: - return Response({'error': 'Task not found'}, status=status.HTTP_404_NOT_FOUND) - -class WeeklyStatsAPIView(APIView): +class DashboardWeeklyViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): permission_classes = [IsAuthenticated] - def get(self, request): - user = request.user - today = timezone.now() + def list(self, request, *args, **kwargs): + user = self.request.user - # Calculate the start and end dates for the current week - current_week_start = today - timezone.timedelta(days=today.weekday()) - current_week_end = current_week_start + timezone.timedelta(days=6) + # Calculate the start and end date for the last 7 days (Monday to Sunday) + today = timezone.now().date() + current_week_start = today - timedelta(days=today.weekday()) + current_week_end = current_week_start + timedelta(days=6) - # Initialize a list to store daily statistics + last_week_start = current_week_start - timedelta(days=7) + last_week_end = last_week_start + timedelta(days=6) + + # Create a list to store daily statistics weekly_stats = [] - # Loop through each day of the week - for i in range(7): - # Calculate the start and end dates for the current day - current_day_start = current_week_start + timezone.timedelta(days=i) - current_day_end = current_day_start + timezone.timedelta(days=1) + # Iterate over each day of the week + for day in range(7): + current_day = current_week_start + timedelta(days=day) + last_day = last_week_start + timedelta(days=day) - # Calculate the start and end dates for the same day over the last 7 days - last_7_days_start = current_day_start - timezone.timedelta(days=7) - last_7_days_end = current_day_end - timezone.timedelta(days=7) + # Calculate stats for this week + tasks_this_week = Todo.objects.filter( + user=user, + completion_date__gte=current_day, + completion_date__lte=current_day + timedelta(days=1) + ).count() - # Calculate statistics for the current day - current_day_stats = self.calculate_stats(user, current_day_start, current_day_end) + completed_this_week = Todo.objects.filter( + user=user, + completed=True, + completion_date__gte=current_day, + completion_date__lte=current_day + timedelta(days=1) + ).count() - # Calculate statistics for the same day over the last 7 days - last_7_days_stats = self.calculate_stats(user, last_7_days_start, last_7_days_end) + # Calculate stats for last week + tasks_last_week = Todo.objects.filter( + user=user, + completion_date__gte=last_day, + completion_date__lte=last_day + timedelta(days=1) + ).count() - # Calculate the percentage change - percent_change_over_all = self.calculate_percent_change( - current_day_stats['overall_completion_rate'], - last_7_days_stats['overall_completion_rate'] - ) + completed_last_week = Todo.objects.filter( + user=user, + completed=True, + completion_date__gte=last_day, + completion_date__lte=last_day + timedelta(days=1) + ).count() - # Calculate percentage change for completed_todo_count - percent_change_todo = self.calculate_percent_change( - current_day_stats['completed_todo_count'], - last_7_days_stats['completed_todo_count'] - ) + daily_stat = { + "date": current_day.strftime("%A"), + "This Week": tasks_this_week, + "Last Week": tasks_last_week, + "Completed This Week": completed_this_week, + "Completed Last Week": completed_last_week, + } - # Calculate percentage change for completed_recurrence_task_count - percent_change_recurrence = self.calculate_percent_change( - current_day_stats['completed_recurrence_task_count'], - last_7_days_stats['completed_recurrence_task_count'] - ) + weekly_stats.append(daily_stat) - # Append the daily statistics to the list - weekly_stats.append({ - 'day_of_week': current_day_start.strftime('%A'), - 'current_day_stats': current_day_stats, - 'last_7_days_stats': last_7_days_stats, - 'percent_change_over_all': percent_change_over_all, - 'percent_change_todo': percent_change_todo, - 'percent_change_recurrence': percent_change_recurrence, - }) + return Response(weekly_stats, status=status.HTTP_200_OK) - response_data = { - 'weekly_stats': weekly_stats, - } - return Response(response_data, status=status.HTTP_200_OK) +# class DashboardStatsAPIView(APIView): +# permission_classes = [IsAuthenticated] - def calculate_stats(self, user, start_date, end_date): - # Calculate task usage statistics for the specified day - todo_count = Todo.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).count() - recurrence_task_count = RecurrenceTask.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).count() +# def get(self, request): +# user = request.user - # Calculate how many tasks were completed on the specified day - completed_todo_count = Todo.objects.filter(user=user, completed=True, last_update__gte=start_date, last_update__lte=end_date).count() - completed_recurrence_task_count = RecurrenceTask.objects.filter(user=user, completed=True, last_update__gte=start_date, last_update__lte=end_date).count() +# # Calculate task usage statistics +# todo_count = Todo.objects.filter(user=user).count() +# recurrence_task_count = RecurrenceTask.objects.filter(user=user).count() - # Calculate subtask completion rate for the specified day - total_subtasks = Todo.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).aggregate(total=Count('subtask__id'))['total'] - completed_subtasks = Todo.objects.filter(user=user, subtask__completed=True, created_at__gte=start_date, created_at__lte=end_date).aggregate(total=Count('subtask__id'))['total'] +# # 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 overall completion rate for the specified day - total_tasks = todo_count + recurrence_task_count - completed_tasks = completed_todo_count + completed_recurrence_task_count - overall_completion_rate = (completed_tasks / total_tasks) * 100 if total_tasks > 0 else 0 +# # 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'] - return { - 'start_date': start_date.strftime('%Y-%m-%d'), - 'end_date': end_date.strftime('%Y-%m-%d'), - 'todo_count': todo_count, - 'recurrence_task_count': recurrence_task_count, - 'completed_todo_count': completed_todo_count, - 'completed_recurrence_task_count': completed_recurrence_task_count, - 'total_subtasks': total_subtasks, - 'completed_subtasks': completed_subtasks, - 'overall_completion_rate': overall_completion_rate, - } +# # 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 - def calculate_percent_change(self, current_value, last_value): - # Calculate the percentage change between current and last values - if last_value != 0: - percent_change = ((current_value - last_value) / last_value) * 100 - else: - percent_change = current_value * 100 # Consider infinite change when the last value is zero +# # pie chart show +# complete_todo_percent_last_week = (completed_todo_count_last_week / todo_count) * 100 if todo_count > 0 else 0 - return round(percent_change, 2) +# 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) + +# def post(self, request): +# # Handle incoming data from the POST request +# # Update the necessary information based on the data + +# task_id = request.data.get('task_id') +# is_completed = request.data.get('is_completed') + +# try: +# task = Todo.objects.get(id=task_id, user=request.user) +# task.completed = is_completed +# task.save() +# return Response({'message': 'Task completion status updated successfully'}, status=status.HTTP_200_OK) +# except Todo.DoesNotExist: +# return Response({'error': 'Task not found'}, status=status.HTTP_404_NOT_FOUND) + +# class WeeklyStatsAPIView(APIView): +# permission_classes = [IsAuthenticated] + +# def get(self, request): +# user = request.user +# today = timezone.now() + +# # Calculate the start and end dates for the current week +# current_week_start = today - timezone.timedelta(days=today.weekday()) +# current_week_end = current_week_start + timezone.timedelta(days=6) + +# # Initialize a list to store daily statistics +# weekly_stats = [] + +# # Loop through each day of the week +# for i in range(7): +# # Calculate the start and end dates for the current day +# current_day_start = current_week_start + timezone.timedelta(days=i) +# current_day_end = current_day_start + timezone.timedelta(days=1) + +# # Calculate the start and end dates for the same day over the last 7 days +# last_7_days_start = current_day_start - timezone.timedelta(days=7) +# last_7_days_end = current_day_end - timezone.timedelta(days=7) + +# # Calculate statistics for the current day +# current_day_stats = self.calculate_stats(user, current_day_start, current_day_end) + +# # Calculate statistics for the same day over the last 7 days +# last_7_days_stats = self.calculate_stats(user, last_7_days_start, last_7_days_end) + +# # Calculate the percentage change +# percent_change_over_all = self.calculate_percent_change( +# current_day_stats['overall_completion_rate'], +# last_7_days_stats['overall_completion_rate'] +# ) + +# # Calculate percentage change for completed_todo_count +# percent_change_todo = self.calculate_percent_change( +# current_day_stats['completed_todo_count'], +# last_7_days_stats['completed_todo_count'] +# ) + +# # Calculate percentage change for completed_recurrence_task_count +# percent_change_recurrence = self.calculate_percent_change( +# current_day_stats['completed_recurrence_task_count'], +# last_7_days_stats['completed_recurrence_task_count'] +# ) + +# # Append the daily statistics to the list +# weekly_stats.append({ +# 'day_of_week': current_day_start.strftime('%A'), +# 'current_day_stats': current_day_stats, +# 'last_7_days_stats': last_7_days_stats, +# 'percent_change_over_all': percent_change_over_all, +# 'percent_change_todo': percent_change_todo, +# 'percent_change_recurrence': percent_change_recurrence, +# }) + +# response_data = { +# 'weekly_stats': weekly_stats, +# } + +# return Response(response_data, status=status.HTTP_200_OK) + +# def calculate_stats(self, user, start_date, end_date): +# # Calculate task usage statistics for the specified day +# todo_count = Todo.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).count() +# recurrence_task_count = RecurrenceTask.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).count() + +# # Calculate how many tasks were completed on the specified day +# completed_todo_count = Todo.objects.filter(user=user, completed=True, last_update__gte=start_date, last_update__lte=end_date).count() +# completed_recurrence_task_count = RecurrenceTask.objects.filter(user=user, completed=True, last_update__gte=start_date, last_update__lte=end_date).count() + +# # Calculate subtask completion rate for the specified day +# total_subtasks = Todo.objects.filter(user=user, created_at__gte=start_date, created_at__lte=end_date).aggregate(total=Count('subtask__id'))['total'] +# completed_subtasks = Todo.objects.filter(user=user, subtask__completed=True, created_at__gte=start_date, created_at__lte=end_date).aggregate(total=Count('subtask__id'))['total'] + +# # Calculate overall completion rate for the specified day +# total_tasks = todo_count + recurrence_task_count +# completed_tasks = completed_todo_count + completed_recurrence_task_count +# overall_completion_rate = (completed_tasks / total_tasks) * 100 if total_tasks > 0 else 0 + +# return { +# 'start_date': start_date.strftime('%Y-%m-%d'), +# 'end_date': end_date.strftime('%Y-%m-%d'), +# 'todo_count': todo_count, +# 'recurrence_task_count': recurrence_task_count, +# 'completed_todo_count': completed_todo_count, +# 'completed_recurrence_task_count': completed_recurrence_task_count, +# 'total_subtasks': total_subtasks, +# 'completed_subtasks': completed_subtasks, +# 'overall_completion_rate': overall_completion_rate, +# } + +# def calculate_percent_change(self, current_value, last_value): +# # Calculate the percentage change between current and last values +# if last_value != 0: +# percent_change = ((current_value - last_value) / last_value) * 100 +# else: +# percent_change = current_value * 100 # Consider infinite change when the last value is zero + +# return round(percent_change, 2) From c18275eeb8ab9ce14f5b153faa95c391da08bf06 Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 22:57:46 +0700 Subject: [PATCH 7/7] Add test for dashboard stats --- backend/dashboard/tests.py | 51 ++++++++++++++++++++++++++++++++------ backend/dashboard/urls.py | 10 +++++--- backend/dashboard/views.py | 10 ++++++-- 3 files changed, 58 insertions(+), 13 deletions(-) diff --git a/backend/dashboard/tests.py b/backend/dashboard/tests.py index 345fc13..943c3de 100644 --- a/backend/dashboard/tests.py +++ b/backend/dashboard/tests.py @@ -1,10 +1,47 @@ -# # tasks/tests.py -# from django.test import TestCase -# from django.utils import timezone -# from rest_framework.test import APIClient -# from tasks.models import Todo, RecurrenceTask -# from tasks.tests.utils import create_test_user -# from django.urls import reverse +from django.test import TestCase +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 + +class DashboardStatsAndWeeklyViewSetTests(TestCase): + def setUp(self): + self.user = create_test_user() + self.client = login_user(self.user) + + 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 + ) + + 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()) + + response = self.client.get(reverse('stats-list')) + self.assertEqual(response.status_code, 200) + + self.assertEqual(response.data['completed_this_week'], 1) + self.assertEqual(response.data['tasks_assigned_this_week'], 1) + self.assertEqual(response.data['tasks_assigned_last_week'], 0) + + 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()) + + response = self.client.get(reverse('weekly-list')) + self.assertEqual(response.status_code, 200) + # class DashboardStatsAPITestCase(TestCase): # def setUp(self): diff --git a/backend/dashboard/urls.py b/backend/dashboard/urls.py index 73429b5..56624ca 100644 --- a/backend/dashboard/urls.py +++ b/backend/dashboard/urls.py @@ -1,9 +1,11 @@ -from django.urls import path +from django.urls import path, include from rest_framework.routers import DefaultRouter from .views import DashboardStatsViewSet, DashboardWeeklyViewSet router = DefaultRouter() -router.register(r'dashboard/stats', DashboardStatsViewSet, basename='dashboard-stats') -router.register(r'dashboard/weekly', DashboardWeeklyViewSet, basename='dashboard-weekly') -urlpatterns = router.urls +router.register(r'dashboard/stats', DashboardStatsViewSet, basename='stats') +router.register(r'dashboard/weekly', DashboardWeeklyViewSet, basename='weekly') +urlpatterns = [ + path('', include(router.urls)), +] diff --git a/backend/dashboard/views.py b/backend/dashboard/views.py index c91f5f9..abe0576 100644 --- a/backend/dashboard/views.py +++ b/backend/dashboard/views.py @@ -9,7 +9,10 @@ from tasks.models import Todo class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): - permission_classes = [IsAuthenticated] + permission_classes = (IsAuthenticated,) + + def get_queryset(self): + return Todo.objects.all() def list(self, request, *args, **kwargs): user = self.request.user @@ -78,7 +81,10 @@ class DashboardStatsViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): class DashboardWeeklyViewSet(viewsets.GenericViewSet, mixins.ListModelMixin): - permission_classes = [IsAuthenticated] + permission_classes = (IsAuthenticated,) + + def get_queryset(self): + return Todo.objects.all() def list(self, request, *args, **kwargs): user = self.request.user