Merge pull request #21 from TurTaskProject/feature/tasks-logic

Add task prioritize calculator
This commit is contained in:
Sirin Puenggun 2023-11-04 18:58:14 +07:00 committed by GitHub
commit 72df95242c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 161 additions and 45 deletions

View File

@ -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),
),
]

View File

@ -1,3 +1,5 @@
from datetime import datetime
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.core import validators from django.core import validators
@ -6,30 +8,35 @@ from django.utils import timezone
class Tag(models.Model): class Tag(models.Model):
""" """
Represents a tag that can be associated with tasks. 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) name = models.CharField(max_length=255)
class Task(models.Model): class Task(models.Model):
""" """
Represents a task, such as Habit, Daily, Todo, or Reward. Represents a task, such as Habit, Daily, Todo, or Reward.
Fields:
- type: The type of the tasks :param type: The type of the tasks
- title: Title of the task. :param title: Title of the task.
- notes: Optional additional notes for the task. :param notes: Optional additional notes for the task.
- tags: Associated tags for the task. :param tags: Associated tags for the task.
- completed: A boolean field indicating whether the task is completed. :param completed: A boolean field indicating whether the task is completed.
- exp: The experience values user will get from the task. :param exp: The experience values user will get from the task.
- priority: The priority of the task (range: 0.1 to 2). :param priority: The priority of the task (1, 2, .., 4), using Eisenhower Matrix Idea.
- difficulty: The difficulty of the task (range: 1 to 5). :param importance: The importance of the task (range: 1 to 5)
- attribute: The attribute linked to the task :param difficulty: The difficulty of the task (range: 1 to 5).
- user: The user who owns the task. :param attribute: The attribute linked to the task
- challenge: Associated challenge (optional). :param user: The user who owns the task.
- reminders: A Many-to-Many relationship with Reminder. :param challenge: Associated challenge (optional).
- fromSystem: A boolean field indicating if the task is from System. :param reminders: A Many-to-Many relationship with Reminder.
- creation_date: Creation date of the task. :param fromSystem: A boolean field indicating if the task is from System.
- last_update: Last updated date of the task. :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 = [ TASK_TYPES = [
('daily', 'Daily'), ('daily', 'Daily'),
@ -46,24 +53,31 @@ class Task(models.Model):
(5, 'Devil'), (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') type = models.CharField(max_length=15, choices=TASK_TYPES, default='habit')
title = models.TextField() title = models.TextField()
notes = models.TextField(default='') notes = models.TextField(default='')
tags = models.ManyToManyField(Tag, blank=True) tags = models.ManyToManyField(Tag, blank=True)
completed = models.BooleanField(default=False) completed = models.BooleanField(default=False)
exp = models.FloatField(default=0) exp = models.FloatField(default=0)
priority = models.FloatField(default=1, validators=[ priority = models.PositiveSmallIntegerField(choices=EISENHOWER_MATRIX, default=4)
validators.MinValueValidator(0.1), importance = models.PositiveSmallIntegerField(choices=[(i, str(i)) for i in range(1, 6)], default=1)
validators.MaxValueValidator(2),
])
difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, default=1) difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, default=1)
attribute = models.CharField(max_length=15, choices=[ attribute = models.CharField(max_length=15, choices=ATTRIBUTE, default='str')
('str', 'Strength'),
('int', 'Intelligence'),
('end', 'Endurance'),
('per', 'Perception'),
('luck', 'Luck'),
], default='str')
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
challenge = models.BooleanField(default=False) challenge = models.BooleanField(default=False)
fromSystem = models.BooleanField(default=False) fromSystem = models.BooleanField(default=False)
@ -73,13 +87,45 @@ class Task(models.Model):
start_event = models.DateTimeField(null=True) start_event = models.DateTimeField(null=True)
end_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.
: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:
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): class Subtask(models.Model):
""" """
Represents a subtask associated with a task. Represents a subtask associated with a task.
- description: Description of the subtask. :param description: Description of the subtask.
- completed: A boolean field indicating whether the subtask is completed. :param completed: A boolean field indicating whether the subtask is completed.
- parent_task: The parent task of the subtask. :param parent_task: The parent task of the subtask.
""" """
description = models.TextField() description = models.TextField()
completed = models.BooleanField(default=False) completed = models.BooleanField(default=False)
@ -89,10 +135,10 @@ class Subtask(models.Model):
class UserNotification(models.Model): class UserNotification(models.Model):
""" """
Represents a user notification. Represents a user notification.
Fields:
- type: The type of the notification (e.g., 'NEW_CHAT_MESSAGE'). :param type: The type of the notification (e.g., 'NEW_CHAT_MESSAGE').
- data: JSON data associated with the notification. :param data: JSON data associated with the notification.
- seen: A boolean field indicating whether the notification has been seen. :param seen: A boolean field indicating whether the notification has been seen.
""" """
NOTIFICATION_TYPES = ( NOTIFICATION_TYPES = (
('LEVEL_UP', 'Level Up'), ('LEVEL_UP', 'Level Up'),
@ -124,12 +170,12 @@ class UserNotification(models.Model):
class Transaction(models.Model): class Transaction(models.Model):
""" """
Represents a transaction involving currencies in the system. Represents a transaction involving currencies in the system.
Fields:
- currency: The type of currency used in the transaction :param currency: The type of currency used in the transaction
- transactionType: The type of the transaction :param transactionType: The type of the transaction
- description: Additional text. :param description: Additional text.
- amount: The transaction amount. :param amount: The transaction amount.
- user: The user involved in the transaction. :param user: The user involved in the transaction.
""" """
CURRENCIES = (('gold', 'Gold'),) CURRENCIES = (('gold', 'Gold'),)
TRANSACTION_TYPES = ( TRANSACTION_TYPES = (

View File

@ -2,7 +2,6 @@ from rest_framework import serializers
from django.utils.dateparse import parse_datetime from django.utils.dateparse import parse_datetime
from .models import Task from .models import Task
from datetime import datetime
class GoogleCalendarEventSerializer(serializers.Serializer): class GoogleCalendarEventSerializer(serializers.Serializer):
summary = serializers.CharField() summary = serializers.CharField()

View File

@ -1,3 +1,5 @@
from datetime import datetime, timedelta
from django.urls import reverse from django.urls import reverse
from rest_framework import status from rest_framework import status
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
@ -11,6 +13,8 @@ class TaskCreateViewTests(APITestCase):
self.user = create_test_user() self.user = create_test_user()
self.client = login_user(self.user) self.client = login_user(self.user)
self.url = reverse("add-task") self.url = reverse("add-task")
self.due_date = datetime.now() + timedelta(days=5)
def test_create_valid_task(self): def test_create_valid_task(self):
""" """
@ -21,9 +25,10 @@ class TaskCreateViewTests(APITestCase):
'type': 'habit', 'type': 'habit',
'exp': 10, 'exp': 10,
'attribute': 'str', 'attribute': 'str',
'priority': 1.5, 'priority': 1,
'difficulty': 1, 'difficulty': 1,
'user': self.user.id, 'user': self.user.id,
'end_event': self.due_date,
} }
response = self.client.post(self.url, data, format='json') response = self.client.post(self.url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED) self.assertEqual(response.status_code, status.HTTP_201_CREATED)
@ -63,9 +68,10 @@ class TaskCreateViewTests(APITestCase):
'title': 'Test Task', 'title': 'Test Task',
'type': 'habit', 'type': 'habit',
'exp': 10, 'exp': 10,
'priority': 1.5, 'priority': 1,
'difficulty': 1, 'difficulty': 1,
'user': 999, # Invalid user ID 'user': 999, # Invalid user ID
'end_event': self.due_date,
} }
response = self.client.post(self.url, data, format='json') response = self.client.post(self.url, data, format='json')

View File

@ -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)