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/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/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/package.json b/frontend/package.json index f057dce..44e4b66 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,6 +42,7 @@ "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.9.1", + "react-datepicker": "^4.23.0", "react-datetime-picker": "^5.5.3", "react-dom": "^18.2.0", "react-icons": "^4.11.0", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 22fdadd..2bb6ef0 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -101,6 +101,9 @@ dependencies: react-bootstrap: specifier: ^2.9.1 version: 2.9.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-datepicker: + specifier: ^4.23.0 + version: 4.23.0(react-dom@18.2.0)(react@18.2.0) react-datetime-picker: specifier: ^5.5.3 version: 5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) @@ -3473,6 +3476,22 @@ packages: - '@types/react-dom' dev: false + /react-datepicker@4.23.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-w+msqlOZ14v6H1UknTKtZw/dw9naFMgAOspf59eY130gWpvy5dvKj/bgsFICDdvxB7PtKWxDcbGlAqCloY1d2A==} + peerDependencies: + react: ^16.9.0 || ^17 || ^18 + react-dom: ^16.9.0 || ^17 || ^18 + dependencies: + '@popperjs/core': 2.11.8 + classnames: 2.3.2 + date-fns: 2.30.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-onclickoutside: 6.13.0(react-dom@18.2.0)(react@18.2.0) + react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0) + dev: false + /react-datetime-picker@5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-bWGEPwGrZjaXTB8P4pbTSDygctLaqTWp0nNibaz8po+l4eTh9gv3yiJ+n4NIcpIJDqZaQJO57Bnij2rAFVQyLw==} peerDependencies: @@ -3520,6 +3539,10 @@ packages: scheduler: 0.23.0 dev: false + /react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + dev: false + /react-fit@1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==} peerDependencies: @@ -3565,6 +3588,30 @@ packages: resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} dev: false + /react-onclickoutside@6.13.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==} + peerDependencies: + react: ^15.5.x || ^16.x || ^17.x || ^18.x + react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} + peerDependencies: + '@popperjs/core': ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + dependencies: + '@popperjs/core': 2.11.8 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-fast-compare: 3.2.2 + warning: 4.0.3 + dev: false + /react-redux@7.2.9(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} peerDependencies: diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 625b9b8..a917ca5 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,7 +11,7 @@ import { SideNav } from "./components/navigations/IconSideNav"; import { Eisenhower } from "./components/EisenhowerMatrix/Eisenhower"; import { PrivateRoute } from "./PrivateRoute"; import { ProfileUpdatePage } from "./components/profile/profilePage"; -import { Dashboard } from "./components/dashboard/Dashboard"; +import { Dashboard } from "./components/dashboard/dashboard"; import { LandingPage } from "./components/landingPage/LandingPage"; import { PublicRoute } from "./PublicRoute"; import { useAuth } from "./hooks/AuthHooks"; diff --git a/frontend/src/components/dashboard/KpiCard.jsx b/frontend/src/components/dashboard/KpiCard.jsx index 85c74c9..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"; @@ -46,10 +53,16 @@ export function KpiCard() {
document.getElementById(`task_detail_modal_${task.id}`).showModal()}>
@@ -74,6 +155,35 @@ export function TaskCard({ task, deleteTask, updateTask }) {
document.getElementById(`task_detail_modal_${task.id}`).showModal()}> + {task.content} +
+ {/* -------- Archive Task Button -------- */} + {mouseIsOver && ( + + )} +Start At
+Complete
+ +End At
+