diff --git a/backend/tasks/api.py b/backend/tasks/api.py index 4736582..9f5d95e 100644 --- a/backend/tasks/api.py +++ b/backend/tasks/api.py @@ -7,7 +7,7 @@ from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from tasks.utils import get_service -from tasks.models import Task +from tasks.models import Todo from tasks.serializers import TaskUpdateSerializer @@ -30,10 +30,10 @@ class GoogleCalendarEventViewset(viewsets.ViewSet): events = service.events().list(calendarId='primary', fields=self.event_fields).execute() for event in events.get('items', []): try: - task = Task.objects.get(google_calendar_id=event['id']) + task = Todo.objects.get(google_calendar_id=event['id']) serializer = TaskUpdateSerializer(instance=task, data=event) return self._validate_serializer(serializer) - except Task.DoesNotExist: + except Todo.DoesNotExist: serializer = TaskUpdateSerializer(data=event, user=request.user) return self._validate_serializer(serializer) diff --git a/backend/tasks/migrations/0010_todo_alter_subtask_parent_task_delete_task.py b/backend/tasks/migrations/0010_todo_alter_subtask_parent_task_delete_task.py new file mode 100644 index 0000000..2e6b65d --- /dev/null +++ b/backend/tasks/migrations/0010_todo_alter_subtask_parent_task_delete_task.py @@ -0,0 +1,47 @@ +# Generated by Django 4.2.6 on 2023-11-06 15:01 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('tasks', '0009_alter_task_options_task_importance_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Todo', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.TextField()), + ('notes', models.TextField(default='')), + ('importance', models.PositiveSmallIntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')], default=1)), + ('difficulty', models.PositiveSmallIntegerField(choices=[(1, 'Easy'), (2, 'Normal'), (3, 'Hard'), (4, 'Very Hard'), (5, 'Devil')], default=1)), + ('challenge', models.BooleanField(default=False)), + ('fromSystem', models.BooleanField(default=False)), + ('creation_date', models.DateTimeField(auto_now_add=True)), + ('last_update', models.DateTimeField(auto_now=True)), + ('google_calendar_id', models.CharField(blank=True, max_length=255, null=True)), + ('start_event', models.DateTimeField(null=True)), + ('end_event', models.DateTimeField(null=True)), + ('priority', models.PositiveSmallIntegerField(choices=[(1, 'Important & Urgent'), (2, 'Important & Not Urgent'), (3, 'Not Important & Urgent'), (4, 'Not Important & Not Urgent')], default=4)), + ('tags', models.ManyToManyField(blank=True, to='tasks.tag')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.AlterField( + model_name='subtask', + name='parent_task', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.todo'), + ), + migrations.DeleteModel( + name='Task', + ), + ] diff --git a/backend/tasks/models.py b/backend/tasks/models.py index 7677acb..9f79e0b 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -14,23 +14,18 @@ class Tag(models.Model): name = models.CharField(max_length=255) -class Task(models.Model): +class Todo(models.Model): """ - Represents a task, such as Habit, Daily, Todo, or Reward. - - :param type: The type of the tasks + Represents a Abstract of task, such as Habit, Daily, Todo, or Reward. + + :param user: The user who owns the task. :param title: Title of the task. :param notes: Optional additional notes for the task. :param tags: Associated tags for the task. :param completed: A boolean field indicating whether the task is completed. - :param exp: The experience values user will get from the task. - :param priority: The priority of the task (1, 2, .., 4), using Eisenhower Matrix Idea. :param importance: The importance of the task (range: 1 to 5) :param difficulty: The difficulty of the task (range: 1 to 5). - :param attribute: The attribute linked to the task - :param user: The user who owns the task. :param challenge: Associated challenge (optional). - :param reminders: A Many-to-Many relationship with Reminder. :param fromSystem: A boolean field indicating if the task is from System. :param creation_date: Creation date of the task. :param last_update: Last updated date of the task. @@ -38,63 +33,44 @@ class Task(models.Model): :param start_event: Start event of the task. :param end_event: End event(Due Date) of the task. """ - TASK_TYPES = [ - ('daily', 'Daily'), - ('habit', 'Habit'), - ('todo', 'Todo'), - ('Long Term Goal', 'Long Term Goal'), - ] + class Difficulty(models.IntegerChoices): + EASY = 1, 'Easy' + NORMAL = 2, 'Normal' + HARD = 3, 'Hard' + VERY_HARD = 4, 'Very Hard' + DEVIL = 5, 'Devil' - DIFFICULTY_CHOICES = [ - (1, 'Easy'), - (2, 'Normal'), - (3, 'Hard'), - (4, 'Very Hard'), - (5, 'Devil'), - ] - - ATTRIBUTE = [ - ('str', 'Strength'), - ('int', 'Intelligence'), - ('end', 'Endurance'), - ('per', 'Perception'), - ('luck', 'Luck'), - ] - - EISENHOWER_MATRIX = [ - (1, 'Important & Urgent'), - (2, 'Important & Not Urgent'), - (3, 'Not Important & Urgent'), - (4, 'Not Important & Not Urgent'), - ] - - type = models.CharField(max_length=15, choices=TASK_TYPES, default='habit') + user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) title = models.TextField() notes = models.TextField(default='') tags = models.ManyToManyField(Tag, blank=True) - completed = models.BooleanField(default=False) - exp = models.FloatField(default=0) - priority = models.PositiveSmallIntegerField(choices=EISENHOWER_MATRIX, default=4) importance = models.PositiveSmallIntegerField(choices=[(i, str(i)) for i in range(1, 6)], default=1) - difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, default=1) - attribute = models.CharField(max_length=15, choices=ATTRIBUTE, default='str') - user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + difficulty = models.PositiveSmallIntegerField(choices=Difficulty.choices, default=Difficulty.EASY) challenge = models.BooleanField(default=False) fromSystem = models.BooleanField(default=False) creation_date = models.DateTimeField(auto_now_add=True) last_update = models.DateTimeField(auto_now=True) - google_calendar_id = models.CharField(blank=True, null=True, max_length=255) + google_calendar_id = models.CharField(max_length=255, null=True, blank=True) start_event = models.DateTimeField(null=True) end_event = models.DateTimeField(null=True) - def calculate_eisenhower_matrix_category(self): - """ - Classify the task into one of the four categories in the Eisenhower Matrix. + class Meta: + abstract = True - :return: The category of the task (1, 2, 3, or 4). - """ + +class Todo(Todo): + + class EisenhowerMatrix(models.IntegerChoices): + IMPORTANT_URGENT = 1, 'Important & Urgent' + IMPORTANT_NOT_URGENT = 2, 'Important & Not Urgent' + NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent' + NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent' + + priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT) + + def calculate_eisenhower_matrix_category(self): if self.end_event: - time_until_due = (self.end_event - datetime.now(timezone.utc)).days + time_until_due = (self.end_event - timezone.now()).days else: time_until_due = float('inf') @@ -102,22 +78,17 @@ class Task(models.Model): importance_threshold = 3 if time_until_due <= urgency_threshold and self.importance >= importance_threshold: - return 1 + return Todo.EisenhowerMatrix.IMPORTANT_URGENT elif time_until_due > urgency_threshold and self.importance >= importance_threshold: - return 2 + return Todo.EisenhowerMatrix.IMPORTANT_NOT_URGENT elif time_until_due <= urgency_threshold and self.importance < importance_threshold: - return 3 + return Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT else: - return 4 + return Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT def save(self, *args, **kwargs): self.priority = self.calculate_eisenhower_matrix_category() - super(Task, self).save(*args, **kwargs) - - class Meta: - verbose_name = 'Task' - verbose_name_plural = 'Tasks' - + super(Todo, self).save(*args, **kwargs) class Subtask(models.Model): @@ -127,9 +98,9 @@ class Subtask(models.Model): :param completed: A boolean field indicating whether the subtask is completed. :param parent_task: The parent task of the subtask. """ + parent_task = models.ForeignKey(Todo, on_delete=models.CASCADE) description = models.TextField() completed = models.BooleanField(default=False) - parent_task = models.ForeignKey(Task, on_delete=models.CASCADE) class UserNotification(models.Model): diff --git a/backend/tasks/serializers.py b/backend/tasks/serializers.py index 494920f..9204c1f 100644 --- a/backend/tasks/serializers.py +++ b/backend/tasks/serializers.py @@ -1,6 +1,6 @@ from rest_framework import serializers from django.utils.dateparse import parse_datetime -from .models import Task +from .models import Todo class GoogleCalendarEventSerializer(serializers.Serializer): @@ -21,7 +21,7 @@ class TaskUpdateSerializer(serializers.ModelSerializer): class Meta: - model = Task + model = Todo fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime') def __init__(self, *args, **kwargs): @@ -30,6 +30,6 @@ class TaskUpdateSerializer(serializers.ModelSerializer): def create(self, validated_data): validated_data['user'] = self.user - task = Task.objects.create(**validated_data) + task = Todo.objects.create(**validated_data) return task \ No newline at end of file diff --git a/backend/tasks/tasks/serializers.py b/backend/tasks/tasks/serializers.py index 7876263..85f0281 100644 --- a/backend/tasks/tasks/serializers.py +++ b/backend/tasks/tasks/serializers.py @@ -1,21 +1,21 @@ from rest_framework import serializers -from ..models import Task +from ..models import Todo class TaskCreateSerializer(serializers.ModelSerializer): class Meta: - model = Task + model = Todo # fields = '__all__' exclude = ('tags',) def create(self, validated_data): # Create a new task with validated data - return Task.objects.create(**validated_data) + return Todo.objects.create(**validated_data) class TaskGeneralSerializer(serializers.ModelSerializer): class Meta: - model = Task + model = Todo fields = '__all__' def create(self, validated_data): # Create a new task with validated data - return Task.objects.create(**validated_data) \ No newline at end of file + return Todo.objects.create(**validated_data) \ No newline at end of file diff --git a/backend/tasks/tasks/views.py b/backend/tasks/tasks/views.py index 0f75ced..4077315 100644 --- a/backend/tasks/tasks/views.py +++ b/backend/tasks/tasks/views.py @@ -2,11 +2,11 @@ from rest_framework import status from rest_framework.response import Response from rest_framework.generics import CreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, DestroyAPIView from rest_framework.permissions import IsAuthenticated -from ..models import Task +from ..models import Todo from .serializers import TaskCreateSerializer, TaskGeneralSerializer class TaskCreateView(CreateAPIView): - queryset = Task.objects.all() + queryset = Todo.objects.all() serializer_class = TaskCreateSerializer permission_classes = [IsAuthenticated] @@ -21,17 +21,17 @@ class TaskCreateView(CreateAPIView): class TaskRetrieveView(RetrieveAPIView): - queryset = Task.objects.all() + queryset = Todo.objects.all() serializer_class = TaskGeneralSerializer permission_classes = [IsAuthenticated] class TaskUpdateView(RetrieveUpdateAPIView): - queryset = Task.objects.all() + queryset = Todo.objects.all() serializer_class = TaskGeneralSerializer permission_classes = [IsAuthenticated] class TaskDeleteView(DestroyAPIView): - queryset = Task.objects.all() + queryset = Todo.objects.all() permission_classes = [IsAuthenticated] \ No newline at end of file diff --git a/backend/tasks/tests/test_deserializer.py b/backend/tasks/tests/test_deserializer.py index aa07480..492e3d2 100644 --- a/backend/tasks/tests/test_deserializer.py +++ b/backend/tasks/tests/test_deserializer.py @@ -6,7 +6,7 @@ from django.utils import timezone from tasks.tests.utils import create_test_user, login_user from tasks.serializers import TaskUpdateSerializer -from tasks.models import Task +from tasks.models import Todo class TaskUpdateSerializerTest(TestCase): def setUp(self): @@ -29,10 +29,10 @@ class TaskUpdateSerializerTest(TestCase): self.assertTrue(serializer.is_valid()) serializer.is_valid() task = serializer.save() - self.assertIsInstance(task, Task) + self.assertIsInstance(task, Todo) def test_serializer_update(self): - task = Task.objects.create(title='Original Task', notes='Original description', user=self.user) + task = Todo.objects.create(title='Original Task', notes='Original description', user=self.user) data = { 'id': '32141cwaNcapufh8jq2conw', diff --git a/backend/tasks/tests/test_task_creation.py b/backend/tasks/tests/test_task_creation.py index af7986f..0fee97d 100644 --- a/backend/tasks/tests/test_task_creation.py +++ b/backend/tasks/tests/test_task_creation.py @@ -5,7 +5,7 @@ from rest_framework import status from rest_framework.test import APITestCase from tasks.tests.utils import create_test_user, login_user -from ..models import Task +from ..models import Todo class TaskCreateViewTests(APITestCase): def setUp(self): @@ -32,8 +32,8 @@ class TaskCreateViewTests(APITestCase): } response = self.client.post(self.url, data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) - self.assertEqual(Task.objects.count(), 1) - self.assertEqual(Task.objects.get().title, 'Test Task') + self.assertEqual(Todo.objects.count(), 1) + self.assertEqual(Todo.objects.get().title, 'Test Task') def test_create_invalid_task(self): """ @@ -45,7 +45,7 @@ class TaskCreateViewTests(APITestCase): response = self.client.post(self.url, data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Task.objects.count(), 0) # No task should be created + self.assertEqual(Todo.objects.count(), 0) # No task should be created def test_missing_required_fields(self): """ @@ -58,7 +58,7 @@ class TaskCreateViewTests(APITestCase): response = self.client.post(self.url, data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Task.objects.count(), 0) # No task should be created + self.assertEqual(Todo.objects.count(), 0) # No task should be created def test_invalid_user_id(self): """ @@ -76,4 +76,4 @@ class TaskCreateViewTests(APITestCase): response = self.client.post(self.url, data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertEqual(Task.objects.count(), 0) # No task should be created + self.assertEqual(Todo.objects.count(), 0) # No task should be created diff --git a/backend/tasks/tests/test_task_eisenhower.py b/backend/tasks/tests/test_task_eisenhower.py index b8b3c22..ca30e5d 100644 --- a/backend/tasks/tests/test_task_eisenhower.py +++ b/backend/tasks/tests/test_task_eisenhower.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta, timezone from django.test import TestCase -from tasks.models import Task +from tasks.models import Todo from tasks.tests.utils import create_test_user class TaskModelTest(TestCase): @@ -10,28 +10,28 @@ class TaskModelTest(TestCase): self.user = create_test_user() def test_eisenhower_matrix_category(self): - task = Task(importance=2, end_event=None, user=self.user) + task = Todo(importance=2, end_event=None, user=self.user) task.save() # 'Not Important & Not Urgent' category (category 4) self.assertEqual(task.calculate_eisenhower_matrix_category(), 4) due_date = datetime.now(timezone.utc) + timedelta(days=1) - task = Task(importance=4, end_event=due_date, user=self.user) + task = Todo(importance=4, end_event=due_date, user=self.user) task.save() # 'Important & Urgent' category (category 1) self.assertEqual(task.calculate_eisenhower_matrix_category(), 1) due_date = datetime.now(timezone.utc) + timedelta(days=10) - task = Task(importance=3, end_event=due_date, user=self.user) + task = Todo(importance=3, end_event=due_date, user=self.user) task.save() # 'Important & Not Urgent' category (category 2) self.assertEqual(task.calculate_eisenhower_matrix_category(), 2) due_date = datetime.now(timezone.utc) + timedelta(days=4) - task = Task(importance=1, end_event=due_date, user=self.user) + task = Todo(importance=1, end_event=due_date, user=self.user) task.save() # 'Not Important & Urgent' category (category 3) diff --git a/backend/tasks/tests/utils.py b/backend/tasks/tests/utils.py index f33b828..b1bef0b 100644 --- a/backend/tasks/tests/utils.py +++ b/backend/tasks/tests/utils.py @@ -1,7 +1,7 @@ from rest_framework.test import APIClient from users.models import CustomUser -from ..models import Task +from ..models import Todo def create_test_user(email="testusertestuser@example.com", username="testusertestuser", @@ -61,4 +61,4 @@ def create_test_task(user, **kwargs): task_attributes = {**defaults, **kwargs} - return Task.objects.create(user=user, **task_attributes) \ No newline at end of file + return Todo.objects.create(user=user, **task_attributes) \ No newline at end of file