From fec588f81f3a2f98ce6b1d4f002af510050a05cb Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 02:39:41 +0700 Subject: [PATCH 1/7] Update model according to last design --- backend/boards/__init__.py | 0 backend/boards/admin.py | 3 + backend/boards/apps.py | 6 + backend/boards/migrations/0001_initial.py | 35 ++++++ backend/boards/migrations/__init__.py | 0 backend/boards/models.py | 20 ++++ backend/boards/tests.py | 3 + backend/boards/urls.py | 5 + backend/boards/views.py | 3 + backend/core/settings.py | 1 + backend/core/urls.py | 1 + backend/tasks/models.py | 136 ++++++++++------------ 12 files changed, 140 insertions(+), 73 deletions(-) create mode 100644 backend/boards/__init__.py create mode 100644 backend/boards/admin.py create mode 100644 backend/boards/apps.py create mode 100644 backend/boards/migrations/0001_initial.py create mode 100644 backend/boards/migrations/__init__.py create mode 100644 backend/boards/models.py create mode 100644 backend/boards/tests.py create mode 100644 backend/boards/urls.py create mode 100644 backend/boards/views.py diff --git a/backend/boards/__init__.py b/backend/boards/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/boards/admin.py b/backend/boards/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/backend/boards/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/backend/boards/apps.py b/backend/boards/apps.py new file mode 100644 index 0000000..7cd6bbc --- /dev/null +++ b/backend/boards/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class BoardsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'boards' diff --git a/backend/boards/migrations/0001_initial.py b/backend/boards/migrations/0001_initial.py new file mode 100644 index 0000000..2196132 --- /dev/null +++ b/backend/boards/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.6 on 2023-11-19 19:19 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Board', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='ListBoard', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('position', models.IntegerField()), + ('board', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boards.board')), + ], + ), + ] diff --git a/backend/boards/migrations/__init__.py b/backend/boards/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/boards/models.py b/backend/boards/models.py new file mode 100644 index 0000000..f0e2c57 --- /dev/null +++ b/backend/boards/models.py @@ -0,0 +1,20 @@ +from django.db import models + +from users.models import CustomUser + +class Board(models.Model): + user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) + name = models.CharField(max_length=255) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self) -> str: + return f"{self.name}" + + +class ListBoard(models.Model): + board = models.ForeignKey(Board, on_delete=models.CASCADE) + name = models.CharField(max_length=255) + position = models.IntegerField() + + def __str__(self) -> str: + return f"{self.name}" diff --git a/backend/boards/tests.py b/backend/boards/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/backend/boards/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/backend/boards/urls.py b/backend/boards/urls.py new file mode 100644 index 0000000..d2d839f --- /dev/null +++ b/backend/boards/urls.py @@ -0,0 +1,5 @@ +from django.urls import path + +urlpatterns = [ + +] diff --git a/backend/boards/views.py b/backend/boards/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/backend/boards/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/backend/core/settings.py b/backend/core/settings.py index 4936805..9e7903c 100644 --- a/backend/core/settings.py +++ b/backend/core/settings.py @@ -53,6 +53,7 @@ INSTALLED_APPS = [ 'users', 'authentications', 'dashboard', + 'boards', 'corsheaders', 'drf_spectacular', diff --git a/backend/core/urls.py b/backend/core/urls.py index 06a4fd2..3ba1133 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -28,4 +28,5 @@ urlpatterns = [ path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'), path('api/', include('dashboard.urls')), + path('api/', include('boards.urls')), ] \ No newline at end of file diff --git a/backend/tasks/models.py b/backend/tasks/models.py index 9a914c0..df3d2e3 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -1,6 +1,8 @@ from django.db import models from django.conf import settings +from boards.models import ListBoard + class Tag(models.Model): """ Represents a tag that can be associated with tasks. @@ -12,7 +14,7 @@ class Tag(models.Model): class Task(models.Model): """ - Represents a Abstract of task, such as Habit, Daily, Todo, or Reward. + Represents a Abstract of task, such as Habit, Recurrence, Todo. :param user: The user who owns the task. :param title: Title of the task. @@ -23,10 +25,6 @@ class Task(models.Model): :param challenge: Associated challenge (optional). :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. """ class Difficulty(models.IntegerChoices): EASY = 1, 'Easy' @@ -45,9 +43,6 @@ class Task(models.Model): 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(max_length=255, null=True, blank=True) - start_event = models.DateTimeField(null=True) - end_event = models.DateTimeField(null=True) class Meta: abstract = True @@ -61,6 +56,11 @@ class Todo(Task): NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent' NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent' + is_active = models.BooleanField(default=True) + is_full_day_event = models.BooleanField(default=False) + start_event = models.DateTimeField(null=True) + end_event = models.DateTimeField(null=True) + google_calendar_id = models.CharField(max_length=255, null=True, blank=True) completed = models.BooleanField(default=False) priority = models.PositiveSmallIntegerField(choices=EisenhowerMatrix.choices, default=EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT) @@ -68,15 +68,68 @@ class Todo(Task): return self.title class RecurrenceTask(Task): + list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE) + rrule = models.CharField(max_length=255) + is_active = models.BooleanField(default=True) + is_full_day_event = models.BooleanField(default=False) + start_event = models.DateTimeField(null=True) + end_event = models.DateTimeField(null=True) completed = models.BooleanField(default=False) - recurrence_rule = models.CharField() + parent_task = models.ForeignKey("self", null=True) def __str__(self) -> str: return f"{self.title} ({self.recurrence_rule})" +class RecurrencePattern(models.Model): + class RecurringType(models.IntegerChoices): + DAILY = 0, 'Daily' + WEEKLY = 1, 'Weekly' + MONTHLY = 2, 'Monthly' + YEARLY = 3, 'Yearly' + + class DayOfWeek(models.IntegerChoices): + MONDAY = 0, 'Monday' + TUESDAY = 1, 'Tuesday' + WEDNESDAY = 2, 'Wednesday' + THURSDAY = 3, 'Thursday' + FRIDAY = 4, 'Friday' + SATURDAY = 5, 'Saturday' + SUNDAY = 6, 'Sunday' + + class WeekOfMonth(models.IntegerChoices): + FIRST = 1, 'First' + SECOND = 2, 'Second' + THIRD = 3, 'Third' + FOURTH = 4, 'Fourth' + LAST = 5, 'Last' + + class MonthOfYear(models.IntegerChoices): + JANUARY = 1, 'January' + FEBRUARY = 2, 'February' + MARCH = 3, 'March' + APRIL = 4, 'April' + MAY = 5, 'May' + JUNE = 6, 'June' + JULY = 7, 'July' + AUGUST = 8, 'August' + SEPTEMBER = 9, 'September' + OCTOBER = 10, 'October' + NOVEMBER = 11, 'November' + DECEMBER = 12, 'December' + + recurrence_task = models.ForeignKey(RecurrenceTask, on_delete=models.CASCADE) + recurring_type = models.IntergerField(choices=RecurringType.choices) + max_occurrences = models.IntegerField(default=0) + day_of_week = models.IntegerField(choices=DayOfWeek.choices) + week_of_month = models.IntegerField(choices=WeekOfMonth.choices) + day_of_month = models.IntegerField(default=0) + month_of_year = models.IntegerField(choices=MonthOfYear.choices) + + class Habit(Task): streak = models.IntegerField(default=0) + current_count = models.IntegerField(default=0) def __str__(self) -> str: return f"{self.title} ({self.streak})" @@ -91,67 +144,4 @@ class Subtask(models.Model): """ parent_task = models.ForeignKey(Todo, on_delete=models.CASCADE) description = models.TextField() - completed = models.BooleanField(default=False) - - -class UserNotification(models.Model): - """ - Represents a user notification. - - :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'), - ('DEATH', 'Death'), - ) - - type = models.CharField(max_length=255, choices=[type for type in NOTIFICATION_TYPES]) - data = models.JSONField(default=dict) - seen = models.BooleanField(default=False) - - @staticmethod - def clean_notification(notifications): - """ - Cleanup function for removing corrupt notification data: - - Removes notifications with null or missing id or type. - """ - if not notifications: - return notifications - - filtered_notifications = [] - - for notification in notifications: - if notification.id is None or notification.type is None: - continue - - return filtered_notifications - - -class Transaction(models.Model): - """ - Represents a transaction involving currencies in the system. - - :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 = ( - ('buy_gold', 'Buy Gold'), - ('spend', 'Spend'), - ('debug', 'Debug'), - ('force_update_gold', 'Force Update Gold'), - ) - - currency = models.CharField(max_length=12, choices=CURRENCIES) - transaction_type = models.CharField(max_length=24, choices=TRANSACTION_TYPES) - description = models.TextField(blank=True) - amount = models.FloatField(default=0) - user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) - - def __str__(self): - return f"Transaction ({self.id})" \ No newline at end of file + completed = models.BooleanField(default=False) \ No newline at end of file From be1c6fd466b3c8c9d850350c67f44328ead7d23a Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 03:16:15 +0700 Subject: [PATCH 2/7] Update Task sub class field --- backend/boards/apps.py | 3 + ...attern_remove_transaction_user_and_more.py | 107 ++++++++++++++++++ backend/tasks/models.py | 11 +- ...006_remove_userstats_endurance_and_more.py | 38 +++++++ backend/users/models.py | 14 +-- 5 files changed, 155 insertions(+), 18 deletions(-) create mode 100644 backend/tasks/migrations/0015_recurrencepattern_remove_transaction_user_and_more.py create mode 100644 backend/users/migrations/0006_remove_userstats_endurance_and_more.py diff --git a/backend/boards/apps.py b/backend/boards/apps.py index 7cd6bbc..d10d8fa 100644 --- a/backend/boards/apps.py +++ b/backend/boards/apps.py @@ -4,3 +4,6 @@ from django.apps import AppConfig class BoardsConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'boards' + + def ready(self): + import boards.signals \ No newline at end of file diff --git a/backend/tasks/migrations/0015_recurrencepattern_remove_transaction_user_and_more.py b/backend/tasks/migrations/0015_recurrencepattern_remove_transaction_user_and_more.py new file mode 100644 index 0000000..ee9c4bc --- /dev/null +++ b/backend/tasks/migrations/0015_recurrencepattern_remove_transaction_user_and_more.py @@ -0,0 +1,107 @@ +# Generated by Django 4.2.6 on 2023-11-19 20:15 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('boards', '0001_initial'), + ('tasks', '0014_recurrencetask_completed_todo_completed'), + ] + + operations = [ + migrations.CreateModel( + name='RecurrencePattern', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recurring_type', models.IntegerField(choices=[(0, 'Daily'), (1, 'Weekly'), (2, 'Monthly'), (3, 'Yearly')])), + ('max_occurrences', models.IntegerField(default=0)), + ('day_of_week', models.IntegerField(choices=[(0, 'Monday'), (1, 'Tuesday'), (2, 'Wednesday'), (3, 'Thursday'), (4, 'Friday'), (5, 'Saturday'), (6, 'Sunday')])), + ('week_of_month', models.IntegerField(choices=[(1, 'First'), (2, 'Second'), (3, 'Third'), (4, 'Fourth'), (5, 'Last')])), + ('day_of_month', models.IntegerField(default=0)), + ('month_of_year', models.IntegerField(choices=[(1, 'January'), (2, 'February'), (3, 'March'), (4, 'April'), (5, 'May'), (6, 'June'), (7, 'July'), (8, 'August'), (9, 'September'), (10, 'October'), (11, 'November'), (12, 'December')])), + ], + ), + migrations.RemoveField( + model_name='transaction', + name='user', + ), + migrations.DeleteModel( + name='UserNotification', + ), + migrations.RemoveField( + model_name='habit', + name='end_event', + ), + migrations.RemoveField( + model_name='habit', + name='google_calendar_id', + ), + migrations.RemoveField( + model_name='habit', + name='start_event', + ), + migrations.RemoveField( + model_name='recurrencetask', + name='google_calendar_id', + ), + migrations.RemoveField( + model_name='recurrencetask', + name='recurrence_rule', + ), + migrations.AddField( + model_name='habit', + name='current_count', + field=models.IntegerField(default=0), + ), + migrations.AddField( + model_name='recurrencetask', + name='is_active', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='recurrencetask', + name='is_full_day_event', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='recurrencetask', + name='list_board', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'), + ), + migrations.AddField( + model_name='recurrencetask', + name='parent_task', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='tasks.recurrencetask'), + ), + migrations.AddField( + model_name='recurrencetask', + name='rrule', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='todo', + name='is_active', + field=models.BooleanField(default=True), + ), + migrations.AddField( + model_name='todo', + name='is_full_day_event', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='todo', + name='list_board', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'), + ), + migrations.DeleteModel( + name='Transaction', + ), + migrations.AddField( + model_name='recurrencepattern', + name='recurrence_task', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tasks.recurrencetask'), + ), + ] diff --git a/backend/tasks/models.py b/backend/tasks/models.py index df3d2e3..87dd3be 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -1,7 +1,7 @@ from django.db import models from django.conf import settings -from boards.models import ListBoard +from boards.models import ListBoard, Board class Tag(models.Model): """ @@ -56,6 +56,7 @@ class Todo(Task): NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent' NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent' + list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True) is_active = models.BooleanField(default=True) is_full_day_event = models.BooleanField(default=False) start_event = models.DateTimeField(null=True) @@ -68,14 +69,14 @@ class Todo(Task): return self.title class RecurrenceTask(Task): - list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE) - rrule = models.CharField(max_length=255) + list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True) + rrule = models.CharField(max_length=255, null=True, blank=True) is_active = models.BooleanField(default=True) is_full_day_event = models.BooleanField(default=False) start_event = models.DateTimeField(null=True) end_event = models.DateTimeField(null=True) completed = models.BooleanField(default=False) - parent_task = models.ForeignKey("self", null=True) + parent_task = models.ForeignKey("self", on_delete=models.CASCADE, null=True) def __str__(self) -> str: return f"{self.title} ({self.recurrence_rule})" @@ -119,7 +120,7 @@ class RecurrencePattern(models.Model): DECEMBER = 12, 'December' recurrence_task = models.ForeignKey(RecurrenceTask, on_delete=models.CASCADE) - recurring_type = models.IntergerField(choices=RecurringType.choices) + recurring_type = models.IntegerField(choices=RecurringType.choices) max_occurrences = models.IntegerField(default=0) day_of_week = models.IntegerField(choices=DayOfWeek.choices) week_of_month = models.IntegerField(choices=WeekOfMonth.choices) diff --git a/backend/users/migrations/0006_remove_userstats_endurance_and_more.py b/backend/users/migrations/0006_remove_userstats_endurance_and_more.py new file mode 100644 index 0000000..a2bf209 --- /dev/null +++ b/backend/users/migrations/0006_remove_userstats_endurance_and_more.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.6 on 2023-11-19 20:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0005_alter_userstats_endurance_and_more'), + ] + + operations = [ + migrations.RemoveField( + model_name='userstats', + name='endurance', + ), + migrations.RemoveField( + model_name='userstats', + name='intelligence', + ), + migrations.RemoveField( + model_name='userstats', + name='luck', + ), + migrations.RemoveField( + model_name='userstats', + name='perception', + ), + migrations.RemoveField( + model_name='userstats', + name='strength', + ), + migrations.AddField( + model_name='customuser', + name='last_name', + field=models.CharField(blank=True, max_length=150), + ), + ] diff --git a/backend/users/models.py b/backend/users/models.py index c2eb9fd..c17dbee 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -5,7 +5,6 @@ from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin -from django.core.validators import MinValueValidator, MaxValueValidator from .managers import CustomAccountManager @@ -15,6 +14,7 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): email = models.EmailField(_('email address'), unique=True) username = models.CharField(max_length=150, unique=True) first_name = models.CharField(max_length=150, blank=True) + last_name = models.CharField(max_length=150, blank=True) start_date = models.DateTimeField(default=timezone.now) about = models.TextField(_('about'), max_length=500, blank=True) profile_pic = models.ImageField(upload_to='profile_pics', null=True, blank=True, default='profile_pics/default.png') @@ -35,7 +35,6 @@ class CustomUser(AbstractBaseUser, PermissionsMixin): # String representation of the user return self.username - def random_luck(): return random.randint(1, 50) @@ -51,17 +50,6 @@ class UserStats(models.Model): health = models.IntegerField(default=100) gold = models.FloatField(default=0.0) experience = models.FloatField(default=0) - strength = models.IntegerField(default=1, - validators=[MinValueValidator(1), - MaxValueValidator(100)]) - intelligence = models.IntegerField(default=1, validators=[MinValueValidator(1), - MaxValueValidator(100)]) - endurance = models.IntegerField(default=1, validators=[MinValueValidator(1), - MaxValueValidator(100)]) - perception = models.IntegerField(default=1, validators=[MinValueValidator(1), - MaxValueValidator(100)]) - luck = models.IntegerField(default=random_luck, validators=[MinValueValidator(1), - MaxValueValidator(50)],) @property def level(self): From a40e1bc6f0896398dcc2d16fbecd8de7447d71aa Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 03:16:32 +0700 Subject: [PATCH 3/7] Add Listboard assign signal --- backend/boards/signals.py | 14 ++++++++++++++ backend/tasks/signals.py | 18 ++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 backend/boards/signals.py diff --git a/backend/boards/signals.py b/backend/boards/signals.py new file mode 100644 index 0000000..87861f1 --- /dev/null +++ b/backend/boards/signals.py @@ -0,0 +1,14 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from boards.models import Board, ListBoard +from users.models import CustomUser + +@receiver(post_save, sender=CustomUser) +def create_default_board(sender, instance, created, **kwargs): + if created: + board = Board.objects.create(user=instance, name="My Default Board") + + ListBoard.objects.create(board=board, name="Todo", position=1) + ListBoard.objects.create(board=board, name="In Progress", position=2) + ListBoard.objects.create(board=board, name="Done", position=3) \ No newline at end of file diff --git a/backend/tasks/signals.py b/backend/tasks/signals.py index af17e57..832be41 100644 --- a/backend/tasks/signals.py +++ b/backend/tasks/signals.py @@ -1,7 +1,8 @@ -from django.db.models.signals import pre_save +from django.db.models.signals import pre_save, post_save from django.dispatch import receiver from django.utils import timezone +from boards.models import ListBoard from tasks.models import Todo @@ -22,4 +23,17 @@ def update_priority(sender, instance, **kwargs): elif time_until_due <= urgency_threshold and instance.importance < importance_threshold: instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_URGENT else: - instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT \ No newline at end of file + instance.priority = Todo.EisenhowerMatrix.NOT_IMPORTANT_NOT_URGENT + + +@receiver(post_save, sender=Todo) +def assign_todo_to_listboard(sender, instance, created, **kwargs): + if created: + user_board = instance.user.board_set.first() + + if user_board: + first_list_board = user_board.listboard_set.order_by('position').first() + + if first_list_board: + instance.list_board = first_list_board + instance.save() \ No newline at end of file From 47c0f6d05412424419734fc22d8cd035529d77c8 Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 03:28:30 +0700 Subject: [PATCH 4/7] Register board and task to admin --- backend/boards/admin.py | 10 ++++++- backend/tasks/admin.py | 28 ++++++++++++++++++- ...lter_recurrencetask_list_board_and_more.py | 25 +++++++++++++++++ ...lter_recurrencetask_list_board_and_more.py | 25 +++++++++++++++++ backend/tasks/models.py | 4 +-- 5 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 backend/tasks/migrations/0016_alter_recurrencetask_list_board_and_more.py create mode 100644 backend/tasks/migrations/0017_alter_recurrencetask_list_board_and_more.py diff --git a/backend/boards/admin.py b/backend/boards/admin.py index 8c38f3f..c22f595 100644 --- a/backend/boards/admin.py +++ b/backend/boards/admin.py @@ -1,3 +1,11 @@ from django.contrib import admin +from .models import Board, ListBoard -# Register your models here. +@admin.register(Board) +class BoardAdmin(admin.ModelAdmin): + list_display = ['name', 'user'] + +@admin.register(ListBoard) +class ListBoardAdmin(admin.ModelAdmin): + list_display = ['name', 'position', 'board'] + list_filter = ['board', 'position'] \ No newline at end of file diff --git a/backend/tasks/admin.py b/backend/tasks/admin.py index 8c38f3f..5c5bbb3 100644 --- a/backend/tasks/admin.py +++ b/backend/tasks/admin.py @@ -1,3 +1,29 @@ from django.contrib import admin +from .models import Tag, Todo, RecurrenceTask, RecurrencePattern, Habit, Subtask -# Register your models here. +@admin.register(Tag) +class TagAdmin(admin.ModelAdmin): + list_display = ['name'] + +@admin.register(Todo) +class TodoAdmin(admin.ModelAdmin): + list_display = ['title', 'list_board', 'is_active', 'priority'] + list_filter = ['list_board', 'is_active', 'priority'] + +@admin.register(RecurrenceTask) +class RecurrenceTaskAdmin(admin.ModelAdmin): + list_display = ['title', 'list_board', 'rrule', 'is_active'] + list_filter = ['list_board', 'rrule', 'is_active'] + +@admin.register(RecurrencePattern) +class RecurrencePatternAdmin(admin.ModelAdmin): + list_display = ['recurrence_task', 'recurring_type', 'day_of_week', 'week_of_month', 'day_of_month', 'month_of_year'] + +@admin.register(Habit) +class HabitAdmin(admin.ModelAdmin): + list_display = ['title', 'streak', 'current_count'] + +@admin.register(Subtask) +class SubtaskAdmin(admin.ModelAdmin): + list_display = ['parent_task', 'description', 'completed'] + list_filter = ['parent_task', 'completed'] diff --git a/backend/tasks/migrations/0016_alter_recurrencetask_list_board_and_more.py b/backend/tasks/migrations/0016_alter_recurrencetask_list_board_and_more.py new file mode 100644 index 0000000..b0edc6e --- /dev/null +++ b/backend/tasks/migrations/0016_alter_recurrencetask_list_board_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.6 on 2023-11-19 20:24 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('boards', '0001_initial'), + ('tasks', '0015_recurrencepattern_remove_transaction_user_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='recurrencetask', + name='list_board', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'), + ), + migrations.AlterField( + model_name='todo', + name='list_board', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'), + ), + ] diff --git a/backend/tasks/migrations/0017_alter_recurrencetask_list_board_and_more.py b/backend/tasks/migrations/0017_alter_recurrencetask_list_board_and_more.py new file mode 100644 index 0000000..dee431c --- /dev/null +++ b/backend/tasks/migrations/0017_alter_recurrencetask_list_board_and_more.py @@ -0,0 +1,25 @@ +# Generated by Django 4.2.6 on 2023-11-19 20:27 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('boards', '0001_initial'), + ('tasks', '0016_alter_recurrencetask_list_board_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='recurrencetask', + name='list_board', + field=models.ForeignKey(default=1, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'), + ), + migrations.AlterField( + model_name='todo', + name='list_board', + field=models.ForeignKey(default=1, null=True, on_delete=django.db.models.deletion.CASCADE, to='boards.listboard'), + ), + ] diff --git a/backend/tasks/models.py b/backend/tasks/models.py index 87dd3be..c67df03 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -56,7 +56,7 @@ class Todo(Task): NOT_IMPORTANT_URGENT = 3, 'Not Important & Urgent' NOT_IMPORTANT_NOT_URGENT = 4, 'Not Important & Not Urgent' - list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True) + list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True, default=1) is_active = models.BooleanField(default=True) is_full_day_event = models.BooleanField(default=False) start_event = models.DateTimeField(null=True) @@ -69,7 +69,7 @@ class Todo(Task): return self.title class RecurrenceTask(Task): - list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True) + list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True, default=1) rrule = models.CharField(max_length=255, null=True, blank=True) is_active = models.BooleanField(default=True) is_full_day_event = models.BooleanField(default=False) From 3c7c966dea41efed092dcbc84b2ba8b7d4dbf39a Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 16:47:42 +0700 Subject: [PATCH 5/7] Add docstring to describe models --- backend/boards/models.py | 14 ++++++++++++++ backend/tasks/models.py | 40 +++++++++++++++++++++++++++++++++++++++- backend/users/models.py | 4 +++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/backend/boards/models.py b/backend/boards/models.py index f0e2c57..de2d107 100644 --- a/backend/boards/models.py +++ b/backend/boards/models.py @@ -3,6 +3,13 @@ from django.db import models from users.models import CustomUser class Board(models.Model): + """ + Kanban board model. + + :param user: The user who owns the board. + :param name: The name of the board. + :param created_at: The date and time when the board was created. + """ user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) name = models.CharField(max_length=255) created_at = models.DateTimeField(auto_now_add=True) @@ -12,6 +19,13 @@ class Board(models.Model): class ListBoard(models.Model): + """ + List inside a Kanban board. + + :param board: The board that the list belongs to. + :param name: The name of the list. + :param position: The position of the list in Kanban. + """ board = models.ForeignKey(Board, on_delete=models.CASCADE) name = models.CharField(max_length=255) position = models.IntegerField() diff --git a/backend/tasks/models.py b/backend/tasks/models.py index c67df03..fc29fe6 100644 --- a/backend/tasks/models.py +++ b/backend/tasks/models.py @@ -49,7 +49,18 @@ class Task(models.Model): class Todo(Task): - + """ + Represent a Todo task. + + :param list_board: The list board that the task belongs to. + :param is_active: A boolean field indicating whether the task is active. (Archive or not) + :param is_full_day_event: A boolean field indicating whether the task is a full day event. + :param start_event: Start date and time of the task. + :param end_event: End date and time of the task. + :param google_calendar_id: The Google Calendar ID of the task. + :param completed: A boolean field indicating whether the task is completed. + :param priority: The priority of the task (range: 1 to 4). + """ class EisenhowerMatrix(models.IntegerChoices): IMPORTANT_URGENT = 1, 'Important & Urgent' IMPORTANT_NOT_URGENT = 2, 'Important & Not Urgent' @@ -69,6 +80,18 @@ class Todo(Task): return self.title class RecurrenceTask(Task): + """ + Represent a Recurrence task. (Occure every day, week, month, year) + + :param list_board: The list board that the task belongs to. + :param rrule: The recurrence rule of the task. + :param is_active: A boolean field indicating whether the task is active. (Archive or not) + :param is_full_day_event: A boolean field indicating whether the task is a full day event. + :param start_event: Start date and time of the task. + :param end_event: End date and time of the task. + :param completed: A boolean field indicating whether the task is completed. + :param parent_task: The parent task of the subtask. + """ list_board = models.ForeignKey(ListBoard, on_delete=models.CASCADE, null=True, default=1) rrule = models.CharField(max_length=255, null=True, blank=True) is_active = models.BooleanField(default=True) @@ -83,6 +106,15 @@ class RecurrenceTask(Task): class RecurrencePattern(models.Model): + """ + :param recurrence_task: The recurrence task that the pattern belongs to. + :param recurring_type: The type of recurrence. + :param max_occurrences: The maximum number of occurrences. + :param day_of_week: The day of the week that event will occure. + :param week_of_month: The week of the month that event will occure. + :param day_of_month: The day of the month that event will occure. + :param month_of_year: The month of the year that event will occure. + """ class RecurringType(models.IntegerChoices): DAILY = 0, 'Daily' WEEKLY = 1, 'Weekly' @@ -129,6 +161,12 @@ class RecurrencePattern(models.Model): class Habit(Task): + """ + Represent a Habit task with streaks. + + :param streak: The streak of the habit. + :param current_count: The current count of the habit. + """ streak = models.IntegerField(default=0) current_count = models.IntegerField(default=0) diff --git a/backend/users/models.py b/backend/users/models.py index c17dbee..7a765a6 100644 --- a/backend/users/models.py +++ b/backend/users/models.py @@ -10,7 +10,9 @@ from .managers import CustomAccountManager class CustomUser(AbstractBaseUser, PermissionsMixin): - # User fields + """ + User model where email is the unique identifier for authentication. + """ email = models.EmailField(_('email address'), unique=True) username = models.CharField(max_length=150, unique=True) first_name = models.CharField(max_length=150, blank=True) From b3efc99d3d5b24b83391a50d6ba7970642eed183 Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 17:03:06 +0700 Subject: [PATCH 6/7] Save all task from calendar in Todo model --- backend/tasks/api.py | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/backend/tasks/api.py b/backend/tasks/api.py index 587f433..3aaa301 100644 --- a/backend/tasks/api.py +++ b/backend/tasks/api.py @@ -8,7 +8,7 @@ from rest_framework.permissions import IsAuthenticated from tasks.utils import get_service, generate_recurrence_rule from tasks.models import Todo, RecurrenceTask -from tasks.serializers import TodoUpdateSerializer, RecurrenceTaskUpdateSerializer +from tasks.serializers import TodoUpdateSerializer class GoogleCalendarEventViewset(viewsets.ViewSet): """Viewset for list or save Google Calendar Events.""" @@ -50,7 +50,11 @@ class GoogleCalendarEventViewset(viewsets.ViewSet): return events def _validate_serializer(self, serializer): - """Validate serializer and return response.""" + """ + Validate serializer and return response. + + :param serializer: The serializer to validate. + """ if serializer.is_valid(): serializer.save() return Response("Validate Successfully", status=200) @@ -61,7 +65,6 @@ class GoogleCalendarEventViewset(viewsets.ViewSet): events = self._get_google_events(request) responses = [] - recurrence_task_ids = [] for event in events: start_datetime = event.get('start', {}).get('dateTime') end_datetime = event.get('end', {}).get('dateTime') @@ -71,25 +74,6 @@ class GoogleCalendarEventViewset(viewsets.ViewSet): event.pop('start') event.pop('end') - if (event.get('recurringEventId') in recurrence_task_ids): - continue - - if (event.get('recurringEventId') is not None): - originalStartTime = event.get('originalStartTime', {}).get('dateTime') - rrule_text = generate_recurrence_rule(event['start_datetime'], event['end_datetime'], originalStartTime) - event['recurrence'] = rrule_text - event.pop('originalStartTime') - recurrence_task_ids.append(event['recurringEventId']) - - try: - task = RecurrenceTask.objects.get(google_calendar_id=event['id']) - serializer = RecurrenceTaskUpdateSerializer(instance=task, data=event) - except RecurrenceTask.DoesNotExist: - serializer = RecurrenceTaskUpdateSerializer(data=event, user=request.user) - - responses.append(self._validate_serializer(serializer)) - continue - try: task = Todo.objects.get(google_calendar_id=event['id']) serializer = TodoUpdateSerializer(instance=task, data=event) From 52edc03abddc817db591600a554116c25c987e02 Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 17:04:05 +0700 Subject: [PATCH 7/7] Remove unuse import --- backend/tasks/api.py | 4 ++-- backend/tasks/serializers.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/tasks/api.py b/backend/tasks/api.py index 3aaa301..fdf2493 100644 --- a/backend/tasks/api.py +++ b/backend/tasks/api.py @@ -6,8 +6,8 @@ from rest_framework import viewsets from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated -from tasks.utils import get_service, generate_recurrence_rule -from tasks.models import Todo, RecurrenceTask +from tasks.utils import get_service +from tasks.models import Todo from tasks.serializers import TodoUpdateSerializer class GoogleCalendarEventViewset(viewsets.ViewSet): diff --git a/backend/tasks/serializers.py b/backend/tasks/serializers.py index 408cb55..a48330e 100644 --- a/backend/tasks/serializers.py +++ b/backend/tasks/serializers.py @@ -1,5 +1,4 @@ from rest_framework import serializers -from django.utils.dateparse import parse_datetime from .models import Todo, RecurrenceTask