From 4a1a42859bd424162bbc0b1434750dd3c8822c1b Mon Sep 17 00:00:00 2001 From: sosokker Date: Sat, 4 Nov 2023 18:37:52 +0700 Subject: [PATCH 1/2] Add Task prioritize --- ...r_task_options_task_importance_and_more.py | 27 +++++++++ backend/tasks/models.py | 58 +++++++++++++++---- backend/tasks/tests/test_task_creation.py | 10 +++- backend/tasks/tests/test_task_eisenhower.py | 38 ++++++++++++ 4 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 backend/tasks/migrations/0009_alter_task_options_task_importance_and_more.py create mode 100644 backend/tasks/tests/test_task_eisenhower.py diff --git a/backend/tasks/migrations/0009_alter_task_options_task_importance_and_more.py b/backend/tasks/migrations/0009_alter_task_options_task_importance_and_more.py new file mode 100644 index 0000000..b57624d --- /dev/null +++ b/backend/tasks/migrations/0009_alter_task_options_task_importance_and_more.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.6 on 2023-11-04 10:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('tasks', '0008_task_end_event_task_start_event'), + ] + + operations = [ + migrations.AlterModelOptions( + name='task', + options={'verbose_name': 'Task', 'verbose_name_plural': 'Tasks'}, + ), + migrations.AddField( + model_name='task', + name='importance', + field=models.PositiveSmallIntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')], default=1), + ), + migrations.AlterField( + model_name='task', + name='priority', + field=models.PositiveSmallIntegerField(choices=[(1, 'Important & Urgent'), (2, 'Important & Not Urgent'), (3, 'Not Important & Urgent'), (4, 'Not Important & Not Urgent')], default=4), + ), + ] diff --git a/backend/tasks/models.py b/backend/tasks/models.py index 686eab9..ccdf473 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -1,3 +1,5 @@ +from datetime import datetime + from django.db import models from django.conf import settings from django.core import validators @@ -46,24 +48,31 @@ class Task(models.Model): (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') 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.FloatField(default=1, validators=[ - validators.MinValueValidator(0.1), - validators.MaxValueValidator(2), - ]) + 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=[ - ('str', 'Strength'), - ('int', 'Intelligence'), - ('end', 'Endurance'), - ('per', 'Perception'), - ('luck', 'Luck'), - ], default='str') + attribute = models.CharField(max_length=15, choices=ATTRIBUTE, default='str') user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) challenge = models.BooleanField(default=False) fromSystem = models.BooleanField(default=False) @@ -73,6 +82,33 @@ class Task(models.Model): start_event = models.DateTimeField(null=True) end_event = models.DateTimeField(null=True) + def calculate_eisenhower_matrix_category(self): + if self.end_event: + time_until_due = (self.end_event - datetime.now(timezone.utc)).days + else: + time_until_due = float('inf') + + urgency_threshold = 3 + importance_threshold = 3 + + if time_until_due <= urgency_threshold and self.importance >= importance_threshold: + return 1 + elif time_until_due > urgency_threshold and self.importance >= importance_threshold: + return 2 + elif time_until_due <= urgency_threshold and self.importance < importance_threshold: + return 3 + else: + return 4 + + 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' + + class Subtask(models.Model): """ diff --git a/backend/tasks/tests/test_task_creation.py b/backend/tasks/tests/test_task_creation.py index e7c1e1f..af7986f 100644 --- a/backend/tasks/tests/test_task_creation.py +++ b/backend/tasks/tests/test_task_creation.py @@ -1,3 +1,5 @@ +from datetime import datetime, timedelta + from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase @@ -11,6 +13,8 @@ class TaskCreateViewTests(APITestCase): self.user = create_test_user() self.client = login_user(self.user) self.url = reverse("add-task") + self.due_date = datetime.now() + timedelta(days=5) + def test_create_valid_task(self): """ @@ -21,9 +25,10 @@ class TaskCreateViewTests(APITestCase): 'type': 'habit', 'exp': 10, 'attribute': 'str', - 'priority': 1.5, + 'priority': 1, 'difficulty': 1, 'user': self.user.id, + 'end_event': self.due_date, } response = self.client.post(self.url, data, format='json') self.assertEqual(response.status_code, status.HTTP_201_CREATED) @@ -63,9 +68,10 @@ class TaskCreateViewTests(APITestCase): 'title': 'Test Task', 'type': 'habit', 'exp': 10, - 'priority': 1.5, + 'priority': 1, 'difficulty': 1, 'user': 999, # Invalid user ID + 'end_event': self.due_date, } response = self.client.post(self.url, data, format='json') diff --git a/backend/tasks/tests/test_task_eisenhower.py b/backend/tasks/tests/test_task_eisenhower.py new file mode 100644 index 0000000..b8b3c22 --- /dev/null +++ b/backend/tasks/tests/test_task_eisenhower.py @@ -0,0 +1,38 @@ +from datetime import datetime, timedelta, timezone + +from django.test import TestCase + +from tasks.models import Task +from tasks.tests.utils import create_test_user + +class TaskModelTest(TestCase): + def setUp(self): + self.user = create_test_user() + + def test_eisenhower_matrix_category(self): + task = Task(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.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.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.save() + + # 'Not Important & Urgent' category (category 3) + self.assertEqual(task.calculate_eisenhower_matrix_category(), 3) \ No newline at end of file From c122812b32a883808c7c5611757fcca3376a1080 Mon Sep 17 00:00:00 2001 From: sosokker Date: Sat, 4 Nov 2023 18:55:24 +0700 Subject: [PATCH 2/2] Update docstring style --- backend/tasks/models.py | 72 ++++++++++++++++++++---------------- backend/tasks/serializers.py | 1 - 2 files changed, 41 insertions(+), 32 deletions(-) diff --git a/backend/tasks/models.py b/backend/tasks/models.py index ccdf473..7677acb 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -8,30 +8,35 @@ from django.utils import timezone class Tag(models.Model): """ Represents a tag that can be associated with tasks. - Fields: - - name: The unique name of the tag. + + :param name: The unique name of the tag. """ name = models.CharField(max_length=255) + class Task(models.Model): """ Represents a task, such as Habit, Daily, Todo, or Reward. - Fields: - - type: The type of the tasks - - title: Title of the task. - - notes: Optional additional notes for the task. - - tags: Associated tags for the task. - - completed: A boolean field indicating whether the task is completed. - - exp: The experience values user will get from the task. - - priority: The priority of the task (range: 0.1 to 2). - - difficulty: The difficulty of the task (range: 1 to 5). - - attribute: The attribute linked to the task - - user: The user who owns the task. - - challenge: Associated challenge (optional). - - reminders: A Many-to-Many relationship with Reminder. - - fromSystem: A boolean field indicating if the task is from System. - - creation_date: Creation date of the task. - - last_update: Last updated date of the task. + + :param type: The type of the tasks + :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. + :param: google_calendar_id: Google Calendar Event ID of the task. + :param start_event: Start event of the task. + :param end_event: End event(Due Date) of the task. """ TASK_TYPES = [ ('daily', 'Daily'), @@ -83,6 +88,11 @@ class Task(models.Model): 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. + + :return: The category of the task (1, 2, 3, or 4). + """ if self.end_event: time_until_due = (self.end_event - datetime.now(timezone.utc)).days else: @@ -113,9 +123,9 @@ class Task(models.Model): class Subtask(models.Model): """ Represents a subtask associated with a task. - - description: Description of the subtask. - - completed: A boolean field indicating whether the subtask is completed. - - parent_task: The parent task of the subtask. + :param description: Description of the subtask. + :param completed: A boolean field indicating whether the subtask is completed. + :param parent_task: The parent task of the subtask. """ description = models.TextField() completed = models.BooleanField(default=False) @@ -125,10 +135,10 @@ class Subtask(models.Model): class UserNotification(models.Model): """ Represents a user notification. - Fields: - - type: The type of the notification (e.g., 'NEW_CHAT_MESSAGE'). - - data: JSON data associated with the notification. - - seen: A boolean field indicating whether the notification has been seen. + + :param type: The type of the notification (e.g., 'NEW_CHAT_MESSAGE'). + :param data: JSON data associated with the notification. + :param seen: A boolean field indicating whether the notification has been seen. """ NOTIFICATION_TYPES = ( ('LEVEL_UP', 'Level Up'), @@ -160,12 +170,12 @@ class UserNotification(models.Model): class Transaction(models.Model): """ Represents a transaction involving currencies in the system. - Fields: - - currency: The type of currency used in the transaction - - transactionType: The type of the transaction - - description: Additional text. - - amount: The transaction amount. - - user: The user involved in the transaction. + + :param currency: The type of currency used in the transaction + :param transactionType: The type of the transaction + :param description: Additional text. + :param amount: The transaction amount. + :param user: The user involved in the transaction. """ CURRENCIES = (('gold', 'Gold'),) TRANSACTION_TYPES = ( diff --git a/backend/tasks/serializers.py b/backend/tasks/serializers.py index e2c6a85..494920f 100644 --- a/backend/tasks/serializers.py +++ b/backend/tasks/serializers.py @@ -2,7 +2,6 @@ from rest_framework import serializers from django.utils.dateparse import parse_datetime from .models import Task -from datetime import datetime class GoogleCalendarEventSerializer(serializers.Serializer): summary = serializers.CharField()