From 4a1a42859bd424162bbc0b1434750dd3c8822c1b Mon Sep 17 00:00:00 2001 From: sosokker Date: Sat, 4 Nov 2023 18:37:52 +0700 Subject: [PATCH] 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