Use Task Abstract model instead of Big Task model

This commit is contained in:
sosokker 2023-11-06 22:02:11 +07:00
parent 603ab30b93
commit 39601926ff
10 changed files with 113 additions and 95 deletions

View File

@ -7,7 +7,7 @@ from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from tasks.utils import get_service from tasks.utils import get_service
from tasks.models import Task from tasks.models import Todo
from tasks.serializers import TaskUpdateSerializer from tasks.serializers import TaskUpdateSerializer
@ -30,10 +30,10 @@ class GoogleCalendarEventViewset(viewsets.ViewSet):
events = service.events().list(calendarId='primary', fields=self.event_fields).execute() events = service.events().list(calendarId='primary', fields=self.event_fields).execute()
for event in events.get('items', []): for event in events.get('items', []):
try: 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) serializer = TaskUpdateSerializer(instance=task, data=event)
return self._validate_serializer(serializer) return self._validate_serializer(serializer)
except Task.DoesNotExist: except Todo.DoesNotExist:
serializer = TaskUpdateSerializer(data=event, user=request.user) serializer = TaskUpdateSerializer(data=event, user=request.user)
return self._validate_serializer(serializer) return self._validate_serializer(serializer)

View File

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

View File

@ -14,23 +14,18 @@ class Tag(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
class Task(models.Model): class Todo(models.Model):
""" """
Represents a task, such as Habit, Daily, Todo, or Reward. Represents a Abstract of task, such as Habit, Daily, Todo, or Reward.
:param type: The type of the tasks :param user: The user who owns the task.
:param title: Title of the task. :param title: Title of the task.
:param notes: Optional additional notes for the task. :param notes: Optional additional notes for the task.
:param tags: Associated tags for the task. :param tags: Associated tags for the task.
:param completed: A boolean field indicating whether the task is completed. :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 importance: The importance of the task (range: 1 to 5)
:param difficulty: The difficulty 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 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 fromSystem: A boolean field indicating if the task is from System.
:param creation_date: Creation date of the task. :param creation_date: Creation date of the task.
:param last_update: Last updated 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 start_event: Start event of the task.
:param end_event: End event(Due Date) of the task. :param end_event: End event(Due Date) of the task.
""" """
TASK_TYPES = [ class Difficulty(models.IntegerChoices):
('daily', 'Daily'), EASY = 1, 'Easy'
('habit', 'Habit'), NORMAL = 2, 'Normal'
('todo', 'Todo'), HARD = 3, 'Hard'
('Long Term Goal', 'Long Term Goal'), VERY_HARD = 4, 'Very Hard'
] DEVIL = 5, 'Devil'
DIFFICULTY_CHOICES = [ user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
(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')
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)
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) importance = models.PositiveSmallIntegerField(choices=[(i, str(i)) for i in range(1, 6)], default=1)
difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, default=1) difficulty = models.PositiveSmallIntegerField(choices=Difficulty.choices, default=Difficulty.EASY)
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) challenge = models.BooleanField(default=False)
fromSystem = models.BooleanField(default=False) fromSystem = models.BooleanField(default=False)
creation_date = models.DateTimeField(auto_now_add=True) creation_date = models.DateTimeField(auto_now_add=True)
last_update = models.DateTimeField(auto_now=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) start_event = models.DateTimeField(null=True)
end_event = models.DateTimeField(null=True) end_event = models.DateTimeField(null=True)
def calculate_eisenhower_matrix_category(self): class Meta:
""" abstract = True
Classify the task into one of the four categories in the Eisenhower Matrix.
: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: 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: else:
time_until_due = float('inf') time_until_due = float('inf')
@ -102,22 +78,17 @@ class Task(models.Model):
importance_threshold = 3 importance_threshold = 3
if time_until_due <= urgency_threshold and self.importance >= importance_threshold: 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: 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: elif time_until_due <= urgency_threshold and self.importance < importance_threshold:
return 3 return Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT
else: else:
return 4 return Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.priority = self.calculate_eisenhower_matrix_category() self.priority = self.calculate_eisenhower_matrix_category()
super(Task, self).save(*args, **kwargs) super(Todo, self).save(*args, **kwargs)
class Meta:
verbose_name = 'Task'
verbose_name_plural = 'Tasks'
class Subtask(models.Model): class Subtask(models.Model):
@ -127,9 +98,9 @@ class Subtask(models.Model):
:param completed: A boolean field indicating whether the subtask is completed. :param completed: A boolean field indicating whether the subtask is completed.
:param parent_task: The parent task of the subtask. :param parent_task: The parent task of the subtask.
""" """
parent_task = models.ForeignKey(Todo, on_delete=models.CASCADE)
description = models.TextField() description = models.TextField()
completed = models.BooleanField(default=False) completed = models.BooleanField(default=False)
parent_task = models.ForeignKey(Task, on_delete=models.CASCADE)
class UserNotification(models.Model): class UserNotification(models.Model):

View File

@ -1,6 +1,6 @@
from rest_framework import serializers 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 Todo
class GoogleCalendarEventSerializer(serializers.Serializer): class GoogleCalendarEventSerializer(serializers.Serializer):
@ -21,7 +21,7 @@ class TaskUpdateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Task model = Todo
fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime') fields = ('id', 'summary', 'description', 'created', 'updated', 'start_datetime', 'end_datetime')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -30,6 +30,6 @@ class TaskUpdateSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
validated_data['user'] = self.user validated_data['user'] = self.user
task = Task.objects.create(**validated_data) task = Todo.objects.create(**validated_data)
return task return task

View File

@ -1,21 +1,21 @@
from rest_framework import serializers from rest_framework import serializers
from ..models import Task from ..models import Todo
class TaskCreateSerializer(serializers.ModelSerializer): class TaskCreateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Task model = Todo
# fields = '__all__' # fields = '__all__'
exclude = ('tags',) exclude = ('tags',)
def create(self, validated_data): def create(self, validated_data):
# Create a new task with 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 TaskGeneralSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Task model = Todo
fields = '__all__' fields = '__all__'
def create(self, validated_data): def create(self, validated_data):
# Create a new task with validated data # Create a new task with validated data
return Task.objects.create(**validated_data) return Todo.objects.create(**validated_data)

View File

@ -2,11 +2,11 @@ from rest_framework import status
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.generics import CreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, DestroyAPIView from rest_framework.generics import CreateAPIView, RetrieveAPIView, RetrieveUpdateAPIView, DestroyAPIView
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
from ..models import Task from ..models import Todo
from .serializers import TaskCreateSerializer, TaskGeneralSerializer from .serializers import TaskCreateSerializer, TaskGeneralSerializer
class TaskCreateView(CreateAPIView): class TaskCreateView(CreateAPIView):
queryset = Task.objects.all() queryset = Todo.objects.all()
serializer_class = TaskCreateSerializer serializer_class = TaskCreateSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -21,17 +21,17 @@ class TaskCreateView(CreateAPIView):
class TaskRetrieveView(RetrieveAPIView): class TaskRetrieveView(RetrieveAPIView):
queryset = Task.objects.all() queryset = Todo.objects.all()
serializer_class = TaskGeneralSerializer serializer_class = TaskGeneralSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
class TaskUpdateView(RetrieveUpdateAPIView): class TaskUpdateView(RetrieveUpdateAPIView):
queryset = Task.objects.all() queryset = Todo.objects.all()
serializer_class = TaskGeneralSerializer serializer_class = TaskGeneralSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
class TaskDeleteView(DestroyAPIView): class TaskDeleteView(DestroyAPIView):
queryset = Task.objects.all() queryset = Todo.objects.all()
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]

View File

@ -6,7 +6,7 @@ from django.utils import timezone
from tasks.tests.utils import create_test_user, login_user from tasks.tests.utils import create_test_user, login_user
from tasks.serializers import TaskUpdateSerializer from tasks.serializers import TaskUpdateSerializer
from tasks.models import Task from tasks.models import Todo
class TaskUpdateSerializerTest(TestCase): class TaskUpdateSerializerTest(TestCase):
def setUp(self): def setUp(self):
@ -29,10 +29,10 @@ class TaskUpdateSerializerTest(TestCase):
self.assertTrue(serializer.is_valid()) self.assertTrue(serializer.is_valid())
serializer.is_valid() serializer.is_valid()
task = serializer.save() task = serializer.save()
self.assertIsInstance(task, Task) self.assertIsInstance(task, Todo)
def test_serializer_update(self): 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 = { data = {
'id': '32141cwaNcapufh8jq2conw', 'id': '32141cwaNcapufh8jq2conw',

View File

@ -5,7 +5,7 @@ from rest_framework import status
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from tasks.tests.utils import create_test_user, login_user from tasks.tests.utils import create_test_user, login_user
from ..models import Task from ..models import Todo
class TaskCreateViewTests(APITestCase): class TaskCreateViewTests(APITestCase):
def setUp(self): def setUp(self):
@ -32,8 +32,8 @@ class TaskCreateViewTests(APITestCase):
} }
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)
self.assertEqual(Task.objects.count(), 1) self.assertEqual(Todo.objects.count(), 1)
self.assertEqual(Task.objects.get().title, 'Test Task') self.assertEqual(Todo.objects.get().title, 'Test Task')
def test_create_invalid_task(self): def test_create_invalid_task(self):
""" """
@ -45,7 +45,7 @@ class TaskCreateViewTests(APITestCase):
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_400_BAD_REQUEST) 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): def test_missing_required_fields(self):
""" """
@ -58,7 +58,7 @@ class TaskCreateViewTests(APITestCase):
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_400_BAD_REQUEST) 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): def test_invalid_user_id(self):
""" """
@ -76,4 +76,4 @@ class TaskCreateViewTests(APITestCase):
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_400_BAD_REQUEST) 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

View File

@ -2,7 +2,7 @@ from datetime import datetime, timedelta, timezone
from django.test import TestCase from django.test import TestCase
from tasks.models import Task from tasks.models import Todo
from tasks.tests.utils import create_test_user from tasks.tests.utils import create_test_user
class TaskModelTest(TestCase): class TaskModelTest(TestCase):
@ -10,28 +10,28 @@ class TaskModelTest(TestCase):
self.user = create_test_user() self.user = create_test_user()
def test_eisenhower_matrix_category(self): 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() task.save()
# 'Not Important & Not Urgent' category (category 4) # 'Not Important & Not Urgent' category (category 4)
self.assertEqual(task.calculate_eisenhower_matrix_category(), 4) self.assertEqual(task.calculate_eisenhower_matrix_category(), 4)
due_date = datetime.now(timezone.utc) + timedelta(days=1) 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() task.save()
# 'Important & Urgent' category (category 1) # 'Important & Urgent' category (category 1)
self.assertEqual(task.calculate_eisenhower_matrix_category(), 1) self.assertEqual(task.calculate_eisenhower_matrix_category(), 1)
due_date = datetime.now(timezone.utc) + timedelta(days=10) 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() task.save()
# 'Important & Not Urgent' category (category 2) # 'Important & Not Urgent' category (category 2)
self.assertEqual(task.calculate_eisenhower_matrix_category(), 2) self.assertEqual(task.calculate_eisenhower_matrix_category(), 2)
due_date = datetime.now(timezone.utc) + timedelta(days=4) 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() task.save()
# 'Not Important & Urgent' category (category 3) # 'Not Important & Urgent' category (category 3)

View File

@ -1,7 +1,7 @@
from rest_framework.test import APIClient from rest_framework.test import APIClient
from users.models import CustomUser from users.models import CustomUser
from ..models import Task from ..models import Todo
def create_test_user(email="testusertestuser@example.com", username="testusertestuser", def create_test_user(email="testusertestuser@example.com", username="testusertestuser",
@ -61,4 +61,4 @@ def create_test_task(user, **kwargs):
task_attributes = {**defaults, **kwargs} task_attributes = {**defaults, **kwargs}
return Task.objects.create(user=user, **task_attributes) return Todo.objects.create(user=user, **task_attributes)