Compare commits

..

659 Commits
sprint2 ... qa

Author SHA1 Message Date
38252ac5ea Merge pull request #389 from KiwiTechLLC/dev
Dev into qa
2024-03-07 12:40:47 +05:30
8fb627ec03 fixed expire time 2024-03-07 12:40:01 +05:30
c3aa31788f Merge pull request #388 from KiwiTechLLC/ZBKBCK-56
set refresh token time to 365 days
2024-03-06 16:32:16 +05:30
09aecc36b3 set refresh token time to 365 days 2024-03-06 16:21:19 +05:30
348c5946dd token time changed to 1m 2024-03-04 19:32:46 +05:30
8464fbbe7b Merge pull request #385 from KiwiTechLLC/dev
increased count for version objects
2023-12-11 14:28:46 +05:30
5603853896 increased count for version objects 2023-12-11 14:25:24 +05:30
85871592ca Merge pull request #382 from KiwiTechLLC/dev
updated prod base url
2023-12-07 12:01:20 +05:30
82b7fe7000 updated prod base url 2023-12-07 12:00:16 +05:30
51341529d9 Merge pull request #379 from KiwiTechLLC/dev
updated frontend prod url
2023-12-06 16:18:52 +05:30
92679061e2 updated frontend prod url 2023-12-06 16:17:41 +05:30
d7f198c15f Merge pull request #376 from KiwiTechLLC/dev
added prod link and base url
2023-12-04 17:56:07 +05:30
67e4e96d85 added prod link and base url 2023-12-04 17:54:56 +05:30
236d8ffd85 Merge pull request #373 from KiwiTechLLC/dev
Dev into qa
2023-12-04 17:08:29 +05:30
df32e5ed37 added prod link and base url 2023-12-04 17:05:16 +05:30
e3796f2204 auth token expiry set to 1 min 2023-11-27 12:50:02 +05:30
03f0a4c363 Merge branch 'dev' of https://github.com/KiwiTechLLC/ZODBank-Backend into dev 2023-11-27 12:46:42 +05:30
a3a4a8d091 auth token expiry set to 1 min 2023-11-27 12:43:17 +05:30
595212e58f Merge pull request #370 from KiwiTechLLC/dev
Dev into qa
2023-11-16 17:34:11 +05:30
772c0c89b5 Merge pull request #369 from KiwiTechLLC/ZBKBCK-56
added optional last name in google login
2023-11-16 17:33:14 +05:30
d614d13136 added optional last name in google login 2023-11-16 17:32:34 +05:30
5665389db4 Merge pull request #367 from KiwiTechLLC/dev
Dev into qa
2023-11-16 17:09:33 +05:30
93ce63b1d4 Merge pull request #366 from KiwiTechLLC/ZBKBCK-56
changed message
2023-11-16 17:08:56 +05:30
4f2b42dc08 changed message 2023-11-16 17:07:46 +05:30
aa9459e3f7 Merge pull request #364 from KiwiTechLLC/dev
Dev into qa
2023-11-08 19:12:48 +05:30
5c05a988a5 new line in faq_json file 2023-11-08 18:54:54 +05:30
2aff4e52f0 Merge pull request #363 from KiwiTechLLC/ZBKBCK-56
modified user notification setting, added optional value for sms notitication as false, added fixture file for faq's
2023-11-08 18:50:38 +05:30
bdc92163c3 added fixture file for faq's 2023-11-08 18:47:28 +05:30
9abf549ed4 modified user notification setting, added optional value for sms notification as false 2023-11-08 15:02:23 +05:30
f04016fb45 Merge pull request #361 from KiwiTechLLC/dev
Dev into qa
2023-10-27 14:45:46 +05:30
057c58b709 Merge pull request #360 from KiwiTechLLC/ZBKBCK-55
added status for login and sign up from google and apple
2023-10-27 12:23:25 +05:30
b1d8949b08 added status for login and sign up from google and apple 2023-10-27 12:05:47 +05:30
ced3db76c1 Merge pull request #358 from KiwiTechLLC/dev
Dev
2023-10-06 18:19:55 +05:30
801bc45bc5 Merge pull request #357 from KiwiTechLLC/ZBKBCK-54
to user type added
2023-10-06 18:12:47 +05:30
d1927b24ee to user type added 2023-10-06 18:10:56 +05:30
62e5e4c062 Merge pull request #355 from KiwiTechLLC/dev
Dev into qa
2023-10-06 17:57:33 +05:30
012d93f70f Merge pull request #354 from KiwiTechLLC/ZBKBCK-54
added to user type as per required changes, handled apple signup for existing users
2023-10-06 17:55:01 +05:30
a1f9f93654 added to user type as per required changes, handled apple signup for existing users 2023-10-06 17:29:41 +05:30
ac372e62af Merge pull request #352 from KiwiTechLLC/dev
Dev into qa
2023-10-05 17:46:56 +05:30
652fe9e680 Merge pull request #351 from KiwiTechLLC/ZBKBCK-54
fixed user notificatio setting issue
2023-10-05 17:43:19 +05:30
13665f4c9a fixed user notificatio setting issue 2023-10-05 17:38:57 +05:30
810752793b Merge pull request #349 from KiwiTechLLC/dev
Dev into qa
2023-10-05 14:56:04 +05:30
bf5453c7b7 Merge pull request #348 from KiwiTechLLC/ZBKBCK-54
fixed login into another device issue
2023-10-05 13:24:56 +05:30
6e3166967e fixed login into another device issue 2023-10-05 13:07:42 +05:30
49b264e918 Merge pull request #346 from KiwiTechLLC/dev
Dev into qa
2023-10-03 19:12:52 +05:30
47a00f313a Merge pull request #345 from KiwiTechLLC/ZBKBCK-54
added mail for user activation, handled fcm token for deleted user
2023-10-03 18:57:55 +05:30
ad4d782e72 added mail for user activation, handled fcm token for deleted user 2023-10-03 18:54:47 +05:30
742ee0ee67 Merge pull request #343 from KiwiTechLLC/dev
Dev into qa
2023-09-29 18:27:22 +05:30
8050e70cf7 Merge pull request #342 from KiwiTechLLC/ZBKBCK-54
handled deactivated users for social login
2023-09-29 16:11:08 +05:30
18a53e1c48 handled deactivated users for social login 2023-09-29 16:09:22 +05:30
f1333491e0 Merge pull request #341 from KiwiTechLLC/ZBKBCK-54
notification mark as read api modified for clear all, list sorting set to updated at field, added same field
2023-09-29 15:09:47 +05:30
bd7eddb275 notification mark as read api modified for clear all, list sorting set to updated at field, added same field 2023-09-29 14:58:06 +05:30
4c34c2496b Merge pull request #340 from KiwiTechLLC/ZBKBCK-54
notification create modified to update or create
2023-09-28 19:35:16 +05:30
e121c92fb4 notification create modified to update or create 2023-09-28 19:09:57 +05:30
18143e0219 modified send push method 2023-09-27 19:31:09 +05:30
251a912948 revert access token time to 3 hrs 2023-09-27 12:36:28 +05:30
f7bb83cebb added notification type in serializer 2023-09-26 19:11:04 +05:30
994e9a270e Merge pull request #339 from KiwiTechLLC/ZBKBCK-54
added notification type in push data
2023-09-26 17:27:10 +05:30
32c35f8649 added notification type in push data 2023-09-26 17:24:40 +05:30
ea02d7f5bb token expire time 1 min for testing 2023-09-25 11:45:50 +05:30
648628d3ea Merge branch 'dev' of https://github.com/KiwiTechLLC/ZODBank-Backend into dev 2023-09-25 11:44:57 +05:30
6ba3d7d8db token expire time 1 min for testing 2023-09-25 11:44:04 +05:30
db119b2273 Merge pull request #337 from KiwiTechLLC/dev
Dev
2023-09-23 17:14:00 +05:30
651405ddef Merge pull request #336 from KiwiTechLLC/ZBKBCK-52
feedback changes and added mark all read
2023-09-22 23:29:38 +05:30
3afd7fecf3 feedback changes and added mark all read 2023-09-22 19:55:10 +05:30
c484669a2d Merge pull request #335 from KiwiTechLLC/ZBKBCK-52
fixed guardian reject issue, updated ids in notification method, remo…
2023-09-13 18:18:07 +05:30
d1a4b86b09 fixed guardian reject issue, updated ids in notification method, remove unnecessary condition from analytics and user management 2023-09-13 18:15:07 +05:30
53f522ae41 Merge pull request #333 from KiwiTechLLC/dev
Dev
2023-09-13 16:09:10 +05:30
a3c2b68a0d Merge pull request #332 from KiwiTechLLC/ZBKBCK-52
fixed article reward points notification for 0 points earned
2023-09-13 14:50:24 +05:30
e157e98a17 fixed article reward points notification for 0 points earned 2023-09-13 14:45:47 +05:30
a653518cfd fixed article reward points notification for 0 points earned 2023-09-13 14:38:18 +05:30
af25dc4e82 Merge pull request #331 from KiwiTechLLC/ZBKBCK-52
modified get article card current page method
2023-09-13 14:05:55 +05:30
eaf67b682f modified get article card current page method 2023-09-13 14:03:13 +05:30
71bbef84aa Merge pull request #330 from KiwiTechLLC/ZBKBCK-52
modified get article card current page method
2023-09-13 13:40:10 +05:30
085607128b modified get article card current page method 2023-09-13 13:36:25 +05:30
fbdef7c0c4 Merge branch 'dev' of https://github.com/KiwiTechLLC/ZODBank-Backend into dev 2023-09-13 13:34:14 +05:30
535e2e7f56 Merge pull request #329 from KiwiTechLLC/ZBKADM-73-test-cases
added a test case, updated coverage file
2023-09-13 12:15:37 +05:30
08f54f28a4 added a test case, updated coverage file 2023-09-12 23:08:17 +05:30
de710c8a7b Merge pull request #328 from KiwiTechLLC/ZBKADM-73-test-cases
updated coverage file
2023-09-12 15:18:55 +05:30
af06dddbeb updated coverage file 2023-09-12 15:03:12 +05:30
0a9dde2038 Merge branch 'dev' of https://github.com/KiwiTechLLC/ZODBank-Backend into dev 2023-09-12 14:58:35 +05:30
f11959d6bc Merge pull request #327 from KiwiTechLLC/ZBKADM-73-test-cases
test cases for admin user management, analytics and notification
2023-09-12 14:54:26 +05:30
7aee372606 test cases for admin user management, analytics and notification 2023-09-12 14:51:28 +05:30
1dec5ace3f test cases for admin user management, analytics and notification 2023-09-12 14:42:44 +05:30
2444b6f55e test cases for admin user management, analytics and notification 2023-09-12 14:40:50 +05:30
52a40c085f Merge pull request #325 from KiwiTechLLC/dev
Dev
2023-09-11 17:54:18 +05:30
3ecbc1d303 Merge pull request #324 from KiwiTechLLC/ZBKBCK-52
make special password method modified
2023-09-11 17:37:42 +05:30
ca0041caa6 make special password method modified 2023-09-11 17:01:03 +05:30
c800853e9b make special password method modified 2023-09-11 16:45:07 +05:30
373c1b70ab make special password method modified 2023-09-11 16:40:07 +05:30
3a84b1ea75 Merge pull request #323 from KiwiTechLLC/ZBKADM-73-test-cases
test cases for web admin auth
2023-09-11 14:45:25 +05:30
bf1004696a added test cases for auth 2023-09-11 14:39:01 +05:30
d937c1bb92 Merge branch 'dev' into ZBKADM-73-test-cases 2023-09-11 13:00:04 +05:30
78fe41ae44 Merge pull request #321 from KiwiTechLLC/dev
Dev
2023-09-11 12:43:14 +05:30
0a877b410e Merge pull request #320 from KiwiTechLLC/ZBKBCK-52
modified add junior api for exsiting junior.
2023-09-11 12:22:31 +05:30
9d4e7b05e4 modified add junior api for exsiting junior. 2023-09-11 12:08:22 +05:30
b20e1cf516 Merge pull request #319 from KiwiTechLLC/ZBKBCK-52
api to check whether given user exist or not
2023-09-09 16:52:03 +05:30
6b0ea91742 api to check whether given user exist or not 2023-09-08 20:55:32 +05:30
859d26d073 test cases for web admin auth 2023-09-08 19:07:06 +05:30
1d44c642df Merge pull request #318 from KiwiTechLLC/dev
Dev
2023-09-08 17:56:57 +05:30
154b9de32b Merge pull request #317 from KiwiTechLLC/ZBKBCK-52
check answer api fixed
2023-09-08 16:56:18 +05:30
4a554272a0 check answer api fixed 2023-09-08 16:54:37 +05:30
8533a27cb7 Merge pull request #316 from KiwiTechLLC/ZBKBCK-52
check answer api fixed
2023-09-08 16:54:31 +05:30
7db6502a89 check answer api fixed 2023-09-08 16:41:59 +05:30
a8e9a09d3f Merge pull request #315 from KiwiTechLLC/ZBKBCK-52
modified task description field, modified check answer api and create…
2023-09-08 16:17:20 +05:30
1e97d7bd6b modified task description field, modified check answer api and create task api 2023-09-08 16:14:29 +05:30
b084b255dc Merge pull request #314 from KiwiTechLLC/ZBKADM-73-test-cases
test cases for web admin article
2023-09-08 11:08:18 +05:30
441842df74 test cases for web admin article 2023-09-07 19:47:25 +05:30
53c5b7079e Merge pull request #312 from KiwiTechLLC/dev
Dev
2023-09-07 15:29:44 +05:30
78fb5f5650 Merge pull request #311 from KiwiTechLLC/ZBKBCK-50
modification in create task api
2023-09-07 15:13:33 +05:30
f3478c972e modification in create task api 2023-09-07 15:10:18 +05:30
f63d9ddea0 Merge pull request #310 from KiwiTechLLC/ZBKBCK-50
modified article publish and un-publish api, sonar issues, modificati…
2023-09-07 13:51:57 +05:30
bc18c67527 modified article publish and un-publish api, sonar issues, modification in create task api 2023-09-07 13:49:06 +05:30
3ef9053290 Merge pull request #309 from KiwiTechLLC/dev
Dev
2023-09-06 18:10:45 +05:30
0471a3d588 Merge pull request #308 from KiwiTechLLC/ZBKBCK-50
modified some success messages.
2023-09-06 17:41:49 +05:30
ffb99f5099 modified some success messages. 2023-09-06 15:26:10 +05:30
fdfe6e7dad Merge pull request #306 from KiwiTechLLC/dev
Dev
2023-09-06 12:33:22 +05:30
8183edf319 Merge pull request #305 from KiwiTechLLC/ZBKBCK-51
modified create task api, added badge count in notification list
2023-09-05 19:36:09 +05:30
8f214d11a7 modified create task api, added badge count in notification list 2023-09-05 19:29:57 +05:30
b5a89df59a Merge pull request #304 from KiwiTechLLC/ZBKBCK-50
modified pagination in task list and junior task list api
2023-09-05 14:02:16 +05:30
5524eeed64 modified pagination in task list and junior task list api 2023-09-05 13:57:28 +05:30
aeaa7d7ab8 Merge pull request #303 from KiwiTechLLC/sprint6-bugs
handle scenerio for task after disassociate
2023-09-05 13:14:01 +05:30
69be3bb2ac Merge pull request #302 from KiwiTechLLC/ZBKBCK-50
modified cors allowed origin values, added yml file for qa stage and …
2023-09-04 18:55:12 +05:30
be9f600bcc modified cord allowed origin values, added yml file for qa stage and prod 2023-09-04 18:52:23 +05:30
65d0932893 sonar issues 2023-09-04 16:52:50 +05:30
9b14eedb18 handle scenerio for task after disassociate 2023-09-04 16:49:18 +05:30
6c96ea0820 Merge pull request #301 from KiwiTechLLC/ZBKBCK-49
added cors Allow specific origins setting, unpublish article api, pag…
2023-09-04 16:08:22 +05:30
a211baa10a added cors Allow specific origins setting, unpublish article api, pagination in notification list 2023-09-04 15:46:36 +05:30
a93dc83bd1 Merge pull request #300 from KiwiTechLLC/sprint6-bugs
Sprint6 bugs
2023-09-04 15:27:42 +05:30
116fb00358 optimized task status code 2023-09-04 15:22:15 +05:30
20fa6e43da changes in task list API and add special character in password for added junior 2023-09-04 15:02:47 +05:30
2cffc4e128 Merge pull request #299 from KiwiTechLLC/ZBKBCK-49
added and modifid pagination
2023-09-04 13:16:15 +05:30
ec585d35f3 added and modifid pagination 2023-09-04 13:11:25 +05:30
d62efa2139 added and modifid pagination 2023-09-04 13:09:54 +05:30
7b75a3233c add current page and total page in task list API 2023-09-04 12:52:04 +05:30
e1ef289c69 Merge pull request #298 from KiwiTechLLC/sprint6-bugs
Sprint6 bugs
2023-09-01 17:05:50 +05:30
4f79a690c1 unrestrict logout and refresh token api while login in multiple device 2023-09-01 16:25:05 +05:30
0af2a35206 task assign to multiple junior 2023-09-01 12:14:15 +05:30
dd0a0cd2fa Merge pull request #296 from KiwiTechLLC/dev
Dev
2023-08-29 21:37:12 +05:30
a262b03292 Merge pull request #295 from KiwiTechLLC/sprint6-bugs
answer api
2023-08-29 21:32:11 +05:30
d24f075110 answer api 2023-08-29 21:28:36 +05:30
674493e1ad Merge pull request #293 from KiwiTechLLC/dev
Dev
2023-08-29 20:00:01 +05:30
f7624bc1e7 Merge pull request #292 from KiwiTechLLC/sprint6-bugs
answer api
2023-08-29 19:47:55 +05:30
dc12b35842 answer api 2023-08-29 19:38:14 +05:30
a80f9db557 Merge pull request #291 from KiwiTechLLC/sprint6-bugs
otp expiry
2023-08-29 19:05:18 +05:30
3ad29e677d article page 2023-08-29 18:56:51 +05:30
8b0a5d9a8e otp expiry 2023-08-29 18:33:47 +05:30
16d823f97d Merge pull request #290 from KiwiTechLLC/sprint6-bugs
elif for guardian code
2023-08-29 17:51:42 +05:30
d4008d6cc2 elif for guardian code 2023-08-29 17:50:58 +05:30
7cf3481ec6 Merge branch 'stage' into qa 2023-08-29 12:40:43 +05:30
de4b230cd1 Merge pull request #288 from KiwiTechLLC/dev
Dev
2023-08-29 12:11:06 +05:30
3dae22a870 Merge pull request #287 from KiwiTechLLC/sprint6-bugs
Sprint6 bugs
2023-08-29 11:56:58 +05:30
a8d291474a search junior task 2023-08-29 11:52:47 +05:30
e6482167ae Merge branch 'sprint6-bugs' of github.com:KiwiTechLLC/ZODBank-Backend into sprint6-bugs 2023-08-29 11:51:11 +05:30
9a932d31b5 Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint6-bugs 2023-08-29 11:50:44 +05:30
e2c84eb83d search junior task 2023-08-29 11:49:59 +05:30
5bfc3966b3 Merge branch 'dev' into sprint6-bugs 2023-08-29 10:43:34 +05:30
37d191eef8 add message for blank search 2023-08-29 10:41:44 +05:30
ad53e0086c Merge pull request #286 from KiwiTechLLC/dev
Dev
2023-08-28 20:35:49 +05:30
e9ee8ec8b2 Merge pull request #285 from KiwiTechLLC/sprint6-bugs
condition in add junior
2023-08-28 20:14:43 +05:30
cc5ecc0647 Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint6-bugs 2023-08-28 20:10:36 +05:30
63dfd731fc condition in add junior 2023-08-28 20:09:39 +05:30
e9d4d7091e Merge pull request #284 from KiwiTechLLC/ZBKBCK-48
added notification for existing junior add, modified leaderboard meth…
2023-08-28 19:51:20 +05:30
219bae792e added notification for existing junior add, modified leaderboard method at every places 2023-08-28 19:39:39 +05:30
df3cab99a5 Merge pull request #283 from KiwiTechLLC/sprint6-bugs
remove guardian request and resend otp
2023-08-28 18:58:23 +05:30
d2242d4c64 remove guardian request and resend otp 2023-08-28 18:39:29 +05:30
b7d5916f8e Merge pull request #282 from KiwiTechLLC/ZBKBCK-48
fixed add junior multiple device notification issue, changed send mul…
2023-08-28 15:56:43 +05:30
5e17edcf3f fixed add junior multiple device notification issue, changed send multiple user notification method, modified add fcm token method 2023-08-28 15:42:30 +05:30
b6fe943bfc Merge pull request #281 from KiwiTechLLC/sprint6-bugs
add non in delete API
2023-08-28 13:46:28 +05:30
8e529b292d add non in delete API 2023-08-28 13:38:35 +05:30
acd3592b2d Merge pull request #280 from KiwiTechLLC/dev
Dev
2023-08-28 12:55:32 +05:30
61ca2e7d2d Merge pull request #279 from KiwiTechLLC/sprint6-bugs
change message
2023-08-28 12:53:24 +05:30
966f94eb1e change message 2023-08-28 12:41:29 +05:30
f424c99478 Merge pull request #278 from KiwiTechLLC/sprint6-bugs
guardian_status update
2023-08-28 12:13:10 +05:30
34359360e0 guardian_status update 2023-08-28 12:10:13 +05:30
c2c9210a9a some changes 2023-08-28 11:54:38 +05:30
e3feab22a2 some changes 2023-08-28 11:51:12 +05:30
685a822e52 Merge pull request #277 from KiwiTechLLC/sprint6-bugs
Sprint6 bugs
2023-08-28 10:48:48 +05:30
e57344e54f Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint6-bugs 2023-08-28 10:38:14 +05:30
235fc9baac guardian code status in junior list API 2023-08-28 10:37:52 +05:30
3dc8268b07 Merge pull request #276 from KiwiTechLLC/ZBKBCK-48
fixed approve junior notification issue
2023-08-26 23:00:39 +05:30
8be8d56036 fixed approve junior notification issue 2023-08-26 21:36:01 +05:30
c79a891c89 Merge pull request #275 from KiwiTechLLC/sprint6-bugs
Sprint6 bugs
2023-08-26 18:24:09 +05:30
eb4e09964d changes of guardian code status in necessary API 2023-08-26 17:48:13 +05:30
cf9376663c list of guardian code status 2023-08-26 17:09:10 +05:30
58b569225a Merge pull request #274 from KiwiTechLLC/dev
Dev
2023-08-25 21:00:25 +05:30
9891060ee3 Merge pull request #273 from KiwiTechLLC/ZBKBCK-48
added notification for getting points after reading article
2023-08-25 20:10:46 +05:30
5b2ab2275e added notification for getting points after reading article 2023-08-25 19:45:24 +05:30
91471f69bd Merge pull request #272 from KiwiTechLLC/dev
Dev
2023-08-25 18:33:50 +05:30
9b6a84c7d1 Merge pull request #270 from KiwiTechLLC/sprint5
Sprint5
2023-08-25 18:02:13 +05:30
e9d4fbe373 Merge pull request #271 from KiwiTechLLC/ZBKBCK-48
added notification when admin adds a new article
2023-08-25 17:12:27 +05:30
21b92f8c74 added notification when admin adds a new article 2023-08-25 17:05:01 +05:30
c46b2ef52c sonar fixes 2023-08-25 16:59:17 +05:30
c47f6222d9 requested task not expired 2023-08-25 16:36:58 +05:30
1333c64d4f Merge pull request #269 from KiwiTechLLC/dev
Dev
2023-08-25 12:58:52 +05:30
b82902081f Merge pull request #268 from KiwiTechLLC/sprint5
remove code
2023-08-25 12:58:09 +05:30
e9315beab9 remove code 2023-08-25 12:57:15 +05:30
56631ac633 Merge pull request #267 from KiwiTechLLC/dev
Dev
2023-08-25 12:53:12 +05:30
3a938720dd Merge pull request #266 from KiwiTechLLC/ZBKBCK-48
added notification for top leaderboard junior, added related celery t…
2023-08-25 12:30:01 +05:30
464899f7d3 added notification for top leaderboard junior, added related celery task, created method to send notification to multiple users 2023-08-25 12:26:38 +05:30
8a436bb79f Merge pull request #265 from KiwiTechLLC/ZBKBCK-48
modified top-list api, junior-list api and junior-points api, nodifie…
2023-08-24 20:08:07 +05:30
2e0ceb8c92 modified top-list api, junior-list api and junior-points api, nodified rank method, added mail for deactivating user from admin 2023-08-24 19:34:59 +05:30
10efc952b7 Merge pull request #264 from KiwiTechLLC/dev
Dev
2023-08-24 19:19:05 +05:30
1a2fd2d3a8 Merge pull request #263 from KiwiTechLLC/sprint5
assessment answer
2023-08-24 18:59:38 +05:30
a65eb2f77d assessment answer 2023-08-24 18:58:31 +05:30
3d06139c4f Merge pull request #262 from KiwiTechLLC/sprint5
assement id
2023-08-24 18:27:17 +05:30
f5a03e2fdf assement id 2023-08-24 18:26:15 +05:30
226ddfa7e6 Merge pull request #261 from KiwiTechLLC/sprint5
article-list optimization
2023-08-24 18:05:32 +05:30
1028822908 article-list optimization 2023-08-24 18:00:14 +05:30
342f27fc62 Merge pull request #260 from KiwiTechLLC/sprint5
limit for 3 guardian code and article list API optimization
2023-08-24 16:15:30 +05:30
e9aa2dfda9 limit for 3 guardian code and article list API optimization 2023-08-24 16:08:20 +05:30
924eaab24b Merge pull request #259 from KiwiTechLLC/dev
Dev
2023-08-24 14:59:40 +05:30
624e7a4edb Merge pull request #258 from KiwiTechLLC/ZBKBCK-47
added optional name as user
2023-08-24 14:56:52 +05:30
3072bd5cdb added optional name as user 2023-08-24 14:54:46 +05:30
930b58cf05 added optional name as user 2023-08-24 14:51:24 +05:30
339c49577e Merge pull request #257 from KiwiTechLLC/sprint5
Sprint5
2023-08-24 14:10:17 +05:30
09f006eb13 Merge pull request #256 from KiwiTechLLC/ZBKBCK-47
added optional user as name
2023-08-24 13:41:50 +05:30
cd3b385756 added optional user as name 2023-08-24 13:33:34 +05:30
ab1a2be679 remove junior by guardian 2023-08-24 13:32:08 +05:30
11605540d7 remove guardian code request 2023-08-24 13:18:23 +05:30
8cd4864748 add three guardian at a time 2023-08-24 13:13:25 +05:30
ad0b5bfc75 Merge pull request #255 from KiwiTechLLC/ZBKBCK-47
changed notification method, added celery task to notify expiring task
2023-08-24 12:29:25 +05:30
f541608656 changed notification method, added celery task to notify expiring task 2023-08-24 12:10:18 +05:30
b1b7c42438 Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint5 2023-08-24 11:27:13 +05:30
8f9450b743 set guardian code is None 2023-08-24 11:10:20 +05:30
bb3441e4ed remove guardian code in both case 2023-08-23 19:23:31 +05:30
5b9f7b1828 Merge pull request #254 from KiwiTechLLC/sprint5
google login with user type and is published article only display
2023-08-23 17:47:12 +05:30
89dda61bff google login with user type and is published article only display 2023-08-23 17:46:20 +05:30
31f071a463 Merge pull request #253 from KiwiTechLLC/sprint5
google login with user type and is published article only display
2023-08-23 17:26:17 +05:30
20644a6293 google login with user type and is published article only display 2023-08-23 17:15:54 +05:30
a8b01d4c3a Merge pull request #252 from KiwiTechLLC/ZBKBCK-47
mark ad read api modified
2023-08-23 13:01:58 +05:30
c41dee333c Merge pull request #251 from KiwiTechLLC/dev
Dev
2023-08-23 12:57:22 +05:30
56e1484b87 mark ad read api modified 2023-08-23 12:56:57 +05:30
557a7d7a5c Merge pull request #250 from KiwiTechLLC/sprint5
response 308 in force update
2023-08-23 12:38:21 +05:30
4e0c6a91f4 response 308 in force update 2023-08-23 12:29:53 +05:30
f931e20334 Merge pull request #249 from KiwiTechLLC/sprint5
changes in profile API
2023-08-23 11:54:48 +05:30
8d159ac3a4 changes in profile API 2023-08-23 11:45:38 +05:30
95ce57ec7d Merge pull request #248 from KiwiTechLLC/sprint5
force update not affect admin's api
2023-08-23 11:20:22 +05:30
7068551050 force update not affect admin's api 2023-08-23 11:16:46 +05:30
c23d65f67c Merge pull request #245 from KiwiTechLLC/sprint5
force update
2023-08-22 19:35:26 +05:30
73f05dc843 Merge pull request #247 from KiwiTechLLC/ZBKBCK-47
added notitifcation for association rejected, approved, added mark as…
2023-08-22 19:28:44 +05:30
290089b9ef added notitifcation for association rejected, approved, added mark as read api 2023-08-22 19:26:30 +05:30
5f3a9c35fa junior guardiuan code 2023-08-22 19:14:57 +05:30
b92115168d Merge pull request #246 from KiwiTechLLC/ZBKBCK-47
added notitifcation for association rejected, approved, added mark as…
2023-08-22 19:13:30 +05:30
19acef10ef added notitifcation for association rejected, approved, added mark as read api 2023-08-22 19:03:39 +05:30
05fd76a50b force update 2023-08-22 18:29:22 +05:30
72c6a122fb Merge pull request #244 from KiwiTechLLC/dev
Dev
2023-08-22 16:19:10 +05:30
60e6d96379 Merge pull request #243 from KiwiTechLLC/ZBKBCK-47
minor changes
2023-08-22 16:11:16 +05:30
fad39d399f minor changes 2023-08-22 16:04:58 +05:30
d573d56a35 minor changes 2023-08-22 16:01:08 +05:30
df30474fac Merge pull request #242 from KiwiTechLLC/dev
Dev
2023-08-22 13:51:09 +05:30
08a08960ee Merge pull request #241 from KiwiTechLLC/sprint5
force update and mail by celery task
2023-08-22 13:49:21 +05:30
f8e529600b force update and mail by celery task 2023-08-22 13:46:37 +05:30
9765c90b9b Merge pull request #240 from KiwiTechLLC/sprint5
swagger update
2023-08-22 11:31:20 +05:30
930c10b578 swagger update 2023-08-22 11:28:52 +05:30
8ca74e4b03 Merge pull request #239 from KiwiTechLLC/ZBKBCK-47
notification changes
2023-08-22 10:57:57 +05:30
95dad86b12 notification changes 2023-08-21 20:10:39 +05:30
7c1725206b Merge pull request #238 from KiwiTechLLC/dev
Dev
2023-08-21 19:24:51 +05:30
dfff643c71 Merge pull request #237 from KiwiTechLLC/sprint5
Error response
2023-08-21 19:11:10 +05:30
214566ec8f Error response 2023-08-21 19:09:44 +05:30
210e1a39e9 Merge pull request #236 from KiwiTechLLC/sprint5
swagger update
2023-08-21 18:45:48 +05:30
e380f3f6ab swagger update 2023-08-21 18:44:18 +05:30
99a59162a2 swagger update 2023-08-21 18:12:03 +05:30
987d60ef01 Merge pull request #235 from KiwiTechLLC/dev
Dev
2023-08-21 16:57:59 +05:30
67a5f670f8 Merge pull request #234 from KiwiTechLLC/sprint5
Sprint5
2023-08-21 16:03:09 +05:30
82189d3953 signup 2023-08-21 16:00:39 +05:30
65ecd9a125 Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint5 2023-08-21 15:59:47 +05:30
a7bce32993 signup 2023-08-21 15:59:20 +05:30
4495d8999c Merge pull request #233 from KiwiTechLLC/sprint5
swagger fixes
2023-08-21 15:15:31 +05:30
61e94c9469 Merge branch 'dev' into sprint5 2023-08-21 15:13:35 +05:30
1c3d4731c1 swagger fixes 2023-08-21 15:11:43 +05:30
46853011be Merge pull request #232 from KiwiTechLLC/ZBKADM-72-minor-changes
minor changes
2023-08-21 15:04:44 +05:30
2216411fd2 Merge branch 'dev' into ZBKADM-72-minor-changes 2023-08-21 14:33:36 +05:30
606c1fa6e6 minor changes in excel import api method 2023-08-21 14:28:06 +05:30
1040d763ad Merge pull request #231 from KiwiTechLLC/dev
Dev
2023-08-21 12:58:09 +05:30
092cbfad3e Merge branch 'qa' into dev 2023-08-21 12:57:57 +05:30
249a469d89 Merge pull request #230 from KiwiTechLLC/sprint5
resolve bugs
2023-08-21 12:54:46 +05:30
d202774df1 resolve bugs 2023-08-21 12:39:05 +05:30
1adf0c2f70 resolve bugs 2023-08-21 12:24:01 +05:30
b193166914 Merge pull request #229 from KiwiTechLLC/sprint5
login issue
2023-08-20 14:14:45 +05:30
48b455e38a login issue 2023-08-20 14:12:17 +05:30
6d54a718b9 Merge pull request #226 from KiwiTechLLC/ZBKBCK-346
[ZBKBCK-346] change password and forgot password api has been optimised
2023-08-18 20:26:02 +05:30
454f66b3a0 conflict has been resolved 2023-08-18 19:50:14 +05:30
5045f5acda Merge pull request #228 from KiwiTechLLC/ZBKADM-72-minor-changes
added description for api, changes in upload image and file to alibab…
2023-08-18 19:01:34 +05:30
e84180a6c2 Merge branch 'dev' into ZBKADM-72-minor-changes 2023-08-18 18:46:47 +05:30
21e006ae2a added description for api, changes in upload image and file to alibaba method 2023-08-18 18:37:58 +05:30
92e5104e3f expiry date of otp 2023-08-18 18:37:01 +05:30
dfb73a7d36 Merge pull request #227 from KiwiTechLLC/sprint5
Sprint5
2023-08-18 18:32:21 +05:30
3921f76f22 remove print statement 2023-08-18 18:31:27 +05:30
cdf1a7b74e add comma 2023-08-18 18:29:46 +05:30
b4026a4f11 middleware 2023-08-18 18:12:14 +05:30
71a3e36bf3 [ZBKBCK-346] change password and forgot password api has been optimised 2023-08-18 16:57:42 +05:30
4bc91abebf Merge pull request #225 from KiwiTechLLC/sprint5
remove threading from login api
2023-08-18 16:40:35 +05:30
5b236dfc81 remove threading from login api 2023-08-18 16:37:42 +05:30
150bb9250d Merge pull request #222 from KiwiTechLLC/sprint5
Sprint5
2023-08-18 16:31:35 +05:30
51d3b77ff7 password validation, deactivated user's middleware 2023-08-18 15:45:57 +05:30
92c67dd455 Merge pull request #224 from KiwiTechLLC/ZBKADM-72-minor-changes
article bug fixed, changed image name in get image url
2023-08-18 15:07:46 +05:30
9a5447bca2 article bug fixed, changed image name in get image url 2023-08-18 14:52:38 +05:30
392086760f Merge pull request #223 from KiwiTechLLC/ZBKADM-72
csv/excel method changed, uploading to alibaba cloud and providing li…
2023-08-18 13:47:36 +05:30
ceaf584332 csv/excel method changed, uploading to alibaba cloud and providing link to frontend 2023-08-18 11:46:40 +05:30
ef4c459229 handle deleted user scenario in complete task api 2023-08-18 11:45:21 +05:30
dd2890bca6 handle delete scenerio in approve task and junior 2023-08-18 11:04:05 +05:30
b46109e487 FAQ list and creation 2023-08-17 16:06:58 +05:30
3f6c9a2d99 FAQ list and creation 2023-08-17 15:48:26 +05:30
f74302df04 Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint5 2023-08-17 14:22:33 +05:30
9bdd324c4e Merge pull request #221 from KiwiTechLLC/email-verification-issue
remove print statement for expiry date
2023-08-17 13:28:55 +05:30
04ed9c668c remove print statement for expiry date 2023-08-17 13:24:20 +05:30
e1d092d663 Merge pull request #220 from KiwiTechLLC/ZBKADM-72
added required packages in reuirements fil
2023-08-17 13:10:11 +05:30
4e8243c17e added required packages in reuirements fil 2023-08-17 13:07:59 +05:30
13ba311822 Merge pull request #218 from KiwiTechLLC/ZBKADM-72
csv/excel export api
2023-08-17 12:45:38 +05:30
043c8c2f63 csv excel export api 2023-08-17 12:30:39 +05:30
a5f239b9d9 Merge pull request #219 from KiwiTechLLC/email-verification-issue
print statement for expiry date
2023-08-17 12:29:31 +05:30
36427b50c1 print statement for expiry date 2023-08-17 12:24:33 +05:30
728d19da99 FAQ model 2023-08-17 12:21:13 +05:30
5da07002e0 Merge pull request #217 from KiwiTechLLC/ZBKBCK-47
notification modification added more payload
2023-08-16 16:48:53 +05:30
f04b4aeaad Merge pull request #216 from KiwiTechLLC/stage-hotFix
user notification issue
2023-08-16 16:43:16 +05:30
c7c55f2a04 user notification issue 2023-08-16 16:41:08 +05:30
3017b0ece0 changes in notification method 2023-08-16 16:40:34 +05:30
e73113fcca Merge branch 'dev' into ZBKBCK-47 2023-08-16 16:39:36 +05:30
ed1b5dd453 Merge pull request #215 from KiwiTechLLC/dev
Dev
2023-08-16 16:34:54 +05:30
c278670c39 Merge pull request #214 from KiwiTechLLC/sprint5
email verification and user notification bug
2023-08-16 16:28:58 +05:30
60af098e1e email verfication and user notification bug 2023-08-16 16:16:05 +05:30
bcf308b6eb Merge pull request #213 from KiwiTechLLC/qa
Qa
2023-08-16 11:39:02 +05:30
74328f37b2 Merge pull request #212 from KiwiTechLLC/dev
Dev
2023-08-16 11:38:39 +05:30
d29a60558f Merge pull request #211 from KiwiTechLLC/sprint5
user type
2023-08-16 11:38:12 +05:30
451c3bdae7 user type 2023-08-16 11:37:21 +05:30
54748a6704 Merge pull request #210 from KiwiTechLLC/qa
Qa
2023-08-14 18:24:10 +05:30
d8f4467d98 Merge pull request #209 from KiwiTechLLC/dev
guardian code status
2023-08-14 18:23:41 +05:30
7c809776b6 guardian code status 2023-08-14 18:22:50 +05:30
74e704570a Merge pull request #208 from KiwiTechLLC/qa
Qa
2023-08-14 17:34:25 +05:30
a9cc05c675 Merge pull request #207 from KiwiTechLLC/dev
Dev
2023-08-14 17:33:56 +05:30
e46d649fdc notification modification added more payload 2023-08-14 16:03:36 +05:30
adb827f5a0 Merge pull request #206 from KiwiTechLLC/sprint4
Sprint4
2023-08-14 15:26:27 +05:30
a02dfd4e31 login api with user type 2023-08-14 15:23:41 +05:30
d3b0be953e login api with user type 2023-08-14 15:16:16 +05:30
11b9f00285 Merge branch 'login_api_reduced_queries' of github.com:KiwiTechLLC/ZODBank-Backend into login_api_reduced_queries 2023-08-14 15:01:43 +05:30
4bc16c56bd csv/xls report 2023-08-14 13:57:29 +05:30
08dc9f8538 Merge pull request #205 from KiwiTechLLC/sprint4
sonar fixes
2023-08-14 12:20:48 +05:30
6373d1e02c sonar fixes 2023-08-14 12:13:14 +05:30
136d0b732a Merge pull request #204 from KiwiTechLLC/qa
Qa
2023-08-14 11:46:35 +05:30
83d7d119be Merge pull request #203 from KiwiTechLLC/dev
Dev
2023-08-14 11:46:04 +05:30
a1d959299a Merge pull request #202 from KiwiTechLLC/sprint4
is complete key
2023-08-14 11:45:37 +05:30
070637bf1d is complete key 2023-08-14 11:43:59 +05:30
b974cc2010 Merge pull request #201 from KiwiTechLLC/qa
Qa
2023-08-11 17:08:10 +05:30
19a6475097 Merge pull request #200 from KiwiTechLLC/dev
Dev
2023-08-11 16:55:51 +05:30
fd9a4902ae Merge pull request #199 from KiwiTechLLC/sprint4
guardian code chnages
2023-08-11 16:55:21 +05:30
082f93ff9d guardian code chnages 2023-08-11 16:54:27 +05:30
25eaee31a4 Merge pull request #198 from KiwiTechLLC/ZBKADM-71
added pagination in leaderboard admin
2023-08-11 16:02:13 +05:30
d5c30f2029 added pagination in leaderboard admin 2023-08-11 15:56:22 +05:30
cae28e0b54 Merge pull request #197 from KiwiTechLLC/sprint4
is complete article key
2023-08-11 15:41:35 +05:30
54200dba52 is complete article key 2023-08-11 15:39:25 +05:30
846a9d42d4 Merge pull request #196 from KiwiTechLLC/ZBKADM-72-sonar-issues
sonar issues
2023-08-11 14:44:25 +05:30
69c19cf097 sonar issues 2023-08-11 14:40:49 +05:30
18cb885410 Merge pull request #193 from KiwiTechLLC/ZBKADM-71
leaderboard ranking
2023-08-11 14:13:05 +05:30
4ca60af5da Merge pull request #195 from KiwiTechLLC/dev
Dev
2023-08-11 13:50:53 +05:30
4d7209df19 Merge pull request #194 from KiwiTechLLC/sprint4
remove deleted user from leaderboard
2023-08-11 13:40:48 +05:30
94c76b7f2e remove deleted user from leaderboard 2023-08-11 13:39:25 +05:30
af8ea39200 leaderboard ranking 2023-08-11 13:37:44 +05:30
6e9304b2cc Merge pull request #192 from KiwiTechLLC/sprint4
is complete article key
2023-08-11 13:04:54 +05:30
5eab8f4907 is complete article key 2023-08-11 13:04:05 +05:30
807526acfa Merge pull request #191 from KiwiTechLLC/dev
Dev
2023-08-11 12:24:28 +05:30
f750487b88 Merge pull request #190 from KiwiTechLLC/sprint4
add correct and attempt answer
2023-08-11 12:21:47 +05:30
8b0032f6d2 junior point table 2023-08-11 12:18:35 +05:30
bb65f81200 add correct and attempt answer 2023-08-11 11:48:02 +05:30
04db40e100 Merge pull request #189 from KiwiTechLLC/sprint4
create article
2023-08-10 21:24:15 +05:30
1e162255d7 create article 2023-08-10 20:17:24 +05:30
f4149379c2 Merge pull request #188 from KiwiTechLLC/dev
Dev
2023-08-10 19:21:12 +05:30
a479f3cb62 Merge pull request #187 from KiwiTechLLC/sprint4
task points
2023-08-10 19:06:32 +05:30
fbd6f9ece5 task points 2023-08-10 19:05:33 +05:30
11c84208b7 task points 2023-08-10 19:00:37 +05:30
4f02cef0f9 Optimised login api 2023-08-10 18:43:03 +05:30
b8f1acaed8 Merge pull request #185 from KiwiTechLLC/dev
Dev
2023-08-10 18:37:06 +05:30
80dffb7942 Merge pull request #184 from KiwiTechLLC/ZBKADM-71
image name fix, remove '|' character
2023-08-10 18:21:10 +05:30
0a8c2cf56d image name fix, remove '|' character 2023-08-10 18:17:49 +05:30
9e5dd5e3d5 Merge pull request #183 from KiwiTechLLC/dev
Dev
2023-08-10 17:04:06 +05:30
03fb4f4176 Merge pull request #182 from KiwiTechLLC/sprint4
current page update in article api
2023-08-10 15:51:09 +05:30
22afe7e555 current page update in article api 2023-08-10 15:43:31 +05:30
69723b362f current page update in article api 2023-08-10 15:30:46 +05:30
9e612c7171 Merge pull request #181 from KiwiTechLLC/sprint4
junior points
2023-08-10 12:20:34 +05:30
7a9be0326a junior points 2023-08-10 12:19:08 +05:30
aaa1c55227 Merge pull request #180 from KiwiTechLLC/sprint4
guardian code
2023-08-10 12:07:49 +05:30
656f0da89a guardian code 2023-08-10 12:06:52 +05:30
b961044e4d Merge pull request #179 from KiwiTechLLC/dev
Dev
2023-08-10 11:57:29 +05:30
28e71e132c Merge pull request #178 from KiwiTechLLC/sprint4
Sprint4
2023-08-10 11:56:53 +05:30
b30643299f junior points 2023-08-10 11:56:19 +05:30
b6b70af13f remove guardian code request 2023-08-10 11:02:32 +05:30
22dd7fc10b remove guardian code request 2023-08-10 10:55:33 +05:30
2498394127 Merge pull request #177 from KiwiTechLLC/dev
Dev
2023-08-09 19:22:42 +05:30
924bc20193 Merge pull request #176 from KiwiTechLLC/sprint4
points
2023-08-09 19:21:50 +05:30
af7582b9e2 points 2023-08-09 19:20:56 +05:30
856d27d803 Merge pull request #175 from KiwiTechLLC/sprint4
points remove from serializer
2023-08-09 18:54:12 +05:30
19f56280e4 points remove from serializer 2023-08-09 18:53:18 +05:30
2fc65d462d Merge pull request #174 from KiwiTechLLC/dev
Dev
2023-08-09 18:11:40 +05:30
d3800dbc85 Merge pull request #173 from KiwiTechLLC/sprint4
guardian code update
2023-08-09 18:10:18 +05:30
9373dc4697 Merge pull request #172 from KiwiTechLLC/ZBKADM-71
analytics section api, users counts, new signup count, task report
2023-08-09 17:20:53 +05:30
32eaa6c3f2 guardian code update 2023-08-09 17:18:07 +05:30
11ff8fc700 conflict resolved 2023-08-09 17:03:42 +05:30
9c75cb1615 analytics section api, users counts, new signup count, task report 2023-08-09 16:57:19 +05:30
3f8b0e2eb7 Merge pull request #171 from KiwiTechLLC/sprint4
financial learning section api
2023-08-09 16:44:27 +05:30
69ce6857e8 financial learning section api 2023-08-09 16:36:33 +05:30
cdf656fdad Merge pull request #170 from KiwiTechLLC/dev
Dev
2023-08-09 14:51:13 +05:30
3efa31efe4 Merge pull request #169 from KiwiTechLLC/sprint4
Sprint4
2023-08-09 14:39:01 +05:30
275b09d2ea Merge branch 'sprint4' of github.com:KiwiTechLLC/ZODBank-Backend into sprint4 2023-08-09 14:38:09 +05:30
3cb0e3ed8b guardian code add in junior list api 2023-08-09 14:37:38 +05:30
823c5ea4c4 Merge pull request #168 from KiwiTechLLC/ZBKADM-69
otp, verify otp and login bug resolved
2023-08-09 14:21:58 +05:30
c9ee482512 otp, verify otp and login bug resolved 2023-08-09 14:16:20 +05:30
08750813ce Merge pull request #167 from KiwiTechLLC/sprint4
Sprint4
2023-08-09 13:54:08 +05:30
75f4f66285 Merge branch 'dev' into sprint4 2023-08-09 13:49:39 +05:30
881bda739b article card api, check answer api 2023-08-09 13:48:02 +05:30
d86f082e58 article card api, check answer api 2023-08-09 13:37:06 +05:30
8eaf8751c2 Merge pull request #165 from KiwiTechLLC/dev
Dev
2023-08-08 17:14:36 +05:30
b12c5071fe Merge pull request #164 from KiwiTechLLC/sprint4
changes in add junior api
2023-08-08 16:37:08 +05:30
f75201b3dd changes in add junior api 2023-08-08 16:36:03 +05:30
6867920754 Merge pull request #163 from KiwiTechLLC/dev
Dev
2023-08-08 16:07:14 +05:30
45ea7013f8 Merge pull request #162 from KiwiTechLLC/sprint4
junior list api
2023-08-08 16:06:52 +05:30
7d428f5eb5 junior list api 2023-08-08 16:05:56 +05:30
22ba4288d9 Merge pull request #161 from KiwiTechLLC/dev
Dev
2023-08-08 15:04:50 +05:30
9ff64e64ef Merge pull request #160 from KiwiTechLLC/sprint4
top junior api
2023-08-08 15:04:03 +05:30
4b23394569 top junior api 2023-08-08 15:03:24 +05:30
81893c8841 Merge pull request #159 from KiwiTechLLC/ZBKADM-69
change in admin login
2023-08-08 14:38:43 +05:30
10a1ea9b76 change in admin login 2023-08-08 14:29:57 +05:30
7ab16cb3de Merge pull request #158 from KiwiTechLLC/dev
Dev
2023-08-08 14:24:44 +05:30
7fca493d0d Merge pull request #157 from KiwiTechLLC/sprint4
junior position changes and decrease api response time
2023-08-08 14:02:27 +05:30
85e4ae8761 junior position changes and decrease api response time 2023-08-08 13:54:04 +05:30
6ebd5ae410 Merge pull request #156 from KiwiTechLLC/sprint4
guardian list api changes
2023-08-08 12:35:43 +05:30
f57b111555 guardian list api changes 2023-08-08 11:48:53 +05:30
6596414675 guardian list api changes 2023-08-08 11:46:27 +05:30
ed7fd3c15d Merge pull request #155 from KiwiTechLLC/ZBKADM-69
notification list modified and mark as read method changed
2023-08-07 20:48:37 +05:30
8cf7148114 Merge pull request #154 from KiwiTechLLC/ZBKADM-69
change method to get real time
2023-08-07 19:19:00 +05:30
b98443479f Merge pull request #153 from KiwiTechLLC/sprint4
junior article points table
2023-08-07 19:12:55 +05:30
f917244265 junior article points table 2023-08-07 19:12:14 +05:30
ceb5bc13c3 Merge pull request #152 from KiwiTechLLC/ZBKADM-69
task list api modified
2023-08-07 18:22:20 +05:30
ae0fc4fe8d article list api 2023-08-07 17:06:31 +05:30
0a1b9c7e70 Merge pull request #151 from KiwiTechLLC/ZBKADM-69
changes in edit api, included first name, last name and username
2023-08-07 15:14:46 +05:30
f2cf1488e9 changes in edit api, included first name, last name and username 2023-08-07 15:12:54 +05:30
0426974539 Merge pull request #150 from KiwiTechLLC/sprint4
guardian list api
2023-08-07 13:44:20 +05:30
b0e26f41b9 Merge branch 'dev' into sprint4 2023-08-07 13:43:49 +05:30
c20249730a guardian list api 2023-08-07 13:42:47 +05:30
b3b499e661 guardian list api 2023-08-07 13:41:33 +05:30
f377e283fd guardian list api 2023-08-07 13:25:01 +05:30
648a2ec4d5 Merge pull request #149 from KiwiTechLLC/ZBKADM-69
admin login api modified
2023-08-07 12:13:51 +05:30
83ec922584 admin login api modified 2023-08-07 12:10:36 +05:30
88221ec77a notifications API 2023-08-07 11:28:34 +05:30
e853346910 notifications API 2023-08-07 11:26:23 +05:30
a088764b7b Merge pull request #148 from KiwiTechLLC/ZBKADM-69
edit user validations, active-inactive api
2023-08-07 11:04:20 +05:30
4a2f36cde8 edit user validations, active-inactive api 2023-08-07 11:01:37 +05:30
401ee1ddf8 Merge pull request #147 from KiwiTechLLC/ZBKADM-69
Zbkadm 69
2023-08-04 16:46:05 +05:30
baacb1a18f user edit api 2023-08-04 16:37:46 +05:30
5f1c645e3a user list api 2023-08-04 16:27:46 +05:30
07d150309b Merge pull request #146 from KiwiTechLLC/dev
Dev
2023-08-04 15:04:55 +05:30
79648637aa Merge pull request #145 from KiwiTechLLC/ZBKADM-69
user detail view api, searching user list, count
2023-08-04 13:21:47 +05:30
756bea0471 user detail view api, searching user list, count 2023-08-04 13:15:32 +05:30
465632f519 Merge pull request #144 from KiwiTechLLC/ZBKADM-69
user detail api, changed image upload method
2023-08-03 18:38:53 +05:30
b9e2d9bc8a user detail api, changed image upload method 2023-08-03 18:35:19 +05:30
d82c8cd4ae Merge pull request #143 from KiwiTechLLC/ZBKADM-69
user detail api, changed image upload method
2023-08-03 18:24:56 +05:30
dd0f2b027a task list api modified 2023-08-03 18:17:01 +05:30
3806d1f3a6 user detail api, changed image upload method 2023-08-03 18:14:54 +05:30
3801e6e027 Merge pull request #142 from KiwiTechLLC/dev
Dev
2023-08-03 14:59:49 +05:30
f3e2ab9a34 notification list modified and mark as read method changed 2023-08-02 20:45:46 +05:30
685f627707 change method to get real time 2023-08-02 19:16:29 +05:30
b2d172eae5 Merge pull request #141 from KiwiTechLLC/sprint4
migration file
2023-08-02 16:58:24 +05:30
404825dc85 migration file 2023-08-02 16:57:40 +05:30
6e6f0a55d0 Merge pull request #140 from KiwiTechLLC/sprint4
changes in remove guardian code api
2023-08-02 16:26:56 +05:30
75d0b12008 changes in remove guardian code api 2023-08-02 16:26:20 +05:30
2a4011ca5d Merge pull request #139 from KiwiTechLLC/sprint4
sprint4 reassign task after expired
2023-08-02 15:21:38 +05:30
ebb468166e sprint4 reassign task after expired 2023-08-02 15:20:43 +05:30
e89fc513cb Merge pull request #138 from KiwiTechLLC/dev
Dev
2023-08-02 14:25:06 +05:30
06176912ee Merge pull request #137 from KiwiTechLLC/sprint4
change junior task list api response
2023-08-02 14:24:38 +05:30
ed8fc156ac change junior task list api response 2023-08-02 14:23:36 +05:30
e1af1c200d Merge pull request #136 from KiwiTechLLC/dev
Dev
2023-08-02 11:33:29 +05:30
bff97f59b2 Merge pull request #135 from KiwiTechLLC/ZBKADM-67
changes in article update api and get image url method
2023-08-02 11:19:21 +05:30
af121f5a53 changes in article update api and get image url method 2023-08-02 11:16:12 +05:30
4c3aa03e13 Merge pull request #134 from KiwiTechLLC/sprint4
api response time
2023-08-02 11:00:42 +05:30
f3a8b52617 api response time 2023-08-02 11:00:02 +05:30
0c3a77cd11 Merge pull request #133 from KiwiTechLLC/ZBKADM-67
change in update api, added method to upload and  get image url
2023-08-01 20:19:09 +05:30
d3564efbb9 change in update api, added method to upload and get image url 2023-08-01 20:15:01 +05:30
329df77790 Merge pull request #132 from KiwiTechLLC/ZBKADM-67
article list api changes, changed related name for survey_options to …
2023-08-01 13:16:37 +05:30
ee92c98f34 article list api changes, changed related name for survey_options to options 2023-08-01 13:14:22 +05:30
4fddc7b3e2 Merge pull request #131 from KiwiTechLLC/qa
Qa
2023-07-28 20:19:40 +05:30
b238379a22 Merge pull request #130 from KiwiTechLLC/dev
Dev
2023-07-28 19:49:56 +05:30
fbce04b97e Merge pull request #129 from KiwiTechLLC/ZBKADM-67
fixed add junior api
2023-07-28 19:30:39 +05:30
afaed69eec fixed add junior api 2023-07-28 19:19:59 +05:30
3c0eaf676e Merge pull request #128 from KiwiTechLLC/ZBKADM-67
fixed add junior api
2023-07-28 19:00:41 +05:30
6ffaa66a4d fixed add junior api 2023-07-28 18:57:11 +05:30
4b8e7189c1 Merge pull request #127 from KiwiTechLLC/ZBKADM-67
changes in approve junior and add juinior api, added search in junior…
2023-07-28 18:11:54 +05:30
d68f24543d changes in approve junior and add juinior api, added search in junior list api 2023-07-28 17:32:02 +05:30
3534561697 Merge pull request #126 from KiwiTechLLC/ZBKADM-67
some changes in artcle api
2023-07-28 16:50:26 +05:30
de774111c0 some changes in artcle api 2023-07-28 16:08:40 +05:30
ed18758cc5 Merge pull request #125 from KiwiTechLLC/qa
Qa
2023-07-28 11:13:10 +05:30
c081bc621d Merge pull request #124 from KiwiTechLLC/dev
merging dev into qa
2023-07-27 22:04:51 +05:30
bab50e4492 Merge pull request #123 from KiwiTechLLC/ZBKADM-67
some changes in admin password reset related api
2023-07-27 19:36:01 +05:30
151a177e76 some changes in admin password reset related api 2023-07-27 19:29:12 +05:30
73a62186a7 Merge pull request #122 from KiwiTechLLC/ZBKADM-67
added request status in expired task cronjob
2023-07-27 15:32:17 +05:30
83c66ab3b6 added request status in expired task cronjob 2023-07-27 15:31:25 +05:30
94e53e1dcd Merge pull request #121 from KiwiTechLLC/ZBKADM-67
added task to send otp on email
2023-07-27 13:12:36 +05:30
68b77ef766 added task to send otp on email 2023-07-27 12:59:41 +05:30
d2c8b18e3f Merge pull request #120 from KiwiTechLLC/dev
merging dev itno qa
2023-07-27 11:37:25 +05:30
a2000459d3 Merge pull request #119 from KiwiTechLLC/ZBKADM-67
some changes in forgot password api
2023-07-27 11:06:09 +05:30
c079f3ceca some changes in forgot password api 2023-07-27 10:59:10 +05:30
671cdfc27b Merge pull request #118 from KiwiTechLLC/ZBKADM-67
change password api
2023-07-26 17:51:32 +05:30
a83e27b12a Merge branch 'dev' into ZBKADM-67 2023-07-26 17:45:22 +05:30
87aa1af02b change password api 2023-07-26 17:16:36 +05:30
bdc0ba4fd5 Merge pull request #117 from KiwiTechLLC/dev
Dev
2023-07-26 16:58:38 +05:30
5f3026f08f Merge pull request #116 from KiwiTechLLC/sprint3
jira-28 notification
2023-07-26 16:51:46 +05:30
a323ad7df7 jira-28 notification 2023-07-26 16:42:26 +05:30
2b6e943d8c jira-28 notification 2023-07-26 16:38:36 +05:30
3af2584d6a Merge pull request #115 from KiwiTechLLC/dev
Dev
2023-07-26 11:09:40 +05:30
bca0d8d49b Merge pull request #114 from KiwiTechLLC/sprint3
requirement .txt file add
2023-07-26 11:08:02 +05:30
b860d937f5 requirement .txt file add 2023-07-26 11:06:58 +05:30
62cf2b14bf Merge pull request #113 from KiwiTechLLC/dev
merging dev into qa
2023-07-26 11:01:45 +05:30
11b10bf751 Merge pull request #112 from KiwiTechLLC/ZBKADM-67
added api for forgot password, verify otp and resend otp for admin
2023-07-25 19:57:49 +05:30
d0ef4391cc conflict resolved 2023-07-25 19:51:27 +05:30
5a1b419863 Merge pull request #111 from KiwiTechLLC/sprint3
jira-290 cron job for expired task
2023-07-25 19:49:24 +05:30
7bf0e62604 added api for forgot password, verify otp and resend otp for admin 2023-07-25 19:43:14 +05:30
80843502a6 jira-290 cron job for expired task 2023-07-25 19:13:25 +05:30
cbd3d139a5 Merge pull request #110 from KiwiTechLLC/dev
Dev
2023-07-25 17:36:49 +05:30
3b31d08996 Merge pull request #109 from KiwiTechLLC/sprint3
Sprint3
2023-07-25 17:34:44 +05:30
3cdd685ea8 sonar issues 2023-07-25 17:21:18 +05:30
c4152c0773 Merge branch 'sprint3' of github.com:KiwiTechLLC/ZODBank-Backend into sprint3 2023-07-25 17:15:35 +05:30
8a454c3f97 sonar issues 2023-07-25 17:15:16 +05:30
71405780f8 Merge branch 'dev' into sprint3 2023-07-25 17:07:58 +05:30
a8558b5e6e sonar issues 2023-07-25 17:06:26 +05:30
7c4f9b2506 jira-291 start task api 2023-07-25 16:54:48 +05:30
cc3490b059 Merge pull request #108 from KiwiTechLLC/ZBKADM-67
article card folder name change
2023-07-25 15:52:22 +05:30
32476149bc article card folder name change 2023-07-25 15:50:43 +05:30
c57224ad0e Merge pull request #107 from KiwiTechLLC/sprint3
jira-28 invite guardian
2023-07-25 15:29:36 +05:30
c8a8e1149b jira-28 invite guardian 2023-07-25 15:27:29 +05:30
7c69dfd49f Merge pull request #106 from KiwiTechLLC/ZBKADM-67
modified yml file, default article card image upload api,
2023-07-25 14:46:56 +05:30
6dcf045131 Merge branch 'dev' into ZBKADM-67 2023-07-25 14:22:37 +05:30
6e84814117 modified yml file, default article card image upload api, 2023-07-25 14:09:51 +05:30
c587ba322e Merge pull request #105 from KiwiTechLLC/sprint3
start task api
2023-07-25 13:29:32 +05:30
2db0e47e3b resolve conflict 2023-07-25 13:21:20 +05:30
52cdd4b0ba resolve conflict 2023-07-25 13:19:53 +05:30
6be0682913 junior guardian relation table 2023-07-25 13:17:27 +05:30
bfeeb5e41f Merge branch 'dev' into sprint3 2023-07-25 12:56:48 +05:30
bb56fc7a33 try and except in api 2023-07-25 12:53:56 +05:30
e6d45f3bf2 start task api 2023-07-25 12:02:59 +05:30
7cb792c6cf Merge pull request #104 from KiwiTechLLC/dev
Dev
2023-07-25 11:52:36 +05:30
49b648e86b Merge pull request #103 from KiwiTechLLC/sprint3
Sprint3
2023-07-25 11:33:08 +05:30
79e85fa968 jira-27 invite guardian api 2023-07-25 11:17:57 +05:30
5ea28bbd75 Merge pull request #102 from KiwiTechLLC/dev
Dev
2023-07-24 15:56:54 +05:30
096961976d sonar issues 2023-07-24 15:28:54 +05:30
4d104bab1c Merge pull request #101 from KiwiTechLLC/sprint3
bugs and push notification for create task
2023-07-24 15:19:40 +05:30
ed28117a7f bugs and push notification for create task 2023-07-24 15:10:59 +05:30
180104ece8 Merge pull request #100 from KiwiTechLLC/dev
Dev
2023-07-24 11:04:08 +05:30
0a7c081f63 Merge branch 'qa' into dev 2023-07-24 11:03:59 +05:30
a2b4f3b758 update position of the junior 2023-07-24 11:00:22 +05:30
d86f7d3436 guardian code 2023-07-21 18:38:16 +05:30
a5a309cf5b comment top junior call api 2023-07-21 18:33:18 +05:30
0a5eaa233f add qa top -junior url 2023-07-21 17:33:21 +05:30
b32d4def2d add qa top -junior url 2023-07-21 16:25:21 +05:30
410282036e Merge pull request #99 from KiwiTechLLC/sprint3
add dev url for top junior api
2023-07-21 16:23:07 +05:30
2a225ea79d add dev url for top junior api 2023-07-21 16:19:30 +05:30
65e012dd60 Merge pull request #98 from KiwiTechLLC/sprint3
add print in junior list
2023-07-21 14:49:52 +05:30
8013767d71 add print in junior list 2023-07-21 14:49:38 +05:30
fdd0b88b11 add print in junior list 2023-07-21 14:48:18 +05:30
bb1351c309 Merge pull request #96 from KiwiTechLLC/ZBKBCK-001
article api changes
2023-07-21 12:32:32 +05:30
c15bd2339a Merge pull request #97 from KiwiTechLLC/dev
Dev
2023-07-21 12:07:26 +05:30
5ee01af825 article api changes 2023-07-21 11:35:58 +05:30
b8382e225c Merge pull request #95 from KiwiTechLLC/ZBKBCK-001
added article point
2023-07-20 18:25:23 +05:30
532b56c687 added article point 2023-07-20 18:01:53 +05:30
6fe29f0070 Merge pull request #94 from KiwiTechLLC/sprint3
real time changes
2023-07-20 17:13:31 +05:30
96d71d951e real time changes 2023-07-20 17:09:49 +05:30
effad5cddc Merge pull request #93 from KiwiTechLLC/dev
Dev
2023-07-20 13:25:53 +05:30
8c0da25814 Merge branch 'qa' into dev 2023-07-20 13:25:42 +05:30
b032300ab7 Merge pull request #92 from KiwiTechLLC/sprint3
admin login
2023-07-20 12:45:39 +05:30
4b09ff41cb admin login 2023-07-20 12:44:48 +05:30
8762c00dba Merge pull request #90 from KiwiTechLLC/ZDBBCK-001
sign up issue resolved
2023-07-20 12:31:36 +05:30
4410712b11 sign up issue resolved 2023-07-20 12:29:41 +05:30
e2f737c5b4 Merge pull request #91 from KiwiTechLLC/sprint3
Sprint3
2023-07-20 12:29:02 +05:30
0f5dfc2e9a google login chnages 2023-07-20 12:27:22 +05:30
b488b08f3f google login chnages 2023-07-20 12:25:41 +05:30
7102c44055 Merge pull request #89 from KiwiTechLLC/sprint3
Sprint3
2023-07-19 18:05:07 +05:30
8e2d9c44b9 Merge pull request #88 from KiwiTechLLC/ZDBBCK-001
celery settings
2023-07-19 17:59:15 +05:30
fa96364901 Merge branch 'dev' into sprint3 2023-07-19 17:51:58 +05:30
49abf12ce8 jira-34 referral code validation API 2023-07-19 17:50:10 +05:30
4002d1e561 celery settings 2023-07-19 17:48:27 +05:30
9f9926da14 jira-34 referral code validation API 2023-07-19 17:45:20 +05:30
555d8231e7 Merge pull request #87 from KiwiTechLLC/ZDBBCK-001
notification set up and celery configuration
2023-07-19 17:04:06 +05:30
3223a2b050 notification set up and celery configuration 2023-07-19 17:00:58 +05:30
2e7d36485c Merge pull request #86 from KiwiTechLLC/sprint3
jira-34 allocated points
2023-07-19 15:04:41 +05:30
2e3870dc53 Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint3 2023-07-19 15:04:04 +05:30
86e2c75afc jira-34 allocated points 2023-07-19 15:01:18 +05:30
e238c85674 Merge pull request #85 from KiwiTechLLC/ZDBBCK-001
cors error settings
2023-07-19 15:00:12 +05:30
5429140a5c cors error settings 2023-07-19 14:58:14 +05:30
6e6ba881f6 Merge pull request #84 from KiwiTechLLC/ZDBBCK-001
checking email settings
2023-07-19 13:15:40 +05:30
74fc656bd1 checking email settings 2023-07-19 13:11:36 +05:30
e9b0e480e6 Merge pull request #83 from KiwiTechLLC/ZDBBCK-001
checking email settings
2023-07-18 19:39:27 +05:30
49fc8d9dca checking email settings 2023-07-18 19:37:30 +05:30
39271282e2 Merge pull request #82 from KiwiTechLLC/sprint3
jira-34 points earned
2023-07-18 19:27:13 +05:30
9b84d176af jira-34 points earned 2023-07-18 19:18:38 +05:30
49336e2e18 Merge pull request #81 from KiwiTechLLC/ZDBBCK-001
checking email settings
2023-07-18 18:30:13 +05:30
0df748ccbf checking email settings 2023-07-18 18:27:27 +05:30
9d600a1ea8 Merge pull request #80 from KiwiTechLLC/sprint3
email setting changes
2023-07-18 17:34:31 +05:30
06d841f444 email setting changes 2023-07-18 17:32:34 +05:30
e3a5d52a1d Merge pull request #79 from KiwiTechLLC/sprint3
sonar fixes
2023-07-18 16:58:20 +05:30
130dcd83e7 sonar fixes 2023-07-18 16:56:24 +05:30
19a2a3d1db Merge pull request #78 from KiwiTechLLC/ZDBBCK-001
remove fcm token
2023-07-18 16:32:51 +05:30
02f01b8a16 Merge pull request #77 from KiwiTechLLC/sprint3
Sprint3
2023-07-18 16:31:20 +05:30
3d84c03163 remove fcm token 2023-07-18 16:29:11 +05:30
5cc43aa2b8 jira-33 mark as complete the task 2023-07-18 15:59:26 +05:30
4c0cac7cb0 jira-32 list of all task of junior API, changes in Search task API 2023-07-18 15:19:50 +05:30
6aa198d1ad Merge pull request #76 from KiwiTechLLC/ZDBBCK-001
[ZDBBCK-001]:- notification app, api for device registration and fcm token
2023-07-18 15:03:18 +05:30
ba4d5933de notification app, api for device registration and fcm token 2023-07-18 14:13:30 +05:30
4d04f16cee Merge branch 'dev' of github.com:KiwiTechLLC/ZODBank-Backend into sprint3 2023-07-18 11:02:03 +05:30
032186bf43 task list 2023-07-18 11:01:16 +05:30
04342133ef Merge pull request #75 from KiwiTechLLC/ZDBBCK-001
[ZDBBCK-001]:- web admin article api
2023-07-17 19:18:11 +05:30
751af642b8 added admin permission 2023-07-17 18:58:09 +05:30
e90326fcb6 Merge branch 'dev' into ZDBBCK-001 2023-07-17 18:50:43 +05:30
0cec3a5cf1 Merge pull request #74 from KiwiTechLLC/sprint3
Sprint3
2023-07-17 18:28:06 +05:30
ae7250b34a changes in env 2023-07-17 18:17:00 +05:30
8f1f49de45 jira-21 approve task API 2023-07-17 18:00:19 +05:30
de5d3b19d0 conflict resolved 2023-07-17 17:53:11 +05:30
2b0fc56190 Merge pull request #73 from KiwiTechLLC/sprint3
sonar issues
2023-07-17 17:13:42 +05:30
4779749b0c success msg 2023-07-17 17:05:45 +05:30
6d6d21137f sonar issues 2023-07-17 17:00:33 +05:30
cd944b10f5 Merge pull request #72 from KiwiTechLLC/sprint3
sonar issues
2023-07-17 16:34:19 +05:30
1399b585e8 sonar issues 2023-07-17 16:07:06 +05:30
2d3100286d Merge pull request #71 from KiwiTechLLC/sprint2
sonar issues fixed
2023-07-17 12:21:13 +05:30
761e723cd3 Merge pull request #70 from KiwiTechLLC/sprint2
sonar issue
2023-07-15 23:08:38 +05:30
71e7601d6b Merge pull request #69 from KiwiTechLLC/sprint2
Sprint2
2023-07-14 21:31:27 +05:30
45410fa0ca list, view, edit api for article 2023-07-14 18:47:07 +05:30
ac00543312 Merge pull request #68 from KiwiTechLLC/sprint2
Sprint2
2023-07-14 15:22:40 +05:30
fb74c6c207 Merge pull request #67 from KiwiTechLLC/qa
change access token duration
2023-07-14 12:46:38 +05:30
b3f35d271b change access token duration 2023-07-14 12:45:08 +05:30
a4d9997580 web_admin module added, api for article created 2023-07-13 19:51:17 +05:30
c43db90219 Merge pull request #65 from KiwiTechLLC/sprint2
jira-18 approval API
2023-07-13 19:30:32 +05:30
87cb49d34d Merge pull request #66 from KiwiTechLLC/qa
Qa
2023-07-13 19:25:57 +05:30
d3b7b6cd07 Merge pull request #64 from KiwiTechLLC/dev
Dev
2023-07-13 17:33:54 +05:30
1ea4720ab4 Merge pull request #63 from KiwiTechLLC/sprint2
referral code
2023-07-13 17:33:12 +05:30
f7cfd53594 Merge pull request #62 from KiwiTechLLC/dev
Dev
2023-07-13 16:42:28 +05:30
dae6ffbed1 Merge pull request #61 from KiwiTechLLC/sprint2
changes in image size
2023-07-13 16:41:33 +05:30
75bc387bec Merge pull request #60 from KiwiTechLLC/dev
Dev
2023-07-13 15:43:21 +05:30
aadced6a13 Merge pull request #59 from KiwiTechLLC/sprint2
check size of the image
2023-07-13 13:56:30 +05:30
c3f7750349 Merge pull request #58 from KiwiTechLLC/sprint2
Sprint2
2023-07-13 13:06:56 +05:30
0c760a58f3 Merge pull request #57 from KiwiTechLLC/dev
Dev
2023-07-13 11:29:55 +05:30
b156e62dc8 Merge pull request #55 from KiwiTechLLC/dev
Dev
2023-07-12 16:53:30 +05:30
b11137bfad changes in files 2023-07-12 16:27:52 +05:30
23fb2a0695 Merge pull request #51 from KiwiTechLLC/dev
Dev
2023-07-11 13:15:46 +05:30
fe8ddd02a5 Merge pull request #49 from KiwiTechLLC/dev
Dev
2023-07-11 12:06:29 +05:30
3dc16ce79c Merge pull request #44 from KiwiTechLLC/dev
Dev
2023-07-09 19:27:19 +05:30
b75756c43a Merge pull request #41 from KiwiTechLLC/dev
Dev
2023-07-07 18:06:40 +05:30
c01c2b4e23 Merge pull request #37 from KiwiTechLLC/dev
Dev
2023-07-06 20:18:01 +05:30
ddfc4e946c Merge branch 'qa' of github.com:KiwiTechLLC/ZODBank-Backend into qa 2023-07-03 12:58:35 +05:30
4261b5ad29 Merge pull request #31 from KiwiTechLLC/qa
Qa
2023-06-30 17:22:54 +05:30
f2533d9a08 Merge pull request #30 from KiwiTechLLC/dev
textual changes
2023-06-30 17:22:16 +05:30
6c0c3e0aca Merge pull request #29 from KiwiTechLLC/qa
Qa
2023-06-30 16:26:16 +05:30
4e22dd9871 Merge pull request #28 from KiwiTechLLC/dev
jira-6 change country name length
2023-06-30 16:25:39 +05:30
c6b220d6f2 Merge pull request #27 from KiwiTechLLC/qa
Qa
2023-06-30 15:54:15 +05:30
e5b88794ad Merge pull request #26 from KiwiTechLLC/dev
jira-274 user can use same number multiple time
2023-06-30 15:53:50 +05:30
6cbe824d9e jira-13 alibaba bucket name change 2023-06-30 15:24:49 +05:30
b5b793dc88 Merge pull request #25 from KiwiTechLLC/qa
Qa
2023-06-30 11:19:12 +05:30
df438241ce Merge pull request #24 from KiwiTechLLC/dev
Dev
2023-06-30 11:18:19 +05:30
1b5019e347 Merge pull request #22 from KiwiTechLLC/qa
Qa
2023-06-29 21:49:47 +05:30
bb06238623 Merge pull request #21 from KiwiTechLLC/dev
Dev
2023-06-29 21:48:53 +05:30
8c1e96a3df Merge pull request #19 from KiwiTechLLC/qa
Qa
2023-06-29 21:25:52 +05:30
ada987413b Merge pull request #18 from KiwiTechLLC/dev
Dev
2023-06-29 21:25:18 +05:30
545fa8229b Merge pull request #12 from KiwiTechLLC/qa
Qa to stage First commit
2023-06-28 17:53:09 +05:30
323168b7d0 Merge pull request #11 from KiwiTechLLC/dev
Dev
2023-06-28 11:56:12 +05:30
112 changed files with 13926 additions and 576 deletions

BIN
.coverage Normal file

Binary file not shown.

3
.gitignore vendored
View File

@ -6,7 +6,6 @@ media/
*.name
*.iml
*.log
*.xml
*.pyo
.DS_Store
.idea
@ -21,4 +20,4 @@ static/*
__pycache__/
*.env
ve/*
celerybeat-schedule

View File

@ -2,7 +2,7 @@
from django.contrib import admin
"""Import django app"""
from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails
from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails, ForceUpdate
# Register your models here.
@admin.register(UserDelete)
@ -39,6 +39,19 @@ class UserEmailOtpAdmin(admin.ModelAdmin):
"""Return object in email and otp format"""
return self.email + '-' + self.otp
@admin.register(ForceUpdate)
class ForceUpdateAdmin(admin.ModelAdmin):
"""Force update"""
list_display = ['version', 'device_type']
readonly_fields = ('device_type',)
def has_add_permission(self, request):
count = ForceUpdate.objects.all().count()
if count < 2:
return True
return False
def has_delete_permission(self, request, obj=None):
return False
@admin.register(UserDeviceDetails)
class UserDeviceDetailsAdmin(admin.ModelAdmin):
"""User profile admin"""

View File

@ -5,9 +5,11 @@ from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer
"""App django"""
from account.utils import custom_error_response
from account.models import UserDeviceDetails
from account.models import UserDeviceDetails, ForceUpdate
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from junior.models import Junior
from guardian.models import Guardian
# Custom middleware
# when user login with
# multiple device simultaneously
@ -15,6 +17,18 @@ from base.messages import ERROR_CODE, SUCCESS_CODE
# multiple devices only
# user can login in single
# device at a time"""
# force update
# use 308 status code for force update
def custom_response(custom_error, response_status = status.HTTP_404_NOT_FOUND):
"""custom response"""
response = Response(custom_error.data, status=response_status)
# Set content type header to "application/json"
response['Content-Type'] = 'application/json'
# Render the response as JSON
renderer = JSONRenderer()
response.content = renderer.render(response.data)
return response
class CustomMiddleware(object):
"""Custom middleware"""
def __init__(self, get_response):
@ -26,15 +40,34 @@ class CustomMiddleware(object):
response = self.get_response(request)
# Code to be executed after the view is called
device_id = request.META.get('HTTP_DEVICE_ID')
user_type = request.META.get('HTTP_USER_TYPE')
version = request.META.get('HTTP_VERSION')
device_type = str(request.META.get('HTTP_TYPE'))
api_endpoint = request.path
unrestricted_api = ('/api/v1/user/login/', '/api/v1/logout/', '/api/v1/generate-token/')
if request.user.is_authenticated:
"""device details"""
device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
if device_id and not device_details:
custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
response = Response(custom_error.data, status=status.HTTP_404_NOT_FOUND)
# Set content type header to "application/json"
response['Content-Type'] = 'application/json'
# Render the response as JSON
renderer = JSONRenderer()
response.content = renderer.render(response.data)
# device details
if device_id:
device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
if not device_details and api_endpoint not in unrestricted_api:
custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
response = custom_response(custom_error)
if user_type and str(user_type) == str(NUMBER['one']):
junior = Junior.objects.filter(auth=request.user, is_active=False).last()
if junior:
custom_error = custom_error_response(ERROR_CODE['2075'], response_status=status.HTTP_404_NOT_FOUND)
response = custom_response(custom_error)
elif user_type and str(user_type) == str(NUMBER['two']):
guardian = Guardian.objects.filter(user=request.user, is_active=False).last()
if guardian:
custom_error = custom_error_response(ERROR_CODE['2075'], response_status=status.HTTP_404_NOT_FOUND)
response = custom_response(custom_error)
if version and device_type:
force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last()
if not force_update:
custom_error = custom_error_response(ERROR_CODE['2079'],
response_status=status.HTTP_308_PERMANENT_REDIRECT)
response = custom_response(custom_error, status.HTTP_308_PERMANENT_REDIRECT)
return response

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-20 11:38
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0008_userdevicedetails'),
]
operations = [
migrations.AlterField(
model_name='userdevicedetails',
name='device_id',
field=models.CharField(blank=True, max_length=500, null=True),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 4.2.2 on 2023-08-22 07:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('account', '0009_alter_userdevicedetails_device_id'),
]
operations = [
migrations.CreateModel(
name='ForceUpdate',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('version', models.CharField(blank=True, max_length=50, null=True)),
('device_type', models.CharField(blank=True, choices=[('1', 'android'), ('2', 'ios')], default=None, max_length=15, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Force Update Version',
'verbose_name_plural': 'Force Update Version',
'db_table': 'force_update',
},
),
]

View File

@ -2,8 +2,9 @@
"""Django import"""
from django.db import models
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
"""App import"""
from base.constants import USER_TYPE
from base.constants import USER_TYPE, DEVICE_TYPE
# Create your models here.
class UserProfile(models.Model):
@ -152,7 +153,7 @@ class UserDeviceDetails(models.Model):
"""
user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='user_device_details')
"""Device ID"""
device_id = models.CharField(max_length=500)
device_id = models.CharField(max_length=500, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@ -165,3 +166,25 @@ class UserDeviceDetails(models.Model):
def __str__(self):
return self.user.email
class ForceUpdate(models.Model):
"""
Force update
"""
"""Version ID"""
version = models.CharField(max_length=50, null=True, blank=True)
device_type = models.CharField(max_length=15, choices=DEVICE_TYPE, null=True, blank=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta information """
db_table = 'force_update'
verbose_name = 'Force Update Version'
verbose_name_plural = 'Force Update Version'
def __str__(self):
return self.version

View File

@ -1,9 +1,10 @@
"""Account serializer"""
"""Django Import"""
import random
# Import Refresh token of jwt
from rest_framework import serializers
from django.contrib.auth.models import User
from rest_framework_simplejwt.tokens import RefreshToken
import secrets
"""App import"""
# Import guardian's model,
# Import junior's model,
@ -17,9 +18,9 @@ from rest_framework_simplejwt.tokens import RefreshToken
from guardian.models import Guardian
from junior.models import Junior
from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp
from base.constants import GUARDIAN, JUNIOR, SUPERUSER
from base.messages import ERROR_CODE_REQUIRED, ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR
from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp, ForceUpdate
from base.constants import GUARDIAN, JUNIOR, SUPERUSER, NUMBER
from base.messages import ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR
from .utils import delete_user_account_condition_social, delete_user_account_condition
# In this serializer file
@ -40,7 +41,7 @@ from .utils import delete_user_account_condition_social, delete_user_account_con
# email verification serializer,
# phone otp serializer
# create all serializer here
class GoogleLoginSerializer(serializers.Serializer):
"""google login serializer"""
access_token = serializers.CharField(max_length=5000, required=True)
@ -90,6 +91,7 @@ class ResetPasswordSerializer(serializers.Serializer):
def create(self, validated_data):
verification_code = validated_data.pop('verification_code')
password = validated_data.pop('password')
# fetch email otp object of the user
user_opt_details = UserEmailOtp.objects.filter(otp=verification_code, is_verified=True).last()
if user_opt_details:
user_details = User.objects.filter(email=user_opt_details.email).last()
@ -102,50 +104,108 @@ class ResetPasswordSerializer(serializers.Serializer):
return user_opt_details
return ''
class ChangePasswordSerializer(serializers.Serializer):
"""Update Password after verification"""
current_password = serializers.CharField(max_length=100)
current_password = serializers.CharField(max_length=100, required=True)
new_password = serializers.CharField(required=True)
class Meta(object):
"""Meta info"""
model = User
def validate_current_password(self, value):
user = self.context
# check old password
if self.context.password not in ('', None) and user.check_password(value):
return value
raise serializers.ValidationError(ERROR_CODE['2015'])
def create(self, validated_data):
"""
change password
"""
new_password = validated_data.pop('new_password')
current_password = validated_data.pop('current_password')
"""Check new password is different from current password"""
# Check new password is different from current password
if new_password == current_password:
raise serializers.ValidationError({"details": ERROR_CODE['2026']})
user_details = User.objects.filter(email=self.context).last()
if user_details:
user_details.set_password(new_password)
user_details.save()
return {'password':new_password}
return ''
user_details = self.context
user_details.set_password(new_password)
user_details.save()
return {'password':new_password}
class ForgotPasswordSerializer(serializers.Serializer):
"""Forget password serializer"""
email = serializers.EmailField()
email = serializers.EmailField(required=True)
def validate_email(self, value):
"""
validate email exist ot not
value: string
return none
"""
if not User.objects.get(email=value):
raise serializers.ValidationError({'details': ERROR_CODE['2004']})
return value
class AdminLoginSerializer(serializers.ModelSerializer):
"""admin login serializer"""
email = serializers.EmailField(required=True)
password = serializers.CharField(required=True)
class Meta:
"""
meta class
"""
model = User
fields = ('email', 'password')
def validate(self, attrs):
user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True
).only('id', 'first_name', 'last_name', 'email',
'username', 'is_active', 'is_superuser').first()
if not user or not user.check_password(attrs['password']):
raise serializers.ValidationError({'details': ERROR_CODE['2002']})
self.context.update({'user': user})
return attrs
def create(self, validated_data):
"""
used to return the user object after validation
"""
return self.context['user']
class SuperUserSerializer(serializers.ModelSerializer):
"""Super admin serializer"""
user_type = serializers.SerializerMethodField('get_user_type')
auth_token = serializers.SerializerMethodField('get_auth_token')
refresh_token = serializers.SerializerMethodField('get_refresh_token')
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj)
access_token = str(refresh.access_token)
return access_token
def get_refresh_token(self, obj):
refresh = RefreshToken.for_user(obj)
refresh_token = str(refresh)
return refresh_token
def get_user_type(self, obj):
"""user type"""
return SUPERUSER
return str(NUMBER['three'])
class Meta(object):
"""Meta info"""
model = User
fields = ['id', 'username', 'email', 'first_name', 'last_name', 'is_active', 'user_type']
fields = ['id', 'auth_token', 'refresh_token', 'username', 'email', 'first_name',
'last_name', 'is_active', 'user_type']
class GuardianSerializer(serializers.ModelSerializer):
@ -156,6 +216,7 @@ class GuardianSerializer(serializers.ModelSerializer):
last_name = serializers.SerializerMethodField('get_last_name')
auth_token = serializers.SerializerMethodField('get_auth_token')
refresh_token = serializers.SerializerMethodField('get_refresh_token')
sign_up = serializers.SerializerMethodField()
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj.user)
@ -169,10 +230,17 @@ class GuardianSerializer(serializers.ModelSerializer):
def get_user_type(self, obj):
"""user type"""
email_verified = UserEmailOtp.objects.filter(email=obj.user.username).last()
if email_verified and email_verified.user_type != None:
return email_verified.user_type
return '2'
if self.context.get('user_type', ''):
return self.context.get('user_type')
# remove the below code once user_type can be passed
# from everywhere from where this serializer is being called
else:
email_verified = UserEmailOtp.objects.filter(
email=obj.user.username
).last()
if email_verified and email_verified.user_type is not None:
return email_verified.user_type
return str(NUMBER['two'])
def get_auth(self, obj):
"""user email address"""
@ -186,12 +254,16 @@ class GuardianSerializer(serializers.ModelSerializer):
"""user last name"""
return obj.user.last_name
def get_sign_up(self, obj):
return True if self.context.get('sign_up', '') else False
class Meta(object):
"""Meta info"""
model = Guardian
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active',
'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name']
'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active', 'is_deleted',
'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type',
'country_name', 'sign_up']
class JuniorSerializer(serializers.ModelSerializer):
@ -202,6 +274,7 @@ class JuniorSerializer(serializers.ModelSerializer):
last_name = serializers.SerializerMethodField('get_last_name')
auth_token = serializers.SerializerMethodField('get_auth_token')
refresh_token = serializers.SerializerMethodField('get_refresh_token')
sign_up = serializers.SerializerMethodField()
def get_auth_token(self, obj):
refresh = RefreshToken.for_user(obj.auth)
@ -209,7 +282,7 @@ class JuniorSerializer(serializers.ModelSerializer):
return access_token
def get_refresh_token(self, obj):
refresh = RefreshToken.for_user(obj.user)
refresh = RefreshToken.for_user(obj.auth)
refresh_token = str(refresh)
return refresh_token
@ -217,7 +290,7 @@ class JuniorSerializer(serializers.ModelSerializer):
email_verified = UserEmailOtp.objects.filter(email=obj.auth.username).last()
if email_verified and email_verified.user_type is not None:
return email_verified.user_type
return '1'
return str(NUMBER['one'])
def get_auth(self, obj):
return obj.auth.username
@ -228,19 +301,23 @@ class JuniorSerializer(serializers.ModelSerializer):
def get_last_name(self, obj):
return obj.auth.last_name
def get_sign_up(self, obj):
return True if self.context.get('sign_up', '') else False
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active',
'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited']
'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_password_set',
'is_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited',
'is_deleted', 'sign_up']
class EmailVerificationSerializer(serializers.ModelSerializer):
"""Email verification serializer"""
class Meta(object):
"""Meta info"""
model = UserEmailOtp
fields = '__all__'
fields = ('email',)
@ -251,6 +328,7 @@ class DefaultTaskImagesSerializer(serializers.ModelSerializer):
model = DefaultTaskImages
fields = ['id', 'task_name', 'image_url']
def create(self, validated_data):
# create default task object
data = DefaultTaskImages.objects.create(**validated_data)
return data
@ -273,10 +351,11 @@ class UserDeleteSerializer(serializers.ModelSerializer):
data = validated_data.get('reason')
passwd = self.context['password']
signup_method = self.context['signup_method']
random_num = random.randint(0, 10000)
random_num = secrets.randbelow(10001)
user_tb = User.objects.filter(id=user.id).last()
user_type_datas = UserEmailOtp.objects.filter(email=user.email).last()
if user_tb and user_tb.check_password(passwd) and signup_method == '1':
# check password and sign up method
if user_tb and user_tb.check_password(passwd) and signup_method == str(NUMBER['one']):
user_type_data = user_type_datas.user_type
instance = delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num)
return instance
@ -303,19 +382,28 @@ class UpdateUserNotificationSerializer(serializers.ModelSerializer):
fields = ['push_notification', 'email_notification', 'sms_notification']
def create(self, validated_data):
instance = UserNotification.objects.filter(user=self.context).last()
if instance:
instance.push_notification = validated_data.get('push_notification',instance.push_notification)
instance.email_notification = validated_data.get('email_notification', instance.email_notification)
instance.sms_notification = validated_data.get('sms_notification', instance.sms_notification)
instance.save()
else:
instance = UserNotification.objects.create(user=self.context)
instance, _ = UserNotification.objects.update_or_create(
user=self.context,
defaults={
'push_notification': validated_data.get('push_notification'),
'email_notification': validated_data.get('email_notification'),
'sms_notification': validated_data.get('sms_notification', False),
})
return instance
class UserPhoneOtpSerializer(serializers.ModelSerializer):
"""User Phone serializers"""
class Meta(object):
"""Meta info"""
model = UserPhoneOtp
fields = '__all__'
class ForceUpdateSerializer(serializers.ModelSerializer):
""" ForceUpdate Serializer
"""
class Meta(object):
""" meta info """
model = ForceUpdate
fields = ('id', 'version', 'device_type')

View File

@ -8,7 +8,7 @@
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi {{full_name}},
Hello,
</p>
</td>
</tr>

View File

@ -15,7 +15,7 @@
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
You are receiving this email for join the ZOD bank platform. Please use <b>{{ url }} </b> link to join the platform.
You are receiving this email for joining the ZOD bank platform. Please use <b>{{ url }} </b> link to join the platform.
<br> Your credentials are:- username = <b>{{email}}</b> and password <b>{{password}}</b><br> <br>Below are the steps to complete the account and how to use this platform.
</p>

View File

@ -1,21 +1,21 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
{{subject}}
Support Mail
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi {{name}},
Hi Support Team,
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
<b>{{name}}</b> have some queries and need some support. Please support them by using their email address <b> {{sender}}</b>. <br> <br> <b>Queries are:- </b> <br> {{ message }}
<b>{{name}}</b> have some queries and need some support. Please support them by using their email address <b> {{sender}}</b>. <br> <br> <b>Queries are:- </b> <br><li> {{ message }}</li>
</p>
</td>
</tr>

View File

@ -0,0 +1,22 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
Account Activated
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi User,
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
We're pleased to inform you that your account has been successfully reactivated by our admin team. Welcome back to ZOD ! <br><br> You can now access all the features and services as before. If you have any questions or need assistance, please feel free to reach out to our support team. <br><br> Thank you for being a valued member of our community.
</p>
</td>
</tr>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
Account Deactivated
{% endblock %}
{% block plain %}
<tr>
<td style="padding: 0 27px 15px;">
<p style="margin: 0; font-size: 16px; line-height: 20px; padding: 36px 0 0; font-weight: 500; color: #1f2532;">
Hi User,
</p>
</td>
</tr>
<tr>
<td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;">
Your account has been deactivated by admin. Please reach out to the admin for assistance.
</p>
</td>
</tr>
{% endblock %}

View File

@ -1,5 +1,60 @@
"""Test cases file of account"""
"""Django import"""
"""
test cases file of account
"""
# django imports
from django.test import TestCase
from rest_framework.test import APIClient
from rest_framework import status
from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework_simplejwt.tokens import RefreshToken
class UserLoginTestCase(TestCase):
"""
test cases for login
"""
def setUp(self):
"""
set up data
:return:
"""
self.client = APIClient()
self.user_email = 'user@example.com'
self.user = User.objects.create_superuser(username=self.user_email, email=self.user_email)
self.user.set_password('user@1234')
self.user.save()
def test_admin_login_success(self):
"""
test admin login with valid credentials
:return:
"""
url = reverse('account:admin-login')
data = {
'email': self.user_email,
'password': 'user@1234',
}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('auth_token', response.data['data'])
self.assertIn('refresh_token', response.data['data'])
self.assertEqual(response.data['data']['username'], data['email'])
def test_admin_login_invalid_credentials(self):
"""
test admin login with invalid credentials
:return:
"""
url = reverse('account:admin-login')
data = {
'email': self.user_email,
'password': 'user@1235',
}
response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertNotIn('auth_token', response.data)
self.assertNotIn('refresh_token', response.data)
# Add more test cases as needed
# Create your tests here.

View File

@ -28,18 +28,17 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer
ForgotPasswordAPIView, ResetPasswordAPIView, ChangePasswordAPIView, UpdateProfileImage,
GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet,
DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet,
UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView)
UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView,
AdminLoginViewSet, ForceUpdateViewSet)
"""Router"""
router = routers.SimpleRouter()
"""API End points with router"""
router.register('user', UserLogin, basename='user')
"""super admin login"""
router.register('admin', UserLogin, basename='admin')
router.register('admin', AdminLoginViewSet, basename='admin')
"""google login end point"""
router.register('google-login', GoogleLoginViewSet, basename='admin')
router.register('send-phone-otp', SendPhoneOtp, basename='send-phone-otp')
router.register('user-phone-verification', UserPhoneVerification, basename='user-phone-verification')
"""email verification end point"""
router.register('user-email-verification', UserEmailVerification, basename='user-email-verification')
"""Resend email otp end point"""
@ -56,6 +55,8 @@ router.register('delete', DeleteUserProfileAPIViewSet, basename='delete')
router.register('user-notification', UserNotificationAPIViewSet, basename='user-notification')
"""update user account notification"""
router.register('update-user-notification', UpdateUserNotificationAPIViewSet, basename='update-user-notification')
# Force update entry API
router.register('force-update', ForceUpdateViewSet, basename='force-update')
"""Define url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),

View File

@ -1,4 +1,6 @@
"""Account utils"""
from celery import shared_task
import random
"""Import django"""
from django.conf import settings
from rest_framework import viewsets, status
@ -10,6 +12,7 @@ import string
from datetime import datetime
from calendar import timegm
from uuid import uuid4
# Import secrets module for generating random number
import secrets
from rest_framework import serializers
# Django App Import
@ -19,10 +22,11 @@ from rest_framework import serializers
# Import messages from base package"""
from junior.models import Junior
from guardian.models import Guardian
from account.models import UserDelete
from account.models import UserDelete, UserDeviceDetails
from base.messages import ERROR_CODE
from django.utils import timezone
from base.constants import NUMBER
from junior.models import JuniorPoints
# Define delete
# user account condition,
# Define delete
@ -42,7 +46,7 @@ from base.messages import ERROR_CODE
# referral code,
# Define function for generating
# alphanumeric code
# otp expiry
def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num):
"""delete user account"""
if user_type == '1' and user_type_data == '1':
@ -55,6 +59,7 @@ def delete_user_account_condition(user, user_type_data, user_type, user_tb, data
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
d_email = user_tb.email
o_mail = user.email
# update user email with dummy email
user_tb.save()
"""create object in user delete model"""
instance = UserDelete.objects.create(user=user_tb, d_email=d_email, old_email=o_mail,
@ -74,6 +79,7 @@ def delete_user_account_condition_social(user, user_type,user_tb, data, random_n
user_tb.username = str(random_num) + str('@D_') + '{}'.format(user_tb.username).lower()
dummy_email = user_tb.email
old_mail = user.email
# update user email with dummy email
user_tb.save()
"""create object in user delete model"""
instance_data = UserDelete.objects.create(user=user_tb, d_email=dummy_email, old_email=old_mail,
@ -84,23 +90,30 @@ def junior_account_update(user_tb):
"""junior account delete"""
junior_data = Junior.objects.filter(auth__email=user_tb.email).first()
if junior_data:
# Update junior account
junior_data.is_active = False
junior_data.is_verified = False
junior_data.guardian_code = '{}'
junior_data.guardian_code = None
junior_data.guardian_code_status = None
junior_data.is_deleted = True
junior_data.save()
JuniorPoints.objects.filter(junior=junior_data).delete()
def guardian_account_update(user_tb):
"""update guardian account after delete the user account"""
guardian_data = Guardian.objects.filter(user__email=user_tb.email).first()
if guardian_data:
# Update guardian account
guardian_data.is_active = False
guardian_data.is_verified = False
guardian_data.is_deleted = True
guardian_data.save()
jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
"""Disassociate relation between guardian and junior"""
for data in jun_data:
data.guardian_code.remove(guardian_data.guardian_code)
data.save()
@shared_task()
def send_otp_email(recipient_email, otp):
"""Send otp on email with template"""
from_email = settings.EMAIL_FROM_ADDRESS
@ -116,7 +129,45 @@ def send_otp_email(recipient_email, otp):
)
return otp
def send_support_email(name, sender, subject, message):
@shared_task()
def send_all_email(template_name, email, otp):
"""
Send all type of email by passing template name
template_name: string
email: string
otp: string
"""
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [email]
send_templated_mail(
template_name=template_name,
from_email=from_email,
recipient_list=recipient_list,
context={
'verification_code': otp
}
)
return otp
@shared_task
def user_device_details(user, device_id):
"""
Used to store the device id of the user
user: user object
device_id: string
return
"""
device_details, created = UserDeviceDetails.objects.get_or_create(user__id=user)
if device_details:
device_details.device_id = device_id
device_details.save()
return True
return False
def send_support_email(name, sender, message):
"""Send otp on email with template"""
to_email = [settings.EMAIL_FROM_ADDRESS]
from_email = settings.DEFAULT_ADDRESS
@ -128,17 +179,19 @@ def send_support_email(name, sender, subject, message):
context={
'name': name.title(),
'sender': sender,
'subject': subject,
'message': message
}
)
return name
def custom_response(detail, data=None, response_status=status.HTTP_200_OK):
def custom_response(detail, data=None, response_status=status.HTTP_200_OK, count=None):
"""Custom response code"""
if not data:
"""when data is none"""
data = None
return Response({"data": data, "message": detail, "status": "success", "code": response_status})
return Response({"data": data, "message": detail, "status": "success",
"code": response_status, "count": count})
def custom_error_response(detail, response_status):
@ -151,8 +204,12 @@ def custom_error_response(detail, response_status):
if not detail:
"""when details is empty"""
detail = {}
return Response({"error": detail, "status": "failed", "code": response_status})
if response_status == 406:
return Response({"error": detail, "status": "failed", "code": response_status,},
status=status.HTTP_308_PERMANENT_REDIRECT)
else:
return Response({"error": detail, "status": "failed", "code": response_status},
status=status.HTTP_400_BAD_REQUEST)
def get_user_data(attrs):
"""
@ -223,3 +280,47 @@ def generate_code(value, user_id):
return code
OTP_EXPIRY = timezone.now() + timezone.timedelta(days=1)
def get_user_full_name(user_obj):
"""
to get user's full name
"""
return f"{user_obj.first_name} {user_obj.last_name}" if user_obj.first_name or user_obj.last_name else "User"
def make_special_password(length=10):
"""
to make secured password
:param length:
:return:
"""
# Define character sets
lowercase_letters = string.ascii_lowercase
uppercase_letters = string.ascii_uppercase
digits = string.digits
special_characters = '@#$%&*?'
# Combine character sets
alphabets = lowercase_letters + uppercase_letters
# Create a password with random characters
password = [
secrets.choice(uppercase_letters) +
secrets.choice(lowercase_letters) +
secrets.choice(digits) +
secrets.choice(special_characters) +
''.join(secrets.choice(alphabets) for _ in range(length - 4))
]
return ''.join(password)
def task_status_fun(status_value):
"""task status"""
task_status_value = ['1']
if str(status_value) == '2':
task_status_value = ['2', '4']
elif str(status_value) == '3':
task_status_value = ['3', '5', '6']
return task_status_value

View File

@ -1,9 +1,13 @@
"""Account view """
"""Django import"""
import threading
from notifications.utils import remove_fcm_token
# django imports
from rest_framework.viewsets import GenericViewSet, mixins
from datetime import datetime, timedelta
from rest_framework import viewsets, status, views
from rest_framework.decorators import action
import random
import logging
from django.utils import timezone
import jwt
@ -16,35 +20,39 @@ import google.auth.transport.requests
from rest_framework import status
import requests
from rest_framework.response import Response
from rest_framework import mixins
from django.conf import settings
"""App Import"""
# local imports
from guardian.models import Guardian
from junior.models import Junior
from junior.models import Junior, JuniorPoints
from guardian.utils import upload_image_to_alibaba
from account.models import UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification
from account.models import (UserDeviceDetails, UserPhoneOtp, UserEmailOtp, DefaultTaskImages, UserNotification,
ForceUpdate)
from django.contrib.auth.models import User
"""Account serializer"""
from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer,
ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer,
GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer,
DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer,
UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer)
UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer,
AdminLoginSerializer, ForceUpdateSerializer)
from rest_framework_simplejwt.tokens import RefreshToken
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, ZOD, JUN, GRD
from base.constants import NUMBER, ZOD, JUN, GRD, USER_TYPE_FLAG
from guardian.tasks import generate_otp
from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response,
generate_code)
generate_code, OTP_EXPIRY, user_device_details, send_all_email)
from junior.serializers import JuniorProfileSerializer
from guardian.serializers import GuardianProfileSerializer
class GoogleLoginMixin:
class GoogleLoginMixin(object):
"""google login mixin"""
@staticmethod
def google_login(self, request):
def google_login(request):
"""google login function"""
access_token = request.data.get('access_token')
user_type = request.data.get('user_type')
user_type = request.META.get('HTTP_USER_TYPE')
device_id = request.META.get('HTTP_DEVICE_ID')
if not access_token:
return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST)
@ -66,7 +74,7 @@ class GoogleLoginMixin:
user_info = response.json()
email = user_info['email']
first_name = user_info['given_name']
last_name = user_info['family_name']
last_name = user_info['family_name'] if 'family_name' in user_info and user_info['family_name'] else user_info['given_name']
profile_picture = user_info['picture']
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
@ -77,14 +85,43 @@ class GoogleLoginMixin:
if user_data.exists():
if str(user_type) == '1':
junior_query = Junior.objects.filter(auth=user_data.last()).last()
if not junior_query:
return custom_error_response(
ERROR_CODE["2071"],
response_status=status.HTTP_400_BAD_REQUEST
)
if not junior_query.is_active:
return custom_error_response(
ERROR_CODE["2075"],
response_status=status.HTTP_404_NOT_FOUND
)
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
elif str(user_type) == '2':
guardian_query = Guardian.objects.filter(user=user_data.last()).last()
if not guardian_query:
return custom_error_response(
ERROR_CODE["2070"],
response_status=status.HTTP_400_BAD_REQUEST
)
if not guardian_query.is_active:
return custom_error_response(
ERROR_CODE["2075"],
response_status=status.HTTP_404_NOT_FOUND
)
serializer = GuardianSerializer(guardian_query)
else:
return custom_error_response(
ERROR_CODE["2069"],
response_status=status.HTTP_400_BAD_REQUEST
)
device_detail, created = UserDeviceDetails.objects.get_or_create(user=user_data.last())
if device_detail:
device_detail.device_id = device_id
device_detail.save()
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
if not User.objects.filter(email__iexact=email).exists():
else:
user_obj = User.objects.create(username=email, email=email, first_name=first_name, last_name=last_name)
if str(user_type) == '1':
junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True,
@ -92,14 +129,26 @@ class GoogleLoginMixin:
junior_code=generate_code(JUN, user_obj.id),
referral_code=generate_code(ZOD, user_obj.id)
)
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
serializer = JuniorSerializer(junior_query, context={'sign_up': True})
position = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior_query, position=position)
elif str(user_type) == '2':
guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True,
image=profile_picture,signup_method='2',
guardian_code=generate_code(GRD, user_obj.id),
referral_code=generate_code(ZOD, user_obj.id)
)
serializer = GuardianSerializer(guardian_query)
serializer = GuardianSerializer(guardian_query, context={'sign_up': True})
else:
user_obj.delete()
return custom_error_response(
ERROR_CODE["2069"],
response_status=status.HTTP_400_BAD_REQUEST
)
device_detail, created = UserDeviceDetails.objects.get_or_create(user=user_obj)
if device_detail:
device_detail.device_id = device_id
device_detail.save()
# Return a JSON response with the user's email and name
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
@ -110,28 +159,57 @@ class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet):
serializer_class = GoogleLoginSerializer
def create(self, request):
"""create method"""
"""Payload
{
"access_token",
"user_type": "1"
}"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
return self.google_login(request)
class SigninWithApple(views.APIView):
"""This API is for sign in with Apple for app."""
"""This API is for sign in with Apple for app.
Payload
{
"access_token",
"user_type": "1"
}"""
def post(self, request):
token = request.data.get("access_token")
user_type = request.data.get("user_type")
user_type = request.META.get('HTTP_USER_TYPE')
device_id = request.META.get('HTTP_DEVICE_ID')
try:
decoded_data = jwt.decode(token, options={"verify_signature": False})
user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True}
if decoded_data.get("email"):
try:
user = User.objects.get(email=decoded_data.get("email"))
user = User.objects.get(email__iexact=decoded_data.get("email"))
if str(user_type) == '1':
junior_query = Junior.objects.filter(auth=user).last()
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
guardian_query = Guardian.objects.filter(user=user).last()
serializer = GuardianSerializer(guardian_query)
junior_data = Junior.objects.filter(auth=user).last()
if not junior_data:
return custom_error_response(
ERROR_CODE["2071"],
response_status=status.HTTP_400_BAD_REQUEST
)
serializer = JuniorSerializer(junior_data)
elif str(user_type) == '2':
guardian_data = Guardian.objects.filter(user=user).last()
if not guardian_data:
return custom_error_response(
ERROR_CODE["2070"],
response_status=status.HTTP_400_BAD_REQUEST
)
serializer = GuardianSerializer(guardian_data)
else:
return custom_error_response(
ERROR_CODE["2069"],
response_status=status.HTTP_400_BAD_REQUEST
)
device_detail, created = UserDeviceDetails.objects.get_or_create(user=user)
if device_detail:
device_detail.device_id = device_id
device_detail.save()
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
@ -142,13 +220,25 @@ class SigninWithApple(views.APIView):
signup_method='3',
junior_code=generate_code(JUN, user.id),
referral_code=generate_code(ZOD, user.id))
serializer = JuniorSerializer(junior_query)
if str(user_type) == '2':
serializer = JuniorSerializer(junior_query, context={'sign_up': True})
position = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior_query, position=position)
elif str(user_type) == '2':
guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True,
signup_method='3',
guardian_code=generate_code(GRD, user.id),
referral_code=generate_code(ZOD, user.id))
serializer = GuardianSerializer(guardian_query)
serializer = GuardianSerializer(guardian_query, context={'sign_up': True})
else:
user.delete()
return custom_error_response(
ERROR_CODE["2069"],
response_status=status.HTTP_400_BAD_REQUEST
)
device_detail, created = UserDeviceDetails.objects.get_or_create(user=user)
if device_detail:
device_detail.device_id = device_id
device_detail.save()
return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK)
except Exception as e:
@ -184,18 +274,42 @@ class UpdateProfileImage(views.APIView):
return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST)
class ChangePasswordAPIView(views.APIView):
"""change password"""
"""
change password"
"""
serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = ChangePasswordSerializer(context=request.user, data=request.data)
"""
POST request to change current login user password
Payload
{ "current_password":"Demo@123",
"new_password":"Demo@123"
}
"""
serializer = ChangePasswordSerializer(
context=request.user,
data=request.data
)
if serializer.is_valid():
serializer.save()
return custom_response(SUCCESS_CODE['3007'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
return custom_response(
SUCCESS_CODE['3007'],
response_status=status.HTTP_200_OK
)
return custom_error_response(
serializer.errors,
response_status=status.HTTP_400_BAD_REQUEST
)
class ResetPasswordAPIView(views.APIView):
"""Reset password"""
"""Reset password
Payload
{
"verification_code":"373770",
"password":"Demo@1323"
}"""
def post(self, request):
serializer = ResetPasswordSerializer(data=request.data)
if serializer.is_valid():
@ -204,44 +318,46 @@ class ResetPasswordAPIView(views.APIView):
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class ForgotPasswordAPIView(views.APIView):
"""Forgot password"""
"""
Forgot password
"""
serializer_class = ForgotPasswordSerializer
def post(self, request):
serializer = ForgotPasswordSerializer(data=request.data)
if serializer.is_valid():
email = serializer.validated_data['email']
try:
User.objects.get(email=email)
except User.DoesNotExist:
return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND)
verification_code = ''.join([str(random.randrange(9)) for _ in range(6)])
# Send the verification code to the user's email
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [email]
send_templated_mail(
template_name='email_reset_verification.email',
from_email=from_email,
recipient_list=recipient_list,
context={
'verification_code': verification_code
}
)
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.get_or_create(email=email)
if created:
user_data.expired_at = expiry
user_data.save()
if user_data:
user_data.otp = verification_code
user_data.expired_at = expiry
user_data.save()
return custom_response(SUCCESS_CODE['3015'],
response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
"""
Payload
{
"email": "abc@yopmail.com"
}
"""
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
email = serializer.validated_data['email']
# generate otp
verification_code = generate_otp()
# Send the verification code to the user's email
send_all_email.delay(
'email_reset_verification.email', email, verification_code
)
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.get_or_create(
email=email
)
if created:
user_data.expired_at = expiry
user_data.save()
if user_data:
user_data.otp = verification_code
user_data.expired_at = expiry
user_data.save()
return custom_response(
SUCCESS_CODE['3015'],
response_status=status.HTTP_200_OK
)
class SendPhoneOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
queryset = UserPhoneOtp.objects.all()
serializer_class = UserPhoneOtpSerializer
def create(self, request, *args, **kwargs):
otp = generate_otp()
@ -258,7 +374,6 @@ class SendPhoneOtp(viewsets.ModelViewSet):
class UserPhoneVerification(viewsets.ModelViewSet):
"""Send otp on phone"""
queryset = UserPhoneOtp.objects.all()
serializer_class = UserPhoneOtpSerializer
def list(self, request, *args, **kwargs):
try:
@ -274,25 +389,50 @@ class UserPhoneVerification(viewsets.ModelViewSet):
return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
class UserLogin(viewsets.ViewSet):
"""User login"""
@action(methods=['post'], detail=False)
def login(self, request):
username = request.data.get('username')
password = request.data.get('password')
user_type = request.META.get('HTTP_USER_TYPE')
device_id = request.META.get('HTTP_DEVICE_ID')
user = authenticate(request, username=username, password=password)
try:
if user is not None:
login(request, user)
guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last()
if guardian_data:
serializer = GuardianSerializer(guardian_data).data
junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last()
if junior_data:
serializer = JuniorSerializer(junior_data).data
if str(user_type) == USER_TYPE_FLAG["TWO"]:
guardian_data = Guardian.objects.filter(user__username=username).last()
if guardian_data:
if guardian_data.is_verified:
serializer = GuardianSerializer(
guardian_data, context={'user_type': user_type}
).data
else:
return custom_error_response(
ERROR_CODE["2070"],
response_status=status.HTTP_401_UNAUTHORIZED
)
elif str(user_type) == USER_TYPE_FLAG["FIRST"]:
junior_data = Junior.objects.filter(auth__username=username).last()
if junior_data:
if junior_data.is_verified:
serializer = JuniorSerializer(
junior_data, context={'user_type': user_type}
).data
else:
return custom_error_response(
ERROR_CODE["2071"],
response_status=status.HTTP_401_UNAUTHORIZED
)
else:
return custom_error_response(
ERROR_CODE["2069"],
response_status=status.HTTP_401_UNAUTHORIZED
)
# storing device id in using celery task so the time would be reduced
# user_device_details.delay(user.id, device_id)
device_details, created = UserDeviceDetails.objects.get_or_create(user=user)
if device_details:
device_details.device_id = device_id
@ -306,9 +446,12 @@ class UserLogin(viewsets.ViewSet):
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
refresh_token = str(refresh)
data = {"auth_token":access_token, "refresh_token":refresh_token, "is_profile_complete": False,
"user_type": email_verified.user_type,
}
data = {
"auth_token":access_token,
"refresh_token":refresh_token,
"is_profile_complete": False,
"user_type": user_type,
}
is_verified = False
if email_verified:
is_verified = email_verified.is_verified
@ -317,43 +460,73 @@ class UserLogin(viewsets.ViewSet):
email_verified.otp = otp
email_verified.save()
data.update({"email_otp":otp})
send_otp_email(username, otp)
return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified},
response_status=status.HTTP_200_OK)
send_otp_email.delay(username, otp)
return custom_response(
ERROR_CODE['2024'],
{"email_otp": otp, "is_email_verified": is_verified},
response_status=status.HTTP_200_OK
)
data.update({"is_email_verified": is_verified})
return custom_response(None, data, response_status=status.HTTP_200_OK)
return custom_response(
SUCCESS_CODE['3003'],
data,
response_status=status.HTTP_200_OK
)
@action(methods=['post'], detail=False)
def admin_login(self, request):
username = request.data.get('username')
email = request.data.get('email')
password = request.data.get('password')
user = authenticate(request, username=username, password=password)
try:
if user is not None:
login(request, user)
if user.is_superuser:
serializer = SuperUserSerializer(user)
return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_401_UNAUTHORIZED)
except Exception as e:
logging.error(e)
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
refresh_token = str(refresh)
data = {"auth_token": access_token, "refresh_token":refresh_token, "user_type": '3'}
return custom_response(None, data, response_status=status.HTTP_200_OK)
user = User.objects.filter(email__iexact=email, is_superuser=True
).only('id', 'first_name', 'last_name', 'email',
'username', 'is_active', 'is_superuser').first()
if not user or not user.check_password(password):
return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST)
serializer = SuperUserSerializer(user)
return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK)
class AdminLoginViewSet(viewsets.GenericViewSet):
"""
admin login api
"""
serializer_class = AdminLoginSerializer
@action(methods=['post'], url_name='login', url_path='login', detail=False)
def admin_login(self, request, *args, **kwargs):
"""
:param request:
:return:
"""
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token)
refresh_token = str(refresh)
data = {"auth_token": access_token, "refresh_token": refresh_token, "username": user.username,
"email": user.email, "first_name": user.first_name, "last_name": user.last_name,
"is_active": user.is_active, "user_type": '3', "is_superuser": user.is_superuser}
return custom_response(None, data)
class UserEmailVerification(viewsets.ModelViewSet):
"""User Email verification"""
"""User Email verification
Payload
{
"email":"ramu@yopmail.com",
"otp":"361123"
}"""
serializer_class = EmailVerificationSerializer
queryset = UserEmailOtp.objects.all()
http_method_names = ('post',)
def list(self, request, *args, **kwargs):
def create(self, request, *args, **kwargs):
try:
user_obj = User.objects.filter(username=self.request.GET.get('email')).last()
email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'),
otp=self.request.GET.get('otp')).last()
user_obj = User.objects.filter(username=self.request.data.get('email')).last()
email_data = UserEmailOtp.objects.filter(email=self.request.data.get('email'),
otp=self.request.data.get('otp')).last()
if email_data:
input_datetime_str = str(email_data.expired_at)
input_format = "%Y-%m-%d %H:%M:%S.%f%z"
@ -367,12 +540,12 @@ class UserEmailVerification(viewsets.ModelViewSet):
email_data.is_verified = True
email_data.save()
if email_data.user_type == '1':
junior_data = Junior.objects.filter(auth__email=self.request.GET.get('email')).last()
junior_data = Junior.objects.filter(auth__email=self.request.data.get('email')).last()
if junior_data:
junior_data.is_verified = True
junior_data.save()
else:
guardian_data = Guardian.objects.filter(user__email=self.request.GET.get('email')).last()
guardian_data = Guardian.objects.filter(user__email=self.request.data.get('email')).last()
if guardian_data:
guardian_data.is_verified = True
guardian_data.save()
@ -390,11 +563,12 @@ class UserEmailVerification(viewsets.ModelViewSet):
class ReSendEmailOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
queryset = UserEmailOtp.objects.all()
serializer_class = EmailVerificationSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""Param
{"email":"ashok@yopmail.com"}
"""
otp = generate_otp()
if User.objects.filter(email=request.data['email']):
expiry = timezone.now() + timezone.timedelta(days=1)
@ -406,36 +580,39 @@ class ReSendEmailOtp(viewsets.ModelViewSet):
email_data.otp = otp
email_data.expired_at = expiry
email_data.save()
send_otp_email(request.data['email'], otp)
send_otp_email.delay(request.data['email'], otp)
return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST)
class ProfileAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
queryset = User.objects.all()
serializer_class = JuniorProfileSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
"""profile view"""
if str(self.request.GET.get('user_type')) == '1':
"""profile view
Params
user_type"""
user_type = request.META.get('HTTP_USER_TYPE')
if str(user_type) == '1':
junior_data = Junior.objects.filter(auth=self.request.user).last()
if junior_data:
serializer = JuniorProfileSerializer(junior_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
elif str(self.request.GET.get('user_type')) == '2':
elif str(user_type) == '2':
guardian_data = Guardian.objects.filter(user=self.request.user).last()
if guardian_data:
serializer = GuardianProfileSerializer(guardian_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(None, response_status=status.HTTP_400_BAD_REQUEST)
class UploadImageAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
queryset = DefaultTaskImages.objects.all()
"""upload task image"""
serializer_class = DefaultTaskImagesSerializer
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""profile view"""
"""upload images"""
image_data = request.data['image_url']
filename = f"default_task_images/{image_data.name}"
if image_data.size == NUMBER['zero']:
@ -451,9 +628,9 @@ class UploadImageAPIViewSet(viewsets.ModelViewSet):
class DefaultImageAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset"""
queryset = DefaultTaskImages.objects.all()
serializer_class = DefaultTaskImagesDetailsSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = DefaultTaskImages.objects.all()
@ -462,7 +639,13 @@ class DefaultImageAPIViewSet(viewsets.ModelViewSet):
class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet):
""" Delete user API view set """
""" Delete user API view set
{"user_type":1,
"signup_method":"1",
"password":"Demo@123"}
signup_method 1 for manual
2 for google login
3 for apple login"""
@action(detail=False, methods=['POST'], url_path='user-account',serializer_class=UserDeleteSerializer,
permission_classes=[IsAuthenticated])
@ -482,24 +665,28 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet):
class UserNotificationAPIViewSet(viewsets.ModelViewSet):
"""notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UserNotificationSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
"""profile view"""
queryset = self.queryset.filter(user=request.user)
"""notification view"""
queryset = UserNotification.objects.filter(user=request.user)
serializer = UserNotificationSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
"""Update notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UpdateUserNotificationSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""profile view"""
"""Payload
{"email_notification": false,
"sms_notification": false,
"push_notification": false}
"""
serializer = UpdateUserNotificationSerializer(data=request.data,
context=request.user)
if serializer.is_valid():
@ -509,17 +696,21 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
class SendSupportEmail(views.APIView):
"""support email api"""
"""support email api
payload
name
email
message
"""
permission_classes = (IsAuthenticated,)
def post(self, request):
name = request.data.get('name')
sender = request.data.get('email')
subject = request.data.get('subject')
message = request.data.get('message')
if name and sender and subject and message:
if name and sender and message:
try:
send_support_email(name, sender, subject, message)
send_support_email(name, sender, message)
return custom_response(SUCCESS_CODE['3019'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
@ -531,6 +722,10 @@ class LogoutAPIView(views.APIView):
permission_classes = (IsAuthenticated,)
def post(self, request):
remove_fcm_token(
request.auth.payload['user_id'],
request.META['HTTP_AUTHORIZATION'].split(" ")[1],
request.data.get('registration_id', ""))
logout(request)
request.session.flush()
return custom_response(SUCCESS_CODE['3020'], response_status=status.HTTP_200_OK)
@ -549,3 +744,27 @@ class AccessTokenAPIView(views.APIView):
data = {"auth_token": access_token}
return custom_response(None, data, response_status=status.HTTP_200_OK)
class ForceUpdateViewSet(GenericViewSet, mixins.CreateModelMixin):
"""FAQ view set"""
serializer_class = ForceUpdateSerializer
http_method_names = ['post']
def create(self, request, *args, **kwargs):
"""
faq create api method
:param request:
:param args: version, device type
:param kwargs:
:return: success message
"""
if ForceUpdate.objects.all().count() >= 4:
return custom_error_response(ERROR_CODE['2080'], response_status=status.HTTP_400_BAD_REQUEST)
obj_data = [ForceUpdate(**item) for item in request.data]
try:
ForceUpdate.objects.bulk_create(obj_data)
return custom_response(SUCCESS_CODE["3046"], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)

View File

@ -21,34 +21,13 @@ GRD = 'GRD'
NUMBER = {
'point_zero': 0.0, 'zero': 0, 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7,
'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': 15,
'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19,
'twenty_four': 24,
'twenty_one': 21,
'twenty_two': 22,
'twenty_five': 25,
'thirty': 30,
'thirty_five': 35,
'thirty_six': 36,
'forty': 40,
'fifty': 50,
'fifty_nine': 59,
'sixty': 60,
'seventy_five': 75,
'eighty': 80,
'ninty_five': 95,
'ninty_six': 96,
'ninety_nine': 99,
'hundred': 100,
'one_one_nine': 119,
'one_twenty': 120,
'four_zero_four': 404,
'five_hundred': 500,
'minus_one': -1,
'point_three': 0.3,
'point_seven': 0.7
'sixteen': 16, 'seventeen': 17, 'eighteen': 18, 'nineteen': 19, 'twenty': 20,
'twenty_one': 21, 'twenty_two': 22,'twenty_three': 23, 'twenty_four': 24, 'twenty_five': 25,
'thirty': 30, 'forty': 40, 'fifty': 50, 'sixty': 60, 'seventy': 70, 'eighty': 80, 'ninty': 90,
'ninety_nine': 99, 'hundred': 100, 'thirty_six_hundred': 3600
}
none = "none"
# Super Admin string constant for 'role'
SUPER_ADMIN = "Super Admin"
@ -64,37 +43,57 @@ FILE_SIZE = 5 * 1024 * 1024
# String constant for configurable date for allocation lock period
ALLOCATION_LOCK_DATE = 1
sort_dict = {
'1': 'name',
'2': '-name'
}
# guardian code status tuple
guardian_code_tuple = ('1','3')
"""user type"""
USER_TYPE = (
('1', 'junior'),
('2', 'guardian'),
('3', 'superuser')
)
DEVICE_TYPE = (
('1', 'android'),
('2', 'ios')
)
USER_TYPE_FLAG = {
"FIRST" : "1",
"TWO" : "2",
"THREE": "3"
}
"""gender"""
GENDERS = (
('1', 'Male'),
('2', 'Female')
)
"""Task status"""
# Task status"""
TASK_STATUS = (
('1', 'pending'),
('2', 'in-progress'),
('3', 'rejected'),
('4', 'requested'),
('5', 'completed')
('5', 'completed'),
('6', 'expired')
)
"""sign up method"""
# sign up method
SIGNUP_METHODS = (
('1', 'manual'),
('2', 'google'),
('3', 'apple')
)
"""relationship"""
# guardian code status
GUARDIAN_CODE_STATUS = (
('1', 'no guardian code'),
('2', 'exist guardian code'),
('3', 'request for guardian code')
)
# article status
ARTICLE_STATUS = (
('1', 'read'),
('2', 'in_progress'),
('3', 'completed')
)
# relationship
RELATIONSHIP = (
('1', 'parent'),
('2', 'legal_guardian')
@ -107,9 +106,11 @@ IN_PROGRESS = 2
REJECTED = 3
REQUESTED = 4
COMPLETED = 5
EXPIRED = 6
TASK_POINTS = 5
# duplicate name used defined in constant PROJECT_NAME
PROJECT_NAME = 'Zod Bank'
# define user type constant
GUARDIAN = 'guardian'
JUNIOR = 'junior'
SUPERUSER = 'superuser'
@ -120,3 +121,17 @@ BYTE_IMAGE_SIZE = 1024
# validate file size
MAX_FILE_SIZE = 1024 * 1024 * 5
ARTICLE_SURVEY_POINTS = 5
MAX_ARTICLE_CARD = 6
# min and max survey
MIN_ARTICLE_SURVEY = 5
MAX_ARTICLE_SURVEY = 10
# already register
Already_register_user = "duplicate key value violates unique constraint"
ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images'
DATE_FORMAT = '%Y-%m-%d'

View File

@ -23,15 +23,15 @@ ERROR_CODE_REQUIRED = {
# Error code
ERROR_CODE = {
"2000": "Email not found.",
"2000": "Invalid email address. Please enter a registered email.",
"2001": "This is your existing password. Please choose other one",
"2002": "Invalid login credentials.",
"2002": "Invalid username or password.",
"2003": "An account already exists with this email address.",
"2004": "User not found.",
"2005": "Your account has been activated.",
"2006": "Your account is not activated.",
"2007": "Your account already activated.",
"2008": "Invalid OTP.",
"2008": "The OTP entered is not correct.",
"2009": "The user provided cannot be found or the reset password token has become invalid/timed out.",
"2010": "Invalid Link.",
"2011": "Your profile has not been completed yet.",
@ -42,18 +42,21 @@ ERROR_CODE = {
"2016": "Invalid search.",
"2017": "{model} object with {pk} does not exist",
"2018": "Attached File not found",
"2019": "Either File extension or File size doesn't meet the requirements",
"2019": "Invalid Referral code",
"2020": "Enter valid mobile number",
"2021": "Already register",
"2021": "User registered",
"2022": "Invalid Guardian code",
"2023": "Invalid user",
# email not verified
"2024": "Email not verified",
"2025": "Invalid input. Expected a list of strings.",
# check old and new password
"2026": "New password should not same as old password",
"2027": "data should contain `identityToken`",
"2028": "You are not authorized person to sign up on this platform",
"2029": "Validity of otp verification is expired",
"2029": "Validity of otp verification has expired. Please request a new one.",
"2030": "Use correct user type and token",
# invalid password
"2031": "Invalid password",
"2032": "Failed to send email",
"2033": "Missing required fields",
@ -62,44 +65,125 @@ ERROR_CODE = {
"2035": "Image should not be 0 kb",
"2036": "Choose valid user",
# log in multiple device msg
"2037": "You are already log in another device"
"2037": "You are already log in another device",
"2038": "Choose valid action for task",
# card length limit
"2039": "Add at least one article card or maximum 6",
"2040": "Add at least 5 article survey or maximum 10",
# add article msg
"2041": "Article with given id doesn't exist.",
"2042": "Article Card with given id doesn't exist.",
"2043": "Article Survey with given id doesn't exist.",
"2044": "Task does not exist",
"2045": "Invalid guardian",
# past due date
"2046": "Due date must be future date",
# invalid junior id msg
"2047": "Invalid Junior ID ",
"2048": "Choose right file for image",
# task request
"2049": "This task is already requested ",
"2059": "Already exist junior",
# task status
"2060": "Task does not exist or not in pending state",
"2061": "Please insert image or check the image is valid or not.",
# email not null
"2062": "Please enter email address",
"2063": "Unauthorized access.",
"2064": "To change your password first request an OTP and get it verify then change your password.",
"2065": "Passwords do not match. Please try again.",
"2066": "Task does not exist or not in expired state",
"2067": "Action not allowed. User type missing.",
"2068": "No guardian associated with this junior",
"2069": "Invalid user type",
"2070": "You are not registered as a guardian in our system. Please try again as junior.",
"2071": "You are not registered as a junior in our system. Please try again as guardian.",
"2072": "You can not approve or reject this task because junior does not exist in the system",
"2073": "You can not approve or reject this junior because junior does not exist in the system",
"2074": "You can not complete this task because you does not exist in the system",
# deactivate account
"2075": "Your account is deactivated. Please contact with admin",
"2076": "This junior already associated with you",
"2077": "You can not add guardian",
"2078": "This junior is not associated with you",
# force update
"2079": "Please update your app version for enjoying uninterrupted services",
"2080": "Can not add App version",
"2081": "A junior can only be associated with a maximum of 3 guardian",
# guardian code not exist
"2082": "Guardian code does not exist",
"2083": "You can not start this task because guardian is not associate with you",
"2084": "You can not complete this task because guardian is not associate with you",
"2085": "You can not take action on this task because junior is not associate with you"
}
"""Success message code"""
SUCCESS_CODE = {
"3000": "ok",
# Success code for password
"3001": "Sign up successfully",
# Success code for Thank you
"3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.",
# Success code for account activation
"3003": "Log in successful",
"3003": "Log in successful.",
# Success code for password reset
"3004": "Password reset link has been sent to your email address",
"3004": "Password reset link has been sent to your email address.",
# Success code for link verified
"3005": "Your account is deleted successfully.",
"3005": "Your account has been deleted successfully.",
# Success code for password reset
"3006": "Your password has been reset successfully.",
"3006": "Password reset successful. You can now log in with your new password.",
# Success code for password update
"3007": "Your password has been changed successfully.",
# Success code for valid link
"3008": "You have a valid link.",
# Success code for logged out
"3009": "You have successfully logged out!",
"3009": "You have successfully logged out.",
# Success code for check all fields
"3010": "All fields are valid",
"3011": "Email OTP Verified successfully",
"3012": "Phone OTP Verified successfully",
"3010": "All fields are valid.",
"3011": "Email OTP has been verified successfully.",
"3012": "Phone OTP has been verified successfully.",
"3013": "Valid Guardian code",
"3014": "Password has been updated successfully.",
"3015": "Verification code sent on your email.",
"3016": "Send otp on your Email successfully",
"3015": "Verification code has been sent on your email.",
"3016": "An OTP has been sent on your email.",
"3017": "Profile image update successfully",
"3018": "Task created successfully",
"3019": "Support Email sent successfully",
"3020": "Logged out successfully.",
"3021": "Add junior successfully",
"3022": "Remove junior successfully",
"3023": "Approved junior successfully",
"3024": "Reject junior request successfully"
"3021": "Junior has been added successfully.",
"3022": "Junior has been removed successfully.",
"3023": "Junior has been approved successfully.",
"3024": "Junior request is rejected successfully.",
"3025": "Task is approved successfully.",
"3026": "Task is rejected successfully.",
"3027": "Article has been created successfully.",
"3028": "Article has been updated successfully.",
"3029": "Article has been deleted successfully.",
"3030": "Article Card has been removed successfully.",
"3031": "Article Survey has been removed successfully.",
"3032": "Task request sent successfully.",
"3033": "Valid Referral code.",
"3034": "Invite guardian successfully.",
"3035": "Task started successfully.",
"3036": "Task reassign successfully.",
"3037": "Profile has been updated successfully.",
"3038": "Status has been changed successfully.",
# notification read
"3039": "Notification read successfully.",
# start article
"3040": "Start article successfully.",
# complete article
"3041": "Article completed successfully.",
# submit assessment successfully
"3042": "Assessment completed successfully.",
# read article
"3043": "Read article card successfully.",
# remove guardian code request
"3044": "Remove guardian code request successfully.",
# create faq
"3045": "Create FAQ data.",
"3046": "Add App version successfully."
}
"""status code error"""
STATUS_CODE_ERROR = {

34
base/pagination.py Normal file
View File

@ -0,0 +1,34 @@
"""
web_admin pagination file
"""
# third party imports
from collections import OrderedDict
from rest_framework.pagination import PageNumberPagination
from account.utils import custom_response
from base.constants import NUMBER
class CustomPageNumberPagination(PageNumberPagination):
"""
custom paginator class
"""
# Set the desired page size
page_size = NUMBER['ten']
page_size_query_param = 'page_size'
# Set a maximum page size if needed
max_page_size = NUMBER['hundred']
def get_paginated_response(self, data):
"""
:param data: queryset to be paginated
:return: return a OrderedDict
"""
return custom_response(None, OrderedDict([
('count', self.page.paginator.count),
('data', data),
('current_page', self.page.number),
('total_pages', self.page.paginator.num_pages),
]))

89
base/tasks.py Normal file
View File

@ -0,0 +1,89 @@
"""
web_admin tasks file
"""
import datetime
# third party imports
from celery import shared_task
from templated_email import send_templated_mail
# django imports
from django.conf import settings
from django.db.models import F, Window
from django.db.models.functions.window import Rank
# local imports
from base.constants import PENDING, IN_PROGRESS, JUNIOR, GUARDIAN
from guardian.models import JuniorTask
from junior.models import JuniorPoints
from notifications.constants import PENDING_TASK_EXPIRING, IN_PROGRESS_TASK_EXPIRING, NOTIFICATION_DICT, TOP_JUNIOR
from notifications.models import Notification
from notifications.utils import send_notification, get_from_user_details, send_notification_multiple_user
@shared_task
def send_email(recipient_list, template, context: dict = None):
"""
used to send otp on email
:param context:
:param recipient_list: e-mail list
:param template: email template
"""
if context is None:
context = {}
from_email = settings.EMAIL_FROM_ADDRESS
send_templated_mail(
template_name=template,
from_email=from_email,
recipient_list=recipient_list,
context=context
)
return True
@shared_task()
def notify_task_expiry():
"""
task to send notification for those task which expiring soon
:return:
"""
all_pending_tasks = JuniorTask.objects.filter(
junior__is_verified=True,
task_status__in=[PENDING, IN_PROGRESS],
due_date__range=[datetime.datetime.now().date(),
(datetime.datetime.now().date() + datetime.timedelta(days=1))])
if pending_tasks := all_pending_tasks.filter(task_status=PENDING):
for task in pending_tasks:
send_notification(PENDING_TASK_EXPIRING, task.guardian.user_id, GUARDIAN, task.junior.auth_id,
{'task_id': task.id})
if in_progress_tasks := all_pending_tasks.filter(task_status=IN_PROGRESS):
for task in in_progress_tasks:
send_notification(IN_PROGRESS_TASK_EXPIRING, task.junior.auth_id, JUNIOR, task.guardian.user_id,
{'task_id': task.id})
return True
@shared_task()
def notify_top_junior():
"""
task to send notification for top leaderboard junior to all junior's
:return:
"""
junior_points_qs = JuniorPoints.objects.filter(
junior__is_verified=True
).select_related(
'junior', 'junior__auth'
).annotate(rank=Window(
expression=Rank(),
order_by=[F('total_points').desc(), 'junior__created_at'])
).order_by('-total_points', 'junior__created_at')
prev_top_position = junior_points_qs.filter(position=1).first()
new_top_position = junior_points_qs.filter(rank=1).first()
if prev_top_position != new_top_position:
send_notification_multiple_user(TOP_JUNIOR, new_top_position.junior.auth_id, JUNIOR,
{'points': new_top_position.total_points})
for junior_point in junior_points_qs:
junior_point.position = junior_point.rank
junior_point.save()
return True

BIN
celerybeat-schedule Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

39
docker-compose-prod.yml Normal file
View File

@ -0,0 +1,39 @@
version: '3'
services:
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "8000:8000"
volumes:
- ./nginx:/etc/nginx/conf.d
- .:/usr/src/app
depends_on:
- web
web:
build: .
container_name: prod_django
restart: always
command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info"
volumes:
- .:/usr/src/app
broker:
image: rabbitmq:3.7
container_name: prod_rabbitmq
volumes:
- .:/usr/src/app
ports:
- 5673:5673
worker:
build: .
image: celery
container_name: prod_celery
restart: "always"
command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E"
volumes:
- .:/usr/src/app
depends_on:
- broker

39
docker-compose-qa.yml Normal file
View File

@ -0,0 +1,39 @@
version: '3'
services:
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "8000:8000"
volumes:
- ./nginx:/etc/nginx/conf.d
- .:/usr/src/app
depends_on:
- web
web:
build: .
container_name: qa_django
restart: always
command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info"
volumes:
- .:/usr/src/app
broker:
image: rabbitmq:3.7
container_name: qa_rabbitmq
volumes:
- .:/usr/src/app
ports:
- 5673:5673
worker:
build: .
image: celery
container_name: qa_celery
restart: "always"
command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E"
volumes:
- .:/usr/src/app
depends_on:
- broker

39
docker-compose-stage.yml Normal file
View File

@ -0,0 +1,39 @@
version: '3'
services:
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "8000:8000"
volumes:
- ./nginx:/etc/nginx/conf.d
- .:/usr/src/app
depends_on:
- web
web:
build: .
container_name: stage_django
restart: always
command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info"
volumes:
- .:/usr/src/app
broker:
image: rabbitmq:3.7
container_name: stage_rabbitmq
volumes:
- .:/usr/src/app
ports:
- 5673:5673
worker:
build: .
image: celery
container_name: stage_celery
restart: "always"
command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E"
volumes:
- .:/usr/src/app
depends_on:
- broker

View File

@ -3,6 +3,7 @@ services:
nginx:
image: nginx:latest
container_name: nginx
restart: always
ports:
- "8000:8000"
volumes:
@ -13,6 +14,26 @@ services:
web:
build: .
container_name: django
restart: always
command: bash -c "pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate && gunicorn zod_bank.wsgi -b 0.0.0.0:8000 -t 300 --log-level=info"
volumes:
- .:/usr/src/app
broker:
image: rabbitmq:3.7
container_name: rabbitmq
volumes:
- .:/usr/src/app
ports:
- 5673:5673
worker:
build: .
image: celery
container_name: dev_celery
restart: "always"
command: bash -c " celery -A zod_bank.celery worker --concurrency=1 -B -l DEBUG -E"
volumes:
- .:/usr/src/app
depends_on:
- broker

112
fixtures/faq.json Normal file
View File

@ -0,0 +1,112 @@
[
{
"model": "junior.faq",
"pk": 1,
"fields": {
"question": "What is ZOD ?",
"description": "We are a future neobank for under 18. We aim to provide children with the ability to use debit cards under the watchfull eye of their parents.",
"status": 1,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 2,
"fields": {
"question": "What is financial literacy ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 3,
"fields": {
"question": "How can we win with Zod ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 4,
"fields": {
"question": "What is a budget ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 5,
"fields": {
"question": "What is the difference between stocks and bonds ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 6,
"fields": {
"question": "What is compound interest ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 7,
"fields": {
"question": "What is diversification ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 8,
"fields": {
"question": "What is a 401(k) ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 9,
"fields": {
"question": "What is an emergency fund ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
},
{
"model": "junior.faq",
"pk": 10,
"fields": {
"question": "What is a mortgage ?",
"description": "",
"status": 2,
"created_at": "2023-11-08T12:32:55.291Z",
"updated_at": "2023-11-08T12:32:55.291Z"
}
}
]

View File

@ -0,0 +1,28 @@
# Generated by Django 4.2.2 on 2023-07-18 07:17
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0015_alter_guardian_options_alter_juniortask_options'),
]
operations = [
migrations.AddField(
model_name='juniortask',
name='completed_on',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='juniortask',
name='rejected_on',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='juniortask',
name='requested_on',
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.2 on 2023-07-24 13:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0016_juniortask_completed_on_juniortask_rejected_on_and_more'),
]
operations = [
migrations.AddField(
model_name='juniortask',
name='is_invited',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='juniortask',
name='is_password_set',
field=models.BooleanField(default=True),
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 4.2.2 on 2023-07-24 13:23
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('guardian', '0017_juniortask_is_invited_juniortask_is_password_set'),
]
operations = [
migrations.RemoveField(
model_name='juniortask',
name='is_invited',
),
migrations.RemoveField(
model_name='juniortask',
name='is_password_set',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.2 on 2023-07-24 13:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0018_remove_juniortask_is_invited_and_more'),
]
operations = [
migrations.AddField(
model_name='guardian',
name='is_invited',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='guardian',
name='is_password_set',
field=models.BooleanField(default=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-25 07:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0019_guardian_is_invited_guardian_is_password_set'),
]
operations = [
migrations.AlterField(
model_name='juniortask',
name='task_status',
field=models.CharField(choices=[('1', 'pending'), ('2', 'in-progress'), ('3', 'rejected'), ('4', 'requested'), ('5', 'completed'), ('6', 'expired')], default=1, max_length=15),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-08-17 12:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0020_alter_juniortask_task_status'),
]
operations = [
migrations.AddField(
model_name='guardian',
name='is_deleted',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-09-08 10:41
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('guardian', '0021_guardian_is_deleted'),
]
operations = [
migrations.AlterField(
model_name='juniortask',
name='task_description',
field=models.CharField(blank=True, max_length=500, null=True),
),
]

View File

@ -5,9 +5,36 @@ from django.contrib.auth import get_user_model
"""Import Django app"""
from base.constants import GENDERS, TASK_STATUS, PENDING, TASK_POINTS, SIGNUP_METHODS
"""import Junior model"""
from junior.models import Junior
import junior.models
"""Add user model"""
User = get_user_model()
# Create your models here.
# Define junior model with
# various fields like
# phone, country code,
# country name,
# gender,
# date of birth,
# profile image,
# signup method,
# guardian code,
# referral code,
# referral code that used by the guardian
# is invited guardian
# profile is active or not
# profile is complete or not
# passcode
# guardian is verified or not
"""Define junior task model"""
# define name of the Task
# task description
# points of the task
# default image of the task
# image uploaded by junior
# task status
# task approved or not
# Create your models here.
class Guardian(models.Model):
@ -26,6 +53,12 @@ class Guardian(models.Model):
gender = models.CharField(choices=GENDERS, max_length=15, null=True, blank=True, default=None)
"""date of birth of the guardian"""
dob = models.DateField(max_length=15, null=True, blank=True, default=None)
# invited junior"""
is_invited = models.BooleanField(default=False)
# Profile activity"""
is_password_set = models.BooleanField(default=True)
# guardian profile deleted or not"""
is_deleted = models.BooleanField(default=False)
"""Profile activity"""
is_active = models.BooleanField(default=True)
"""guardian is verified or not"""
@ -64,7 +97,7 @@ class JuniorTask(models.Model):
"""task details"""
task_name = models.CharField(max_length=100)
"""task description"""
task_description = models.CharField(max_length=500)
task_description = models.CharField(max_length=500, null=True, blank=True)
"""points of the task"""
points = models.IntegerField(default=TASK_POINTS)
"""last date of the task"""
@ -74,13 +107,19 @@ class JuniorTask(models.Model):
"""image that is uploaded by junior"""
image = models.URLField(null=True, blank=True, default=None)
"""associated junior with the task"""
junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior', verbose_name='Junior')
junior = models.ForeignKey('junior.Junior', on_delete=models.CASCADE, related_name='junior', verbose_name='Junior')
"""task status"""
task_status = models.CharField(choices=TASK_STATUS, max_length=15, default=PENDING)
"""task is active or not"""
is_active = models.BooleanField(default=True)
"""Task is approved or not"""
is_approved = models.BooleanField(default=False)
"""request task on particular date"""
requested_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
"""reject task on particular date"""
rejected_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
"""complete task on particular date"""
completed_on = models.DateTimeField(auto_now_add=False, null=True, blank=True)
"""Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

View File

@ -1,23 +1,95 @@
"""Serializer of Guardian"""
"""Third party Django app"""
# third party imports
import logging
import random
from django.contrib.auth import password_validation
from rest_framework import serializers
# Import Refresh token of jwt
from rest_framework_simplejwt.tokens import RefreshToken
from django.db import transaction
from django.contrib.auth.models import User
"""Import Django app"""
from datetime import datetime, time
import pytz
from django.utils import timezone
# Import guardian's model,
# Import junior's model,
# Import account's model,
# Import constant from
# base package,
# Import messages from
# base package,
# Import some functions
# local imports
from .models import Guardian, JuniorTask
from account.models import UserProfile, UserEmailOtp, UserNotification
from account.utils import generate_code
from account.serializers import JuniorSerializer
from junior.serializers import JuniorDetailSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, JUN, ZOD, GRD
from junior.models import Junior, JuniorPoints
from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user, GUARDIAN
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
from .utils import real_time, convert_timedelta_into_datetime, update_referral_points
# notification's constant
from notifications.constants import TASK_APPROVED, TASK_REJECTED, TASK_ASSIGNED
# send notification function
from notifications.utils import send_notification
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
# In this serializer file
# define user serializer,
# define password validation
# create guardian serializer,
# task serializer,
# guardian serializer,
# task details serializer,
# top junior serializer,
# guardian profile serializer,
# approve junior serializer,
# approve task serializer,
from rest_framework import serializers
class PasswordValidator:
"""Password validation"""
def __init__(self, min_length=8, max_length=None, require_uppercase=True, require_numbers=True):
self.min_length = min_length
self.max_length = max_length
self.require_uppercase = require_uppercase
self.require_numbers = require_numbers
def __call__(self, value):
self.enforce_password_policy(value)
def enforce_password_policy(self, password):
# add validation for password
special_characters = "!@#$%^&*()_-+=<>?/[]{}|"
if len(password) < self.min_length:
raise serializers.ValidationError(
_("Password must be at least %(min_length)d characters long.") % {'min_length': self.min_length}
)
if self.max_length is not None and len(password) > self.max_length:
# must be 8 character
raise serializers.ValidationError(
_("Password must be at most %(max_length)d characters long.") % {'max_length': self.max_length}
)
if self.require_uppercase and not any(char.isupper() for char in password):
# must contain upper case letter
raise serializers.ValidationError(_("Password must contain at least one uppercase letter."))
if self.require_numbers and not any(char.isdigit() for char in password):
# must contain digit
raise serializers.ValidationError(_("Password must contain at least one digit."))
if self.require_numbers and not any(char in special_characters for char in password):
# must contain special character
raise serializers.ValidationError(_("Password must contain at least one special character."))
class UserSerializer(serializers.ModelSerializer):
"""User serializer"""
auth_token = serializers.SerializerMethodField('get_auth_token')
password = serializers.CharField(write_only=True, validators=[PasswordValidator()])
class Meta(object):
"""Meta info"""
@ -37,11 +109,15 @@ class UserSerializer(serializers.ModelSerializer):
try:
"""Create user profile"""
user = User.objects.create_user(username=email, email=email, password=password)
UserNotification.objects.create(user=user)
if user_type == '1':
Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id),
referral_code=generate_code(ZOD, user.id))
if user_type == '2':
UserNotification.objects.get_or_create(user=user)
if user_type == str(NUMBER['one']):
# create junior profile
junior = Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id),
referral_code=generate_code(ZOD, user.id))
position = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior, position=position)
if user_type == str(NUMBER['two']):
# create guardian profile
Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id),
referral_code=generate_code(ZOD, user.id))
return user
@ -52,10 +128,11 @@ class UserSerializer(serializers.ModelSerializer):
otp_verified = False
if otp and otp.is_verified:
otp_verified = True
raise serializers.ValidationError({"details":ERROR_CODE['2021'], "otp_verified":bool(otp_verified),
raise serializers.ValidationError({"details": ERROR_CODE['2021'], "otp_verified":bool(otp_verified),
"code": 400, "status":"failed",
})
# update guardian profile
class CreateGuardianSerializer(serializers.ModelSerializer):
"""Create guardian serializer"""
"""Basic info"""
@ -65,9 +142,12 @@ class CreateGuardianSerializer(serializers.ModelSerializer):
"""Contact details"""
phone = serializers.CharField(max_length=20, required=False)
country_code = serializers.IntegerField(required=False)
# basic info
family_name = serializers.CharField(max_length=100, required=False)
dob = serializers.DateField(required=False)
# code info
referral_code = serializers.CharField(max_length=100, required=False)
# image info
image = serializers.URLField(required=False)
class Meta(object):
@ -111,7 +191,6 @@ class CreateGuardianSerializer(serializers.ModelSerializer):
guardian.country_code = validated_data.get('country_code', guardian.country_code)
guardian.passcode = validated_data.get('passcode', guardian.passcode)
guardian.country_name = validated_data.get('country_name', guardian.country_name)
guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used)
guardian.image = validated_data.get('image', guardian.image)
"""Complete profile of the junior if below all data are filled"""
complete_profile_field = all([guardian.gender, guardian.country_name,
@ -120,6 +199,9 @@ class CreateGuardianSerializer(serializers.ModelSerializer):
guardian.is_complete_profile = False
if complete_profile_field:
guardian.is_complete_profile = True
referral_code_used = validated_data.get('referral_code_used')
update_referral_points(guardian.referral_code, referral_code_used)
guardian.referral_code_used = validated_data.get('referral_code_used', guardian.referral_code_used)
guardian.save()
return guardian
@ -136,13 +218,33 @@ class TaskSerializer(serializers.ModelSerializer):
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'task_name','task_description','points', 'due_date', 'junior', 'default_image']
fields = ['id', 'task_name','task_description','points', 'due_date','default_image']
def validate_due_date(self, value):
"""validation on due date"""
if value < datetime.today().date():
raise serializers.ValidationError({"details": ERROR_CODE['2046'],
"code": 400, "status": "failed",
})
return value
def create(self, validated_data):
"""create default task image data"""
validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last()
guardian = self.context['guardian']
# update image of the task
images = self.context['image']
validated_data['default_image'] = images
instance = JuniorTask.objects.create(**validated_data)
junior_data = self.context['junior_data']
tasks_created = []
for junior in junior_data:
# create task
task_data = validated_data.copy()
task_data['guardian'] = guardian
task_data['default_image'] = images
task_data['junior'] = junior
instance = JuniorTask.objects.create(**task_data)
tasks_created.append(instance)
send_notification.delay(TASK_ASSIGNED, guardian.user_id, GUARDIAN,
junior.auth_id, {'task_id': instance.id})
return instance
class GuardianDetailSerializer(serializers.ModelSerializer):
@ -167,35 +269,92 @@ class GuardianDetailSerializer(serializers.ModelSerializer):
"""Meta info"""
model = Guardian
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'guardian_code','is_active', 'is_complete_profile', 'created_at', 'image', 'is_deleted',
'updated_at']
class TaskDetailsSerializer(serializers.ModelSerializer):
"""Task detail serializer"""
junior = JuniorDetailSerializer()
remaining_time = serializers.SerializerMethodField('get_remaining_time')
is_expired = serializers.SerializerMethodField('get_is_expired')
def get_remaining_time(self, obj):
""" remaining time to complete task"""
due_date_datetime = datetime.combine(obj.due_date, datetime.max.time())
# fetch real time
# current_datetime = real_time()
# new code
due_date_datetime = due_date_datetime.astimezone(pytz.utc)
current_datetime = timezone.now().astimezone(pytz.utc)
# Perform the subtraction
if due_date_datetime > current_datetime:
time_difference = due_date_datetime - current_datetime
time_only = convert_timedelta_into_datetime(time_difference)
return str(time_difference.days) + ' days ' + str(time_only)
return str(NUMBER['zero']) + ' days ' + '00:00:00:00000'
def get_is_expired(self, obj):
""" task expired or not"""
if obj.due_date < datetime.today().date():
return True
return False
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image',
'junior', 'task_status', 'is_active', 'created_at','updated_at']
'requested_on', 'rejected_on', 'completed_on', 'is_expired',
'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at']
class TaskDetailsjuniorSerializer(serializers.ModelSerializer):
"""Task detail serializer"""
guardian = GuardianDetailSerializer()
remaining_time = serializers.SerializerMethodField('get_remaining_time')
def get_remaining_time(self, obj):
""" remaining time to complete task"""
due_date_datetime = datetime.combine(obj.due_date, datetime.max.time())
# fetch real time
# current_datetime = real_time()
# new code
due_date_datetime = due_date_datetime.astimezone(pytz.utc)
current_datetime = timezone.now().astimezone(pytz.utc)
# Perform the subtraction
if due_date_datetime > current_datetime:
time_difference = due_date_datetime - current_datetime
time_only = convert_timedelta_into_datetime(time_difference)
return str(time_difference.days) + ' days ' + str(time_only)
return str(NUMBER['zero']) + ' days ' + '00:00:00:00000'
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'guardian', 'task_name', 'task_description', 'points', 'due_date','default_image', 'image',
'requested_on', 'rejected_on', 'completed_on',
'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at']
class TopJuniorSerializer(serializers.ModelSerializer):
"""Top junior serializer"""
junior = JuniorDetailSerializer()
position = serializers.IntegerField()
position = serializers.SerializerMethodField()
class Meta(object):
"""Meta info"""
model = JuniorPoints
fields = ['id', 'junior', 'total_task_points', 'position', 'created_at', 'updated_at']
fields = ['id', 'junior', 'total_points', 'position', 'created_at', 'updated_at']
def to_representation(self, instance):
"""Convert instance to representation"""
representation = super().to_representation(instance)
representation['position'] = instance.position
return representation
@staticmethod
def get_position(obj):
""" get position/rank """
return obj.rank
class GuardianProfileSerializer(serializers.ModelSerializer):
"""junior serializer"""
@ -227,6 +386,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer):
"""total filled fields count"""
total_field_list = [obj.user.first_name, obj.country_name,
obj.gender, obj.dob, obj.image]
# count total complete field
total_complete_field = [data for data in total_field_list if data != '' and data is not None]
return len(total_complete_field)
@ -240,7 +400,8 @@ class GuardianProfileSerializer(serializers.ModelSerializer):
fields = ['id', 'email', 'first_name', 'last_name', 'country_name','country_code', 'phone', 'gender', 'dob',
'guardian_code', 'notification_count', 'total_count', 'complete_field_count', 'referral_code',
'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method',
'updated_at', 'passcode']
'updated_at', 'passcode','is_deleted']
class ApproveJuniorSerializer(serializers.ModelSerializer):
"""approve junior serializer"""
@ -252,8 +413,117 @@ class ApproveJuniorSerializer(serializers.ModelSerializer):
def create(self, validated_data):
"""update guardian code"""
instance = self.context['junior']
instance.guardian_code = [self.context['guardian_code']]
guardian_code = self.context['guardian_code']
index = instance.guardian_code.index(guardian_code)
instance.guardian_code_status[index] = str(NUMBER['two'])
instance.save()
return instance
class ApproveTaskSerializer(serializers.ModelSerializer):
"""approve task serializer"""
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'task_status', 'is_approved']
def create(self, validated_data):
"""update task status """
with transaction.atomic():
instance = self.context['task_instance']
junior = self.context['junior']
junior_details = Junior.objects.filter(id=junior).last()
junior_data, created = JuniorPoints.objects.get_or_create(junior=junior_details)
if self.context['action'] == str(NUMBER['one']):
# approve the task
instance.task_status = str(NUMBER['five'])
instance.is_approved = True
# update total task point
junior_data.total_points = junior_data.total_points + instance.points
# update complete time of task
# instance.completed_on = real_time()
instance.completed_on = timezone.now().astimezone(pytz.utc)
send_notification.delay(TASK_APPROVED, instance.guardian.user_id, GUARDIAN,
junior_details.auth_id, {'task_id': instance.id})
else:
# reject the task
instance.task_status = str(NUMBER['three'])
instance.is_approved = False
# update reject time of task
# instance.rejected_on = real_time()
instance.rejected_on = timezone.now().astimezone(pytz.utc)
send_notification.delay(TASK_REJECTED, instance.guardian.user_id, GUARDIAN,
junior_details.auth_id, {'task_id': instance.id})
instance.save()
junior_data.save()
return junior_details
class GuardianDetailListSerializer(serializers.ModelSerializer):
"""Guardian serializer"""
first_name = serializers.SerializerMethodField('get_first_name')
last_name = serializers.SerializerMethodField('get_last_name')
email = serializers.SerializerMethodField('get_email')
image = serializers.SerializerMethodField('get_image')
guardian_id = serializers.SerializerMethodField('get_guardian_id')
guardian_code = serializers.SerializerMethodField('get_guardian_code')
gender = serializers.SerializerMethodField('get_gender')
phone = serializers.SerializerMethodField('get_phone')
country_name = serializers.SerializerMethodField('get_country_name')
dob = serializers.SerializerMethodField('get_dob')
guardian_code_status = serializers.SerializerMethodField('get_guardian_code_status')
# code info
class Meta(object):
"""Meta info"""
model = JuniorGuardianRelationship
fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'dob',
'guardian_code', 'gender', 'phone', 'country_name', 'created_at', 'guardian_code_status',
'updated_at']
def get_guardian_id(self,obj):
"""first name of guardian"""
return obj.guardian.id
def get_first_name(self,obj):
"""first name of guardian"""
return obj.guardian.user.first_name
def get_last_name(self,obj):
"""last name of guardian"""
return obj.guardian.user.last_name
def get_email(self,obj):
"""email of guardian"""
return obj.guardian.user.email
def get_image(self,obj):
"""guardian image"""
return obj.guardian.image
def get_guardian_code(self,obj):
""" guardian code"""
return obj.guardian.guardian_code
def get_gender(self,obj):
""" guardian gender"""
return obj.guardian.gender
def get_phone(self,obj):
"""guardian phone"""
return obj.guardian.phone
def get_country_name(self,obj):
""" guardian country name """
return obj.guardian.country_name
def get_dob(self,obj):
"""guardian dob """
return obj.guardian.dob
def get_guardian_code_status(self,obj):
"""guardian code status"""
if obj.guardian.guardian_code in obj.junior.guardian_code:
index = obj.junior.guardian_code.index(obj.guardian.guardian_code)
data = obj.junior.guardian_code_status[index]
return data

View File

@ -1,6 +1,12 @@
"""task files"""
"""Django import"""
import random
# Django import
import secrets
def generate_otp():
"""generate random otp"""
return ''.join([str(random.randrange(9)) for _ in range(6)])
"""
generate random otp
"""
digits = "0123456789"
return "".join(secrets.choice(digits) for _ in range(6))

View File

@ -1,8 +1,9 @@
""" Urls files"""
"""Django import"""
from django.urls import path, include
from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView,
SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView)
from .views import (SignupViewset, UpdateGuardianProfile, CreateTaskAPIView, TaskListAPIView,
SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView,
GuardianListAPIView)
"""Third party import"""
from rest_framework import routers
@ -24,8 +25,6 @@ router.register('sign-up', SignupViewset, basename='sign-up')
router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile')
# Create Task API"""
router.register('create-task', CreateTaskAPIView, basename='create-task')
# All Task list API"""
router.register('all-task-list', AllTaskListAPIView, basename='all-task-list')
# Task list bases on the status API"""
router.register('task-list', TaskListAPIView, basename='task-list')
# Leaderboard API"""
@ -34,6 +33,10 @@ router.register('top-junior', TopJuniorListAPIView, basename='top-junior')
router.register('filter-task', SearchTaskListAPIView, basename='filter-task')
# Approve junior API"""
router.register('approve-junior', ApproveJuniorAPIView, basename='approve-junior')
# Approve junior API"""
router.register('approve-task', ApproveTaskAPIView, basename='approve-task')
# guardian list API"""
router.register('guardian-list', GuardianListAPIView, basename='guardian-list')
# Define Url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),

View File

@ -3,8 +3,26 @@
import oss2
"""Import setting"""
from django.conf import settings
import logging
import requests
from django.core.exceptions import ObjectDoesNotExist
"""Import tempfile"""
import tempfile
# Import date time module's function
from datetime import datetime, time
# import Number constant
from base.constants import NUMBER, GUARDIAN
# Import Junior's model
from junior.models import Junior, JuniorPoints
# Import guardian's model
from .models import JuniorTask
# Import app from celery
from zod_bank.celery import app
# notification's constant
from notifications.constants import REFERRAL_POINTS
# send notification function
from notifications.utils import send_notification
# Define upload image on
# ali baba cloud
@ -13,6 +31,12 @@ import tempfile
# then check bucket name
# then upload on ali baba
# bucket and reform the image url"""
# fetch real time data without depend on system time
# convert time delta into date time object
# update junior total points
# update referral points
# if any one use referral code of the junior
# junior earn 5 points
def upload_image_to_alibaba(image, filename):
"""upload image on oss alibaba bucket"""
@ -20,13 +44,91 @@ def upload_image_to_alibaba(image, filename):
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
"""write image in temporary file"""
temp_file.write(image.read())
"""auth of bucket"""
auth = oss2.Auth(settings.ALIYUN_OSS_ACCESS_KEY_ID, settings.ALIYUN_OSS_ACCESS_KEY_SECRET)
"""fetch bucket details"""
bucket = oss2.Bucket(auth, settings.ALIYUN_OSS_ENDPOINT, settings.ALIYUN_OSS_BUCKET_NAME)
# Upload the temporary file to Alibaba OSS
bucket.put_object_from_file(filename, temp_file.name)
"""create perfect url for image"""
new_filename = filename.replace(' ', '%20')
return upload_file_to_alibaba(temp_file, filename)
def upload_base64_image_to_alibaba(image, filename):
"""
upload image on oss alibaba bucket
"""
# Save the image object to a temporary file
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
# write image in temporary file
temp_file.write(image)
return upload_file_to_alibaba(temp_file, filename)
def upload_excel_file_to_alibaba(response, filename):
"""
upload excel file on oss alibaba bucket
"""
# Save the image object to a temporary file
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
# write image in temporary file
temp_file.write(response.content)
return upload_file_to_alibaba(temp_file, filename)
def upload_file_to_alibaba(temp_file, filename):
"""auth of bucket"""
auth = oss2.Auth(settings.ALIYUN_OSS_ACCESS_KEY_ID, settings.ALIYUN_OSS_ACCESS_KEY_SECRET)
"""fetch bucket details"""
bucket = oss2.Bucket(auth, settings.ALIYUN_OSS_ENDPOINT, settings.ALIYUN_OSS_BUCKET_NAME)
# Upload the temporary file to Alibaba OSS
bucket.put_object_from_file(filename, temp_file.name)
"""create perfect url for image"""
new_filename = filename.replace(' ', '%20')
return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}"
def real_time():
"""fetch real time from world time api"""
url = time_url
response = requests.get(url)
if response.status_code == 200:
data = response.json()
time_str = data['datetime']
realtime = datetime.fromisoformat(time_str.replace('Z', '+00:00')).replace(tzinfo=None)
return realtime
else:
logging.error("Could not fetch error")
return None
def convert_timedelta_into_datetime(time_difference):
"""convert date time"""
# convert timedelta into datetime format
hours = time_difference.seconds // NUMBER['thirty_six_hundred']
minutes = (time_difference.seconds // NUMBER['sixty']) % NUMBER['sixty']
seconds = time_difference.seconds % NUMBER['sixty']
microseconds = time_difference.microseconds
# Create a new time object with the extracted time components
time_only = time(hours, minutes, seconds, microseconds)
return time_only
def update_referral_points(referral_code, referral_code_used):
"""Update benefit of referral points"""
junior_queryset = Junior.objects.filter(referral_code=referral_code_used).last()
if junior_queryset and junior_queryset.referral_code != referral_code:
# create data if junior points is not exist
junior_query, created = JuniorPoints.objects.get_or_create(junior=junior_queryset)
if junior_query:
# update junior total points and referral points
junior_query.total_points = junior_query.total_points + NUMBER['five']
junior_query.referral_points = junior_query.referral_points + NUMBER['five']
junior_query.save()
send_notification.delay(REFERRAL_POINTS, None, GUARDIAN, junior_queryset.auth_id, {})
@app.task
def update_expired_task_status(data=None):
"""
Update task of the status if due date is in past
"""
try:
task_status = [str(NUMBER['one']), str(NUMBER['two'])]
JuniorTask.objects.filter(due_date__lt=datetime.today().date(),
task_status__in=task_status).update(task_status=str(NUMBER['six']))
except ObjectDoesNotExist as e:
logging.error(str(e))

View File

@ -1,203 +1,410 @@
"""Views of Guardian"""
"""Third party Django app"""
import math
# django imports
# Import IsAuthenticated
# Import viewsets and status
# Import PageNumberPagination
# Import User
# Import timezone
from django.db.models import F, Window
from django.db.models.functions.window import Rank
from rest_framework.permissions import IsAuthenticated
from rest_framework import viewsets, status
from rest_framework.pagination import PageNumberPagination
from django.contrib.auth.models import User
from base.constants import guardian_code_tuple
from rest_framework.filters import SearchFilter
from django.utils import timezone
from PIL import Image
from datetime import datetime, timedelta
"""Import Django app"""
from base.pagination import CustomPageNumberPagination
# Import guardian's model,
# Import junior's model,
# Import account's model,
# Import constant from
# base package,
# Import messages from
# base package,
# Import some functions
# from utils file
# Import account's serializer
# Import account's task
# Import notification constant
# Import send_notification function
from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializer, TaskDetailsSerializer,
TopJuniorSerializer, ApproveJuniorSerializer)
TopJuniorSerializer, ApproveJuniorSerializer, ApproveTaskSerializer,
GuardianDetailListSerializer)
from .models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints
from junior.serializers import JuniorDetailSerializer
from account.models import UserEmailOtp, UserNotification
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
from account.models import UserEmailOtp, UserNotification, UserDeviceDetails
from .tasks import generate_otp
from account.utils import send_otp_email
from account.utils import custom_response, custom_error_response
from account.utils import custom_response, custom_error_response, send_otp_email, task_status_fun
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from base.constants import NUMBER, GUARDIAN_CODE_STATUS, GUARDIAN
from .utils import upload_image_to_alibaba
from django.db.models import Sum
from notifications.constants import REGISTRATION, TASK_ASSIGNED, ASSOCIATE_APPROVED, ASSOCIATE_REJECTED
from notifications.utils import send_notification
""" Define APIs """
# Define Signup API,
# update guardian profile,
# list of all task
# list of task according to the status of the task
# create task API
# search task by name of the task API
# top junior API,
# approve junior API
# approve task API
# Create your views here.
# create approve task API
class SignupViewset(viewsets.ModelViewSet):
"""Signup view set"""
queryset = User.objects.all()
serializer_class = UserSerializer
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""Create user profile"""
if request.data['user_type'] in ['1', '2']:
device_id = request.META.get('HTTP_DEVICE_ID')
if request.data['user_type'] in [str(NUMBER['one']), str(NUMBER['two'])]:
serializer = UserSerializer(context=request.data['user_type'], data=request.data)
if serializer.is_valid():
serializer.save()
user = serializer.save()
"""Generate otp"""
otp = generate_otp()
# expire otp after 1 day
expiry = timezone.now() + timezone.timedelta(days=1)
# create user email otp object
UserEmailOtp.objects.create(email=request.data['email'], otp=otp,
user_type=str(request.data['user_type']), expired_at=expiry)
"""Send email to the register user"""
send_otp_email(request.data['email'], otp)
return custom_response(SUCCESS_CODE['3001'], {"email_otp": otp},
send_otp_email.delay(request.data['email'], otp)
UserDeviceDetails.objects.create(user=user, device_id=device_id)
return custom_response(SUCCESS_CODE['3001'],
response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST)
class UpdateGuardianProfile(viewsets.ViewSet):
class UpdateGuardianProfile(viewsets.ModelViewSet):
"""Update guardian profile"""
queryset = Guardian.objects.all()
serializer_class = CreateGuardianSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""Create guardian profile"""
data = request.data
image = request.data.get('image')
image_url = ''
if image:
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
image_url = upload_image_to_alibaba(image, filename)
data = {"image":image_url}
serializer = CreateGuardianSerializer(context={"user":request.user,
"first_name":request.data.get('first_name'),
"last_name": request.data.get('last_name'),
"image":image_url},
data=data)
if serializer.is_valid():
"""save serializer"""
serializer.save()
return custom_response(None, serializer.data,response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
try:
data = request.data
image = request.data.get('image')
image_url = ''
if image:
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
# upload image on ali baba
image_url = upload_image_to_alibaba(image, filename)
data = {"image":image_url}
serializer = CreateGuardianSerializer(context={"user":request.user,
"first_name":request.data.get('first_name'),
"last_name": request.data.get('last_name'),
"image":image_url},
data=data)
if serializer.is_valid():
"""save serializer"""
serializer.save()
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class AllTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
serializer_class = TaskDetailsSerializer
queryset = JuniorTask.objects.all()
permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
queryset = JuniorTask.objects.filter(guardian__user=request.user)
# use TaskDetailsSerializer serializer
serializer = TaskDetailsSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class TaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
"""Task list
Params
status
search
page
junior"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,)
search_fields = ['task_name', ]
pagination_class = PageNumberPagination
queryset = JuniorTask.objects.all()
http_method_names = ('get',)
def get_queryset(self):
queryset = JuniorTask.objects.filter(guardian__user=self.request.user
).select_related('junior', 'junior__auth'
).order_by('-created_at')
queryset = self.filter_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
status_value = self.request.GET.get('status')
if str(status_value) == '0':
queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at')
else:
queryset = JuniorTask.objects.filter(guardian__user=request.user,
task_status=status_value).order_by('due_date','created_at')
paginator = self.pagination_class()
junior = self.request.GET.get('junior')
queryset = self.get_queryset()
task_status = task_status_fun(status_value)
if status_value:
queryset = queryset.filter(task_status__in=task_status)
if junior:
queryset = queryset.filter(junior=int(junior))
paginator = CustomPageNumberPagination()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
# use TaskDetailsSerializer serializer
serializer = self.serializer_class(paginated_queryset, many=True)
return paginator.get_paginated_response(serializer.data)
class CreateTaskAPIView(viewsets.ModelViewSet):
"""create task for junior"""
serializer_class = TaskSerializer
queryset = JuniorTask.objects.all()
http_method_names = ('post', )
def create(self, request, *args, **kwargs):
image = request.data['default_image']
data = request.data
if 'https' in str(image):
image_data = image
else:
filename = f"images/{image}"
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
image_url = upload_image_to_alibaba(image, filename)
image_data = image_url
data.pop('default_image')
serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data)
if serializer.is_valid():
serializer.save()
return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
"""
image should be in form data
"""
try:
image = request.data['default_image']
junior_ids = request.data['junior'].split(',')
invalid_junior_ids = [junior_id for junior_id in junior_ids if not junior_id.isnumeric()]
if invalid_junior_ids:
# At least one junior value is not an integer
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
allowed_extensions = ['.jpg', '.jpeg', '.png']
if not any(extension in str(image) for extension in allowed_extensions):
return custom_error_response(ERROR_CODE['2048'], response_status=status.HTTP_400_BAD_REQUEST)
if 'https' in str(image):
image_data = image
else:
filename = f"images/{image}"
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'],
response_status=status.HTTP_400_BAD_REQUEST)
image_data = upload_image_to_alibaba(image, filename)
request.data.pop('default_image')
guardian = Guardian.objects.filter(user=request.user).select_related('user').last()
junior_data = Junior.objects.filter(id__in=junior_ids,
guardian_code__contains=[guardian.guardian_code]
).select_related('auth')
if not junior_data:
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
for junior in junior_data:
index = junior.guardian_code.index(guardian.guardian_code)
status_index = junior.guardian_code_status[index]
if status_index == str(NUMBER['three']):
return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST)
# use TaskSerializer serializer
serializer = TaskSerializer(context={"guardian": guardian, "image": image_data,
"junior_data": junior_data}, data=request.data)
if serializer.is_valid():
# save serializer
serializer.save()
# removed send notification method and used in serializer
return custom_response(SUCCESS_CODE['3018'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class SearchTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
"""Filter task"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
queryset = JuniorTask.objects.all()
http_method_names = ('get',)
def get_queryset(self):
"""Get the queryset for the view"""
title = self.request.GET.get('title')
# fetch junior query
junior_queryset = JuniorTask.objects.filter(guardian__user=self.request.user, task_name__icontains=title)\
.order_by('due_date', 'created_at')
return junior_queryset
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
queryset = self.get_queryset()
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
"""Filter task"""
try:
queryset = self.get_queryset()
paginator = self.pagination_class()
# use pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskSerializer serializer
serializer = TaskDetailsSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class TopJuniorListAPIView(viewsets.ModelViewSet):
"""Top juniors list"""
"""Top juniors list
No Params"""
serializer_class = TopJuniorSerializer
permission_classes = [IsAuthenticated]
queryset = JuniorPoints.objects.all()
http_method_names = ('get',)
def get_serializer_context(self):
# context list
context = super().get_serializer_context()
context.update({'view': self})
return context
def get_queryset(self):
queryset = JuniorPoints.objects.filter(
junior__is_verified=True
).select_related(
'junior', 'junior__auth'
).annotate(rank=Window(
expression=Rank(),
order_by=[F('total_points').desc(), 'junior__created_at'])
).order_by('-total_points', 'junior__created_at')
return queryset
def list(self, request, *args, **kwargs):
"""Fetch junior list of those who complete their tasks"""
junior_total_points = self.get_queryset().order_by('-total_task_points')
# Update the position field for each JuniorPoints object
for index, junior in enumerate(junior_total_points):
junior.position = index + 1
junior.save()
serializer = self.get_serializer(junior_total_points, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
try:
junior_total_points = self.get_queryset()[:15]
serializer = self.get_serializer(junior_total_points, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ApproveJuniorAPIView(viewsets.ViewSet):
class ApproveJuniorAPIView(viewsets.ModelViewSet):
"""approve junior by guardian"""
serializer_class = ApproveJuniorSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Get the queryset for the view"""
guardian = Guardian.objects.filter(user__email=self.request.user).last()
junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last()
return guardian, junior_queryset
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
""" junior list"""
queryset = self.get_queryset()
if request.data['action'] == '1':
serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code,
"junior": queryset[1], "action": request.data['action']},
data=request.data)
if serializer.is_valid():
""" Use below param
{"junior_id":"75",
"action":"1"}
"""
try:
relation_obj = JuniorGuardianRelationship.objects.filter(
guardian__user__email=self.request.user,
junior__id=self.request.data.get('junior_id')
).select_related('guardian', 'junior').first()
guardian = relation_obj.guardian
# fetch junior query
junior_queryset = relation_obj.junior
if junior_queryset and (junior_queryset.is_deleted or not junior_queryset.is_active):
return custom_error_response(ERROR_CODE['2073'], response_status=status.HTTP_400_BAD_REQUEST)
# action 1 is use for approve and 2 for reject
if request.data['action'] == '1':
# use ApproveJuniorSerializer serializer
serializer = ApproveJuniorSerializer(context={"guardian_code": guardian.guardian_code,
"junior": junior_queryset,
"action": request.data['action']},
data=request.data)
if serializer.is_valid():
# save serializer
serializer.save()
send_notification.delay(ASSOCIATE_APPROVED, guardian.user_id, GUARDIAN,
junior_queryset.auth_id, {})
return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK)
else:
if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code):
junior_queryset.guardian_code.remove('-')
if junior_queryset.guardian_code_status and ('-' in junior_queryset.guardian_code_status):
junior_queryset.guardian_code_status.remove('-')
index = junior_queryset.guardian_code.index(guardian.guardian_code)
junior_queryset.guardian_code.remove(guardian.guardian_code)
junior_queryset.guardian_code_status.pop(index)
junior_queryset.save()
send_notification.delay(ASSOCIATE_REJECTED, guardian.user_id, GUARDIAN, junior_queryset.auth_id, {})
relation_obj.delete()
return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ApproveTaskAPIView(viewsets.ModelViewSet):
"""approve junior by guardian"""
serializer_class = ApproveTaskSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
""" Params
{"junior_id":"82",
"task_id":"43",
"action":"1"}
action 1 for approve
2 for reject
"""
# action 1 is use for approve and 2 for reject
try:
guardian = Guardian.objects.filter(user__email=self.request.user).last()
# task query
task_queryset = JuniorTask.objects.filter(id=self.request.data.get('task_id'),
guardian=guardian,
junior=self.request.data.get('junior_id')).last()
if task_queryset and guardian.guardian_code not in task_queryset.junior.guardian_code:
return custom_error_response(ERROR_CODE['2084'], response_status=status.HTTP_400_BAD_REQUEST)
if task_queryset and (task_queryset.junior.is_deleted or not task_queryset.junior.is_active):
return custom_error_response(ERROR_CODE['2072'], response_status=status.HTTP_400_BAD_REQUEST)
# use ApproveJuniorSerializer serializer
serializer = ApproveTaskSerializer(context={"guardian_code": guardian.guardian_code,
"task_instance": task_queryset,
"action": str(request.data['action']),
"junior": self.request.data['junior_id']},
data=request.data)
unexpected_task_status = [str(NUMBER['five']), str(NUMBER['six'])]
if (str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid()
and task_queryset and task_queryset.task_status not in unexpected_task_status):
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK)
else:
return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK)
return custom_response(SUCCESS_CODE['3025'], response_status=status.HTTP_200_OK)
elif (str(request.data['action']) == str(NUMBER['two']) and serializer.is_valid()
and task_queryset and task_queryset.task_status not in unexpected_task_status):
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
#
class GuardianListAPIView(viewsets.ModelViewSet):
"""Guardian list of assosicated junior"""
serializer_class = GuardianDetailListSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
""" Guardian list of assosicated junior
No Params"""
try:
guardian_data = JuniorGuardianRelationship.objects.filter(junior__auth__email=self.request.user)
# fetch junior object
if guardian_data:
# use GuardianDetailListSerializer serializer
serializer = GuardianDetailListSerializer(guardian_data, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_response({"status": GUARDIAN_CODE_STATUS[1][0]}, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)

View File

@ -2,8 +2,27 @@
"""Third party Django app"""
from django.contrib import admin
"""Import Django app"""
from .models import Junior, JuniorPoints
from .models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle,
JuniorArticleCard, FAQ)
# Register your models here.
admin.site.register(FAQ)
@admin.register(JuniorArticle)
class JuniorArticleAdmin(admin.ModelAdmin):
"""Junior Admin"""
list_display = ['junior', 'article', 'status', 'is_completed']
def __str__(self):
"""Return email id"""
return self.junior__auth__email
@admin.register(JuniorArticleCard)
class JuniorArticleCardAdmin(admin.ModelAdmin):
"""Junior Admin"""
list_display = ['junior', 'article', 'article_card', 'is_read']
def __str__(self):
"""Return email id"""
return self.junior__auth__email
@admin.register(Junior)
class JuniorAdmin(admin.ModelAdmin):
"""Junior Admin"""
@ -16,8 +35,18 @@ class JuniorAdmin(admin.ModelAdmin):
@admin.register(JuniorPoints)
class JuniorPointsAdmin(admin.ModelAdmin):
"""Junior Points Admin"""
list_display = ['junior', 'total_task_points', 'position']
list_display = ['junior', 'total_points', 'position']
def __str__(self):
"""Return email id"""
return self.junior.auth.email
@admin.register(JuniorGuardianRelationship)
class JuniorGuardianRelationshipAdmin(admin.ModelAdmin):
"""Junior Admin"""
list_display = ['guardian', 'junior', 'relationship']
@admin.register(JuniorArticlePoints)
class JuniorArticlePointsAdmin(admin.ModelAdmin):
"""Junior Admin"""
list_display = ['junior', 'article', 'question', 'submitted_answer', 'is_answer_correct']

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-18 09:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0013_alter_junior_options_alter_juniorpoints_options'),
]
operations = [
migrations.AddField(
model_name='junior',
name='is_password_set',
field=models.BooleanField(default=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-19 09:40
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('junior', '0014_junior_is_password_set'),
]
operations = [
migrations.RenameField(
model_name='juniorpoints',
old_name='total_task_points',
new_name='total_points',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-19 11:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0015_rename_total_task_points_juniorpoints_total_points'),
]
operations = [
migrations.AddField(
model_name='juniorpoints',
name='referral_points',
field=models.IntegerField(blank=True, default=0, null=True),
),
]

View File

@ -0,0 +1,31 @@
# Generated by Django 4.2.2 on 2023-07-25 07:44
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('guardian', '0020_alter_juniortask_task_status'),
('junior', '0016_juniorpoints_referral_points'),
]
operations = [
migrations.CreateModel(
name='JuniorGuardianRelationship',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('relationship', models.CharField(blank=True, choices=[('1', 'parent'), ('2', 'legal_guardian')], default='1', max_length=31, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('guardian', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='guardian_relation', to='guardian.guardian', verbose_name='Guardian')),
('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_relation', to='junior.junior', verbose_name='Junior')),
],
options={
'verbose_name': 'Junior Guardian Relation',
'verbose_name_plural': 'Junior Guardian Relation',
'db_table': 'junior_guardian_relation',
},
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 4.2.2 on 2023-08-02 11:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0017_juniorguardianrelationship'),
]
operations = [
migrations.RemoveField(
model_name='junior',
name='relationship',
),
migrations.AddField(
model_name='junior',
name='guardian_code_approved',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,30 @@
# Generated by Django 4.2.2 on 2023-08-07 13:29
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0004_alter_surveyoption_survey'),
('junior', '0018_remove_junior_relationship_and_more'),
]
operations = [
migrations.CreateModel(
name='JuniorArticlePoints',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('earn_points', models.IntegerField(blank=True, default=5, null=True)),
('is_attempt', models.BooleanField(default=False)),
('is_answer_correct', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_articles', to='web_admin.article')),
('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_details', to='junior.junior', verbose_name='Junior')),
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='questions', to='web_admin.articlesurvey')),
('submitted_answer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='submitted_answer', to='web_admin.surveyoption')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-08-08 05:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0019_juniorarticlepoints'),
]
operations = [
migrations.AddField(
model_name='junior',
name='guardian_code_status',
field=models.CharField(blank=True, choices=[('1', 'no guardian code'), ('2', 'exist guardian code'), ('3', 'request for guardian code')], default='1', max_length=31, null=True),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 4.2.2 on 2023-08-08 09:45
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0004_alter_surveyoption_survey'),
('junior', '0020_junior_guardian_code_status'),
]
operations = [
migrations.AlterField(
model_name='juniorarticlepoints',
name='submitted_answer',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='submitted_answer', to='web_admin.surveyoption'),
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 4.2.2 on 2023-08-09 09:34
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0004_alter_surveyoption_survey'),
('junior', '0021_alter_juniorarticlepoints_submitted_answer'),
]
operations = [
migrations.CreateModel(
name='JuniorArticle',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_completed', models.BooleanField(default=False)),
('status', models.CharField(blank=True, choices=[('1', 'read'), ('2', 'in_progress'), ('3', 'completed')], default='1', max_length=10, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_articles_details', to='web_admin.article')),
('junior', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_article', to='junior.junior', verbose_name='Junior')),
],
),
]

View File

@ -0,0 +1,27 @@
# Generated by Django 4.2.2 on 2023-08-09 10:47
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0004_alter_surveyoption_survey'),
('junior', '0022_juniorarticle'),
]
operations = [
migrations.CreateModel(
name='JuniorArticleCard',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_read', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_articles_detail', to='web_admin.article')),
('article_card', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='junior_article_card', to='web_admin.articlecard')),
('junior', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_article_card', to='junior.junior', verbose_name='Junior')),
],
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2.2 on 2023-08-10 08:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0023_juniorarticlecard'),
]
operations = [
migrations.AddField(
model_name='juniorarticle',
name='current_card_page',
field=models.IntegerField(blank=True, default=0, null=True),
),
migrations.AddField(
model_name='juniorarticle',
name='current_que_page',
field=models.IntegerField(blank=True, default=0, null=True),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.2 on 2023-08-10 14:46
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('junior', '0024_juniorarticle_current_card_page_and_more'),
]
operations = [
migrations.AlterField(
model_name='juniorarticle',
name='junior',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='juniors_article', to='junior.junior', verbose_name='Junior'),
),
]

View File

@ -0,0 +1,39 @@
# Generated by Django 4.2.2 on 2023-08-17 09:04
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0025_alter_juniorarticle_junior'),
]
operations = [
migrations.CreateModel(
name='FAQ',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question', models.IntegerField(max_length=100)),
('description', models.CharField(max_length=500)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'FAQ',
'verbose_name_plural': 'FAQ',
},
),
migrations.AlterModelOptions(
name='juniorarticle',
options={'verbose_name': 'Junior Article', 'verbose_name_plural': 'Junior Article'},
),
migrations.AlterModelOptions(
name='juniorarticlecard',
options={'verbose_name': 'Junior Article Card', 'verbose_name_plural': 'Junior Article Card'},
),
migrations.AlterModelOptions(
name='juniorarticlepoints',
options={'verbose_name': 'Junior Article Points', 'verbose_name_plural': 'Junior Article Points'},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-08-17 09:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0026_faq_alter_juniorarticle_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='faq',
name='question',
field=models.CharField(max_length=100),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-08-17 10:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0027_alter_faq_question'),
]
operations = [
migrations.AddField(
model_name='faq',
name='status',
field=models.IntegerField(blank=True, default=1, null=True),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-08-17 12:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0028_faq_status'),
]
operations = [
migrations.AddField(
model_name='junior',
name='is_deleted',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 4.2.2 on 2023-08-26 08:59
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('junior', '0029_junior_is_deleted'),
]
operations = [
migrations.RemoveField(
model_name='junior',
name='guardian_code_status',
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.2 on 2023-08-26 08:59
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('junior', '0030_remove_junior_guardian_code_status'),
]
operations = [
migrations.AddField(
model_name='junior',
name='guardian_code_status',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(blank=True, default=None, max_length=10, null=True), null=True, size=None),
),
]

View File

@ -6,7 +6,11 @@ from django.contrib.auth import get_user_model
"""Import ArrayField"""
from django.contrib.postgres.fields import ArrayField
"""Import django app"""
from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP
from base.constants import GENDERS, SIGNUP_METHODS, RELATIONSHIP, GUARDIAN_CODE_STATUS, ARTICLE_STATUS
# Import guardian's model
from guardian.models import Guardian
# Import web admin's model
from web_admin.models import SurveyOption, ArticleSurvey, Article, ArticleCard
"""Define User model"""
User = get_user_model()
# Create your models here.
@ -31,7 +35,7 @@ User = get_user_model()
"""Define junior points model"""
# points of the junior
# position of the junior
# define junior guardian relation model
class Junior(models.Model):
"""Junior model"""
auth = models.ForeignKey(User, on_delete=models.CASCADE, related_name='junior_profile', verbose_name='Email')
@ -46,9 +50,6 @@ class Junior(models.Model):
dob = models.DateField(max_length=15, null=True, blank=True, default=None)
# Image of the junior"""
image = models.URLField(null=True, blank=True, default=None)
# relationship"""
relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True,
default='1')
# Sign up method"""
signup_method = models.CharField(max_length=31, choices=SIGNUP_METHODS, default='1')
# Codes"""
@ -63,12 +64,21 @@ class Junior(models.Model):
is_invited = models.BooleanField(default=False)
# Profile activity"""
is_active = models.BooleanField(default=True)
# check password is set or not
is_password_set = models.BooleanField(default=True)
# junior profile is complete or not"""
is_complete_profile = models.BooleanField(default=False)
# junior profile deleted or not"""
is_deleted = models.BooleanField(default=False)
# passcode of the junior profile"""
passcode = models.IntegerField(null=True, blank=True, default=None)
# junior is verified or not"""
is_verified = models.BooleanField(default=False)
"""guardian code is approved or not"""
guardian_code_approved = models.BooleanField(default=False)
# # guardian code status"""
guardian_code_status = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None), null=True,
)
# Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@ -87,8 +97,10 @@ class Junior(models.Model):
class JuniorPoints(models.Model):
"""Junior model"""
junior = models.OneToOneField(Junior, on_delete=models.CASCADE, related_name='junior_points')
# Contact details"""
total_task_points = models.IntegerField(blank=True, null=True, default=0)
# Total earned points"""
total_points = models.IntegerField(blank=True, null=True, default=0)
# referral points"""
referral_points = models.IntegerField(blank=True, null=True, default=0)
# position of the junior"""
position = models.IntegerField(blank=True, null=True, default=99999)
# Profile created and updated time"""
@ -105,3 +117,128 @@ class JuniorPoints(models.Model):
def __str__(self):
"""Return email id"""
return f'{self.junior.auth}'
class JuniorGuardianRelationship(models.Model):
"""Junior Guardian relationship model"""
guardian = models.ForeignKey(Guardian, on_delete=models.CASCADE, related_name='guardian_relation',
verbose_name='Guardian')
# associated junior with the task
junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='junior_relation', verbose_name='Junior')
# relation between guardian and junior"""
relationship = models.CharField(max_length=31, choices=RELATIONSHIP, null=True, blank=True,
default='1')
"""Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
db_table = 'junior_guardian_relation'
"""verbose name of the model"""
verbose_name = 'Junior Guardian Relation'
verbose_name_plural = 'Junior Guardian Relation'
def __str__(self):
"""Return email id"""
return f'{self.guardian.user}'
class JuniorArticlePoints(models.Model):
"""
Survey Options model
"""
# associated junior with the task
junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_details', verbose_name='Junior')
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles')
question = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='questions')
submitted_answer = models.ForeignKey(SurveyOption, on_delete=models.SET_NULL, null=True,
related_name='submitted_answer')
# earn points"""
earn_points = models.IntegerField(blank=True, null=True, default=5)
is_attempt = models.BooleanField(default=False)
is_answer_correct = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
verbose_name = 'Junior Article Points'
# another name of the model"""
verbose_name_plural = 'Junior Article Points'
def __str__(self):
"""Return title"""
return f'{self.id} | {self.question}'
class JuniorArticle(models.Model):
"""
Survey Options model
"""
# associated junior with the task
junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_article',
verbose_name='Junior')
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles_details')
# article completed"""
is_completed = models.BooleanField(default=False)
status = models.CharField(max_length=10, choices=ARTICLE_STATUS, null=True, blank=True, default='1')
current_card_page = models.IntegerField(blank=True, null=True, default=0)
current_que_page = models.IntegerField(blank=True, null=True, default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
verbose_name = 'Junior Article'
# another name of the model"""
verbose_name_plural = 'Junior Article'
def __str__(self):
"""Return title"""
return f'{self.id} | {self.article}'
class JuniorArticleCard(models.Model):
"""
Survey Options model
"""
# associated junior with the task
junior = models.ForeignKey(Junior, on_delete=models.CASCADE, related_name='juniors_article_card',
verbose_name='Junior')
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='junior_articles_detail')
article_card = models.ForeignKey(ArticleCard, on_delete=models.CASCADE, related_name='junior_article_card')
# article card read"""
is_read = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
verbose_name = 'Junior Article Card'
# another name of the model"""
verbose_name_plural = 'Junior Article Card'
def __str__(self):
"""Return title"""
return f'{self.id} | {self.article}'
class FAQ(models.Model):
"""FAQ model"""
# questions"""
question = models.CharField(max_length=100)
# answer"""
description = models.CharField(max_length=500)
# status
status = models.IntegerField(default=1, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta(object):
""" Meta class """
verbose_name = 'FAQ'
# another name of the model"""
verbose_name_plural = 'FAQ'
def __str__(self):
"""Return email id"""
return f'{self.question}'

View File

@ -1,22 +1,30 @@
"""Serializer file for junior"""
"""Import Django 3rd party app"""
# third party imports
import pytz
# django imports
from rest_framework import serializers
from django.contrib.auth.models import User
from django.db import transaction
import random
from datetime import datetime
from django.utils import timezone
from rest_framework_simplejwt.tokens import RefreshToken
"""Import django app"""
from account.utils import send_otp_email, generate_code
from junior.models import Junior, JuniorPoints
# local imports
from account.utils import send_otp_email, generate_code, make_special_password
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, FAQ
from guardian.tasks import generate_otp
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD
from base.constants import (PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED,
GUARDIAN_CODE_STATUS, JUNIOR, GUARDIAN)
from guardian.models import Guardian, JuniorTask
from account.models import UserEmailOtp
from junior.utils import junior_notification_email, junior_approval_mail
from account.models import UserEmailOtp, UserNotification
from junior.utils import junior_notification_email, junior_approval_mail, get_junior_leaderboard_rank
from guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime
from notifications.utils import send_notification
from notifications.constants import (ASSOCIATE_REQUEST, ASSOCIATE_JUNIOR, TASK_ACTION,
)
from web_admin.models import ArticleCard
class ListCharField(serializers.ListField):
"""Serializer for Array field"""
@ -80,18 +88,33 @@ class CreateJuniorSerializer(serializers.ModelSerializer):
if junior:
"""update details according to the data get from request"""
junior.gender = validated_data.get('gender',junior.gender)
"""Update guardian code"""
junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code)
"""condition for guardian code"""
# Update guardian code"""
# condition for guardian code
if guardian_code:
junior.guardian_code = guardian_code
junior.dob = validated_data.get('dob',junior.dob)
if junior.guardian_code and guardian_code:
if guardian_code[0] in junior.guardian_code:
raise serializers.ValidationError({"error":ERROR_CODE['2076'],"code":"400", "status":"failed"})
if not junior.guardian_code:
junior.guardian_code = []
junior.guardian_code_status = []
junior.guardian_code.extend(guardian_code)
junior.guardian_code_status.extend(str(NUMBER['three']))
elif len(junior.guardian_code) < 3 and len(guardian_code) < 3:
junior.guardian_code.extend(guardian_code)
junior.guardian_code_status.extend(str(NUMBER['three']))
else:
raise serializers.ValidationError({"error":ERROR_CODE['2081'],"code":"400", "status":"failed"})
guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last()
if guardian_data:
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior)
send_notification.delay(ASSOCIATE_REQUEST, junior.auth_id, JUNIOR, guardian_data.user_id, {})
# junior_approval_mail.delay(user.email, user.first_name) removed as per changes
junior.dob = validated_data.get('dob', junior.dob)
junior.passcode = validated_data.get('passcode', junior.passcode)
junior.country_name = validated_data.get('country_name', junior.country_name)
"""Update country code and phone number"""
junior.phone = validated_data.get('phone', junior.phone)
junior.country_code = validated_data.get('country_code', junior.country_code)
junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used)
junior.image = validated_data.get('image', junior.image)
"""Complete profile of the junior if below all data are filled"""
complete_profile_field = all([junior.gender, junior.country_name, junior.image,
@ -99,6 +122,9 @@ class CreateJuniorSerializer(serializers.ModelSerializer):
junior.is_complete_profile = False
if complete_profile_field:
junior.is_complete_profile = True
referral_code_used = validated_data.get('referral_code_used')
update_referral_points(junior.referral_code, referral_code_used)
junior.referral_code_used = validated_data.get('referral_code_used', junior.referral_code_used)
junior.save()
return junior
@ -130,8 +156,8 @@ class JuniorDetailSerializer(serializers.ModelSerializer):
"""Meta info"""
model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'is_invited', 'referral_code','is_active', 'is_complete_profile', 'created_at',
'image', 'updated_at']
'guardian_code', 'image', 'is_invited', 'referral_code','is_active', 'is_complete_profile',
'created_at', 'image', 'is_deleted', 'updated_at']
class JuniorDetailListSerializer(serializers.ModelSerializer):
"""junior serializer"""
@ -147,6 +173,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
rejected_task = serializers.SerializerMethodField('get_rejected_task')
pending_task = serializers.SerializerMethodField('get_pending_task')
position = serializers.SerializerMethodField('get_position')
guardian_code_status = serializers.SerializerMethodField('get_guardian_code_status')
def get_auth(self, obj):
@ -163,13 +190,13 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
return data
def get_position(self, obj):
return get_junior_leaderboard_rank(obj)
def get_points(self, obj):
data = JuniorPoints.objects.filter(junior=obj).last()
if data:
return data.position
return 99999
def get_points(self, obj):
data = sum(JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).values_list('points', flat=True))
return data
return data.total_points
return NUMBER['zero']
def get_in_progress_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=IN_PROGRESS).count()
@ -191,13 +218,21 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
def get_pending_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=PENDING).count()
return data
def get_guardian_code_status(self, obj):
if self.context['guardian_code'] in obj.guardian_code:
index = obj.guardian_code.index(self.context['guardian_code'])
if obj.guardian_code_status:
data = obj.guardian_code_status[index]
return data
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'country_name', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'assigned_task','points', 'pending_task', 'in_progress_task', 'completed_task',
'requested_task', 'rejected_task', 'position', 'is_invited']
'requested_task', 'rejected_task', 'position', 'is_invited', 'guardian_code_status',
'is_deleted']
class JuniorProfileSerializer(serializers.ModelSerializer):
"""junior serializer"""
@ -240,22 +275,14 @@ class JuniorProfileSerializer(serializers.ModelSerializer):
fields = ['id', 'email', 'first_name', 'last_name', 'country_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method',
'is_invited', 'passcode']
'is_invited', 'passcode', 'guardian_code_approved', 'is_deleted']
class AddJuniorSerializer(serializers.ModelSerializer):
"""Add junior serializer"""
auth_token = serializers.SerializerMethodField('get_auth_token')
def get_auth_token(self, obj):
"""auth token"""
refresh = RefreshToken.for_user(obj)
access_token = str(refresh.access_token)
return access_token
class Meta(object):
"""Meta info"""
model = Junior
fields = ['id', 'gender','dob', 'relationship', 'auth_token', 'is_invited']
fields = ['id', 'gender', 'dob', 'is_invited']
def create(self, validated_data):
@ -263,31 +290,40 @@ class AddJuniorSerializer(serializers.ModelSerializer):
with transaction.atomic():
email = self.context['email']
guardian = self.context['user']
relationship = self.context['relationship']
profile_image = self.context['image']
full_name = self.context['first_name'] + ' ' + self.context['last_name']
guardian_data = Guardian.objects.filter(user__username=guardian).last()
user_data = User.objects.create(username=email, email=email,
first_name=self.context['first_name'],
last_name=self.context['last_name'])
password = User.objects.make_random_password()
user_data.set_password(password)
special_password = make_special_password()
user_data.set_password(special_password)
user_data.save()
junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'),
image=profile_image,
dob=validated_data.get('dob'), is_invited=True,
relationship=validated_data.get('relationship'),
guardian_code=[guardian_data.guardian_code],
junior_code=generate_code(JUN, user_data.id),
referral_code=generate_code(ZOD, user_data.id),
referral_code_used=guardian_data.referral_code)
referral_code_used=guardian_data.referral_code,
is_password_set=False, is_verified=True,
guardian_code_status=[str(NUMBER['two'])])
JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data,
relationship=relationship)
total_junior = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior_data, position=total_junior)
"""Generate otp"""
otp_value = generate_otp()
expiry_time = timezone.now() + timezone.timedelta(days=1)
UserEmailOtp.objects.create(email=email, otp=otp_value,
user_type='1', expired_at=expiry_time)
"""Send email to the register user"""
send_otp_email(email, otp_value)
user_type='1', expired_at=expiry_time, is_verified=True)
# add push notification
UserNotification.objects.get_or_create(user=user_data)
"""Notification email"""
junior_notification_email(email, full_name, email, password)
junior_approval_mail(guardian, full_name)
junior_notification_email.delay(email, full_name, email, special_password)
# push notification
send_notification.delay(ASSOCIATE_JUNIOR, None, GUARDIAN, junior_data.auth_id, {})
return junior_data
@ -300,8 +336,215 @@ class RemoveJuniorSerializer(serializers.ModelSerializer):
fields = ('id', 'is_invited')
def update(self, instance, validated_data):
if instance:
guardian_code = self.context['guardian_code']
instance.is_invited = False
instance.guardian_code = '{}'
if instance.guardian_code and ('-' in instance.guardian_code):
instance.guardian_code.remove('-')
index = instance.guardian_code.index(guardian_code)
instance.guardian_code.remove(guardian_code)
instance.guardian_code_status.pop(index)
instance.save()
return instance
class CompleteTaskSerializer(serializers.ModelSerializer):
"""User task Serializer"""
class Meta(object):
"""Meta class"""
model = JuniorTask
fields = ('id', 'image')
def update(self, instance, validated_data):
instance.image = validated_data.get('image', instance.image)
instance.requested_on = timezone.now().astimezone(pytz.utc)
instance.task_status = str(NUMBER['four'])
instance.is_approved = False
instance.save()
send_notification.delay(TASK_ACTION, instance.junior.auth_id, JUNIOR,
instance.guardian.user_id, {'task_id': instance.id})
return instance
class JuniorPointsSerializer(serializers.ModelSerializer):
"""Junior points serializer"""
junior_id = serializers.SerializerMethodField('get_junior_id')
total_points = serializers.SerializerMethodField('get_points')
in_progress_task = serializers.SerializerMethodField('get_in_progress_task')
completed_task = serializers.SerializerMethodField('get_completed_task')
requested_task = serializers.SerializerMethodField('get_requested_task')
rejected_task = serializers.SerializerMethodField('get_rejected_task')
pending_task = serializers.SerializerMethodField('get_pending_task')
expired_task = serializers.SerializerMethodField('get_expired_task')
position = serializers.SerializerMethodField('get_position')
def get_junior_id(self, obj):
"""junior id"""
return obj.junior.id
def get_position(self, obj):
return get_junior_leaderboard_rank(obj.junior)
def get_points(self, obj):
"""total points"""
points = JuniorPoints.objects.filter(junior=obj.junior).last()
if points:
return points.total_points
def get_in_progress_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=IN_PROGRESS).count()
def get_completed_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=COMPLETED).count()
def get_requested_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=REQUESTED).count()
def get_rejected_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=REJECTED).count()
def get_pending_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=PENDING).count()
def get_expired_task(self, obj):
return JuniorTask.objects.filter(junior=obj.junior, task_status=EXPIRED).count()
class Meta(object):
"""Meta info"""
model = Junior
fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task',
'requested_task', 'rejected_task', 'expired_task', 'is_deleted']
class AddGuardianSerializer(serializers.ModelSerializer):
"""Add guardian serializer"""
class Meta(object):
"""Meta info"""
model = Guardian
fields = ['id']
def create(self, validated_data):
""" invite and create guardian"""
with transaction.atomic():
email = self.context['email']
junior = self.context['user']
relationship = self.context['relationship']
full_name = self.context['first_name'] + ' ' + self.context['last_name']
junior_data = Junior.objects.filter(auth__username=junior).last()
junior_data.guardian_code_status = GUARDIAN_CODE_STATUS[2][0]
junior_data.save()
instance = User.objects.filter(username=email).last()
if instance:
guardian_data = Guardian.objects.filter(user=instance).update(is_invited=True,
referral_code=generate_code(ZOD,
instance.id),
referral_code_used=junior_data.referral_code,
is_verified=True)
UserNotification.objects.get_or_create(user=instance)
return guardian_data
else:
user = User.objects.create(username=email, email=email,
first_name=self.context['first_name'],
last_name=self.context['last_name'])
password = User.objects.make_random_password()
user.set_password(password)
user.save()
guardian_data = Guardian.objects.create(user=user, is_invited=True,
referral_code=generate_code(ZOD, user.id),
referral_code_used=junior_data.referral_code,
is_password_set=False, is_verified=True)
"""Generate otp"""
otp_value = generate_otp()
expiry_time = timezone.now() + timezone.timedelta(days=1)
UserEmailOtp.objects.create(email=email, otp=otp_value,
user_type=str(NUMBER['two']), expired_at=expiry_time,
is_verified=True)
UserNotification.objects.get_or_create(user=user)
JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data,
relationship=relationship)
"""Notification email"""
junior_notification_email(email, full_name, email, password)
# junior_approval_mail.delay(email, full_name) removed as per changes
send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth_id, JUNIOR, guardian_data.user_id, {})
return guardian_data
class StartTaskSerializer(serializers.ModelSerializer):
"""User task Serializer"""
task_duration = serializers.SerializerMethodField('get_task_duration')
def get_task_duration(self, obj):
""" remaining time to complete task"""
due_date = datetime.combine(obj.due_date, datetime.max.time())
# fetch real time
# real_datetime = real_time()
# new code
due_date = due_date.astimezone(pytz.utc)
real_datetime = timezone.now().astimezone(pytz.utc)
# Perform the subtraction
if due_date > real_datetime:
time_difference = due_date - real_datetime
time_only = convert_timedelta_into_datetime(time_difference)
return str(time_difference.days) + ' days ' + str(time_only)
return str(NUMBER['zero']) + ' days ' + '00:00:00:00000'
class Meta(object):
"""Meta class"""
model = JuniorTask
fields = ('id', 'task_duration')
def update(self, instance, validated_data):
instance.task_status = str(NUMBER['two'])
instance.save()
return instance
class ReAssignTaskSerializer(serializers.ModelSerializer):
"""User task Serializer"""
class Meta(object):
"""Meta class"""
model = JuniorTask
fields = ('id', 'due_date')
def update(self, instance, validated_data):
instance.task_status = str(NUMBER['one'])
instance.due_date = validated_data.get('due_date')
instance.is_approved = False
instance.requested_on = None
instance.save()
return instance
class RemoveGuardianCodeSerializer(serializers.ModelSerializer):
"""User task Serializer"""
class Meta(object):
"""Meta class"""
model = Junior
fields = ('id', )
def update(self, instance, validated_data):
guardian_code = self.context['guardian_code']
if guardian_code in instance.guardian_code:
if instance.guardian_code and ('-' in instance.guardian_code):
instance.guardian_code.remove('-')
if instance.guardian_code_status and ('-' in instance.guardian_code_status):
instance.guardian_code_status.remove('-')
index = instance.guardian_code.index(guardian_code)
instance.guardian_code.remove(guardian_code)
instance.guardian_code_status.pop(index)
else:
raise serializers.ValidationError({"error":ERROR_CODE['2082'],"code":"400", "status":"failed"})
instance.save()
return instance
class FAQSerializer(serializers.ModelSerializer):
"""FAQ Serializer"""
class Meta(object):
"""meta info"""
model = FAQ
fields = ('id', 'question', 'description')
class CreateArticleCardSerializer(serializers.ModelSerializer):
"""Article card Serializer"""
class Meta(object):
"""meta info"""
model = ArticleCard
fields = ('id', 'article')

View File

@ -2,7 +2,11 @@
"""Django import"""
from django.urls import path, include
from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView,
InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView)
InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView,
CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode,
InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView,
StartAssessmentAPIView, CheckAnswerAPIView, CompleteArticleAPIView, ReadArticleCardAPIView,
CreateArticleCardAPIView, RemoveGuardianCodeAPIView, FAQViewSet, CheckJuniorApiViewSet)
"""Third party import"""
from rest_framework import routers
@ -17,21 +21,48 @@ router = routers.SimpleRouter()
# junior list,
# add junior list, invited junior,
# filter-junior,
# remove junior"""
# remove junior,
# junior task list
"""API End points with router"""
router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-update')
# validate guardian code API"""
router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code')
# junior list API"""
router.register('junior-list', JuniorListAPIView, basename='junior-list')
router.register('check-junior', CheckJuniorApiViewSet, basename='check-junior')
# Add junior list API"""
router.register('add-junior', AddJuniorAPIView, basename='add-junior')
# Invited junior list API"""
router.register('invited-junior', InvitedJuniorAPIView, basename='invited-junior')
# Filter junior list API"""
router.register('filter-junior', FilterJuniorAPIView, basename='filter-junior')
# junior's task list API"""
router.register('junior-task-list', JuniorTaskListAPIView, basename='junior-task-list')
# junior's task list API"""
router.register('junior-points', JuniorPointsListAPIView, basename='junior-points')
# validate referral code API"""
router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code')
# invite guardian API"""
router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian')
# start article"""
router.register('start-article', StartArticleAPIView, basename='start-article')
# start assessment api"""
router.register('start-assessment', StartAssessmentAPIView, basename='start-assessment')
# check answer api"""
router.register('check-answer', CheckAnswerAPIView, basename='check-answer')
# start article"""
router.register('create-article-card', CreateArticleCardAPIView, basename='create-article-card')
# FAQ API
router.register('faq', FAQViewSet, basename='faq')
# Define url pattern"""
urlpatterns = [
path('api/v1/', include(router.urls)),
path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view())
path('api/v1/remove-junior/', RemoveJuniorAPIView.as_view()),
path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()),
path('api/v1/start-task/', StartTaskAPIView.as_view()),
path('api/v1/reassign-task/', ReAssignJuniorTaskAPIView.as_view()),
path('api/v1/complete-article/', CompleteArticleAPIView.as_view()),
path('api/v1/read-article-card/', ReadArticleCardAPIView.as_view()),
path('api/v1/remove-guardian-code-request/', RemoveGuardianCodeAPIView.as_view())
]

View File

@ -3,7 +3,10 @@
from django.conf import settings
"""Third party Django app"""
from templated_email import send_templated_mail
from .models import JuniorPoints
from base.constants import NUMBER
from django.db.models import F, Window
from django.db.models.functions.window import Rank
# junior notification
# email for sending email
# when guardian create junior profile
@ -12,6 +15,8 @@ from templated_email import send_templated_mail
# being part of the zod bank and access the platform
# define junior notification email
# junior approval email
from celery import shared_task
@shared_task()
def junior_notification_email(recipient_email, full_name, email, password):
"""Notification email"""
from_email = settings.EMAIL_FROM_ADDRESS
@ -30,7 +35,7 @@ def junior_notification_email(recipient_email, full_name, email, password):
}
)
return full_name
@shared_task()
def junior_approval_mail(guardian, full_name):
"""junior approval mail"""
from_email = settings.EMAIL_FROM_ADDRESS
@ -45,3 +50,33 @@ def junior_approval_mail(guardian, full_name):
}
)
return full_name
def update_positions_based_on_points():
"""Update position of the junior"""
# First, retrieve all the JuniorPoints instances ordered by total_points in descending order.
juniors_points = JuniorPoints.objects.order_by('-total_points', 'created_at')
# Now, iterate through the queryset and update the position field based on the order.
position = NUMBER['one']
for junior_point in juniors_points:
junior_point.position = position
junior_point.save()
position += NUMBER['one']
def get_junior_leaderboard_rank(junior_obj):
"""
to get junior's position/rank
:param junior_obj:
:return: junior's position/rank
"""
queryset = JuniorPoints.objects.filter(
junior__is_verified=True
).select_related('junior', 'junior__auth').annotate(rank=Window(
expression=Rank(),
order_by=[F('total_points').desc(), 'junior__created_at']
)).order_by('-total_points', 'junior__created_at')
junior = next((query for query in queryset if query.junior == junior_obj), None)
return junior.rank if junior else None

View File

@ -1,61 +1,137 @@
"""Junior view file"""
import os
from rest_framework import viewsets, status, generics,views
from rest_framework.permissions import IsAuthenticated
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from PIL import Image
from django.contrib.auth.models import User
from rest_framework.filters import SearchFilter
from django.db.models import F
import datetime
import requests
from rest_framework.viewsets import GenericViewSet, mixins
import math
from base.pagination import CustomPageNumberPagination
"""Django app import"""
from junior.models import Junior
from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\
RemoveJuniorSerializer)
from guardian.models import Guardian
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.core.management import call_command
from drf_yasg.views import get_schema_view
# Import guardian's model,
# Import junior's model,
# Import account's model,
# Import constant from
# base package,
# Import messages from
# base package,
# Import some functions
# from utils file
# Import account's serializer
# Import account's task
# import junior serializer
# Import update_positions_based_on_points
# Import upload_image_to_alibaba
# Import custom_response, custom_error_response
# Import constants
from django.db.models import Sum
from junior.models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle,
JuniorArticleCard, FAQ)
from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,
RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer,
AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer,
RemoveGuardianCodeSerializer, FAQSerializer, CreateArticleCardSerializer)
from guardian.models import Guardian, JuniorTask
from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from account.utils import custom_response, custom_error_response
from base.constants import NUMBER, ARTICLE_STATUS, none, GUARDIAN
from account.utils import custom_response, custom_error_response, task_status_fun
from guardian.utils import upload_image_to_alibaba
from .utils import update_positions_based_on_points
from notifications.utils import send_notification
from notifications.constants import REMOVE_JUNIOR, ARTICLE_REWARD_POINTS, ASSOCIATE_EXISTING_JUNIOR
from web_admin.models import Article, ArticleSurvey, SurveyOption, ArticleCard
from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleListSerializer,
StartAssessmentSerializer)
""" Define APIs """
# Define validate guardian code API,
# update junior profile,
# list of all assosicated junior
# Add junior API
# invite junior API
# search junior API
# remove junior API,
# approve junior API
# create referral code
# validation API
# invite guardian API
# by junior
# Start task
# by junior API
# Create your views here.
class UpdateJuniorProfile(viewsets.ViewSet):
class UpdateJuniorProfile(viewsets.ModelViewSet):
"""Update junior profile"""
queryset = Junior.objects.all()
serializer_class = CreateJuniorSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""Use CreateJuniorSerializer"""
request_data = request.data
image = request.data.get('image')
image_url = ''
if image:
if image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
filename = f"images/{image.name}"
image_url = upload_image_to_alibaba(image, filename)
request_data = {"image": image_url}
serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url,
"first_name": request.data.get('first_name'),
"last_name": request.data.get('last_name')
},
data=request_data)
if serializer.is_valid():
"""save serializer"""
serializer.save()
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
"""Create Junior Profile"""
try:
request_data = request.data
image = request.data.get('image')
image_url = ''
if image:
# check image size
if image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
# convert into file
filename = f"images/{image.name}"
# upload image on ali baba
image_url = upload_image_to_alibaba(image, filename)
request_data = {"image": image_url}
serializer = CreateJuniorSerializer(context={"user":request.user, "image":image_url,
"first_name": request.data.get('first_name'),
"last_name": request.data.get('last_name')
},
data=request_data)
if serializer.is_valid():
"""save serializer"""
serializer.save()
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
if e.detail:
error_detail = e.detail.get('error', None)
else:
error_detail = str(e)
return custom_error_response(error_detail, response_status=status.HTTP_400_BAD_REQUEST)
class ValidateGuardianCode(viewsets.ViewSet):
class ValidateGuardianCode(viewsets.ModelViewSet):
"""Check guardian code exist or not"""
queryset = Guardian.objects.all()
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
"""check guardian code"""
guardian_code = self.request.GET.get('guardian_code').split(',')
for code in guardian_code:
guardian_data = Guardian.objects.filter(guardian_code=code).exists()
if guardian_data:
return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST)
"""check guardian code
Params
"guardian_code"
"""
try:
guardian_code = self.request.GET.get('guardian_code').split(',')
for code in guardian_code:
# fetch guardian object
guardian_data = Guardian.objects.filter(guardian_code=code).exists()
if guardian_data:
# successfully check guardian code
return custom_response(SUCCESS_CODE['3013'], response_status=status.HTTP_200_OK)
else:
return custom_error_response(ERROR_CODE["2022"], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class JuniorListAPIView(viewsets.ModelViewSet):
"""Junior list of assosicated guardian"""
@ -63,92 +139,714 @@ class JuniorListAPIView(viewsets.ModelViewSet):
serializer_class = JuniorDetailListSerializer
queryset = Junior.objects.all()
permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,)
search_fields = ['auth__first_name', 'auth__last_name']
http_method_names = ('get',)
def get_queryset(self):
queryset = self.filter_queryset(self.queryset)
return queryset
def list(self, request, *args, **kwargs):
""" junior list"""
guardian_data = Guardian.objects.filter(user__email=request.user).last()
queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
serializer = JuniorDetailListSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
try:
# update_positions_based_on_points, function removed
guardian_data = Guardian.objects.filter(user__email=request.user).last()
# fetch junior object
if guardian_data:
queryset = self.get_queryset()
queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code))
# use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(queryset, context={"guardian_code":
guardian_data.guardian_code}, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CheckJuniorApiViewSet(viewsets.GenericViewSet):
"""
api to check whether given user exist or not
"""
serializer_class = None
permission_classes = [IsAuthenticated]
def get_queryset(self):
junior = Junior.objects.filter(auth__email=self.request.data.get('email')).first()
return junior
def create(self, request, *args, **kwargs):
"""
:param request:
:return:
"""
junior = self.get_queryset()
data = {
'junior_exist': True if junior else False
}
return custom_response(None, data)
class AddJuniorAPIView(viewsets.ModelViewSet):
"""Add Junior by guardian"""
queryset = Junior.objects.all()
serializer_class = AddJuniorSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
""" junior list"""
info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'],
'last_name': request.data['last_name']}
serializer = AddJuniorSerializer(data=request.data, context=info)
if serializer.is_valid():
serializer.save()
return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST)
""" add junior
{ "gender":"1",
"first_name":"abc",
"last_name":"xyz",
"dob":"2023-12-12",
"relationship":"2",
"email":"abc@yopmail.com"
}"""
try:
if user := User.objects.filter(username=request.data['email']).first():
data = self.associate_guardian(user)
if data == none:
return custom_error_response(ERROR_CODE['2077'], response_status=status.HTTP_400_BAD_REQUEST)
elif not data:
return custom_error_response(ERROR_CODE['2076'], response_status=status.HTTP_400_BAD_REQUEST)
elif data == "Max":
return custom_error_response(ERROR_CODE['2081'], response_status=status.HTTP_400_BAD_REQUEST)
return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK)
info_data = {'user': request.user, 'relationship': str(request.data['relationship']),
'email': request.data['email'], 'first_name': request.data['first_name'],
'last_name': request.data['last_name'], 'image':None}
profile_image = request.data.get('image')
if profile_image:
# check image size
if profile_image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
# convert into file
filename = f"images/{profile_image.name}"
# upload image on ali baba
image_url = upload_image_to_alibaba(profile_image, filename)
info_data.update({"image": image_url})
# use AddJuniorSerializer serializer
serializer = AddJuniorSerializer(data=request.data, context=info_data)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
def associate_guardian(self, user):
junior = Junior.objects.filter(auth__email=self.request.data['email']).first()
guardian = Guardian.objects.filter(user=self.request.user).first()
if junior is None:
return none
if junior.guardian_code and (guardian.guardian_code in junior.guardian_code):
return False
if junior.guardian_code and ('-' in junior.guardian_code):
junior.guardian_code.remove('-')
if not junior.guardian_code:
junior.guardian_code = [guardian.guardian_code]
elif type(junior.guardian_code) is list and len(junior.guardian_code) < 3:
junior.guardian_code.append(guardian.guardian_code)
else:
return "Max"
if junior.guardian_code_status and ('-' in junior.guardian_code_status):
junior.guardian_code_status.remove('-')
if not junior.guardian_code_status:
junior.guardian_code_status = [str(NUMBER['two'])]
else:
junior.guardian_code_status.append(str(NUMBER['two']))
junior.save()
jun_data, created = JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior)
if jun_data:
jun_data.relationship = str(self.request.data['relationship'])
jun_data.save()
send_notification.delay(ASSOCIATE_EXISTING_JUNIOR, guardian.user_id, GUARDIAN, junior.auth_id, {})
return True
class InvitedJuniorAPIView(viewsets.ModelViewSet):
"""Junior list of assosicated guardian"""
"""Invited Junior list of assosicated guardian"""
serializer_class = JuniorDetailListSerializer
queryset = Junior.objects.all()
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
http_method_names = ('get',)
def get_queryset(self):
"""Get the queryset for the view"""
guardian = Guardian.objects.filter(user__email=self.request.user).last()
junior_queryset = Junior.objects.filter(guardian_code__icontains=str(guardian.guardian_code),
is_invited=True)
is_invited=True)
return junior_queryset
def list(self, request, *args, **kwargs):
""" junior list"""
queryset = self.get_queryset()
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
""" Invited Junior list of assosicated guardian
No Params"""
try:
queryset = self.get_queryset()
paginator = self.pagination_class()
# pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class FilterJuniorAPIView(viewsets.ModelViewSet):
"""Update guardian profile"""
"""filter junior profile"""
serializer_class = JuniorDetailListSerializer
permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination
queryset = Junior.objects.all()
http_method_names = ('get',)
@swagger_auto_schema(
manual_parameters=[
# Example of a query parameter
openapi.Parameter(
'title',
openapi.IN_QUERY,
description='title of the name',
type=openapi.TYPE_STRING,
),
# Add more parameters as needed
]
)
def get_queryset(self):
"""Get the queryset for the view"""
title = self.request.GET.get('title')
guardian_data = Guardian.objects.filter(user__email=self.request.user).last()
# fetch junior query
queryset = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code),
is_invited=True, auth__first_name=title)
return queryset
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
queryset = self.get_queryset()
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
"""Filter junior"""
try:
queryset = self.get_queryset()
paginator = self.pagination_class()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class RemoveJuniorAPIView(views.APIView):
"""Eater Update API"""
"""Remove junior API
Params
id=37"""
serializer_class = RemoveJuniorSerializer
model = Junior
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
junior_id = self.request.GET.get('id')
guardian = Guardian.objects.filter(user__email=self.request.user).last()
junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code),
is_invited=True).last()
if junior_queryset:
serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True)
try:
junior_id = self.request.GET.get('id')
guardian = Guardian.objects.filter(user__email=self.request.user).last()
# fetch junior query
junior_queryset = Junior.objects.filter(id=junior_id,
guardian_code__icontains=str(guardian.guardian_code)).last()
if junior_queryset:
# use RemoveJuniorSerializer serializer
serializer = RemoveJuniorSerializer(junior_queryset, context={"guardian_code":guardian.guardian_code},
data=request.data, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
JuniorGuardianRelationship.objects.filter(guardian=guardian, junior=junior_queryset).delete()
send_notification.delay(REMOVE_JUNIOR, None, GUARDIAN, junior_queryset.auth_id, {})
return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class JuniorTaskListAPIView(viewsets.ModelViewSet):
"""Junior task list"""
serializer_class = TaskDetailsjuniorSerializer
permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,)
search_fields = ['task_name', ]
pagination_class = PageNumberPagination
http_method_names = ('get',)
def get_queryset(self):
queryset = JuniorTask.objects.filter(junior__auth=self.request.user
).select_related('junior', 'junior__auth'
).order_by('-created_at')
queryset = self.filter_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs):
"""Junior task list
status=0
search='title'
page=1"""
try:
status_value = self.request.GET.get('status')
queryset = self.get_queryset()
task_status = task_status_fun(status_value)
if status_value:
queryset = queryset.filter(task_status__in=task_status)
paginator = CustomPageNumberPagination()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskDetails juniorSerializer serializer
serializer = self.serializer_class(paginated_queryset, many=True)
return paginator.get_paginated_response(serializer.data)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CompleteJuniorTaskAPIView(views.APIView):
"""Payload
task_id
image"""
serializer_class = CompleteTaskSerializer
model = JuniorTask
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
task_id = self.request.data.get('task_id')
image = request.data['image']
if image and image.size == NUMBER['zero']:
return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST)
# create file
filename = f"images/{image.name}"
# upload image
filename = f"images/{image.name}"
image_url = upload_image_to_alibaba(image, filename)
# fetch junior query
task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user
).select_related('guardian', 'junior').last()
if task_queryset:
if task_queryset.guardian.guardian_code not in task_queryset.junior.guardian_code:
return custom_error_response(ERROR_CODE['2085'], response_status=status.HTTP_400_BAD_REQUEST)
elif task_queryset.junior.is_deleted or not task_queryset.junior.is_active:
return custom_error_response(ERROR_CODE['2074'], response_status=status.HTTP_400_BAD_REQUEST)
# use CompleteTaskSerializer serializer
elif task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]:
"""Already request send """
return custom_error_response(ERROR_CODE['2049'], response_status=status.HTTP_400_BAD_REQUEST)
serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3032'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2044'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class JuniorPointsListAPIView(viewsets.ModelViewSet):
"""Junior Points viewset"""
serializer_class = JuniorPointsSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs):
"""Junior Points
No Params"""
try:
# update_positions_based_on_points, function removed
queryset = JuniorPoints.objects.filter(junior__auth__email=self.request.user).last()
# update position of junior
serializer = JuniorPointsSerializer(queryset)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ValidateReferralCode(viewsets.ModelViewSet):
"""Check guardian code exist or not"""
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def get_queryset(self):
"""Get queryset based on referral_code."""
referral_code = self.request.GET.get('referral_code')
if referral_code:
# search referral code in guardian model
guardian_queryset = Guardian.objects.filter(referral_code=referral_code).last()
if guardian_queryset:
return guardian_queryset
else:
# search referral code in junior model
junior_queryset = Junior.objects.filter(referral_code=referral_code).last()
if junior_queryset:
return junior_queryset
return None
def list(self, request, *args, **kwargs):
"""check guardian code"""
try:
if self.get_queryset():
return custom_response(SUCCESS_CODE['3033'], response_status=status.HTTP_200_OK)
return custom_error_response(ERROR_CODE["2019"], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class InviteGuardianAPIView(viewsets.ModelViewSet):
"""Invite guardian by junior"""
serializer_class = AddGuardianSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
""" add guardian
{
"first_name":"abc",
"last_name":"xyz",
"email":"abc@yopmail.com",
"relationship":2
}"""
try:
if request.data['email'] == '':
return custom_error_response(ERROR_CODE['2062'], response_status=status.HTTP_400_BAD_REQUEST)
info = {'user': request.user, 'email': request.data['email'], 'first_name': request.data['first_name'],
'last_name': request.data['last_name'], 'relationship': str(request.data['relationship'])}
# use AddJuniorSerializer serializer
serializer = AddGuardianSerializer(data=request.data, context=info)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
return custom_error_response(ERROR_CODE['2034'], response_status=status.HTTP_400_BAD_REQUEST)
return custom_response(SUCCESS_CODE['3034'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.error, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class StartTaskAPIView(views.APIView):
"""Update junior task API
Paylod
{
"task_id":28
}"""
serializer_class = StartTaskSerializer
model = JuniorTask
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
task_id = self.request.data.get('task_id')
task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last()
print("task_queryset==>",task_queryset)
if task_queryset and task_queryset.task_status == str(NUMBER['one']):
if task_queryset.guardian.guardian_code not in task_queryset.junior.guardian_code:
return custom_error_response(ERROR_CODE['2083'], response_status=status.HTTP_400_BAD_REQUEST)
# use StartTaskSerializer serializer
serializer = StartTaskSerializer(task_queryset, data=request.data, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3035'], serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
# task in another state
return custom_error_response(ERROR_CODE['2060'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ReAssignJuniorTaskAPIView(views.APIView):
"""Update junior task API
Payload
{
"task_id":34,
"due_date":"2023-08-22"
}
"""
serializer_class = ReAssignTaskSerializer
model = JuniorTask
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
task_id = self.request.data.get('task_id')
task_queryset = JuniorTask.objects.filter(id=task_id, guardian__user__email=self.request.user).last()
if task_queryset and task_queryset.task_status == str(NUMBER['six']):
# use StartTaskSerializer serializer
serializer = ReAssignTaskSerializer(task_queryset, data=request.data, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
return custom_response(SUCCESS_CODE['3036'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
# task in another state
return custom_error_response(ERROR_CODE['2066'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class StartArticleAPIView(viewsets.ModelViewSet):
"""Start article"""
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
""" Payload
{
"article_id":"2"
}"""
try:
junior_instance = Junior.objects.filter(auth=self.request.user).last()
article_id = request.data.get('article_id')
article_data = Article.objects.filter(id=article_id).last()
if not JuniorArticle.objects.filter(junior=junior_instance, article=article_data).last():
JuniorArticle.objects.create(junior=junior_instance, article=article_data, status=str(NUMBER['two']),
current_card_page=NUMBER['zero'], current_que_page=NUMBER['zero'])
if article_data:
question_query = ArticleSurvey.objects.filter(article=article_id)
for question in question_query:
if not JuniorArticlePoints.objects.filter(junior=junior_instance,
article=article_data,
question=question):
JuniorArticlePoints.objects.create(junior=junior_instance,
article=article_data,
question=question)
return custom_response(SUCCESS_CODE['3040'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class StartAssessmentAPIView(viewsets.ModelViewSet):
"""Question answer viewset"""
serializer_class = StartAssessmentSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def get_queryset(self):
article_id = self.request.GET.get('article_id')
# if referral_code:
article = Article.objects.filter(id=article_id, is_deleted=False).prefetch_related(
'article_survey'
)
return article
def list(self, request, *args, **kwargs):
"""Params
article_id
"""
try:
queryset = self.get_queryset()
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, context={"user":request.user}, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CheckAnswerAPIView(viewsets.ModelViewSet):
"""Params
question_id=1
answer_id=1"""
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def get_queryset(self):
question_id = self.request.GET.get('question_id')
article = ArticleSurvey.objects.filter(id=question_id).last()
return article
def list(self, request, *args, **kwargs):
""" Params
question_id=1
answer_id=1
"""
try:
answer_id = self.request.GET.get('answer_id')
current_page = self.request.GET.get('current_page')
queryset = self.get_queryset()
submit_ans = SurveyOption.objects.filter(id=answer_id).last()
junior_article_points = JuniorArticlePoints.objects.filter(junior__auth=self.request.user,
question=queryset)
if submit_ans.is_answer:
junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, is_answer_correct=True)
JuniorPoints.objects.filter(junior__auth=self.request.user).update(total_points=
F('total_points') + queryset.points)
else:
junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, earn_points=0,
is_answer_correct=False)
JuniorArticle.objects.filter(junior__auth=self.request.user,
article=queryset.article).update(
current_que_page=int(current_page) + NUMBER['one'])
return custom_response(None, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CompleteArticleAPIView(views.APIView):
"""Params
article_id
"""
permission_classes = [IsAuthenticated]
http_method_names = ('put', 'get',)
def put(self, request, format=None):
try:
article_id = self.request.data.get('article_id')
JuniorArticle.objects.filter(junior__auth=request.user, article__id=article_id).update(
is_completed=True, status=str(NUMBER['three'])
)
return custom_response(SUCCESS_CODE['3041'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
def get(self, request, *args, **kwargs):
""" Params
article_id=1"""
try:
article_id = self.request.GET.get('article_id')
total_earn_points = JuniorArticlePoints.objects.filter(junior__auth=request.user,
article__id=article_id,
is_answer_correct=True).aggregate(
total_earn_points=Sum('earn_points'))['total_earn_points']
data = {"total_earn_points":total_earn_points}
if total_earn_points:
send_notification.delay(ARTICLE_REWARD_POINTS, None, GUARDIAN,
request.user.id, {'points': total_earn_points})
return custom_response(SUCCESS_CODE['3042'], data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ReadArticleCardAPIView(views.APIView):
"""Read article card API"""
permission_classes = [IsAuthenticated]
http_method_names = ('put',)
def put(self, request, *args, **kwargs):
""" Read article card
Payload
{"article_id":"1",
"article_card":"2",
"current_page":"2"}"""
try:
junior_instance = Junior.objects.filter(auth=self.request.user).last()
article = self.request.data.get('article_id')
article_card = self.request.data.get('article_card')
current_page = self.request.data.get('current_page')
JuniorArticleCard.objects.filter(junior=junior_instance,
article__id=article,
article_card__id=article_card).update(is_read=True)
JuniorArticle.objects.filter(junior=junior_instance,
article__id=article).update(current_card_page=int(current_page)+NUMBER['one'])
return custom_response(SUCCESS_CODE['3043'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CreateArticleCardAPIView(viewsets.ModelViewSet):
"""Start article"""
serializer_class = CreateArticleCardSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
""" create article card
Params
{"article_id":1}"""
try:
junior_instance = Junior.objects.filter(auth=self.request.user).last()
article_id = request.data.get('article_id')
article_data = Article.objects.filter(id=article_id).last()
if article_data:
article_cards = ArticleCard.objects.filter(article=article_id)
for article_card in article_cards:
if not JuniorArticleCard.objects.filter(junior=junior_instance,
article=article_data,
article_card=article_card):
JuniorArticleCard.objects.create(junior=junior_instance,
article=article_data,
article_card=article_card)
return custom_response(None, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class RemoveGuardianCodeAPIView(views.APIView):
"""Remove guardian code request API
Payload
{"guardian_code"
:"GRD037"
}"""
serializer_class = RemoveGuardianCodeSerializer
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
guardian_code = self.request.data.get("guardian_code")
guardian_data = Guardian.objects.filter(guardian_code=guardian_code).last()
junior_queryset = Junior.objects.filter(auth=self.request.user).last()
if junior_queryset:
# use RemoveGuardianCodeSerializer serializer
serializer = RemoveGuardianCodeSerializer(junior_queryset, context = {"guardian_code":guardian_code},
data=request.data, partial=True)
if serializer.is_valid():
# save serializer
serializer.save()
JuniorGuardianRelationship.objects.filter(guardian=guardian_data, junior=junior_queryset).delete()
return custom_response(SUCCESS_CODE['3044'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else:
# task in another state
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
error_detail = e.detail.get('error', None)
return custom_error_response(error_detail, response_status=status.HTTP_400_BAD_REQUEST)
class FAQViewSet(GenericViewSet, mixins.CreateModelMixin,
mixins.ListModelMixin):
"""FAQ view set"""
serializer_class = FAQSerializer
permission_classes = [IsAuthenticated]
http_method_names = ['get', 'post']
def get_queryset(self):
return FAQ.objects.filter(status=1).order_by('id')
def create(self, request, *args, **kwargs):
"""
faq create api method
:param request:
:param args: question, description
:param kwargs:
:return: success message
"""
load_fixture = request.query_params.get('load_fixture')
if load_fixture:
call_command('loaddata', 'fixtures/faq.json')
return custom_response(SUCCESS_CODE["3045"], response_status=status.HTTP_200_OK)
obj_data = [FAQ(**item) for item in request.data]
try:
FAQ.objects.bulk_create(obj_data)
return custom_response(SUCCESS_CODE["3045"], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
def list(self, request, *args, **kwargs):
"""
article list api method
:param request:
:param args:
:param kwargs:
:return: list of article
"""
queryset = self.get_queryset()
paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True)
return custom_response(None, data=serializer.data, response_status=status.HTTP_200_OK)

View File

@ -1,11 +1,15 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
"""Django import"""
"""Import OS module"""
# Import OS module
import os
"""Import sys module"""
# Import sys module"""
import sys
# define all function
# execute command line
# Import execute from command line
# fetch django settings
def main():
"""Main function"""

View File

14
notifications/admin.py Normal file
View File

@ -0,0 +1,14 @@
"""
notification admin file
"""
from django.contrib import admin
from notifications.models import Notification
@admin.register(Notification)
class NotificationAdmin(admin.ModelAdmin):
"""Notification Admin"""
list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read']
list_filter = ['notification_type']
search_fields = ['notification_to']

13
notifications/apps.py Normal file
View File

@ -0,0 +1,13 @@
"""
notification app file
"""
# django imports
from django.apps import AppConfig
class NotificationsConfig(AppConfig):
"""
notification app config
"""
default_auto_field = 'django.db.models.BigAutoField'
name = 'notifications'

148
notifications/constants.py Normal file
View File

@ -0,0 +1,148 @@
"""
notification constants file
"""
REGISTRATION = 1
ASSOCIATE_REQUEST = 3
ASSOCIATE_REJECTED = 4
ASSOCIATE_APPROVED = 5
REFERRAL_POINTS = 6
ASSOCIATE_JUNIOR = 7
ASSOCIATE_EXISTING_JUNIOR = 8
TASK_ASSIGNED = 9
TASK_ACTION = 10
TASK_REJECTED = 11
TASK_APPROVED = 12
PENDING_TASK_EXPIRING = 13
IN_PROGRESS_TASK_EXPIRING = 14
TOP_JUNIOR = 15
NEW_ARTICLE_PUBLISHED = 16
ARTICLE_REWARD_POINTS = 17
REMOVE_JUNIOR = 18
TEST_NOTIFICATION = 99
# notification dictionary
NOTIFICATION_DICT = {
REGISTRATION: {
"notification_type": REGISTRATION,
"title": "Successfully registered!",
"body": "You have registered successfully. Now login and complete your profile."
},
# user will receive notification as soon junior
# sign up application using their guardian code for association
ASSOCIATE_REQUEST: {
"notification_type": ASSOCIATE_REQUEST,
"title": "Associate request!",
"body": "You have request from {from_user} to associate with you."
},
# Juniors will receive notification when
# custodians reject their request for associate
ASSOCIATE_REJECTED: {
"notification_type": ASSOCIATE_REJECTED,
"title": "Associate request rejected!",
"body": "Your request to associate has been rejected by {from_user}."
},
# Juniors will receive notification when
# custodians approve their request for associate
ASSOCIATE_APPROVED: {
"notification_type": ASSOCIATE_APPROVED,
"title": "Associate request approved!",
"body": "Your request to associate has been approved by {from_user}."
},
# Juniors will receive Notifications
# for every Points earned by referrals
REFERRAL_POINTS: {
"notification_type": REFERRAL_POINTS,
"title": "Earn Referral points!",
"body": "You earn 5 points for referral."
},
# Juniors will receive notification
# once any custodians add them in their account
ASSOCIATE_JUNIOR: {
"notification_type": ASSOCIATE_JUNIOR,
"title": "Profile already setup!",
"body": "Your guardian has already setup your profile."
},
ASSOCIATE_EXISTING_JUNIOR: {
"notification_type": ASSOCIATE_EXISTING_JUNIOR,
"title": "Associated to guardian",
"body": "Your are associated to your guardian {from_user}."
},
# Juniors will receive Notification
# for every Task Assign by Custodians
TASK_ASSIGNED: {
"notification_type": TASK_ASSIGNED,
"title": "New task assigned!",
"body": "{from_user} has assigned you a new task."
},
# Guardian will receive notification as soon
# as junior send task for approval
TASK_ACTION: {
"notification_type": TASK_ACTION,
"title": "Task completion approval!",
"body": "{from_user} completed their task {task_name}."
},
# Juniors will receive notification as soon
# as their task is rejected by custodians
TASK_REJECTED: {
"notification_type": TASK_REJECTED,
"title": "Task completion rejected!",
"body": "Your task completion request has been rejected by {from_user}."
},
# Juniors will receive notification as soon as their task is approved by custodians
# and for every Points earned by Task completion
TASK_APPROVED: {
"notification_type": TASK_APPROVED,
"title": "Task completion approved!",
"body": "Your task completion request has been approved by {from_user}. "
"Also you earned 5 points for successful completion."
},
# Juniors will receive notification when their task end date about to end
PENDING_TASK_EXPIRING: {
"notification_type": PENDING_TASK_EXPIRING,
"title": "Task expiring soon!",
"body": "Your task {task_name} is expiring soon. Please complete it."
},
# User will receive notification when their assigned task is about to end
# and juniors have not performed any action
IN_PROGRESS_TASK_EXPIRING: {
"notification_type": IN_PROGRESS_TASK_EXPIRING,
"title": "Task expiring soon!",
"body": "{from_user} didn't take any action on assigned task {task_name} and it's expiring soon. "
"Please assist to complete it."
},
# Juniors will receive Notification
# related to Leaderboard progress
TOP_JUNIOR: {
"notification_type": TOP_JUNIOR,
"title": "Leaderboard topper!",
"body": "{from_user} is on top in leaderboard with {points} points."
},
# Juniors will receive notification
# when admin add any new financial learnings
NEW_ARTICLE_PUBLISHED: {
"notification_type": NEW_ARTICLE_PUBLISHED,
"title": "Time to read!",
"body": "A new article has been published."
},
# Juniors will receive notification when they earn points by reading financial Learning
ARTICLE_REWARD_POINTS: {
"notification_type": ARTICLE_REWARD_POINTS,
"title": "Article reward points!",
"body": "You are rewarded with {points} points for reading article and answering questions. "
},
# Juniors will receive notification as soon as their custodians remove them from account
REMOVE_JUNIOR: {
"notification_type": REMOVE_JUNIOR,
"title": "Disassociate by guardian!",
"body": "Your guardian has disassociated you."
},
# Test notification
TEST_NOTIFICATION: {
"notification_type": TEST_NOTIFICATION,
"title": "Test Notification",
"body": "This notification is for testing purpose from {from_user}."
}
}

View File

@ -0,0 +1,30 @@
# Generated by Django 4.2.2 on 2023-07-19 07:40
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Notification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('notification_type', models.CharField(blank=True, max_length=50, null=True)),
('data', models.JSONField(blank=True, default=dict, null=True)),
('is_read', models.BooleanField(default=False)),
('created_at', models.DateTimeField(default=django.utils.timezone.now)),
('notification_from', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='from_notification', to=settings.AUTH_USER_MODEL)),
('notification_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='to_notification', to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-09-29 07:00
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('notifications', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='notification',
name='updated_at',
field=models.DateTimeField(auto_now=True),
),
]

View File

25
notifications/models.py Normal file
View File

@ -0,0 +1,25 @@
"""
notification models file
"""
# django imports
from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone
USER = get_user_model()
class Notification(models.Model):
""" used to save the notifications """
notification_type = models.CharField(max_length=50, blank=True, null=True)
notification_to = models.ForeignKey(USER, related_name='to_notification', on_delete=models.CASCADE)
notification_from = models.ForeignKey(USER, related_name='from_notification', on_delete=models.SET_NULL,
blank=True, null=True)
data = models.JSONField(default=dict, blank=True, null=True)
is_read = models.BooleanField(default=False)
created_at = models.DateTimeField(default=timezone.now)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
""" string representation """
return f"{self.notification_to.id} | {self.notification_to.email}"

View File

@ -0,0 +1,53 @@
"""
notification serializer file
"""
# third party imports
from rest_framework import serializers
# local imports
from notifications.utils import register_fcm_token
from notifications.models import Notification
class RegisterDevice(serializers.Serializer):
"""
used to create and validate register device token
"""
registration_id = serializers.CharField(max_length=250)
device_id = serializers.CharField(max_length=250)
type = serializers.ChoiceField(choices=["ios", "web", "android"])
class Meta:
""" meta class """
fields = ('registration_id', 'type', 'device_id')
def create(self, validated_data):
""" override this method to create device token for users """
registration_id = validated_data['registration_id']
device_type = validated_data['type']
return register_fcm_token(self.context['user_id'], registration_id,
validated_data['device_id'], device_type)
class NotificationListSerializer(serializers.ModelSerializer):
"""List of notification"""
badge = serializers.SerializerMethodField()
class Meta(object):
"""meta info"""
model = Notification
fields = ['id', 'notification_type', 'data', 'badge', 'is_read', 'updated_at']
@staticmethod
def get_badge(obj):
return Notification.objects.filter(notification_to=obj.notification_to, is_read=False).count()
class ReadNotificationSerializer(serializers.ModelSerializer):
"""User task Serializer"""
id = serializers.ListSerializer(child=serializers.IntegerField())
class Meta(object):
"""Meta class"""
model = Notification
fields = ('id',)

98
notifications/tests.py Normal file
View File

@ -0,0 +1,98 @@
"""
notification test file
"""
# third party imports
from fcm_django.models import FCMDevice
# django imports
from django.urls import reverse
from rest_framework import status
from account.models import UserNotification
# local imports
from account.serializers import GuardianSerializer
from notifications.models import Notification
from web_admin.tests.test_set_up import AnalyticsSetUp
class NotificationTestCase(AnalyticsSetUp):
"""
test notification
"""
def setUp(self) -> None:
"""
test data up
:return:
"""
super(NotificationTestCase, self).setUp()
# notification settings create
UserNotification.objects.create(user=self.user)
# notification create
self.notification = Notification.objects.create(notification_to=self.user, notification_from=self.user_3)
# to get guardian/user auth token
self.guardian_data = GuardianSerializer(
self.guardian, context={'user_type': 2}
).data
self.auth_token = self.guardian_data['auth_token']
# api header
self.header = {
'HTTP_AUTHORIZATION': f'Bearer {self.auth_token}',
'Content-Type': "Application/json"
}
def test_notification_list(self):
"""
test notification list
:return:
"""
url = reverse('notifications:notifications-list')
response = self.client.get(url, **self.header)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming only one notification exists in the database
self.assertEqual(Notification.objects.filter(notification_to=self.user).count(), 1)
def test_fcm_register(self):
"""
test fcm register
:return:
"""
url = reverse('notifications:notifications-device')
data = {
'registration_id': 'registration_id',
'device_id': 'device_id',
'type': 'ios'
}
response = self.client.post(url, data, **self.header)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# device created for user
self.assertEqual(FCMDevice.objects.count(), 1)
def test_send_test_notification(self):
"""
test send test notification
:return:
"""
url = reverse('notifications:notifications-test')
response = self.client.get(url, **self.header)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming one notification exists in the database and two created after api run
self.assertEqual(Notification.objects.filter(notification_to=self.user).count(), 3)
def test_mark_as_read(self):
"""
test mark as read
:return:
"""
url = reverse('notifications:notifications-mark-as-read')
data = {
'id': [self.notification.id]
}
response = self.client.patch(url, data, **self.header)
self.notification.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.notification.is_read, True)

18
notifications/urls.py Normal file
View File

@ -0,0 +1,18 @@
"""
notifications urls file
"""
# django imports
from django.urls import path, include
from rest_framework import routers
# local imports
from notifications.views import NotificationViewSet
# initiate router
router = routers.SimpleRouter()
router.register('notifications', NotificationViewSet, basename='notifications')
urlpatterns = [
path('api/v1/', include(router.urls)),
]

165
notifications/utils.py Normal file
View File

@ -0,0 +1,165 @@
"""
notifications utils file
"""
# third party imports
from fcm_django.models import FCMDevice
from celery import shared_task
from firebase_admin.messaging import Message, Notification as FirebaseNotification
# django imports
from django.contrib.auth import get_user_model
from django.db.models import Q
# local imports
from account.models import UserNotification
from account.utils import get_user_full_name
from base.constants import GUARDIAN, JUNIOR
from guardian.models import Guardian, JuniorTask
from junior.models import Junior
from notifications.constants import NOTIFICATION_DICT
from notifications.models import Notification
User = get_user_model()
def register_fcm_token(user_id, registration_id, device_id, device_type):
""" used to register the fcm device token"""
FCMDevice.objects.filter(registration_id=registration_id).delete()
device, _ = FCMDevice.objects.update_or_create(user_id=user_id,
defaults={'device_id': device_id, 'type': device_type,
'active': True,
'registration_id': registration_id})
return device
def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None:
"""
remove access_token and registration_token
"""
try:
# remove fcm token for this device
FCMDevice.objects.filter(user_id=user_id).delete()
except Exception as e:
print(e)
def get_from_user_details(from_user_id, from_user_type):
"""
used to get from user details
"""
from_user = None
from_user_name = None
from_user_image = None
if from_user_id:
if from_user_type == GUARDIAN:
guardian = Guardian.objects.filter(user_id=from_user_id).select_related('user').first()
from_user = guardian.user
from_user_name = get_user_full_name(from_user)
from_user_image = guardian.image
elif from_user_type == JUNIOR:
junior = Junior.objects.filter(auth_id=from_user_id).select_related('auth').first()
from_user = junior.auth
from_user_name = get_user_full_name(from_user)
from_user_image = junior.image
return from_user_name, from_user_image, from_user
def get_notification_data(notification_type, from_user_id, from_user_type, to_user_id, extra_data):
"""
get notification and push data
:param from_user_type: GUARDIAN or JUNIOR
:param notification_type: notification_type
:param from_user_id: from_user obj
:param to_user_id: to_user obj
:param extra_data: any extra data provided
:return: notification and push data
"""
push_data = NOTIFICATION_DICT[notification_type].copy()
notification_data = push_data.copy()
task_name = None
points = extra_data.get('points', None)
if 'task_id' in extra_data:
task = JuniorTask.objects.filter(id=extra_data.get('task_id')).first()
task_name = task.task_name
extra_data['task_name'] = task_name
extra_data['task_image'] = task.image if task.image else task.default_image
from_user_name, from_user_image, from_user = get_from_user_details(from_user_id, from_user_type)
push_data['body'] = push_data['body'].format(from_user=from_user_name, task_name=task_name, points=points)
notification_data['body'] = notification_data['body'].format(from_user=from_user_name,
task_name=task_name, points=points)
push_data['to_user_type'] = GUARDIAN if from_user_type == JUNIOR else JUNIOR
notification_data['to_user_type'] = GUARDIAN if from_user_type == JUNIOR else JUNIOR
notification_data['from_user'] = from_user_name
notification_data['from_user_image'] = from_user_image
notification_data.update(extra_data)
to_user = User.objects.filter(id=to_user_id).first()
return notification_data, push_data, from_user, to_user
@shared_task()
def send_notification(notification_type, from_user_id, from_user_type, to_user_id, extra_data):
"""
used to send the push for the given notification type
"""
notification_data, push_data, from_user, to_user = get_notification_data(notification_type, from_user_id,
from_user_type, to_user_id, extra_data)
user_notification_type = UserNotification.objects.filter(user=to_user).first()
# notification create method changed on 28sep as per changes required
task_id = extra_data['task_id'] if 'task_id' in extra_data else None
Notification.objects.update_or_create(data__has_key='task_id', data__task_id=task_id,
notification_from=from_user, notification_to=to_user,
defaults={
'notification_type': notification_type,
'notification_from': from_user,
'notification_to': to_user,
'data': notification_data
})
if user_notification_type and user_notification_type.push_notification:
send_push(to_user, push_data)
def send_push(user, data):
""" used to send push notification to specific user """
data['notification_type'] = str(data['notification_type'])
user.fcmdevice_set.filter(active=True).send_message(
Message(notification=FirebaseNotification(data['title'], data['body']), data=data)
)
def send_multiple_push(queryset, data):
""" used to send same notification to multiple users """
data['notification_type'] = str(data['notification_type'])
FCMDevice.objects.filter(user__in=queryset, active=True).send_message(
Message(notification=FirebaseNotification(data['title'], data['body']), data=data)
)
@shared_task()
def send_notification_multiple_user(notification_type, from_user_id, from_user_type,
extra_data: dict = {}):
"""
used to send notification to multiple user for the given notification type
"""
to_user_list = User.objects.filter(junior_profile__is_verified=True, is_superuser=False
).exclude(junior_profile__isnull=True, guardian_profile__isnull=True)
notification_data, push_data, from_user, _ = get_notification_data(notification_type, from_user_id,
from_user_type, None, extra_data)
notification_list = []
for user in to_user_list:
notification_list.append(Notification(notification_type=notification_type,
notification_to=user,
notification_from=from_user,
data=notification_data))
Notification.objects.bulk_create(notification_list)
to_user_list = to_user_list.filter(user_notification__push_notification=True)
send_multiple_push(to_user_list, push_data)

85
notifications/views.py Normal file
View File

@ -0,0 +1,85 @@
"""
notifications views file
"""
# django imports
from django.db.models import Q
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response
from rest_framework import viewsets, status, views
# local imports
from account.utils import custom_response, custom_error_response
from base.messages import SUCCESS_CODE, ERROR_CODE
from base.pagination import CustomPageNumberPagination
from base.tasks import notify_task_expiry, notify_top_junior
from notifications.constants import TEST_NOTIFICATION
from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer
from notifications.utils import send_notification
from notifications.models import Notification
class NotificationViewSet(viewsets.GenericViewSet):
"""
used to do the notification actions
"""
serializer_class = NotificationListSerializer
permission_classes = [IsAuthenticated, ]
def list(self, request, *args, **kwargs) -> Response:
"""
to list user's notifications
:param request:
:return:
"""
queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id']
).select_related('notification_to').order_by('-updated_at', '-id')
paginator = CustomPageNumberPagination()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True)
return paginator.get_paginated_response(serializer.data)
@action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice)
def fcm_registration(self, request):
"""
used to save the fcm token
"""
serializer = self.get_serializer_class()(data=request.data,
context={'user_id': request.auth.payload['user_id']})
serializer.is_valid(raise_exception=True)
serializer.save()
return custom_response(SUCCESS_CODE["3000"])
@action(methods=['get'], detail=False, url_path='test', url_name='test')
def send_test_notification(self, request):
"""
to test send notification, task expiry, top junior
:return:
"""
notify_task_expiry()
notify_top_junior()
notification_type = request.query_params.get('type', TEST_NOTIFICATION)
from_user_type = request.query_params.get('from_user_type')
send_notification(int(notification_type), None, from_user_type, request.auth.payload['user_id'],
{})
if notification_type and request.query_params.get('clear_all'):
Notification.objects.filter(notification_type=notification_type).delete()
return custom_response(SUCCESS_CODE["3000"])
@action(methods=['patch'], url_path='mark-as-read', url_name='mark-as-read', detail=False,
serializer_class=ReadNotificationSerializer)
def mark_as_read(self, request, *args, **kwargs):
"""
notification list
"""
if request.data.get('id'):
Notification.objects.filter(id__in=request.data.get('id')).update(is_read=True)
elif request.query_params.get('mark_all'):
Notification.objects.filter(notification_to_id=request.auth.payload['user_id']).update(is_read=True)
elif request.query_params.get('clear_all'):
Notification.objects.filter(notification_to_id=request.auth.payload['user_id']).delete()
return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK)

View File

@ -8,6 +8,7 @@ async-timeout==4.0.2
billiard==4.1.0
boto3==1.26.157
botocore==1.29.157
CacheControl==0.13.1
cachetools==5.3.1
celery==5.3.1
certifi==2023.5.7
@ -41,8 +42,22 @@ django-timezone-field==5.1
djangorestframework==3.14.0
djangorestframework-simplejwt==5.2.2
drf-yasg==1.21.6
fcm-django==2.0.0
firebase-admin==6.2.0
google-api-core==2.11.1
google-api-python-client==2.93.0
google-auth==2.21.0
google-auth-httplib2==0.1.0
google-cloud-core==2.3.3
google-cloud-firestore==2.11.1
google-cloud-storage==2.10.0
google-crc32c==1.5.0
google-resumable-media==2.5.0
googleapis-common-protos==1.59.1
grpcio==1.56.0
grpcio-status==1.56.0
gunicorn==20.1.0
httplib2==0.22.0
idna==3.4
inflection==0.5.1
itypes==1.2.0
@ -51,17 +66,22 @@ jmespath==0.10.0
kombu==5.3.1
MarkupSafe==2.1.3
msgpack==1.0.5
ntplib==0.4.0
numpy==1.25.1
oss2==2.18.0
packaging==23.1
phonenumbers==8.13.15
Pillow==9.5.0
prompt-toolkit==3.0.38
proto-plus==1.22.3
protobuf==4.23.4
psycopg==3.1.9
pyasn1==0.5.0
pyasn1-modules==0.3.0
pycparser==2.21
pycryptodome==3.18.0
PyJWT==2.7.0
pyparsing==3.1.0
python-crontab==2.7.1
python-dateutil==2.8.2
python-dotenv==1.0.0
@ -79,3 +99,7 @@ uritemplate==4.1.1
urllib3==1.26.16
vine==5.0.0
wcwidth==0.2.6
pandas==2.0.3
XlsxWriter==3.1.2
coverage==7.3.1

0
web_admin/__init__.py Normal file
View File

38
web_admin/admin.py Normal file
View File

@ -0,0 +1,38 @@
"""
web_admin admin file
"""
# django imports
from django.contrib import admin
# local imports
from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption, DefaultArticleCardImage
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
"""Article Admin"""
list_display = ['id', 'title', 'description', 'is_published', 'is_deleted']
@admin.register(ArticleCard)
class ArticleCardAdmin(admin.ModelAdmin):
"""Article Card Admin"""
list_display = ['id', 'article', 'title', 'description', 'image_url']
@admin.register(ArticleSurvey)
class ArticleSurveyAdmin(admin.ModelAdmin):
"""Article Survey Admin"""
list_display = ['id', 'article', 'question', 'points']
@admin.register(SurveyOption)
class SurveyOptionAdmin(admin.ModelAdmin):
"""Survey Option Admin"""
list_display = ['id', 'survey', 'option', 'is_answer']
@admin.register(DefaultArticleCardImage)
class DefaultArticleCardImagesAdmin(admin.ModelAdmin):
"""Default Article Card Images Option Admin"""
list_display = ['image_name', 'image_url']

13
web_admin/apps.py Normal file
View File

@ -0,0 +1,13 @@
"""
web_admin app file
"""
# django imports
from django.apps import AppConfig
class WebAdminConfig(AppConfig):
"""
web admin app config
"""
default_auto_field = 'django.db.models.BigAutoField'
name = 'web_admin'

View File

@ -0,0 +1,61 @@
# Generated by Django 4.2.2 on 2023-07-14 13:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Article',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('is_published', models.BooleanField(default=True)),
('is_deleted', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='ArticleSurvey',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('question', models.CharField(max_length=255)),
('points', models.IntegerField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_survey', to='web_admin.article')),
],
),
migrations.CreateModel(
name='SurveyOption',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('option', models.CharField(max_length=255)),
('is_answer', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('survey', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='survey_options', to='web_admin.articlesurvey')),
],
),
migrations.CreateModel(
name='ArticleCard',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=255)),
('description', models.TextField()),
('image', models.ImageField(upload_to='card_images/')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('article', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='article_cards', to='web_admin.article')),
],
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.2 on 2023-07-20 11:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='articlecard',
name='image',
field=models.URLField(blank=True, default=None, null=True),
),
]

View File

@ -0,0 +1,28 @@
# Generated by Django 4.2.2 on 2023-07-24 14:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0002_alter_articlecard_image'),
]
operations = [
migrations.CreateModel(
name='DefaultArticleCardImage',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('image_name', models.CharField(max_length=20)),
('image_url', models.URLField()),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
),
migrations.RenameField(
model_name='articlecard',
old_name='image',
new_name='image_url',
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.2 on 2023-08-01 07:35
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('web_admin', '0003_defaultarticlecardimage_and_more'),
]
operations = [
migrations.AlterField(
model_name='surveyoption',
name='survey',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='options', to='web_admin.articlesurvey'),
),
]

View File

81
web_admin/models.py Normal file
View File

@ -0,0 +1,81 @@
"""
web_admin model file
"""
# django imports
from django.db import models
class Article(models.Model):
"""
Article model
"""
title = models.CharField(max_length=255)
description = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
is_published = models.BooleanField(default=True)
is_deleted = models.BooleanField(default=False)
def __str__(self):
"""Return title"""
return f'{self.id} | {self.title}'
class ArticleCard(models.Model):
"""
Article Card model
"""
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_cards')
title = models.CharField(max_length=255)
description = models.TextField()
image_url = models.URLField(null=True, blank=True, default=None)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
"""Return title"""
return f'{self.id} | {self.title}'
class ArticleSurvey(models.Model):
"""
Article Survey model
"""
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='article_survey')
question = models.CharField(max_length=255)
points = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
"""Return title"""
return f'{self.id} | {self.question}'
class SurveyOption(models.Model):
"""
Survey Options model
"""
survey = models.ForeignKey(ArticleSurvey, on_delete=models.CASCADE, related_name='options')
option = models.CharField(max_length=255)
is_answer = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
"""Return title"""
return f'{self.id} | {self.option}'
class DefaultArticleCardImage(models.Model):
"""
Default images upload in oss bucket
"""
image_name = models.CharField(max_length=20)
image_url = models.URLField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
"""return image_name as an object"""
return self.image_name

26
web_admin/permission.py Normal file
View File

@ -0,0 +1,26 @@
"""
web_admin permission classes
"""
# django imports
from rest_framework import permissions
class AdminPermission(permissions.BasePermission):
"""
to check for usertype admin only
"""
def has_permission(self, request, view):
"""
Return True if user_type is admin
"""
if request.user.is_superuser:
return True
return False
def has_object_permission(self, request, view, obj):
"""
check for object level permission
"""
if request.user.is_superuser:
return True
return False

View File

View File

@ -0,0 +1,146 @@
"""
web_admin analytics serializer file
"""
# third party imports
from rest_framework import serializers
# django imports
from django.contrib.auth import get_user_model
from account.utils import get_user_full_name
# local imports
from base.constants import USER_TYPE, JUNIOR
from junior.models import JuniorPoints, Junior
USER = get_user_model()
class JuniorLeaderboardSerializer(serializers.ModelSerializer):
"""
junior leaderboard serializer
"""
name = serializers.SerializerMethodField()
first_name = serializers.SerializerMethodField()
last_name = serializers.SerializerMethodField()
class Meta:
"""
meta class
"""
model = Junior
fields = ('id', 'name', 'first_name', 'last_name', 'is_active', 'image', 'is_deleted')
@staticmethod
def get_name(obj):
"""
:param obj: junior object
:return: full name
"""
return get_user_full_name(obj.auth)
@staticmethod
def get_first_name(obj):
"""
:param obj: junior object
:return: first name
"""
return obj.auth.first_name
@staticmethod
def get_last_name(obj):
"""
:param obj: junior object
:return: last name
"""
return obj.auth.last_name
class LeaderboardSerializer(serializers.ModelSerializer):
"""
leaderboard serializer
"""
user_id = serializers.SerializerMethodField()
user_type = serializers.SerializerMethodField()
junior = JuniorLeaderboardSerializer()
rank = serializers.IntegerField()
class Meta:
"""
meta class
"""
model = JuniorPoints
fields = ('user_id', 'user_type', 'total_points', 'rank', 'junior')
@staticmethod
def get_user_id(obj):
return obj.junior.auth.id
@staticmethod
def get_user_type(obj):
return JUNIOR
class UserCSVReportSerializer(serializers.ModelSerializer):
"""
user csv/xls report serializer
"""
name = serializers.SerializerMethodField()
phone_number = serializers.SerializerMethodField()
user_type = serializers.SerializerMethodField()
is_active = serializers.SerializerMethodField()
date_joined = serializers.SerializerMethodField()
class Meta:
"""
meta class
"""
model = USER
fields = ('name', 'email', 'phone_number', 'user_type', 'is_active', 'date_joined')
@staticmethod
def get_name(obj):
"""
:param obj: user object
:return: full name
"""
return get_user_full_name(obj)
@staticmethod
def get_phone_number(obj):
"""
:param obj: user object
:return: user phone number
"""
if profile := (obj.guardian_profile.all().first() or obj.junior_profile.all().first()):
return f"+{profile.country_code}{profile.phone}" \
if profile.country_code and profile.phone else profile.phone
@staticmethod
def get_user_type(obj):
"""
:param obj: user object
:return: user type
"""
if obj.guardian_profile.all().first():
return dict(USER_TYPE).get('2').capitalize()
elif obj.junior_profile.all().first():
return dict(USER_TYPE).get('1').capitalize()
@staticmethod
def get_is_active(obj):
"""
:param obj: user object
:return: user type
"""
if profile := (obj.guardian_profile.all().first() or obj.junior_profile.all().first()):
return "Active" if profile.is_active else "Inactive"
@staticmethod
def get_date_joined(obj):
"""
:param obj: user obj
:return: formatted date
"""
date = obj.date_joined.strftime("%d %b %Y")
return date

View File

@ -0,0 +1,370 @@
"""
web_admin serializers file
"""
# django imports
from rest_framework import serializers
from django.contrib.auth import get_user_model
from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE_SURVEY, MAX_ARTICLE_SURVEY, NUMBER,
USER_TYPE, ARTICLE_CARD_IMAGE_FOLDER)
# local imports
from base.messages import ERROR_CODE
from guardian.utils import upload_image_to_alibaba
from notifications.constants import NEW_ARTICLE_PUBLISHED
from notifications.utils import send_notification_multiple_user
from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage
from web_admin.utils import pop_id, get_image_url
from junior.models import JuniorArticlePoints, JuniorArticle
USER = get_user_model()
class ArticleCardSerializer(serializers.ModelSerializer):
"""
Article Card serializer
"""
id = serializers.IntegerField(required=False)
image_name = serializers.CharField(required=False)
image_url = serializers.CharField(required=False)
class Meta:
"""
meta class
"""
model = ArticleCard
fields = ('id', 'title', 'description', 'image_name', 'image_url')
def create(self, validated_data):
validated_data['image_url'] = get_image_url(validated_data)
article = Article.objects.all().first()
article_card = ArticleCard.objects.create(article=article, **validated_data)
return article_card
class SurveyOptionSerializer(serializers.ModelSerializer):
"""
survey option serializer
"""
id = serializers.IntegerField(required=False)
class Meta:
"""
meta class
"""
model = SurveyOption
fields = ('id', 'option', 'is_answer')
class ArticleSurveySerializer(serializers.ModelSerializer):
"""
article survey serializer
"""
id = serializers.IntegerField(required=False)
options = SurveyOptionSerializer(many=True)
class Meta:
"""
meta class
"""
model = ArticleSurvey
fields = ('id', 'question', 'options')
class ArticleSerializer(serializers.ModelSerializer):
"""
serializer for article API
"""
article_cards = ArticleCardSerializer(many=True)
article_survey = ArticleSurveySerializer(many=True)
class Meta:
"""
meta class
"""
model = Article
fields = ('id', 'title', 'description', 'is_published', 'article_cards', 'article_survey')
def validate(self, attrs):
"""
to validate request data
:return: validated attrs
"""
article_cards = attrs.get('article_cards', None)
article_survey = attrs.get('article_survey', None)
if not 0 < len(article_cards) <= int(MAX_ARTICLE_CARD):
raise serializers.ValidationError({'details': ERROR_CODE['2039']})
if not int(MIN_ARTICLE_SURVEY) <= len(article_survey) <= int(MAX_ARTICLE_SURVEY):
raise serializers.ValidationError({'details': ERROR_CODE['2040']})
return attrs
def create(self, validated_data):
"""
to create article.
ID in post data dict is for update api.
:return: article object
"""
article_cards = validated_data.pop('article_cards')
article_survey = validated_data.pop('article_survey')
article = Article.objects.create(**validated_data)
for card in article_cards:
card = pop_id(card)
card['image_url'] = get_image_url(card)
ArticleCard.objects.create(article=article, **card)
for survey in article_survey:
survey = pop_id(survey)
options = survey.pop('options')
survey_obj = ArticleSurvey.objects.create(article=article, points=ARTICLE_SURVEY_POINTS, **survey)
for option in options:
option = pop_id(option)
SurveyOption.objects.create(survey=survey_obj, **option)
# All juniors will receive notification when admin add any new financial learnings/article
send_notification_multiple_user.delay(NEW_ARTICLE_PUBLISHED, None, None, {})
return article
def update(self, instance, validated_data):
"""
to update article and related table
:param validated_data:
:param instance: article object,
:return: article object
"""
article_cards = validated_data.pop('article_cards')
article_survey = validated_data.pop('article_survey')
instance.title = validated_data.get('title', instance.title)
instance.description = validated_data.get('description', instance.description)
instance.save()
prev_card = list(ArticleCard.objects.filter(article=instance).values_list('id', flat=True))
# Update or create cards
for card_data in article_cards:
card_id = card_data.get('id', None)
if card_id:
prev_card.remove(card_id)
card = ArticleCard.objects.get(id=card_id, article=instance)
card.title = card_data.get('title', card.title)
card.description = card_data.get('description', card.description)
card.image_url = get_image_url(card_data)
card.save()
else:
card_data = pop_id(card_data)
card_data['image_url'] = get_image_url(card_data)
ArticleCard.objects.create(article=instance, **card_data)
ArticleCard.objects.filter(id__in=prev_card, article=instance).delete()
prev_survey = list(ArticleSurvey.objects.filter(article=instance).values_list('id', flat=True))
# Update or create survey sections
for survey_data in article_survey:
survey_id = survey_data.get('id', None)
options_data = survey_data.pop('options')
if survey_id:
prev_survey.remove(survey_id)
survey = ArticleSurvey.objects.get(id=survey_id, article=instance)
survey.question = survey_data.get('question', survey.question)
survey.save()
else:
survey_data = pop_id(survey_data)
survey = ArticleSurvey.objects.create(article=instance, points=ARTICLE_SURVEY_POINTS, **survey_data)
# Update or create survey options
for option_data in options_data:
option_id = option_data.get('id', None)
if option_id:
option = SurveyOption.objects.get(id=option_id, survey=survey)
option.option = option_data.get('option', option.option)
option.is_answer = option_data.get('is_answer', option.is_answer)
option.save()
else:
option_data = pop_id(option_data)
SurveyOption.objects.create(survey=survey, **option_data)
ArticleSurvey.objects.filter(id__in=prev_survey, article=instance).delete()
return instance
class ArticleStatusChangeSerializer(serializers.ModelSerializer):
"""
Article status change serializer
"""
class Meta:
"""
meta class
"""
model = Article
fields = ('is_published', )
def update(self, instance, validated_data):
"""
:param instance: article object
:param validated_data:
:return:
"""
instance.is_published = validated_data['is_published']
instance.save()
return instance
class DefaultArticleCardImageSerializer(serializers.ModelSerializer):
"""
Article Card serializer
"""
image = serializers.FileField(required=False)
image_url = serializers.URLField(required=False)
class Meta:
"""
meta class
"""
model = DefaultArticleCardImage
fields = ('image_name', 'image', 'image_url')
def validate(self, attrs):
"""
to validate data
:return: validated data
"""
if 'image' not in attrs and attrs.get('image') is None:
raise serializers.ValidationError({'details': ERROR_CODE['2061']})
image = attrs.get('image')
if image and image.size == NUMBER['zero']:
raise serializers.ValidationError(ERROR_CODE['2035'])
return attrs
def create(self, validated_data):
"""
to create and upload image
:return: card_image object
"""
validated_data['image_url'] = get_image_url(validated_data)
card_image = DefaultArticleCardImage.objects.create(**validated_data)
return card_image
class ArticleListSerializer(serializers.ModelSerializer):
"""
serializer for article API
"""
image = serializers.SerializerMethodField('get_image')
total_points = serializers.SerializerMethodField('get_total_points')
is_completed = serializers.SerializerMethodField('get_is_completed')
class Meta(object):
"""
meta class
"""
model = Article
fields = ('id', 'title', 'description', 'image', 'total_points', 'is_completed')
def get_image(self, obj):
"""article image"""
if obj.article_cards.first():
return obj.article_cards.first().image_url
return None
def get_total_points(self, obj):
"""total points of article"""
return obj.article_survey.all().count() * NUMBER['five']
def get_is_completed(self, obj):
"""complete all question"""
context_data = self.context.get('user')
junior_article = JuniorArticle.objects.filter(junior__auth=context_data, article=obj).last()
if junior_article:
return junior_article.is_completed
return False
class ArticleQuestionSerializer(serializers.ModelSerializer):
"""
article survey serializer
"""
id = serializers.IntegerField(required=False)
options = SurveyOptionSerializer(many=True)
is_attempt = serializers.SerializerMethodField('get_is_attempt')
correct_answer = serializers.SerializerMethodField('get_correct_answer')
attempted_answer = serializers.SerializerMethodField('get_attempted_answer')
def get_is_attempt(self, obj):
"""attempt question or not"""
context_data = self.context.get('user')
junior_article_obj = JuniorArticlePoints.objects.filter(junior__auth=context_data, question=obj).last()
if junior_article_obj:
return junior_article_obj.is_attempt
return False
def get_correct_answer(self, obj):
"""attempt question or not"""
ans_obj = SurveyOption.objects.filter(survey=obj, is_answer=True).last()
if ans_obj:
return ans_obj.id
return None
def get_attempted_answer(self, obj):
"""attempt question or not"""
context_data = self.context.get('user')
junior_article_obj = JuniorArticlePoints.objects.filter(junior__auth=context_data,
question=obj).last()
if junior_article_obj and junior_article_obj.submitted_answer:
return junior_article_obj.submitted_answer.id
return None
class Meta(object):
"""
meta class
"""
model = ArticleSurvey
fields = ('id', 'question', 'options', 'points', 'is_attempt', 'correct_answer', 'attempted_answer')
class StartAssessmentSerializer(serializers.ModelSerializer):
"""
serializer for article API
"""
article_survey = ArticleQuestionSerializer(many=True)
current_page = serializers.SerializerMethodField('get_current_page')
def get_current_page(self, obj):
"""current page"""
context_data = self.context.get('user')
data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj).last()
total_count = obj.article_survey.all().count()
if data:
return data.current_que_page if data.current_que_page < total_count else data.current_que_page - 1
return NUMBER['zero']
class Meta(object):
"""
meta class
"""
model = Article
fields = ('article_survey', 'current_page')
class ArticleCardlistSerializer(serializers.ModelSerializer):
"""
Article Card serializer
"""
id = serializers.IntegerField(required=False)
image_name = serializers.CharField(required=False)
image_url = serializers.CharField(required=False)
current_page = serializers.SerializerMethodField('get_current_page')
def get_current_page(self, obj):
"""current page"""
context_data = self.context.get('user')
data = JuniorArticle.objects.filter(junior__auth=context_data, article=obj.article).last()
total_count = self.context.get('card_count')
if data:
return data.current_card_page if data.current_card_page < total_count else data.current_card_page - 1
return NUMBER['zero']
class Meta(object):
"""
meta class
"""
model = ArticleCard
fields = ('id', 'title', 'description', 'image_name', 'image_url', 'current_page')

View File

@ -0,0 +1,136 @@
"""
web_admin auth serializers file
"""
# python imports
from datetime import datetime
# django imports
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.utils import timezone
# local imports
from account.models import UserEmailOtp
from base.constants import USER_TYPE
from base.messages import ERROR_CODE
from guardian.tasks import generate_otp
from base.tasks import send_email
USER = get_user_model()
class AdminOTPSerializer(serializers.ModelSerializer):
"""
admin forgot password serializer
"""
email = serializers.EmailField()
class Meta:
"""
meta class
"""
model = USER
fields = ('email',)
def validate(self, attrs):
""" used to validate the incoming data """
user = USER.objects.filter(email=attrs.get('email')).first()
if not user:
raise serializers.ValidationError({'details': ERROR_CODE['2004']})
elif not user.is_superuser:
raise serializers.ValidationError({'details': ERROR_CODE['2063']})
attrs.update({'user': user})
return attrs
def create(self, validated_data):
"""
to send otp
:return: user_data
"""
email = validated_data['email']
verification_code = generate_otp()
template = 'email_reset_verification.email'
# Send the verification code to the user's email
data = {
"verification_code": verification_code
}
send_email.delay([email], template, data)
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.update_or_create(email=email,
defaults={
"otp": verification_code,
"expired_at": expiry,
"user_type": dict(USER_TYPE).get('3'),
})
return user_data
class AdminVerifyOTPSerializer(serializers.Serializer):
"""
admin verify otp serializer
"""
email = serializers.EmailField()
otp = serializers.CharField(max_length=6, min_length=6)
class Meta:
""" meta class """
fields = ('email', 'otp',)
def validate(self, attrs):
"""
to validate data
:return: validated data
"""
email = attrs.get('email')
otp = attrs.get('otp')
# fetch email otp object of the user
user_otp_details = UserEmailOtp.objects.filter(email=email, otp=otp).last()
if not user_otp_details:
raise serializers.ValidationError({'details': ERROR_CODE['2008']})
if user_otp_details.user_type != dict(USER_TYPE).get('3'):
raise serializers.ValidationError({'details': ERROR_CODE['2008']})
if user_otp_details.expired_at.replace(tzinfo=None) < datetime.utcnow():
raise serializers.ValidationError({'details': ERROR_CODE['2029']})
user_otp_details.is_verified = True
user_otp_details.save()
return attrs
class AdminCreatePasswordSerializer(serializers.ModelSerializer):
"""
admin create new password serializer
"""
email = serializers.EmailField()
new_password = serializers.CharField()
confirm_password = serializers.CharField()
class Meta:
"""
meta class
"""
model = USER
fields = ('email', 'new_password', 'confirm_password')
def validate(self, attrs):
"""
to validate data
:return: validated data
"""
email = attrs.get('email')
new_password = attrs.get('new_password')
confirm_password = attrs.get('confirm_password')
# matching password
if new_password != confirm_password:
raise serializers.ValidationError({'details': ERROR_CODE['2065']})
user_otp_details = UserEmailOtp.objects.filter(email=email, is_verified=True).last()
if not user_otp_details:
raise serializers.ValidationError({'details': ERROR_CODE['2064']})
if user_otp_details.user_type != dict(USER_TYPE).get('3'):
raise serializers.ValidationError({'details': ERROR_CODE['2063']})
user_otp_details.delete()
return attrs

View File

@ -0,0 +1,286 @@
"""
web_admin user_management serializers file
"""
# django imports
from rest_framework import serializers
from django.contrib.auth import get_user_model
from account.utils import get_user_full_name
from base.constants import USER_TYPE, GUARDIAN, JUNIOR
# local imports
from base.messages import ERROR_CODE, SUCCESS_CODE
from guardian.models import Guardian
from junior.models import Junior
USER = get_user_model()
class UserManagementListSerializer(serializers.ModelSerializer):
"""
user management serializer
"""
name = serializers.SerializerMethodField()
country_code = serializers.SerializerMethodField()
phone = serializers.SerializerMethodField()
user_type = serializers.SerializerMethodField()
is_active = serializers.SerializerMethodField()
class Meta:
"""
meta class
"""
model = USER
fields = ('id', 'name', 'email', 'country_code', 'phone', 'user_type', 'is_active')
@staticmethod
def get_name(obj):
"""
:param obj: user object
:return: full name
"""
return get_user_full_name(obj)
@staticmethod
def get_country_code(obj):
"""
:param obj: user object
:return: user phone number
"""
if profile := obj.guardian_profile.all().first():
return profile.country_code if profile.country_code else None
elif profile := obj.junior_profile.all().first():
return profile.country_code if profile.country_code else None
@staticmethod
def get_phone(obj):
"""
:param obj: user object
:return: user phone number
"""
if profile := obj.guardian_profile.all().first():
return profile.phone if profile.phone else None
elif profile := obj.junior_profile.all().first():
return profile.phone if profile.phone else None
@staticmethod
def get_user_type(obj):
"""
:param obj: user object
:return: user type
"""
if obj.guardian_profile.all().first():
return dict(USER_TYPE).get('2')
elif obj.junior_profile.all().first():
return dict(USER_TYPE).get('1')
@staticmethod
def get_is_active(obj):
"""
:param obj: user object
:return: user type
"""
if profile := obj.guardian_profile.all().first():
return profile.is_active
elif profile := obj.junior_profile.all().first():
return profile.is_active
class GuardianSerializer(serializers.ModelSerializer):
"""
guardian serializer
"""
name = serializers.SerializerMethodField()
first_name = serializers.SerializerMethodField()
last_name = serializers.SerializerMethodField()
username = serializers.SerializerMethodField()
email = serializers.EmailField(required=False)
class Meta:
"""
meta class
"""
model = Guardian
fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone',
'is_active', 'country_name', 'image', 'email', 'is_deleted')
def validate(self, attrs):
"""
to validate request data
:return: validated attrs
"""
email = attrs.get('email')
phone = attrs.get('phone')
if USER.objects.filter(email=email).exclude(id=self.context.get('user_id')).exists():
raise serializers.ValidationError({'details': ERROR_CODE['2003']})
if Guardian.objects.filter(phone=phone).exclude(user__id=self.context.get('user_id')).exists():
raise serializers.ValidationError({'details': ERROR_CODE['2012']})
return attrs
def update(self, instance, validated_data):
"""
to update user and its related profile
:param instance: user's guardian object
:param validated_data:
:return: guardian object
"""
instance.user.email = self.validated_data.get('email', instance.user.email)
instance.user.username = self.validated_data.get('email', instance.user.username)
instance.user.save()
instance.country_code = validated_data.get('country_code', instance.country_code)
instance.phone = validated_data.get('phone', instance.phone)
instance.save()
return instance
@staticmethod
def get_name(obj):
"""
:param obj: guardian object
:return: full name
"""
return get_user_full_name(obj.user)
@staticmethod
def get_first_name(obj):
"""
:param obj: guardian object
:return: first name
"""
return obj.user.first_name
@staticmethod
def get_last_name(obj):
"""
:param obj: guardian object
:return: last name
"""
return obj.user.last_name
@staticmethod
def get_username(obj):
"""
:param obj: guardian object
:return: email
"""
return obj.user.username
class JuniorSerializer(serializers.ModelSerializer):
"""
junior serializer
"""
name = serializers.SerializerMethodField()
first_name = serializers.SerializerMethodField()
last_name = serializers.SerializerMethodField()
username = serializers.SerializerMethodField()
email = serializers.EmailField(required=False)
class Meta:
"""
meta class
"""
model = Junior
fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone',
'is_active', 'country_name', 'image', 'email', 'is_deleted')
def validate(self, attrs):
"""
to validate request data
:return: validated attrs
"""
email = attrs.get('email')
phone = attrs.get('phone')
if email and USER.objects.filter(email=email).exclude(id=self.context.get('user_id')).exists():
raise serializers.ValidationError({'details': ERROR_CODE['2003']})
if phone and Junior.objects.filter(phone=phone).exclude(auth__id=self.context.get('user_id')).exists():
raise serializers.ValidationError({'details': ERROR_CODE['2012']})
return attrs
def update(self, instance, validated_data):
"""
:param instance: user's junior object
:param validated_data: validated data
:return: instance
"""
instance.auth.email = self.validated_data.get('email', instance.auth.email)
instance.auth.username = self.validated_data.get('email', instance.auth.username)
instance.auth.save(update_fields=['email', 'username'])
instance.country_code = validated_data.get('country_code', instance.country_code)
instance.phone = validated_data.get('phone', instance.phone)
instance.save(update_fields=['country_code', 'phone'])
return instance
@staticmethod
def get_name(obj):
"""
:param obj: junior object
:return: full name
"""
return get_user_full_name(obj.auth)
@staticmethod
def get_first_name(obj):
"""
:param obj: junior object
:return: first name
"""
return obj.auth.first_name
@staticmethod
def get_last_name(obj):
"""
:param obj: junior object
:return: last name
"""
return obj.auth.last_name
@staticmethod
def get_username(obj):
"""
:param obj: junior object
:return: email
"""
return obj.auth.username
class UserManagementDetailSerializer(serializers.ModelSerializer):
"""
user management details serializer
"""
user_type = serializers.SerializerMethodField()
guardian_profile = GuardianSerializer(many=True)
junior_profile = JuniorSerializer(many=True)
associated_users = serializers.SerializerMethodField()
class Meta:
"""
meta class
"""
model = USER
fields = ('id', 'user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users')
def get_user_type(self, obj):
"""
:param obj: user object
:return: user type
"""
return GUARDIAN if self.context['user_type'] == GUARDIAN else JUNIOR
def get_associated_users(self, obj):
"""
:param obj: user object
:return: associated user
"""
if self.context['user_type'] == GUARDIAN:
profile = obj.guardian_profile.all().only('user_id', 'guardian_code').first()
if profile.guardian_code:
junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code],
is_verified=True).select_related('auth')
serializer = JuniorSerializer(junior, many=True)
return serializer.data
elif self.context['user_type'] == JUNIOR:
profile = obj.junior_profile.all().only('auth_id', 'guardian_code').first()
if profile.guardian_code:
guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code,
is_verified=True).select_related('user')
serializer = GuardianSerializer(guardian, many=True)
return serializer.data

View File

View File

@ -0,0 +1,109 @@
"""
web admin test analytics file
"""
# django imports
from django.urls import reverse
from rest_framework import status
# local imports
from web_admin.tests.test_set_up import AnalyticsSetUp
class AnalyticsViewSetTestCase(AnalyticsSetUp):
"""
test cases for analytics, users count, new sign-ups,
assign tasks report, junior leaderboard, export excel
"""
def setUp(self) -> None:
"""
test data set up
:return:
"""
super(AnalyticsViewSetTestCase, self).setUp()
def test_total_sign_up_count(self):
"""
test total sign up count
:return:
"""
self.client.force_authenticate(self.admin_user)
url = reverse('web_admin:analytics-users-count')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming four users exists in the database
self.assertEqual(response.data['data']['total_users'], 4)
# Assuming two guardians exists in the database
self.assertEqual(response.data['data']['total_guardians'], 2)
# Assuming two juniors exists in the database
self.assertEqual(response.data['data']['total_juniors'], 2)
def test_new_user_sign_ups(self):
"""
test new user sign-ups
:return:
"""
self.client.force_authenticate(self.admin_user)
url = reverse('web_admin:analytics-new-signups')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming four users exists in the database
self.assertEqual(response.data['data'][0]['signups'], 4)
def test_new_user_sign_ups_between_given_dates(self):
"""
test new user sign-ups
:return:
"""
self.client.force_authenticate(self.admin_user)
url = reverse('web_admin:analytics-new-signups')
query_params = {
'start_date': '2023-09-12',
'end_date': '2023-09-13'
}
response = self.client.get(url, query_params)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming four users exists in the database
self.assertEqual(response.data['data'][0]['signups'], 4)
def test_assign_tasks_report(self):
"""
test assign tasks report
:return:
"""
self.client.force_authenticate(self.admin_user)
url = reverse('web_admin:analytics-assign-tasks')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming two completed tasks exists in the database
self.assertEqual(response.data['data']['task_completed'], 2)
# Assuming two pending tasks exists in the database
self.assertEqual(response.data['data']['task_pending'], 2)
# Assuming two in progress tasks exists in the database
self.assertEqual(response.data['data']['task_in_progress'], 2)
# Assuming two requested tasks exists in the database
self.assertEqual(response.data['data']['task_requested'], 2)
# Assuming two rejected tasks exists in the database
self.assertEqual(response.data['data']['task_rejected'], 2)
# Assuming two expired tasks exists in the database
self.assertEqual(response.data['data']['task_expired'], 2)
def test_junior_leaderboard(self):
"""
test junior leaderboard
:return:
"""
self.client.force_authenticate(self.admin_user)
url = reverse('web_admin:analytics-junior-leaderboard')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_export_excel(self):
"""
test export excel
:return:
"""
self.client.force_authenticate(self.admin_user)
url = reverse('web_admin:analytics-export-excel')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertURLEqual(response.data['data'], self.export_excel_url)

View File

@ -0,0 +1,255 @@
"""
web_admin test article file
"""
# django imports
from django.test import TestCase
from django.urls import reverse
from django.contrib.auth import get_user_model
from rest_framework.test import APITestCase
from rest_framework.test import APIClient
from rest_framework import status
# local imports
from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage
from web_admin.tests.test_set_up import ArticleTestSetUp
# user model
User = get_user_model()
class ArticleViewSetTestCase(ArticleTestSetUp):
"""
test cases for article create, update, list, retrieve, delete
"""
def setUp(self):
"""
inherit data here
:return:
"""
super(ArticleViewSetTestCase, self).setUp()
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
def test_article_create_with_default_card_image(self):
"""
test article create with default card_image
:return:
"""
url = reverse(self.article_list_url)
response = self.client.post(url, self.article_data_with_default_card_image, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Check that a new article was created
self.assertEqual(Article.objects.count(), 2)
def test_article_create_with_base64_card_image(self):
"""
test article create with base64 card image
:return:
"""
self.client.force_authenticate(user=self.admin_user)
url = reverse(self.article_list_url)
response = self.client.post(url, self.article_data_with_base64_card_image, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Check that a new article was created
self.assertEqual(Article.objects.count(), 2)
def test_article_update(self):
"""
test article update
:return:
"""
self.client.force_authenticate(user=self.admin_user)
url = reverse(self.article_detail_url, kwargs={'pk': self.article.id})
response = self.client.put(url, self.article_update_data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.article.refresh_from_db()
self.assertEqual(self.article.title, self.article_update_data['title'])
self.assertEqual(self.article.article_cards.count(), 1)
self.assertEqual(self.article.article_survey.count(), 6)
self.assertEqual(self.article.article_survey.first().options.count(), 3)
def test_articles_list(self):
"""
test articles list
:return:
"""
url = reverse(self.article_list_url)
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming only one article exists in the database
self.assertEqual(len(response.data['data']), 1)
def test_article_retrieve(self):
"""
test article retrieve
:return:
"""
url = reverse(self.article_detail_url, kwargs={'pk': self.article.id})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
def test_article_delete(self):
"""
test article delete
:return:
"""
url = reverse(self.article_detail_url, kwargs={'pk': self.article.id})
response = self.client.delete(url)
self.article.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.article.is_deleted, True)
def test_article_create_with_invalid_data(self):
"""
test article create with invalid data
:return:
"""
url = reverse(self.article_list_url)
# Missing article_cards
invalid_data = {
"title": "Invalid Article",
"article_survey": [{"question": "Invalid Survey Question"}]
}
response = self.client.post(url, invalid_data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_article_status_change(self):
"""
test article status change (publish/un-publish)
:return:
"""
url = reverse('web_admin:article-status-change', kwargs={'pk': self.article.id})
data = {
"is_published": False
}
response = self.client.patch(url, data, format='json')
self.article.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.article.is_published, False)
def test_article_card_remove(self):
"""
test article card remove
:return:
"""
url = reverse('web_admin:article-remove-card', kwargs={'pk': self.article_card.id})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(ArticleCard.objects.count(), 0)
def test_article_survey_remove(self):
"""
test article survey remove
:return:
"""
url = reverse('web_admin:article-remove-survey', kwargs={'pk': self.article_survey.id})
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(ArticleSurvey.objects.count(), 0)
def test_article_card_create_with_default_card_image(self):
"""
test article card create with default card_image
:return:
"""
url = reverse('web_admin:article-test-add-card')
response = self.client.post(url, self.article_card_data_with_default_card_image, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Check that a new article card was created
self.assertEqual(ArticleCard.objects.count(), 2)
def test_article_cards_list(self):
"""
test article cards list
:return:
"""
url = reverse('web_admin:article-test-list-card')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming only one article exists in the database
self.assertEqual(len(response.data['data']), 1)
class DefaultArticleCardImagesViewSetTestCase(APITestCase):
"""
test case for default article card image
"""
def setUp(self):
"""
data setup
:return:
"""
self.client = APIClient()
self.admin_user = User.objects.create_user(username='admin@example.com', email='admin@example.com',
password='admin@1234', is_staff=True, is_superuser=True)
self.default_image = DefaultArticleCardImage.objects.create(
image_name="card1.jpg",
image_url="https://example.com/updated_card1.jpg")
def test_default_article_card_image_list(self):
"""
test default article card image list
:return:
"""
self.client.force_authenticate(user=self.admin_user)
url = reverse('web_admin:default-card-images-list')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming only one default article card image exists in the database
self.assertEqual(len(response.data['data']), 1)
class ArticleListViewSetTestCase(ArticleTestSetUp):
"""
test cases for article list for junior
"""
def setUp(self):
"""
data setup
:return:
"""
super(ArticleListViewSetTestCase, self).setUp()
self.client.force_authenticate(user=self.user)
def test_article_list(self):
"""
test article list
:return:
"""
url = reverse('web_admin:article-list-list')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming only one article exists in the database
self.assertEqual(len(response.data['data']), 1)
class ArticleCardListViewSetTestCase(ArticleTestSetUp):
"""
test cases for article card list for junior
"""
def setUp(self):
"""
data setup
:return:
"""
super(ArticleCardListViewSetTestCase, self).setUp()
self.client.force_authenticate(user=self.user)
def test_article_cards_list(self):
"""
test article cards list for junior
:return:
"""
url = reverse('web_admin:article-card-list-list')
query_params = {
'article_id': self.article.id,
}
response = self.client.get(url, query_params)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming only one article exists in the database
self.assertEqual(len(response.data['data']), 1)
# Add more test cases for edge cases, permissions, etc.

View File

@ -0,0 +1,160 @@
"""
web admin test auth file
"""
from datetime import datetime
from django.utils import timezone
from django.urls import reverse
from django.contrib.auth import get_user_model
from rest_framework.test import APITestCase, APIClient
from rest_framework import status
from account.models import UserEmailOtp
from base.constants import USER_TYPE
from guardian.tasks import generate_otp
from web_admin.tests.test_set_up import BaseSetUp
User = get_user_model()
class AdminOTPTestCase(BaseSetUp):
"""
test case to send otp to admin email
"""
def setUp(self):
"""
inherit data here
:return:
"""
super(AdminOTPTestCase, self).setUp()
self.url = reverse('web_admin:admin-otp')
def test_admin_otp_for_valid_email(self):
"""
test admin otp for valid email
:return:
"""
data = {
'email': self.admin_email
}
response = self.client.post(self.url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(UserEmailOtp.objects.count(), 1)
def test_admin_otp_for_invalid_email(self):
"""
test admin otp for invalid email
:return:
"""
data = {
'email': 'notadmin@example.com'
}
response = self.client.post(self.url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
class AdminVerifyOTPTestCase(BaseSetUp):
"""
test case to verify otp for admin email
"""
def setUp(self):
"""
inherit data here
:return:
"""
super(AdminVerifyOTPTestCase, self).setUp()
self.verification_code = generate_otp()
expiry = timezone.now() + timezone.timedelta(days=1)
self.user_email_otp = UserEmailOtp.objects.create(email=self.admin_email,
otp=self.verification_code,
expired_at=expiry,
user_type=dict(USER_TYPE).get('3'),
)
self.url = reverse('web_admin:admin-verify-otp')
def test_admin_verify_otp_with_valid_otp(self):
"""
test admin verify otp with valid otp
:return:
"""
data = {
'email': self.admin_email,
"otp": self.verification_code
}
response = self.client.post(self.url, data)
self.user_email_otp.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.user_email_otp.is_verified, True)
def test_admin_verify_otp_with_invalid_otp(self):
"""
test admin verify otp with invalid otp
:return:
"""
data = {
'email': self.admin_email,
"otp": generate_otp()
}
response = self.client.post(self.url, data)
self.user_email_otp.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(self.user_email_otp.is_verified, False)
class AdminCreateNewPassword(BaseSetUp):
"""
test case to create new password for admin email
"""
def setUp(self):
"""
inherit data here
:return:
"""
super(AdminCreateNewPassword, self).setUp()
self.verification_code = generate_otp()
expiry = timezone.now() + timezone.timedelta(days=1)
self.user_email_otp = UserEmailOtp.objects.create(email=self.admin_email,
otp=self.verification_code,
expired_at=expiry,
user_type=dict(USER_TYPE).get('3'),
)
self.url = reverse('web_admin:admin-create-password')
def test_admin_create_new_password_after_verification(self):
"""
test admin create new password
:return:
"""
self.user_email_otp.is_verified = True
self.user_email_otp.save()
data = {
'email': self.admin_email,
"new_password": "New@1234",
"confirm_password": "New@1234"
}
response = self.client.post(self.url, data)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(UserEmailOtp.objects.count(), 0)
def test_admin_create_new_password_without_verification(self):
"""
test admin create new password
:return:
"""
data = {
'email': self.admin_email,
"new_password": "Some@1234",
"confirm_password": "Some@1234"
}
response = self.client.post(self.url, data)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(UserEmailOtp.objects.count(), 1)

View File

@ -0,0 +1,407 @@
"""
web_admin test set up file
"""
# django imports
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.conf import settings
from rest_framework.test import APITestCase
from rest_framework.test import APIClient
# local imports
from guardian.models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints
from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption
# user model
User = get_user_model()
# image data in base 64 string
base64_image = ("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAoHCBISEhIREhISEhgSERIREREYEhg"
"SGRERGRgZGhgYGBgcIS4lHB4rHxgYJjgmLC8xNTU1GiQ7QDszPy40NTEBDAwMEA8QGhISHjEhISE0NDQ0NDQ0N"
"DQ0NDQ0NDQxNDQ1NDQxNDQ0NDQ0NDQ0NDExNDE0MTQ0NDQ0NDQ0NDQ0P//AABEIALcBEwMBIgACEQEDEQH/xAAb"
"AAACAgMBAAAAAAAAAAAAAAAAAQIEAwUGB//EAEkQAAIBAgMEBgYGBgcIAwAAAAECAAMRBBIhBTFRYQYTIkFxkTJC"
"UoGhsRRDYnKCkhYjU8HR4QcVVGOD0vEkM5OissLT8ERVlP/EABgBAQEBAQEAAAAAAAAAAAAAAAABAgME/8QAIBEBA"
"QEBAAIDAQEBAQAAAAAAAAERAhIhMUFRAyJhE//aAAwDAQACEQMRAD8AuXiiJivAd4rxQgMGO8hHeQSvCRhKiUcheO"
"8CUJG8AZBK8d5G8cBxyMcCV4XkY7wHCKOA4RXheA4RQgOEUJQxHFCFOEUcBiSEhJQJRiREYgStCEIFGEIoBEYXheQ"
"AjkYAwJQihAccjHAcIoQHHIxwiQhFAGFSjkbwhEoCKOA4RQgOEUcAjvFKNPaSNdgjlL2VwV7YHeqneOBuL791pRfv"
"CVU2jRPrOn3qZP8A0ZpmTE0zuqU/A1FQ/lax+EDJGI8jWvlNuNtPORBhUowZGOBISUgJIQJQihAowvCK8AhFeEgcI"
"oQHCKOA7wvFCA4QhAcIrwgShIkxwHHIg90jSqBlzDcb/A2/dAyiCtfUd+4zFiKopo7nciM58ACZDZwtRpA7xSp38c"
"ohFmEIQHCKJmABJNgASSdwA3mBT2i5IWkpINS+YjetIemeRNwo5tfukGo303ACwG6wkab2DVn0NSxVTvSmPQXkbEs"
"RxYjulGviqjE5dBN5kWLRwR7jInDuOcqpiqi75Zw+0GYgWPlI2gKGU3ChTxXsHzGsyriqg3Van4m6z/rzTZCiGGsq"
"vhVJ3wmMa7Sqjvpt96nY/wDIyj4TMu1j61IeK1f+1k/7pWfBHumF8LUFgouWIVBxYmwHn38LwZG+w1ZaiB1DAEstm"
"ABBG/UEgzKJFECJTpqbimmUG1s7Elnc82YsffJCRlKEUIFGKEjeAQheK8gcIoxAJKRhAlHIR3gShISUBxyMcDG1TK"
"DmUkAbwM1x4DWURj1QqVcVKbNkJvc0mtcA99vHdNlNPtnZocGrTFnGrgfWKONt5kos47FCjUSo3oOOrc+yRcofi05"
"zAbYc5KTEj9eq3/uzmBB95kNt4rrKVDtf7tyHXnaynnpce+aYVwTfKFN9XF9bA917A+FpPJHc9KsRkwrjvqMtMeBN"
"2/5QZtaAsiDgqj4CcZtrH/SPoaA71zuPtk5B8m852GJxC01zMbDMqDmzEKAPeZZdGe8r4XFCo1TLqqMEze049Kx7w"
"NB5zT9INsFf9noHNUqHJcepfTfx+UnRqdXSTCYbtuq5Ge3YVvXYnS537o0b681m1cQMyUd+bt1B/dg6L+JhbwDS67"
"inTLOdES7NbuA1NpocPUNSobLnqVGD1ADpRX1VY8QthYbzc981BaqMahu3uEyJhXbcLTZ4fBqgzVDraZmxtNRYWm2"
"o1qbKJ3y2tGnTGttJixG0idFEqZKlQ3N98mLrLisffspvMwUsHUPaLHwmxw2BVe7WXGCqLnQCMNalcJV9qWcDS1NQ"
"nNkzU6Z4vudx4DsDmXlDE7UarUXD0Dq7ZS+8Io1Zj4AE+6blVVQqILKihUG85Rx4k7zzJk3TqYYkhIiSEjCUIQga6"
"8V4ooU4RQkDhFCBKO8jeECV4XkYQJXheRjgOSvIAxwJQvIwgcL0godXUqINASHX7p1+BvNUmthzM67pZhcypU4XRv"
"A7vj85ydGmSRyNjOPXrRbwDKlSm7g2QqzAbyVJNvMTNjsbUr1OsqNYJqiA2CcLc+cxV6eU3vpYHz3yuzm2gv47v5z"
"E6tRdwjqTmYM3BQcoP3mm42Vj6SEhKb1KjdyWyoL7hY7uJmowOw8RWsxXKvcXOUEclGpnR9VUwtJ3apSRKaliEo2"
"JPcAS2pJsNZ15lMU+kONqVGp4Yfq8xV6rXuyi/ZWw0uSL2ufRF9JtdkNTwyZFXmp3kt3knvM4vZuJZ6r16tRFLG7"
"FiMxOg7I3AWsL23Cb+njLgEeIuLXnWLG+frKlybgQXA8ZfwTh0Vh3j4ywEm1UqWDAlpKdtwmdUlbHY6nRQsxGm4c"
"4VOvXSmpZyBYTjdq7aeqSFuq/OV9qbVesddFvumDZeC+kVUp6hdXqMPVpr6R8ToBzYTn11vqOnPPjNro+i+C6uma"
"7DtVhlp/Zog7/AMTDyUcZuryJYdwCgABVG5VAsAOQFoXlkxyt26mDGDIXkgZUTvCLNFCNfETFeRkVK8LyMtYbChwT"
"1iLb1b3Y/hkFe8Ly+mzr31bQE+ja9pE4Rcpa7aWvuG+XE2KccylEHteY/hMbFR3N+Yfwg0oXgHS9u18DMtOhnNkLE"
"86Zt5i8isUInUqSDvBsfGK8Cd4XkLx3gTvC8xfSKanLULj7qhtPeRJtXo+q5P3iU+SkRpjBtKl1lKonFSR4jUTjMJ"
"SHWWPebTuhWTvQtySrTb4Egzkm2biDUY08PXK5mKE0z6N+zecf6y34WD6EHYM25QFVOLXOpm52fsdFIeoAzb1U7l5"
"24xbLwddNamFrOfV3Jl56jWdBRwVR99OrT+8EI8w9/hJ/P+d+eisF5xnTPaJd1wqXISz1Ld7n0VPgDf3jhOx26hwlC"
"pXcoQgAVbkFnOirqOP755W2Ie7OWOZmLseLHUmd2WajhqisGybt1wrfAmdZsSilU3rVFp2toWy33776i9vA9xM4s1G"
"be3mYUUJN8nWAd1jY+UsHrmGx+FT9XTqK9jlOTthT9phoPObLOoF7zy3AdY4ChkRF1NNcxPgb7p0CYmoqdltw3Humt"
"Tnqblb/AGnthKYOus4rH7Qaq12OncJDEVyxuxuZXYicuutevnnEGedh0fwfU0QzCz18rvxWn6i/HMfvDhNBsTALWqg"
"MLpTHWVeag9lPxNYeGbhOueoWJY7ybmXmfbn/AE6+k80eaYg0YaacmUNJBpiDRhoGa8JjvCBTvIVXyqW4SRMxV0zKR"
"5SB4Z6mW5eopOtkcoF8t58Zq9pF+su7FyR6R3kbhfnNwoBRXXdorD2WHcfGa3aydkNwNj4GZ6+G+flSSow3MR7zLaY"
"yoPrH/MZrc0yK8SpY2i42p+0b8xjOLf2jKC1JLrZpleXFVCfTbztMqVmZtWJ14zWo8vYLffhrBi4TC8jeF4ErwvI3h"
"eBRxnp+4TDeZsV6XuErmYt9tyegZ3ez79VT19RflOCvO+wY/Vp9wfKa5Ss+bnC8LTWdI9pjC4d6gIzt2KQPfUINjbv"
"AALHks0w4vpxtIV6woA3p4cnNro1e1m/KDl8S05g0U9kTLpvuTckknUkneSe8kxGFZcMqbsqg/dEvIk1qb78JfpV7/"
"wAJZXLufa1RpgHNuPfz8ZYaoRulQVIGrNMe2HFJrfjKz6C8z1nvLmwcKHqGq4ulCzkHc9U+gnPUFjyXnOPU9+nt46s"
"52t3s/CfR6S0zo72qVuTkdlPwrp4lpYzSu1Qkkk3JJJPEx5ppzt32sZow0r5pINKjOGkw0wBpNWgZ80Ux5oQJnCniI"
"voh9oeU2hpiRKiTK3/lrUwzISyMpuLMhHZccDMWJw9N1ZM4psynsOQtj3Wc6MLzcBRMeJwqOuV1BHxB4g90mU9OAOk"
"SvNptvZTUQHBzKTa/eOF5pC8zPXpeloPJq8pB5lR5WV6nqZvsDhxluTa81OysOXbkN/hN/cAWA3Rf+LJPtH6OvtHyh"
"1Ce0YZ+UYY8JP8AX61nJjDJ7Rk/oqcWgpPCZkvwjL+p6aTHoFqEC+4b/CUnmw2oP1jeC/ISg4mftpjG+eg4Ydhfuj5"
"Tz4DUeInodD0V8B8pvlz6ZQJ5Z0z2t9JxJRDdKGamnBnv228wFH3ec7fpdtY4bDNkNqlW9OlxW47T/hHxKzy6nTCjw"
"0m2TQaCNorxGQMGNHsbxWvIQq+tS8iX1tK+Gexliqljy3zU9xicyXRr3AkkgADUknQAc7zucFstaVGnSIBIu9Q39Kq"
"3peIAAUchNJ0QwBqVDWYdmibJ9qqRp+UG/iVnZmlymMei9StZ9DT2R5mH0RPZHmZsTT5RdXyjGdigMIvsiAwi+yJf6"
"rkfKMUjwPlGGxSGFX2RJfRl4CXOr5GHV8jGGqn0deAhLnVHgfKKMNXDhxxMRoLzjNE85gqU27rzbDMmGB3Bj4awNBR"
"vVvKaPGbSqYOotRaqWIAeg5sHW51HA67/AJ7p1Gy9o0cWmemdRbPTPpIeY7xz3RC+mj2slNqNRCrklGygKWOa3Zt77"
"TgDsyqfq6g8UInsL4YcpgbDLxXzEl52nk8i/qyt+zf8ss4fZdS+qMPwz1L6MnL5wGHTh8I8WfJzOx8OiIQ6sCSPVO6"
"bMYVDqB8xNqKKD/SZkVB/pHgvm0ybPU+qPOZBsq/qjzkdo9H2Japh6jAsSzUmqNkYnUlD6hPDd4b5z1Q1Q3Vv1qNTN"
"8pbK6Xtfc1iDbmpjxh5V1C7K5LJjZ9u5ZyVSs4Fn6zLcHODoCO863Q79b219LW0RrVFBDFqikEHXtW7wVuQ48LHkd8"
"vjDyq/tjYNarUzJURLCygG1xz01mpbo/i1+soN4syn4CTTDP6dMPUAJtqWsQdbPqQQQdD394tKdTDqxIAWmw9JHBU9"
"28W5bxp4zP/AJxrzqwmxMSWAJoXvuFRifLJOtFZlQEqNBr2rDzM4KojKynLlYEZXV7knuytv3A6aHwnQbP6QOoC4he"
"sXvqA5WUfaJ0O7vPf6RicyM3q1zvSgYjEYhn6tslNFSnZlcZd7EZTvJ4dwE583Ghnqf8AVNGqDUwrimd7Kq3Qk69un"
"3anepF+Jmo2pspDpiafVncKy6o3Dt27Pg4HK8uJrghrHpNxtDo7Up6p2xv0328P9ZpmUqbMCJMaPNEkRMZEBbtZdw9"
"6mVFF2YhUHFibASi06/8Ao/2TnqNinHZp3SlzcjtN7gbe88JdxHZbGwlPDUadEAkqLs3tOdWPnLpqLwMnlEXViTI2x"
"9YvsmLrF9k+cmaYiyCMgh1q8D5x9aOB848ghkEZE1HrR7J84Gry+MnkEMgjIqHXcvj/AChMmQQjIGzGYmvM5EiymbY"
"aTbWyVxFMq2+2jW1BnAK+K2ZWFy+QGyuu9VO+3EcjPV2QzX4/ZtOspSooYH4eElmrKsdGeklPFAU2qIXIuh3dYO8W3"
"Zhbd8Jvmp8J4rtnY1bZ9TrKRZ6ZOa2oynxG4852vRHputVVp4huCiqdCp4VP83nxidfVS8/jtDS5SJonhLQPf5GBmm"
"FPqTwh1J4S1FArimw3eXGYcbs6nWUCoCCPRcGzIfst+7ce+XYXHEQOLx+yatElizOg+sXeuvrp3feGm+4WUGVaah1q"
"hVNiAai9W191jc5O70dOVzeehF14jzE0+L2PSZ+spVFoPmzNaxVj7RUEENzBHO8DlKaI5DremxF75SM6g6HMpKuvO5"
"te1wbwq1FtlqqdPRqLVCqDpuLAMh+Hdczpf6opub18QatiCqhxTUaWvckvm78wYGQbZgFwMVTI1AzKrNY9zFXUH8sN"
"OQxVGrYgXqLuKsi1CRzUGzjwHumKmhAzKM3fY5l3cGBJB0Oh7+8Tqf0fojdXpr9kKoUeC5tPO3KA2DRvm+k2PENluN"
"wBs2tu6+6Qc9QJRr026t11y5hoN18oKmx4g28Zv8ABbfOiYhAL6ZwDY3G7dlbv0uDodDHV2PhdBUxIPeMzrpzUk6Hm"
"NZA7OwY/wDmDv8AWo6g9x7OsQWn2PTdc+FqKg9j06RPDJvQ/dIGtyDOe2ps1b5cTT6snRal7o7buxUta+ugYBuU3iU"
"sPRXrFx1RFGmc1EZbk20LKQBcgW3cpdp7ewdQij9Io1C4IyZg3WADW62sdATuhl5tj+jVRLtTOcezuPumjdGU2YEHm"
"J6i7bNps3+09WBvpB7oh+zmUlfAG3KafpZsFBQfG08W7IqhjTdFdHU2yhSigg6ixN9+8SWNSuIwuHarUSlT1aowVeX"
"EnkBc+6e0bLwKUKNOkm5FA8T3k8ydZxf9HOy87VMUV9ECmneATqSPh4C3Gegii3CSNI2ECBJ9SeERonhL6GMgSJAmU"
"0WkeobhAx2E5na3S+nRqNSp0alZkYo5DKiq47u8/CdV1DTgekn9H9avXqYinVS9RsxRhaxsBoR4SU9sn6Z4o6jZx/O"
"5+VOQPTTGDU7Nb3M/+SaM9DNr0/QZvwYgr8LiMbH24m41z/jB/mTJsXK3P6eYj/62p/xH/wDHCaf6Dtz++80jk0yvV"
"TIFhMxSRKTpiMVxItaZssWWBTxNBKilHUMDoRPOukPRl8O5r4fNbeRv04Ed4nqBSQeiGBBAIMzZqy48ewfSerS0/XL"
"9lajADwE3CdPrKAaddjxNSdXieimGdixpjXlMQ6JYQfViZ9xc5rmm6ff3NU/4n8pibp4f7O/vqfynVHozhR9WvlD+o"
"MMPq0/KI2njy5JunL92G86n8pH9Nqn9nH5iZ2I2Phx9Wn5ZlXZVAfVp5RtM5cOemlfuw6ebfxkW6Y4o7qFPyc/vnoK"
"bIo/s18hLC7Io/s18hH+jOXmn6W4zuo0/yt/GH6V439lT/I38Z6imyKPsL5CZP6rpD1F8hHszl5Q3SbHndTQf4ZMP0"
"j2kfUT/AIRnqpwNMeovkIhhE9lfyiPaenkWJ2ljapBqUaVQqCFL4ZWsO+2YTD1uL/s9D/8AHT/yT2Q4VPZHkJjxgSl"
"TqVOrz9WjPlA1awvYR7PTyX6VtDJ1YpqE3ZBhkC777gvHWYsMMcj56dMI1iMwoICAd+uWdRiemBqXFE06Q3dvD9Yb/"
"eFTn7M0+Ix+MqHTH0wDpYBqHxyD5xl/TZ+NZWw+Mdi1SmCSbsxoJr78s6LZeKxlWm+HxFOpiKDBUYIQjUgN1sgt3bmE"
"0VXZOIqEE1UxGouoxGcn3m9p1WB6GBaYr0atSjW9JUUE07g6KyvdmG/Um3KWc02fjpMDtzCYemKFOnVQU0zCmaToSt7M"
"13tnNyLnXfIVemPsUiebOB8AD84ld8Th8tWmKdanchCCoYi47Bb1XW45ZuU5tcG7OVSx4EkLcd2/v5TWYzrdP0txJOi0"
"1HDKT8SZJOl9YelTpt4XX95lGnsGqd5A/C3zIA+Mm+y6VP8A3ldF43dE+AzQjaJ0xHrUj+FwfgQJap9KqB39YnigPyJnN"
"ddgU+sap9xGf43t8Jmp45NOqwdd/tELTPwAMnlJ841JXXYba9Kp6FS/4WHzEuAk6g3B1B33E45Fx1Q6YWlTXddv1j28WG"
"hnZ4NMtNFIIIUAgm5v36gSSy/C5Z8l2odqZ4SjD2v/AERzLCRGIyBhCbChaEICKwKwhMjGyGYnQwhAr1FMwtTPKEJloivG"
"wgjjj8I4TNtVZpgnvlpFMITUZZ1TnApzihKqLU+cQpc4QhEhS5xVsMrqytqGBVhxB3whCOZxHQHAvuDJf2WImtxX9HFK3Y"
"rOORsR8oQkxdazE9A8QostdSBuBFvlKI6PY6ibg0z4VCscJi9WK3WzRiwmRqGHY3vndi5Hnebangse2hrpSHBEAjhE6rXj"
"GYdEC/arYms/HtkfAS7huhuDXXJmPEkn5whN4xrZ0NkYdPRpIPdLS0kG5QPdCEskKlYcIWHCEJUO3KFuUIQD3QhCB//Z")
# export excel path and
# export excel url
export_excel_path = 'analytics/ZOD_Bank_Analytics.xlsx'
export_excel_url = f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{export_excel_path}"
class BaseSetUp(APITestCase):
"""
basic setup
"""
def setUp(self) -> None:
"""
user data
:return:
"""
# user and admin email
self.user_email = 'user@example.com'
self.admin_email = 'admin@example.com'
self.client = APIClient()
# create user
self.user = User.objects.create_user(username=self.user_email, email=self.user_email)
self.user.set_password('user@1234')
self.user.save()
# create admin
self.admin_user = User.objects.create_user(username=self.admin_email, email=self.admin_email,
is_staff=True, is_superuser=True)
self.admin_user.set_password('admin@1234')
self.admin_user.save()
class ArticleTestSetUp(BaseSetUp):
"""
test cases data set up
for article create, update, list, retrieve and
remove card, survey and add test card, list test card and
default image upload and list
"""
def setUp(self):
"""
set up data for test
create user and admin
create article, article card and article survey and survey options
:return:
"""
super(ArticleTestSetUp, self).setUp()
# create article
self.article = Article.objects.create(title="Existing Article", description="Existing Description",
is_published=True)
# create article card
self.article_card = ArticleCard.objects.create(article=self.article, title="Existing Card 1",
description="Existing Card 1 Description")
# create article survey
self.article_survey = ArticleSurvey.objects.create(article=self.article, points=5,
question="Existing Survey Question 1")
# create article survey options
SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 1", is_answer=True)
SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 2", is_answer=False)
# article api url used for get api
self.article_list_url = 'web_admin:article-list'
# article api url used for post api
self.article_detail_url = 'web_admin:article-detail'
# article card data with default card image
self.article_card_data_with_default_card_image = {
"title": "Card 1",
"description": "Card 1 Description",
"image_name": "card1.jpg",
"image_url": "https://example.com/card1.jpg"
}
# article card data with base64 image
self.article_card_data_with_base64_image = {
"title": "Card base64",
"description": "Card base64 Description",
"image_name": "base64_image.jpg",
"image_url": base64_image
}
# article survey option data
self.article_survey_option_data = [
{"option": "Option 1", "is_answer": True},
{"option": "Option 2", "is_answer": False}
]
# article survey data
self.article_survey_data = [
{
"question": "Survey Question 1",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 2",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 3",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 4",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 5",
"options": self.article_survey_option_data
},
]
# article data with default card image
self.article_data_with_default_card_image = {
"title": "Test Article",
"description": "Test Description",
"article_cards": [
self.article_card_data_with_default_card_image
],
# minimum 5 article survey needed
"article_survey": self.article_survey_data
}
# article data with base64 card image
self.article_data_with_base64_card_image = {
"title": "Test Article",
"description": "Test Description",
"article_cards": [
self.article_card_data_with_base64_image
],
# minimum 5 article survey needed
"article_survey": self.article_survey_data
}
# article update data
self.article_update_data = {
"title": "Updated Article",
"description": "Updated Description",
# updated article card
"article_cards": [
{
"id": self.article_card.id,
"title": "Updated Card 1",
"description": "Updated Card 1 Description",
"image_name": "updated_card1.jpg",
"image_url": "https://example.com/updated_card1.jpg"
}
],
# updated article survey
"article_survey": [
# updated article survey
{
"id": self.article_survey.id,
"question": "Updated Survey Question 1",
"options": [
{"id": self.article_survey.options.first().id,
"option": "Updated Option 1", "is_answer": False},
# New option
{"option": "New Option 3", "is_answer": True}
]
# added new articles
}] + self.article_survey_data
}
class UserManagementSetUp(BaseSetUp):
"""
test cases for user management
users count, new sign-ups,
"""
def setUp(self) -> None:
"""
data setup
create new guardian and junior
:return:
"""
super(UserManagementSetUp, self).setUp()
# guardian codes
self.guardian_code_1 = 'GRD123'
self.guardian_code_2 = 'GRD456'
# guardian 1
self.guardian = Guardian.objects.create(user=self.user, country_code=91, phone='8765876565',
country_name='India', gender=2, is_verified=True,
guardian_code=self.guardian_code_1)
# user 2 email
self.user_email_2 = 'user2@yopmail.com'
# create user 2
self.user_2 = User.objects.create_user(username=self.user_email_2, email=self.user_email_2)
self.user_2.set_password('user2@1234')
self.user_2.save()
# guardian 2
self.guardian_2 = Guardian.objects.create(user=self.user_2, country_code=92, phone='8765876575',
country_name='India', gender=1, is_verified=True,
guardian_code=self.guardian_code_2)
# user 3 email
self.user_email_3 = 'user3@yopmail.com'
# create user 3
self.user_3 = User.objects.create_user(username=self.user_email_3, email=self.user_email_3)
self.user_3.set_password('user3@1234')
self.user_3.save()
# junior 1
self.junior = Junior.objects.create(auth=self.user_3, country_name='India', gender=2,
is_verified=True, guardian_code=[self.guardian_code_1])
# user 4 email
self.user_email_4 = 'user4@yopmail.com'
# create user 4
self.user_4 = User.objects.create_user(username=self.user_email_4, email=self.user_email_4)
self.user_4.set_password('user4@1234')
self.user_4.save()
# junior 2
self.junior_2 = Junior.objects.create(auth=self.user_4, country_code=92, phone='8768763443',
country_name='India', gender=1, is_verified=True,
guardian_code=[self.guardian_code_2])
class AnalyticsSetUp(UserManagementSetUp):
"""
test analytics
task assign report, junior leaderboard
"""
def setUp(self) -> None:
"""
test data set up
create task and assigned to junior
create junior points data
:return:
"""
super(AnalyticsSetUp, self).setUp()
# pending tasks 1
self.pending_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Pending Task 1', task_status=1,
due_date='2023-09-12')
# pending tasks 2
self.pending_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Pending Task 2', task_status=1,
due_date='2023-09-12')
# in progress tasks 1
self.in_progress_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='In progress Task 1', task_status=2,
due_date='2023-09-12')
# in progress tasks 2
self.in_progress_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='In progress Task 2', task_status=2,
due_date='2023-09-12')
# rejected tasks 1
self.rejected_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Rejected Task 1', task_status=3,
due_date='2023-09-12')
# rejected tasks 2
self.rejected_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Rejected Task 2', task_status=3,
due_date='2023-09-12')
# requested task 1
self.requested_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Requested Task 1', task_status=4,
due_date='2023-09-12')
# requested task 2
self.requested_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Requested Task 2', task_status=4,
due_date='2023-09-12')
# completed task 1
self.completed_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Completed Task 1', task_status=5,
due_date='2023-09-12')
# completed task 2
self.completed_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Completed Task 2', task_status=5,
due_date='2023-09-12')
# expired task 1
self.expired_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Expired Task 1', task_status=6,
due_date='2023-09-11')
# expired task 2
self.expired_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Expired Task 2', task_status=6,
due_date='2023-09-11')
# junior point table data
JuniorPoints.objects.create(junior=self.junior_2, total_points=50)
JuniorPoints.objects.create(junior=self.junior, total_points=40)
# export excel url
self.export_excel_url = export_excel_url

Some files were not shown because too many files have changed in this diff Show More