From fd842eae5e18e39216a77c8603492f0efa2918ac Mon Sep 17 00:00:00 2001 From: sosokker Date: Fri, 17 Nov 2023 22:59:29 +0700 Subject: [PATCH 1/9] Add task detail modal --- frontend/package.json | 2 +- frontend/pnpm-lock.yaml | 199 ++++++++++++++++-- .../kanbanBoard/taskDetailModal.jsx | 110 +++++++++- 3 files changed, 289 insertions(+), 22 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index e03bc04..b610c78 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,7 +10,6 @@ "preview": "vite preview" }, "dependencies": { - "@asseinfo/react-kanban": "^2.2.0", "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", @@ -36,6 +35,7 @@ "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-bootstrap": "^2.9.1", + "react-datetime-picker": "^5.5.3", "react-dom": "^18.2.0", "react-icons": "^4.11.0", "react-router-dom": "^6.18.0" diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index acf0447..dcc1b43 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -5,9 +5,6 @@ settings: excludeLinksFromLockfile: false dependencies: - '@asseinfo/react-kanban': - specifier: ^2.2.0 - version: 2.2.0(react-dom@18.2.0)(react@18.2.0) '@dnd-kit/core': specifier: ^6.1.0 version: 6.1.0(react-dom@18.2.0)(react@18.2.0) @@ -83,6 +80,9 @@ dependencies: react-bootstrap: specifier: ^2.9.1 version: 2.9.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-datetime-picker: + specifier: ^5.5.3 + version: 5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) @@ -154,19 +154,6 @@ packages: '@jridgewell/trace-mapping': 0.3.20 dev: true - /@asseinfo/react-kanban@2.2.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-/gCigrNXRHeP9VCo8RipTOrA0vAPRIOThJhR4ibVxe6BLkaWFUEuJ1RMT4fODpRRsE3XsdrfVGKkfpRBKgvxXg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - dependencies: - react: 18.2.0 - react-beautiful-dnd: 13.1.1(react-dom@18.2.0)(react@18.2.0) - react-dom: 18.2.0(react@18.2.0) - transitivePeerDependencies: - - react-native - dev: false - /@babel/code-frame@7.22.13: resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} @@ -1328,6 +1315,16 @@ packages: hoist-non-react-statics: 3.3.2 dev: false + /@types/lodash.memoize@4.1.9: + resolution: {integrity: sha512-glY1nQuoqX4Ft8Uk+KfJudOD7DQbbEDF6k9XpGncaohW3RW4eSWBlx6AA0fZCrh40tZcQNH4jS/Oc59J6Eq+aw==} + dependencies: + '@types/lodash': 4.14.201 + dev: false + + /@types/lodash@4.14.201: + resolution: {integrity: sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==} + dev: false + /@types/parse-json@4.0.2: resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} dev: false @@ -1339,7 +1336,6 @@ packages: resolution: {integrity: sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==} dependencies: '@types/react': 18.2.37 - dev: true /@types/react-redux@7.1.30: resolution: {integrity: sha512-i2kqM6YaUwFKduamV6QM/uHbb0eCP8f8ZQ/0yWf+BsAVVsZPRYJ9eeGWZ3uxLfWwwA0SrPRMTPTqsPFkY3HZdA==} @@ -1390,6 +1386,10 @@ packages: - supports-color dev: true + /@wojtekmaj/date-utils@1.5.1: + resolution: {integrity: sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==} + dev: false + /acorn-jsx@5.3.2(acorn@8.11.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1811,6 +1811,10 @@ packages: engines: {node: '>=6'} dev: false + /detect-element-overflow@1.4.2: + resolution: {integrity: sha512-4m6cVOtvm/GJLjo7WFkPfwXoEIIbM7GQwIh4WEa4g7IsNi1YzwUsGL5ApNLrrHL29bHeNeQ+/iZhw+YHqgE2Fw==} + dev: false + /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} dev: true @@ -2295,6 +2299,13 @@ packages: get-intrinsic: 1.2.2 dev: true + /get-user-locale@2.3.1: + resolution: {integrity: sha512-VEvcsqKYx7zhZYC1CjecrDC5ziPSpl1gSm0qFFJhHSGDrSC+x4+p1KojWC/83QX//j476gFhkVXP/kNUc9q+bQ==} + dependencies: + '@types/lodash.memoize': 4.1.9 + lodash.memoize: 4.1.2 + dev: false + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2728,6 +2739,10 @@ packages: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} dev: true + /lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -2744,6 +2759,10 @@ packages: yallist: 3.1.1 dev: true + /make-event-props@1.6.2: + resolution: {integrity: sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==} + dev: false + /memoize-one@5.2.1: resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} dev: false @@ -3134,6 +3153,97 @@ packages: warning: 4.0.3 dev: false + /react-calendar@4.6.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-MvCPdvxEvq7wICBhFxlYwxS2+IsVvSjTcmlr0Kl3yDRVhoX7btNg0ySJx5hy9rb1eaM4nDpzQcW5c87nfQ8n8w==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + '@wojtekmaj/date-utils': 1.5.1 + clsx: 2.0.0 + get-user-locale: 2.3.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tiny-warning: 1.0.3 + dev: false + + /react-clock@4.5.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-xcSpsehBpX0NHwjEzZ9BP4Ouv54nlYqDMHoone82xW7TpPdkWNrBQd4+SiMQfbpqj1yvh2kSwn6FXffw37gAkw==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + '@wojtekmaj/date-utils': 1.5.1 + clsx: 2.0.0 + get-user-locale: 2.3.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-date-picker@10.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-EwLNYPy+/2p7VwsAWnitPhtkC2tesABkZNlAAIEPZeHucyMlO5KB6z55POdtamu6T6vs0RY2G5EVgxDaxlj0MQ==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + '@wojtekmaj/date-utils': 1.5.1 + clsx: 2.0.0 + get-user-locale: 2.3.1 + make-event-props: 1.6.2 + prop-types: 15.8.1 + react: 18.2.0 + react-calendar: 4.6.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-dom: 18.2.0(react@18.2.0) + react-fit: 1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + update-input-width: 1.4.2 + transitivePeerDependencies: + - '@types/react-dom' + dev: false + + /react-datetime-picker@5.5.3(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-bWGEPwGrZjaXTB8P4pbTSDygctLaqTWp0nNibaz8po+l4eTh9gv3yiJ+n4NIcpIJDqZaQJO57Bnij2rAFVQyLw==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + '@wojtekmaj/date-utils': 1.5.1 + clsx: 2.0.0 + get-user-locale: 2.3.1 + make-event-props: 1.6.2 + prop-types: 15.8.1 + react: 18.2.0 + react-calendar: 4.6.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-clock: 4.5.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-date-picker: 10.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-dom: 18.2.0(react@18.2.0) + react-fit: 1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-time-picker: 6.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + transitivePeerDependencies: + - '@types/react-dom' + dev: false + /react-dom@18.2.0(react@18.2.0): resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -3144,6 +3254,28 @@ packages: scheduler: 0.23.0 dev: false + /react-fit@1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-y/TYovCCBzfIwRJsbLj0rH4Es40wPQhU5GPPq9GlbdF09b0OdzTdMSkBza0QixSlgFzTm6dkM7oTFzaVvaBx+w==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@types/react': 18.2.37 + '@types/react-dom': 18.2.15 + detect-element-overflow: 1.4.2 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + tiny-warning: 1.0.3 + dev: false + /react-icons@4.11.0(react@18.2.0): resolution: {integrity: sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==} peerDependencies: @@ -3217,6 +3349,31 @@ packages: react: 18.2.0 dev: false + /react-time-picker@6.5.2(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-xRamxjndpq3HfnEL+6T3VyirLMEn4D974OJgs9sTP8iJX/RB02rmwy09C9oBThTGuN3ycbozn06iYLn148vcdw==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.37 + '@wojtekmaj/date-utils': 1.5.1 + clsx: 2.0.0 + get-user-locale: 2.3.1 + make-event-props: 1.6.2 + prop-types: 15.8.1 + react: 18.2.0 + react-clock: 4.5.1(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + react-dom: 18.2.0(react@18.2.0) + react-fit: 1.7.1(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0) + update-input-width: 1.4.2 + transitivePeerDependencies: + - '@types/react-dom' + dev: false + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -3545,6 +3702,10 @@ packages: resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} dev: false + /tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + dev: false + /to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -3654,6 +3815,10 @@ packages: picocolors: 1.0.0 dev: true + /update-input-width@1.4.2: + resolution: {integrity: sha512-/p0XLhrQQQ4bMWD7bL9duYObwYCO1qGr8R19xcMmoMSmXuQ7/1//veUnCObQ7/iW6E2pGS6rFkS4TfH4ur7e/g==} + dev: false + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: diff --git a/frontend/src/components/kanbanBoard/taskDetailModal.jsx b/frontend/src/components/kanbanBoard/taskDetailModal.jsx index 35d5e14..adbf7f5 100644 --- a/frontend/src/components/kanbanBoard/taskDetailModal.jsx +++ b/frontend/src/components/kanbanBoard/taskDetailModal.jsx @@ -1,14 +1,116 @@ import React from "react"; +import { FaTasks, FaRegListAlt } from "react-icons/fa"; +import { FaPlus } from "react-icons/fa6"; +import { TbChecklist } from "react-icons/tb"; function TaskDetailModal() { + return ( -
+
+ {/* Title */} +
+
+

+ {}Title +

+

Todo List

+
+
+ + {/* Tags */} +
+
+
+ + +
+
+
+
+ + {/* Description */} +
+

+ + + Description + +

+ +
+ + {/* Difficulty, Challenge and Importance */} +
+
+ +
+ Easy + Normal + Hard + Very Hard + Devil +
+
+
+
+ +
+
+
+
+ +
+
+
+ + {/* Subtask */} +
+

+ + + Subtasks + +

+
+ + +
+
+
- +
-

Hello!

-

Press ESC key or click on ✕ button to close

); From 0962ff13306d5aa654ace0b76b91da11b9c3948b Mon Sep 17 00:00:00 2001 From: sosokker Date: Fri, 17 Nov 2023 23:07:42 +0700 Subject: [PATCH 2/9] Add checked and range handler --- .../kanbanBoard/taskDetailModal.jsx | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/kanbanBoard/taskDetailModal.jsx b/frontend/src/components/kanbanBoard/taskDetailModal.jsx index adbf7f5..7c2247a 100644 --- a/frontend/src/components/kanbanBoard/taskDetailModal.jsx +++ b/frontend/src/components/kanbanBoard/taskDetailModal.jsx @@ -1,9 +1,23 @@ -import React from "react"; +import React, { useState } from "react"; import { FaTasks, FaRegListAlt } from "react-icons/fa"; import { FaPlus } from "react-icons/fa6"; import { TbChecklist } from "react-icons/tb"; function TaskDetailModal() { + const [difficulty, setDifficulty] = useState(50); + const [isChallengeChecked, setChallengeChecked] = useState(true); + const [isImportantChecked, setImportantChecked] = useState(true); + + const handleChallengeChange = () => { + setChallengeChecked(!isChallengeChecked); + }; + + const handleImportantChange = () => { + setImportantChecked(!isImportantChecked); + }; + const handleDifficultyChange = event => { + setDifficulty(parseInt(event.target.value, 10)); + }; return ( @@ -64,7 +78,16 @@ function TaskDetailModal() { {/* Difficulty, Challenge and Importance */}
- +
Easy Normal @@ -73,19 +96,32 @@ function TaskDetailModal() { Devil
+ {/* Challenge Checkbox */}
+ + {/* Important Checkbox */}
From fec588f81f3a2f98ce6b1d4f002af510050a05cb Mon Sep 17 00:00:00 2001 From: sosokker Date: Mon, 20 Nov 2023 02:39:41 +0700 Subject: [PATCH 3/9] 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 4/9] 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 5/9] 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 6/9] 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 7/9] 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 8/9] 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 9/9] 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