Compare commits

..

284 Commits

Author SHA1 Message Date
a211baa10a added cors Allow specific origins setting, unpublish article api, pagination in notification list 2023-09-04 15:46:36 +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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
07d150309b Merge pull request #146 from KiwiTechLLC/dev
Dev
2023-08-04 15:04:55 +05:30
3801e6e027 Merge pull request #142 from KiwiTechLLC/dev
Dev
2023-08-03 14:59:49 +05:30
e89fc513cb Merge pull request #138 from KiwiTechLLC/dev
Dev
2023-08-02 14:25:06 +05:30
e1af1c200d Merge pull request #136 from KiwiTechLLC/dev
Dev
2023-08-02 11:33:29 +05:30
b238379a22 Merge pull request #130 from KiwiTechLLC/dev
Dev
2023-07-28 19:49:56 +05:30
c081bc621d Merge pull request #124 from KiwiTechLLC/dev
merging dev into qa
2023-07-27 22:04:51 +05:30
d2c8b18e3f Merge pull request #120 from KiwiTechLLC/dev
merging dev itno qa
2023-07-27 11:37:25 +05:30
bdc0ba4fd5 Merge pull request #117 from KiwiTechLLC/dev
Dev
2023-07-26 16:58:38 +05:30
3af2584d6a Merge pull request #115 from KiwiTechLLC/dev
Dev
2023-07-26 11:09:40 +05:30
62cf2b14bf Merge pull request #113 from KiwiTechLLC/dev
merging dev into qa
2023-07-26 11:01:45 +05:30
cbd3d139a5 Merge pull request #110 from KiwiTechLLC/dev
Dev
2023-07-25 17:36:49 +05:30
7cb792c6cf Merge pull request #104 from KiwiTechLLC/dev
Dev
2023-07-25 11:52:36 +05:30
5ea28bbd75 Merge pull request #102 from KiwiTechLLC/dev
Dev
2023-07-24 15:56:54 +05:30
180104ece8 Merge pull request #100 from KiwiTechLLC/dev
Dev
2023-07-24 11:04:08 +05:30
62 changed files with 3066 additions and 682 deletions

View File

@ -2,7 +2,7 @@
from django.contrib import admin from django.contrib import admin
"""Import django app""" """Import django app"""
from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails from .models import UserEmailOtp, DefaultTaskImages, UserNotification, UserDelete, UserDeviceDetails, ForceUpdate
# Register your models here. # Register your models here.
@admin.register(UserDelete) @admin.register(UserDelete)
@ -39,6 +39,19 @@ class UserEmailOtpAdmin(admin.ModelAdmin):
"""Return object in email and otp format""" """Return object in email and otp format"""
return self.email + '-' + self.otp 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) @admin.register(UserDeviceDetails)
class UserDeviceDetailsAdmin(admin.ModelAdmin): class UserDeviceDetailsAdmin(admin.ModelAdmin):
"""User profile admin""" """User profile admin"""

View File

@ -5,9 +5,11 @@ from rest_framework.response import Response
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
"""App django""" """App django"""
from account.utils import custom_error_response 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.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER
from junior.models import Junior
from guardian.models import Guardian
# Custom middleware # Custom middleware
# when user login with # when user login with
# multiple device simultaneously # multiple device simultaneously
@ -15,6 +17,18 @@ from base.messages import ERROR_CODE, SUCCESS_CODE
# multiple devices only # multiple devices only
# user can login in single # user can login in single
# device at a time""" # 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): class CustomMiddleware(object):
"""Custom middleware""" """Custom middleware"""
def __init__(self, get_response): def __init__(self, get_response):
@ -26,15 +40,34 @@ class CustomMiddleware(object):
response = self.get_response(request) response = self.get_response(request)
# Code to be executed after the view is called # Code to be executed after the view is called
device_id = request.META.get('HTTP_DEVICE_ID') 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: if request.user.is_authenticated:
"""device details""" # device details
device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last() if device_id:
if device_id and not device_details: device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND) if not device_details and api_endpoint not in unrestricted_api:
response = Response(custom_error.data, status=status.HTTP_404_NOT_FOUND) custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
# Set content type header to "application/json" response = custom_response(custom_error)
response['Content-Type'] = 'application/json' if user_type and str(user_type) == str(NUMBER['one']):
# Render the response as JSON junior = Junior.objects.filter(auth=request.user, is_active=False).last()
renderer = JSONRenderer() if junior:
response.content = renderer.render(response.data) 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 return response

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""" """Django import"""
from django.db import models from django.db import models
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
"""App import""" """App import"""
from base.constants import USER_TYPE from base.constants import USER_TYPE, DEVICE_TYPE
# Create your models here. # Create your models here.
class UserProfile(models.Model): class UserProfile(models.Model):
@ -165,3 +166,25 @@ class UserDeviceDetails(models.Model):
def __str__(self): def __str__(self):
return self.user.email 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

@ -18,7 +18,7 @@ import secrets
from guardian.models import Guardian from guardian.models import Guardian
from junior.models import Junior from junior.models import Junior
from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp from account.models import UserEmailOtp, DefaultTaskImages, UserDelete, UserNotification, UserPhoneOtp, ForceUpdate
from base.constants import GUARDIAN, JUNIOR, SUPERUSER, NUMBER from base.constants import GUARDIAN, JUNIOR, SUPERUSER, NUMBER
from base.messages import ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR from base.messages import ERROR_CODE, SUCCESS_CODE, STATUS_CODE_ERROR
from .utils import delete_user_account_condition_social, delete_user_account_condition from .utils import delete_user_account_condition_social, delete_user_account_condition
@ -104,10 +104,12 @@ class ResetPasswordSerializer(serializers.Serializer):
return user_opt_details return user_opt_details
return '' return ''
class ChangePasswordSerializer(serializers.Serializer): class ChangePasswordSerializer(serializers.Serializer):
"""Update Password after verification""" """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) new_password = serializers.CharField(required=True)
class Meta(object): class Meta(object):
"""Meta info""" """Meta info"""
model = User model = User
@ -118,25 +120,36 @@ class ChangePasswordSerializer(serializers.Serializer):
if self.context.password not in ('', None) and user.check_password(value): if self.context.password not in ('', None) and user.check_password(value):
return value return value
raise serializers.ValidationError(ERROR_CODE['2015']) raise serializers.ValidationError(ERROR_CODE['2015'])
def create(self, validated_data): def create(self, validated_data):
"""
change password
"""
new_password = validated_data.pop('new_password') new_password = validated_data.pop('new_password')
current_password = validated_data.pop('current_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: if new_password == current_password:
raise serializers.ValidationError({"details": ERROR_CODE['2026']}) 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): class ForgotPasswordSerializer(serializers.Serializer):
"""Forget password 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): class AdminLoginSerializer(serializers.ModelSerializer):
"""admin login serializer""" """admin login serializer"""
@ -152,12 +165,12 @@ class AdminLoginSerializer(serializers.ModelSerializer):
def validate(self, attrs): def validate(self, attrs):
user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True user = User.objects.filter(email__iexact=attrs['email'], is_superuser=True
).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() ).only('id', 'first_name', 'last_name', 'email',
'username', 'is_active', 'is_superuser').first()
if not user: if not user or not user.check_password(attrs['password']):
raise serializers.ValidationError({'details': ERROR_CODE['2002']})
elif not user.check_password(attrs['password']):
raise serializers.ValidationError({'details': ERROR_CODE['2002']}) raise serializers.ValidationError({'details': ERROR_CODE['2002']})
self.context.update({'user': user}) self.context.update({'user': user})
return attrs return attrs
@ -216,10 +229,17 @@ class GuardianSerializer(serializers.ModelSerializer):
def get_user_type(self, obj): def get_user_type(self, obj):
"""user type""" """user type"""
email_verified = UserEmailOtp.objects.filter(email=obj.user.username).last() if self.context.get('user_type', ''):
if email_verified and email_verified.user_type is not None: return self.context.get('user_type')
return email_verified.user_type # remove the below code once user_type can be passed
return str(NUMBER['two']) # 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): def get_auth(self, obj):
"""user email address""" """user email address"""
@ -237,7 +257,7 @@ class GuardianSerializer(serializers.ModelSerializer):
"""Meta info""" """Meta info"""
model = Guardian model = Guardian
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active', 'phone', 'family_name', 'gender', 'dob', 'referral_code', 'is_active', 'is_deleted',
'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name'] 'is_complete_profile', 'passcode', 'image', 'created_at', 'updated_at', 'user_type', 'country_name']
@ -280,14 +300,15 @@ class JuniorSerializer(serializers.ModelSerializer):
model = Junior model = Junior
fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code', fields = ['id', 'auth_token', 'refresh_token', 'email', 'first_name', 'last_name', 'country_code',
'phone', 'gender', 'dob', 'guardian_code', 'referral_code','is_active', 'is_password_set', '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_complete_profile', 'created_at', 'image', 'updated_at', 'user_type', 'country_name','is_invited',
'is_deleted']
class EmailVerificationSerializer(serializers.ModelSerializer): class EmailVerificationSerializer(serializers.ModelSerializer):
"""Email verification serializer""" """Email verification serializer"""
class Meta(object): class Meta(object):
"""Meta info""" """Meta info"""
model = UserEmailOtp model = UserEmailOtp
fields = '__all__' fields = ('email',)
@ -369,3 +390,12 @@ class UserPhoneOtpSerializer(serializers.ModelSerializer):
"""Meta info""" """Meta info"""
model = UserPhoneOtp model = UserPhoneOtp
fields = '__all__' fields = '__all__'
class ForceUpdateSerializer(serializers.ModelSerializer):
""" ForceUpdate Serializer
"""
class Meta(object):
""" meta info """
model = ForceUpdate
fields = ('id', 'version', 'device_type')

View File

@ -8,14 +8,14 @@
<tr> <tr>
<td style="padding: 0 27px 15px;"> <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;"> <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> </p>
</td> </td>
</tr> </tr>
<tr> <tr>
<td style="padding: 0 27px 22px;"> <td style="padding: 0 27px 22px;">
<p style="margin: 0;font-size: 14px; font-weight: 400; line-height: 21px; color: #1f2532;"> <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> </p>
</td> </td>
</tr> </tr>

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

@ -29,7 +29,7 @@ from .views import (UserLogin, SendPhoneOtp, UserPhoneVerification, UserEmailVer
GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet, GoogleLoginViewSet, SigninWithApple, ProfileAPIViewSet, UploadImageAPIViewSet,
DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet, DefaultImageAPIViewSet, DeleteUserProfileAPIViewSet, UserNotificationAPIViewSet,
UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView, UpdateUserNotificationAPIViewSet, SendSupportEmail, LogoutAPIView, AccessTokenAPIView,
AdminLoginViewSet) AdminLoginViewSet, ForceUpdateViewSet)
"""Router""" """Router"""
router = routers.SimpleRouter() router = routers.SimpleRouter()
@ -39,8 +39,6 @@ router.register('user', UserLogin, basename='user')
router.register('admin', AdminLoginViewSet, basename='admin') router.register('admin', AdminLoginViewSet, basename='admin')
"""google login end point""" """google login end point"""
router.register('google-login', GoogleLoginViewSet, basename='admin') 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""" """email verification end point"""
router.register('user-email-verification', UserEmailVerification, basename='user-email-verification') router.register('user-email-verification', UserEmailVerification, basename='user-email-verification')
"""Resend email otp end point""" """Resend email otp end point"""
@ -57,6 +55,8 @@ router.register('delete', DeleteUserProfileAPIViewSet, basename='delete')
router.register('user-notification', UserNotificationAPIViewSet, basename='user-notification') router.register('user-notification', UserNotificationAPIViewSet, basename='user-notification')
"""update user account notification""" """update user account notification"""
router.register('update-user-notification', UpdateUserNotificationAPIViewSet, basename='update-user-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""" """Define url pattern"""
urlpatterns = [ urlpatterns = [
path('api/v1/', include(router.urls)), path('api/v1/', include(router.urls)),

View File

@ -1,4 +1,6 @@
"""Account utils""" """Account utils"""
from celery import shared_task
"""Import django""" """Import django"""
from django.conf import settings from django.conf import settings
from rest_framework import viewsets, status from rest_framework import viewsets, status
@ -20,10 +22,11 @@ from rest_framework import serializers
# Import messages from base package""" # Import messages from base package"""
from junior.models import Junior from junior.models import Junior
from guardian.models import Guardian from guardian.models import Guardian
from account.models import UserDelete from account.models import UserDelete, UserDeviceDetails
from base.messages import ERROR_CODE from base.messages import ERROR_CODE
from django.utils import timezone from django.utils import timezone
from base.constants import NUMBER
from junior.models import JuniorPoints
# Define delete # Define delete
# user account condition, # user account condition,
# Define delete # Define delete
@ -43,7 +46,7 @@ from django.utils import timezone
# referral code, # referral code,
# Define function for generating # Define function for generating
# alphanumeric code # alphanumeric code
# otp expiry
def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num): def delete_user_account_condition(user, user_type_data, user_type, user_tb, data, random_num):
"""delete user account""" """delete user account"""
if user_type == '1' and user_type_data == '1': if user_type == '1' and user_type_data == '1':
@ -90,8 +93,11 @@ def junior_account_update(user_tb):
# Update junior account # Update junior account
junior_data.is_active = False junior_data.is_active = False
junior_data.is_verified = 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() junior_data.save()
JuniorPoints.objects.filter(junior=junior_data).delete()
def guardian_account_update(user_tb): def guardian_account_update(user_tb):
"""update guardian account after delete the user account""" """update guardian account after delete the user account"""
@ -100,12 +106,14 @@ def guardian_account_update(user_tb):
# Update guardian account # Update guardian account
guardian_data.is_active = False guardian_data.is_active = False
guardian_data.is_verified = False guardian_data.is_verified = False
guardian_data.is_deleted = True
guardian_data.save() guardian_data.save()
jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code)) jun_data = Junior.objects.filter(guardian_code__icontains=str(guardian_data.guardian_code))
"""Disassociate relation between guardian and junior""" """Disassociate relation between guardian and junior"""
for data in jun_data: for data in jun_data:
data.guardian_code.remove(guardian_data.guardian_code) data.guardian_code.remove(guardian_data.guardian_code)
data.save() data.save()
@shared_task()
def send_otp_email(recipient_email, otp): def send_otp_email(recipient_email, otp):
"""Send otp on email with template""" """Send otp on email with template"""
from_email = settings.EMAIL_FROM_ADDRESS from_email = settings.EMAIL_FROM_ADDRESS
@ -121,6 +129,44 @@ def send_otp_email(recipient_email, otp):
) )
return otp return otp
@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, subject, message): def send_support_email(name, sender, subject, message):
"""Send otp on email with template""" """Send otp on email with template"""
to_email = [settings.EMAIL_FROM_ADDRESS] to_email = [settings.EMAIL_FROM_ADDRESS]
@ -158,8 +204,12 @@ def custom_error_response(detail, response_status):
if not detail: if not detail:
"""when details is empty""" """when details is empty"""
detail = {} 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): def get_user_data(attrs):
""" """
@ -231,3 +281,10 @@ def generate_code(value, user_id):
OTP_EXPIRY = timezone.now() + timezone.timedelta(days=1) 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"

View File

@ -1,11 +1,13 @@
"""Account view """ """Account view """
import threading
from notifications.utils import remove_fcm_token from notifications.utils import remove_fcm_token
# django imports # django imports
from rest_framework.viewsets import GenericViewSet, mixins
from datetime import datetime, timedelta from datetime import datetime, timedelta
from rest_framework import viewsets, status, views from rest_framework import viewsets, status, views
from rest_framework.decorators import action from rest_framework.decorators import action
import random
import logging import logging
from django.utils import timezone from django.utils import timezone
import jwt import jwt
@ -23,22 +25,23 @@ from django.conf import settings
# local imports # local imports
from guardian.models import Guardian 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 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 from django.contrib.auth.models import User
from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer, from .serializers import (SuperUserSerializer, GuardianSerializer, JuniorSerializer, EmailVerificationSerializer,
ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer, ForgotPasswordSerializer, ResetPasswordSerializer, ChangePasswordSerializer,
GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer, GoogleLoginSerializer, UpdateGuardianImageSerializer, UpdateJuniorProfileImageSerializer,
DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer, DefaultTaskImagesSerializer, DefaultTaskImagesDetailsSerializer, UserDeleteSerializer,
UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer, UserNotificationSerializer, UpdateUserNotificationSerializer, UserPhoneOtpSerializer,
AdminLoginSerializer) AdminLoginSerializer, ForceUpdateSerializer)
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
from base.messages import ERROR_CODE, SUCCESS_CODE 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 guardian.tasks import generate_otp
from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response, from account.utils import (send_otp_email, send_support_email, custom_response, custom_error_response,
generate_code, OTP_EXPIRY) generate_code, OTP_EXPIRY, user_device_details, send_all_email)
from junior.serializers import JuniorProfileSerializer from junior.serializers import JuniorProfileSerializer
from guardian.serializers import GuardianProfileSerializer from guardian.serializers import GuardianProfileSerializer
@ -48,7 +51,8 @@ class GoogleLoginMixin(object):
def google_login(request): def google_login(request):
"""google login function""" """google login function"""
access_token = request.data.get('access_token') 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: if not access_token:
return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST) return Response({'error': 'Access token is required.'}, status=status.HTTP_400_BAD_REQUEST)
@ -81,14 +85,29 @@ class GoogleLoginMixin(object):
if user_data.exists(): if user_data.exists():
if str(user_type) == '1': if str(user_type) == '1':
junior_query = Junior.objects.filter(auth=user_data.last()).last() 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
)
serializer = JuniorSerializer(junior_query) serializer = JuniorSerializer(junior_query)
if str(user_type) == '2': elif str(user_type) == '2':
guardian_query = Guardian.objects.filter(user=user_data.last()).last() 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
)
serializer = GuardianSerializer(guardian_query) serializer = GuardianSerializer(guardian_query)
else:
return custom_error_response(
ERROR_CODE["2069"],
response_status=status.HTTP_400_BAD_REQUEST
)
return custom_response(SUCCESS_CODE['3003'], serializer.data, return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK) 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) user_obj = User.objects.create(username=email, email=email, first_name=first_name, last_name=last_name)
if str(user_type) == '1': if str(user_type) == '1':
junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True, junior_query = Junior.objects.create(auth=user_obj, is_verified=True, is_active=True,
@ -97,13 +116,25 @@ class GoogleLoginMixin(object):
referral_code=generate_code(ZOD, user_obj.id) referral_code=generate_code(ZOD, user_obj.id)
) )
serializer = JuniorSerializer(junior_query) serializer = JuniorSerializer(junior_query)
if str(user_type) == '2': 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, guardian_query = Guardian.objects.create(user=user_obj, is_verified=True, is_active=True,
image=profile_picture,signup_method='2', image=profile_picture,signup_method='2',
guardian_code=generate_code(GRD, user_obj.id), guardian_code=generate_code(GRD, user_obj.id),
referral_code=generate_code(ZOD, user_obj.id) referral_code=generate_code(ZOD, user_obj.id)
) )
serializer = GuardianSerializer(guardian_query) serializer = GuardianSerializer(guardian_query)
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 a JSON response with the user's email and name
return custom_response(SUCCESS_CODE['3003'], serializer.data, return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK) response_status=status.HTTP_200_OK)
@ -114,16 +145,26 @@ class GoogleLoginViewSet(GoogleLoginMixin, viewsets.GenericViewSet):
serializer_class = GoogleLoginSerializer serializer_class = GoogleLoginSerializer
def create(self, request): def create(self, request):
"""create method""" """Payload
{
"access_token",
"user_type": "1"
}"""
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
return self.google_login(request) return self.google_login(request)
class SigninWithApple(views.APIView): 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): def post(self, request):
token = request.data.get("access_token") 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: try:
decoded_data = jwt.decode(token, options={"verify_signature": False}) decoded_data = jwt.decode(token, options={"verify_signature": False})
user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True} user_data = {"email": decoded_data.get('email'), "username": decoded_data.get('email'), "is_active": True}
@ -131,11 +172,26 @@ class SigninWithApple(views.APIView):
try: try:
user = User.objects.get(email=decoded_data.get("email")) user = User.objects.get(email=decoded_data.get("email"))
if str(user_type) == '1': if str(user_type) == '1':
junior_query = Junior.objects.filter(auth=user).last() junior_data = Junior.objects.filter(auth=user).last()
serializer = JuniorSerializer(junior_query) if not junior_data:
if str(user_type) == '2': return custom_error_response(
guardian_query = Guardian.objects.filter(user=user).last() ERROR_CODE["2071"],
serializer = GuardianSerializer(guardian_query) 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
)
return custom_response(SUCCESS_CODE['3003'], serializer.data, return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK) response_status=status.HTTP_200_OK)
@ -147,12 +203,24 @@ class SigninWithApple(views.APIView):
junior_code=generate_code(JUN, user.id), junior_code=generate_code(JUN, user.id),
referral_code=generate_code(ZOD, user.id)) referral_code=generate_code(ZOD, user.id))
serializer = JuniorSerializer(junior_query) serializer = JuniorSerializer(junior_query)
if str(user_type) == '2': 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, guardian_query = Guardian.objects.create(user=user, is_verified=True, is_active=True,
signup_method='3', signup_method='3',
guardian_code=generate_code(GRD, user.id), guardian_code=generate_code(GRD, user.id),
referral_code=generate_code(ZOD, user.id)) referral_code=generate_code(ZOD, user.id))
serializer = GuardianSerializer(guardian_query) serializer = GuardianSerializer(guardian_query)
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, return custom_response(SUCCESS_CODE['3003'], serializer.data,
response_status=status.HTTP_200_OK) response_status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
@ -188,18 +256,42 @@ class UpdateProfileImage(views.APIView):
return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE['2036'],response_status=status.HTTP_400_BAD_REQUEST)
class ChangePasswordAPIView(views.APIView): class ChangePasswordAPIView(views.APIView):
"""change password""" """
change password"
"""
serializer_class = ChangePasswordSerializer serializer_class = ChangePasswordSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def post(self, request): 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(): if serializer.is_valid():
serializer.save() serializer.save()
return custom_response(SUCCESS_CODE['3007'], response_status=status.HTTP_200_OK) return custom_response(
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) 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): class ResetPasswordAPIView(views.APIView):
"""Reset password""" """Reset password
Payload
{
"verification_code":"373770",
"password":"Demo@1323"
}"""
def post(self, request): def post(self, request):
serializer = ResetPasswordSerializer(data=request.data) serializer = ResetPasswordSerializer(data=request.data)
if serializer.is_valid(): if serializer.is_valid():
@ -208,44 +300,46 @@ class ResetPasswordAPIView(views.APIView):
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
class ForgotPasswordAPIView(views.APIView): class ForgotPasswordAPIView(views.APIView):
"""Forgot password""" """
Forgot password
"""
serializer_class = ForgotPasswordSerializer
def post(self, request): def post(self, request):
serializer = ForgotPasswordSerializer(data=request.data)
if serializer.is_valid(): """
email = serializer.validated_data['email'] Payload
try: {
User.objects.get(email=email) "email": "abc@yopmail.com"
except User.DoesNotExist: }
return custom_error_response(ERROR_CODE['2004'], response_status=status.HTTP_404_NOT_FOUND) """
verification_code = generate_otp() serializer = self.serializer_class(data=request.data)
# Send the verification code to the user's email serializer.is_valid(raise_exception=True)
from_email = settings.EMAIL_FROM_ADDRESS email = serializer.validated_data['email']
recipient_list = [email] # generate otp
send_templated_mail( verification_code = generate_otp()
template_name='email_reset_verification.email', # Send the verification code to the user's email
from_email=from_email, send_all_email.delay(
recipient_list=recipient_list, 'email_reset_verification.email', email, verification_code
context={ )
'verification_code': verification_code expiry = timezone.now() + timezone.timedelta(days=1)
} user_data, created = UserEmailOtp.objects.get_or_create(
) email=email
expiry = OTP_EXPIRY )
user_data, created = UserEmailOtp.objects.get_or_create(email=email) if created:
if created: user_data.expired_at = expiry
user_data.expired_at = expiry user_data.save()
user_data.save() if user_data:
if user_data: user_data.otp = verification_code
user_data.otp = verification_code user_data.expired_at = expiry
user_data.expired_at = expiry user_data.save()
user_data.save() return custom_response(
return custom_response(SUCCESS_CODE['3015'], SUCCESS_CODE['3015'],
response_status=status.HTTP_200_OK) response_status=status.HTTP_200_OK
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) )
class SendPhoneOtp(viewsets.ModelViewSet): class SendPhoneOtp(viewsets.ModelViewSet):
"""Send otp on phone""" """Send otp on phone"""
queryset = UserPhoneOtp.objects.all()
serializer_class = UserPhoneOtpSerializer serializer_class = UserPhoneOtpSerializer
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
otp = generate_otp() otp = generate_otp()
@ -262,7 +356,6 @@ class SendPhoneOtp(viewsets.ModelViewSet):
class UserPhoneVerification(viewsets.ModelViewSet): class UserPhoneVerification(viewsets.ModelViewSet):
"""Send otp on phone""" """Send otp on phone"""
queryset = UserPhoneOtp.objects.all()
serializer_class = UserPhoneOtpSerializer serializer_class = UserPhoneOtpSerializer
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
try: try:
@ -278,25 +371,50 @@ class UserPhoneVerification(viewsets.ModelViewSet):
return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE["2008"], response_status=status.HTTP_400_BAD_REQUEST)
class UserLogin(viewsets.ViewSet): class UserLogin(viewsets.ViewSet):
"""User login""" """User login"""
@action(methods=['post'], detail=False) @action(methods=['post'], detail=False)
def login(self, request): def login(self, request):
username = request.data.get('username') username = request.data.get('username')
password = request.data.get('password') password = request.data.get('password')
user_type = request.META.get('HTTP_USER_TYPE')
device_id = request.META.get('HTTP_DEVICE_ID') device_id = request.META.get('HTTP_DEVICE_ID')
user = authenticate(request, username=username, password=password) user = authenticate(request, username=username, password=password)
try: try:
if user is not None: if user is not None:
login(request, user) login(request, user)
guardian_data = Guardian.objects.filter(user__username=username, is_verified=True).last() if str(user_type) == USER_TYPE_FLAG["TWO"]:
if guardian_data: guardian_data = Guardian.objects.filter(user__username=username).last()
serializer = GuardianSerializer(guardian_data).data if guardian_data:
junior_data = Junior.objects.filter(auth__username=username, is_verified=True).last() if guardian_data.is_verified:
if junior_data: serializer = GuardianSerializer(
serializer = JuniorSerializer(junior_data).data 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) device_details, created = UserDeviceDetails.objects.get_or_create(user=user)
if device_details: if device_details:
device_details.device_id = device_id device_details.device_id = device_id
@ -310,9 +428,12 @@ class UserLogin(viewsets.ViewSet):
refresh = RefreshToken.for_user(user) refresh = RefreshToken.for_user(user)
access_token = str(refresh.access_token) access_token = str(refresh.access_token)
refresh_token = str(refresh) refresh_token = str(refresh)
data = {"auth_token":access_token, "refresh_token":refresh_token, "is_profile_complete": False, data = {
"user_type": email_verified.user_type, "auth_token":access_token,
} "refresh_token":refresh_token,
"is_profile_complete": False,
"user_type": user_type,
}
is_verified = False is_verified = False
if email_verified: if email_verified:
is_verified = email_verified.is_verified is_verified = email_verified.is_verified
@ -321,23 +442,30 @@ class UserLogin(viewsets.ViewSet):
email_verified.otp = otp email_verified.otp = otp
email_verified.save() email_verified.save()
data.update({"email_otp":otp}) data.update({"email_otp":otp})
send_otp_email(username, otp) send_otp_email.delay(username, otp)
return custom_response(ERROR_CODE['2024'], {"email_otp": otp, "is_email_verified": is_verified}, return custom_response(
response_status=status.HTTP_200_OK) ERROR_CODE['2024'],
{"email_otp": otp, "is_email_verified": is_verified},
response_status=status.HTTP_200_OK
)
data.update({"is_email_verified": is_verified}) data.update({"is_email_verified": is_verified})
return custom_response(SUCCESS_CODE['3003'], 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) @action(methods=['post'], detail=False)
def admin_login(self, request): def admin_login(self, request):
email = request.data.get('email') email = request.data.get('email')
password = request.data.get('password') password = request.data.get('password')
user = User.objects.filter(email__iexact=email, is_superuser=True user = User.objects.filter(email__iexact=email, is_superuser=True
).only('id', 'first_name', 'last_name', 'email', 'is_superuser').first() ).only('id', 'first_name', 'last_name', 'email',
'username', 'is_active', 'is_superuser').first()
if not user: if not user or not user.check_password(password):
return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST)
elif not user.check_password(password):
return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE["2002"], response_status=status.HTTP_400_BAD_REQUEST)
serializer = SuperUserSerializer(user) serializer = SuperUserSerializer(user)
return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK) return custom_response(SUCCESS_CODE['3003'], serializer.data, response_status=status.HTTP_200_OK)
@ -367,15 +495,20 @@ class AdminLoginViewSet(viewsets.GenericViewSet):
class UserEmailVerification(viewsets.ModelViewSet): class UserEmailVerification(viewsets.ModelViewSet):
"""User Email verification""" """User Email verification
Payload
{
"email":"ramu@yopmail.com",
"otp":"361123"
}"""
serializer_class = EmailVerificationSerializer serializer_class = EmailVerificationSerializer
queryset = UserEmailOtp.objects.all() http_method_names = ('post',)
def list(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
try: try:
user_obj = User.objects.filter(username=self.request.GET.get('email')).last() user_obj = User.objects.filter(username=self.request.data.get('email')).last()
email_data = UserEmailOtp.objects.filter(email=self.request.GET.get('email'), email_data = UserEmailOtp.objects.filter(email=self.request.data.get('email'),
otp=self.request.GET.get('otp')).last() otp=self.request.data.get('otp')).last()
if email_data: if email_data:
input_datetime_str = str(email_data.expired_at) input_datetime_str = str(email_data.expired_at)
input_format = "%Y-%m-%d %H:%M:%S.%f%z" input_format = "%Y-%m-%d %H:%M:%S.%f%z"
@ -389,12 +522,12 @@ class UserEmailVerification(viewsets.ModelViewSet):
email_data.is_verified = True email_data.is_verified = True
email_data.save() email_data.save()
if email_data.user_type == '1': 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: if junior_data:
junior_data.is_verified = True junior_data.is_verified = True
junior_data.save() junior_data.save()
else: 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: if guardian_data:
guardian_data.is_verified = True guardian_data.is_verified = True
guardian_data.save() guardian_data.save()
@ -412,14 +545,15 @@ class UserEmailVerification(viewsets.ModelViewSet):
class ReSendEmailOtp(viewsets.ModelViewSet): class ReSendEmailOtp(viewsets.ModelViewSet):
"""Send otp on phone""" """Send otp on phone"""
queryset = UserEmailOtp.objects.all()
serializer_class = EmailVerificationSerializer serializer_class = EmailVerificationSerializer
permission_classes = [IsAuthenticated] http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""Param
{"email":"ashok@yopmail.com"}
"""
otp = generate_otp() otp = generate_otp()
if User.objects.filter(email=request.data['email']): if User.objects.filter(email=request.data['email']):
expiry = OTP_EXPIRY expiry = timezone.now() + timezone.timedelta(days=1)
email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email']) email_data, created = UserEmailOtp.objects.get_or_create(email=request.data['email'])
if created: if created:
email_data.expired_at = expiry email_data.expired_at = expiry
@ -428,34 +562,37 @@ class ReSendEmailOtp(viewsets.ModelViewSet):
email_data.otp = otp email_data.otp = otp
email_data.expired_at = expiry email_data.expired_at = expiry
email_data.save() 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) return custom_response(SUCCESS_CODE['3016'], response_status=status.HTTP_200_OK)
else: else:
return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE["2023"], response_status=status.HTTP_400_BAD_REQUEST)
class ProfileAPIViewSet(viewsets.ModelViewSet): class ProfileAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset""" """Profile viewset"""
queryset = User.objects.all()
serializer_class = JuniorProfileSerializer serializer_class = JuniorProfileSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""profile view""" """profile view
if str(self.request.GET.get('user_type')) == '1': 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() junior_data = Junior.objects.filter(auth=self.request.user).last()
if junior_data: if junior_data:
serializer = JuniorProfileSerializer(junior_data) serializer = JuniorProfileSerializer(junior_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) 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() guardian_data = Guardian.objects.filter(user=self.request.user).last()
if guardian_data: if guardian_data:
serializer = GuardianProfileSerializer(guardian_data) serializer = GuardianProfileSerializer(guardian_data)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) 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): class UploadImageAPIViewSet(viewsets.ModelViewSet):
"""upload task image""" """upload task image"""
queryset = DefaultTaskImages.objects.all()
serializer_class = DefaultTaskImagesSerializer serializer_class = DefaultTaskImagesSerializer
http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""upload images""" """upload images"""
image_data = request.data['image_url'] image_data = request.data['image_url']
@ -473,9 +610,9 @@ class UploadImageAPIViewSet(viewsets.ModelViewSet):
class DefaultImageAPIViewSet(viewsets.ModelViewSet): class DefaultImageAPIViewSet(viewsets.ModelViewSet):
"""Profile viewset""" """Profile viewset"""
queryset = DefaultTaskImages.objects.all()
serializer_class = DefaultTaskImagesDetailsSerializer serializer_class = DefaultTaskImagesDetailsSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""profile view""" """profile view"""
queryset = DefaultTaskImages.objects.all() queryset = DefaultTaskImages.objects.all()
@ -484,7 +621,13 @@ class DefaultImageAPIViewSet(viewsets.ModelViewSet):
class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet): 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, @action(detail=False, methods=['POST'], url_path='user-account',serializer_class=UserDeleteSerializer,
permission_classes=[IsAuthenticated]) permission_classes=[IsAuthenticated])
@ -504,24 +647,28 @@ class DeleteUserProfileAPIViewSet(viewsets.GenericViewSet):
class UserNotificationAPIViewSet(viewsets.ModelViewSet): class UserNotificationAPIViewSet(viewsets.ModelViewSet):
"""notification viewset""" """notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UserNotificationSerializer serializer_class = UserNotificationSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""profile view""" """notification view"""
queryset = self.queryset.filter(user=request.user) queryset = UserNotification.objects.filter(user=request.user)
serializer = UserNotificationSerializer(queryset, many=True) serializer = UserNotificationSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet): class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
"""Update notification viewset""" """Update notification viewset"""
queryset = UserNotification.objects.all()
serializer_class = UpdateUserNotificationSerializer serializer_class = UpdateUserNotificationSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""profile view""" """Payload
{"email_notification": false,
"sms_notification": false,
"push_notification": false}
"""
serializer = UpdateUserNotificationSerializer(data=request.data, serializer = UpdateUserNotificationSerializer(data=request.data,
context=request.user) context=request.user)
if serializer.is_valid(): if serializer.is_valid():
@ -531,7 +678,12 @@ class UpdateUserNotificationAPIViewSet(viewsets.ModelViewSet):
class SendSupportEmail(views.APIView): class SendSupportEmail(views.APIView):
"""support email api""" """support email api
payload
name
email
message
"""
permission_classes = (IsAuthenticated,) permission_classes = (IsAuthenticated,)
def post(self, request): def post(self, request):
@ -575,3 +727,27 @@ class AccessTokenAPIView(views.APIView):
data = {"auth_token": access_token} data = {"auth_token": access_token}
return custom_response(None, data, response_status=status.HTTP_200_OK) 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() >= 2:
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

@ -27,7 +27,7 @@ NUMBER = {
'ninety_nine': 99, 'hundred': 100, 'thirty_six_hundred': 3600 'ninety_nine': 99, 'hundred': 100, 'thirty_six_hundred': 3600
} }
none = "none"
# Super Admin string constant for 'role' # Super Admin string constant for 'role'
SUPER_ADMIN = "Super Admin" SUPER_ADMIN = "Super Admin"
@ -43,13 +43,24 @@ FILE_SIZE = 5 * 1024 * 1024
# String constant for configurable date for allocation lock period # String constant for configurable date for allocation lock period
ALLOCATION_LOCK_DATE = 1 ALLOCATION_LOCK_DATE = 1
# guardian code status tuple
guardian_code_tuple = ('1','3')
"""user type""" """user type"""
USER_TYPE = ( USER_TYPE = (
('1', 'junior'), ('1', 'junior'),
('2', 'guardian'), ('2', 'guardian'),
('3', 'superuser') ('3', 'superuser')
) )
DEVICE_TYPE = (
('1', 'android'),
('2', 'ios')
)
USER_TYPE_FLAG = {
"FIRST" : "1",
"TWO" : "2",
"THREE": "3"
}
"""gender""" """gender"""
GENDERS = ( GENDERS = (
('1', 'Male'), ('1', 'Male'),
@ -70,6 +81,18 @@ SIGNUP_METHODS = (
('2', 'google'), ('2', 'google'),
('3', 'apple') ('3', 'apple')
) )
# 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
RELATIONSHIP = ( RELATIONSHIP = (
('1', 'parent'), ('1', 'parent'),
@ -106,7 +129,9 @@ MAX_ARTICLE_CARD = 6
MIN_ARTICLE_SURVEY = 5 MIN_ARTICLE_SURVEY = 5
MAX_ARTICLE_SURVEY = 10 MAX_ARTICLE_SURVEY = 10
# real time url # already register
time_url = "http://worldtimeapi.org/api/timezone/Asia/Riyadh" Already_register_user = "duplicate key value violates unique constraint"
ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images' ARTICLE_CARD_IMAGE_FOLDER = 'article-card-images'
DATE_FORMAT = '%Y-%m-%d'

View File

@ -94,7 +94,24 @@ ERROR_CODE = {
"2065": "Passwords do not match. Please try again.", "2065": "Passwords do not match. Please try again.",
"2066": "Task does not exist or not in expired state", "2066": "Task does not exist or not in expired state",
"2067": "Action not allowed. User type missing.", "2067": "Action not allowed. User type missing.",
"2068": "No guardian associated with this junior" "2068": "No guardian associated with this junior",
"2069": "Invalid user type",
"2070": "You do not find as a guardian",
"2071": "You do not find as a junior",
"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 associate with you",
"2077": "You can not add guardian",
"2078": "This junior is not associate 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"
} }
"""Success message code""" """Success message code"""
@ -130,8 +147,8 @@ SUCCESS_CODE = {
"3018": "Task created successfully", "3018": "Task created successfully",
"3019": "Support Email sent successfully", "3019": "Support Email sent successfully",
"3020": "Logged out successfully.", "3020": "Logged out successfully.",
"3021": "Add junior successfully", "3021": "Added junior successfully",
"3022": "Remove junior successfully", "3022": "Removed junior successfully",
"3023": "Junior is approved successfully", "3023": "Junior is approved successfully",
"3024": "Junior request is rejected successfully", "3024": "Junior request is rejected successfully",
"3025": "Task is approved successfully", "3025": "Task is approved successfully",
@ -149,7 +166,21 @@ SUCCESS_CODE = {
"3037": "Profile has been updated successfully.", "3037": "Profile has been updated successfully.",
"3038": "Status has been changed successfully.", "3038": "Status has been changed successfully.",
# notification read # notification read
"3039": "Notification read successfully" "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"""
STATUS_CODE_ERROR = { STATUS_CODE_ERROR = {

46
base/pagination.py Normal file
View File

@ -0,0 +1,46 @@
"""
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),
]))
def get_paginated_dict_response(self, data):
"""
:param data: queryset to be paginated
:return: return a simple dict obj
"""
return {
'count': self.page.paginator.count,
'data': data,
'current_page': self.page.number,
'total_pages': self.page.paginator.num_pages,
}

View File

@ -1,29 +1,88 @@
""" """
web_admin tasks file web_admin tasks file
""" """
import datetime
# third party imports # third party imports
from celery import shared_task from celery import shared_task
from templated_email import send_templated_mail from templated_email import send_templated_mail
# django imports # django imports
from django.conf import settings 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
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 @shared_task
def send_email_otp(email, verification_code): def send_email(recipient_list, template, context: dict = None):
""" """
used to send otp on email used to send otp on email
:param email: e-mail :param context:
:param verification_code: otp :param recipient_list: e-mail list
:param template: email template
""" """
if context is None:
context = {}
from_email = settings.EMAIL_FROM_ADDRESS from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [email]
send_templated_mail( send_templated_mail(
template_name='email_reset_verification.email', template_name=template,
from_email=from_email, from_email=from_email,
recipient_list=recipient_list, recipient_list=recipient_list,
context={ context=context
'verification_code': verification_code
}
) )
return True 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(
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, None, None, 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

Binary file not shown.

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

@ -57,6 +57,8 @@ class Guardian(models.Model):
is_invited = models.BooleanField(default=False) is_invited = models.BooleanField(default=False)
# Profile activity""" # Profile activity"""
is_password_set = models.BooleanField(default=True) is_password_set = models.BooleanField(default=True)
# guardian profile deleted or not"""
is_deleted = models.BooleanField(default=False)
"""Profile activity""" """Profile activity"""
is_active = models.BooleanField(default=True) is_active = models.BooleanField(default=True)
"""guardian is verified or not""" """guardian is verified or not"""

View File

@ -1,6 +1,7 @@
"""Serializer of Guardian""" """Serializer of Guardian"""
# third party imports # third party imports
import logging import logging
from django.contrib.auth import password_validation
from rest_framework import serializers from rest_framework import serializers
# Import Refresh token of jwt # Import Refresh token of jwt
from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.tokens import RefreshToken
@ -23,17 +24,19 @@ from account.models import UserProfile, UserEmailOtp, UserNotification
from account.utils import generate_code from account.utils import generate_code
from junior.serializers import JuniorDetailSerializer from junior.serializers import JuniorDetailSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, JUN, ZOD, GRD from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user, GUARDIAN
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
from .utils import real_time, convert_timedelta_into_datetime, update_referral_points from .utils import real_time, convert_timedelta_into_datetime, update_referral_points
# notification's constant # notification's constant
from notifications.constants import TASK_POINTS, TASK_REJECTED from notifications.constants import TASK_APPROVED, TASK_REJECTED
# send notification function # send notification function
from notifications.utils import send_notification from notifications.utils import send_notification
from django.core.exceptions import ValidationError
from django.utils.translation import gettext as _
# In this serializer file # In this serializer file
# define user serializer, # define user serializer,
# define password validation
# create guardian serializer, # create guardian serializer,
# task serializer, # task serializer,
# guardian serializer, # guardian serializer,
@ -42,10 +45,51 @@ from notifications.utils import send_notification
# guardian profile serializer, # guardian profile serializer,
# approve junior serializer, # approve junior serializer,
# approve task 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): class UserSerializer(serializers.ModelSerializer):
"""User serializer""" """User serializer"""
auth_token = serializers.SerializerMethodField('get_auth_token') auth_token = serializers.SerializerMethodField('get_auth_token')
password = serializers.CharField(write_only=True, validators=[PasswordValidator()])
class Meta(object): class Meta(object):
"""Meta info""" """Meta info"""
@ -68,8 +112,10 @@ class UserSerializer(serializers.ModelSerializer):
UserNotification.objects.get_or_create(user=user) UserNotification.objects.get_or_create(user=user)
if user_type == str(NUMBER['one']): if user_type == str(NUMBER['one']):
# create junior profile # create junior profile
Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id), junior = Junior.objects.create(auth=user, junior_code=generate_code(JUN, user.id),
referral_code=generate_code(ZOD, 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']): if user_type == str(NUMBER['two']):
# create guardian profile # create guardian profile
Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id), Guardian.objects.create(user=user, guardian_code=generate_code(GRD, user.id),
@ -82,7 +128,7 @@ class UserSerializer(serializers.ModelSerializer):
otp_verified = False otp_verified = False
if otp and otp.is_verified: if otp and otp.is_verified:
otp_verified = True 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", "code": 400, "status":"failed",
}) })
@ -172,7 +218,7 @@ class TaskSerializer(serializers.ModelSerializer):
class Meta(object): class Meta(object):
"""Meta info""" """Meta info"""
model = JuniorTask 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): def validate_due_date(self, value):
"""validation on due date""" """validation on due date"""
@ -183,11 +229,20 @@ class TaskSerializer(serializers.ModelSerializer):
return value return value
def create(self, validated_data): def create(self, validated_data):
"""create default task image data""" """create default task image data"""
validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last() guardian = Guardian.objects.filter(user=self.context['user']).last()
# update image of the task # update image of the task
images = self.context['image'] images = self.context['image']
validated_data['default_image'] = images junior_ids = self.context['junior_data']
instance = JuniorTask.objects.create(**validated_data) junior_data = junior_ids[0].split(',')
tasks_created = []
for junior_id in junior_data:
task_data = validated_data.copy()
task_data['guardian'] = guardian
task_data['default_image'] = images
task_data['junior'] = Junior.objects.filter(id=junior_id).last()
instance = JuniorTask.objects.create(**task_data)
tasks_created.append(instance)
return instance return instance
class GuardianDetailSerializer(serializers.ModelSerializer): class GuardianDetailSerializer(serializers.ModelSerializer):
@ -212,7 +267,7 @@ class GuardianDetailSerializer(serializers.ModelSerializer):
"""Meta info""" """Meta info"""
model = Guardian model = Guardian
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob',
'guardian_code','is_active', 'is_complete_profile', 'created_at', 'image', 'guardian_code','is_active', 'is_complete_profile', 'created_at', 'image', 'is_deleted',
'updated_at'] 'updated_at']
class TaskDetailsSerializer(serializers.ModelSerializer): class TaskDetailsSerializer(serializers.ModelSerializer):
"""Task detail serializer""" """Task detail serializer"""
@ -277,10 +332,11 @@ class TaskDetailsjuniorSerializer(serializers.ModelSerializer):
'requested_on', 'rejected_on', 'completed_on', 'requested_on', 'rejected_on', 'completed_on',
'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at'] 'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at']
class TopJuniorSerializer(serializers.ModelSerializer): class TopJuniorSerializer(serializers.ModelSerializer):
"""Top junior serializer""" """Top junior serializer"""
junior = JuniorDetailSerializer() junior = JuniorDetailSerializer()
position = serializers.IntegerField() position = serializers.SerializerMethodField()
class Meta(object): class Meta(object):
"""Meta info""" """Meta info"""
@ -290,9 +346,13 @@ class TopJuniorSerializer(serializers.ModelSerializer):
def to_representation(self, instance): def to_representation(self, instance):
"""Convert instance to representation""" """Convert instance to representation"""
representation = super().to_representation(instance) representation = super().to_representation(instance)
representation['position'] = instance.position
return representation return representation
@staticmethod
def get_position(obj):
""" get position/rank """
return obj.rank
class GuardianProfileSerializer(serializers.ModelSerializer): class GuardianProfileSerializer(serializers.ModelSerializer):
"""junior serializer""" """junior serializer"""
@ -338,7 +398,7 @@ class GuardianProfileSerializer(serializers.ModelSerializer):
fields = ['id', 'email', 'first_name', 'last_name', 'country_name','country_code', 'phone', 'gender', 'dob', 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', 'guardian_code', 'notification_count', 'total_count', 'complete_field_count', 'referral_code',
'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method', 'is_active', 'is_complete_profile', 'created_at', 'image', 'signup_method',
'updated_at', 'passcode'] 'updated_at', 'passcode','is_deleted']
class ApproveJuniorSerializer(serializers.ModelSerializer): class ApproveJuniorSerializer(serializers.ModelSerializer):
@ -351,8 +411,9 @@ class ApproveJuniorSerializer(serializers.ModelSerializer):
def create(self, validated_data): def create(self, validated_data):
"""update guardian code""" """update guardian code"""
instance = self.context['junior'] instance = self.context['junior']
instance.guardian_code = [self.context['guardian_code']] guardian_code = self.context['guardian_code']
instance.guardian_code_approved = True index = instance.guardian_code.index(guardian_code)
instance.guardian_code_status[index] = str(NUMBER['two'])
instance.save() instance.save()
return instance return instance
@ -380,17 +441,17 @@ class ApproveTaskSerializer(serializers.ModelSerializer):
# update complete time of task # update complete time of task
# instance.completed_on = real_time() # instance.completed_on = real_time()
instance.completed_on = timezone.now().astimezone(pytz.utc) instance.completed_on = timezone.now().astimezone(pytz.utc)
send_notification.delay(TASK_POINTS, None, junior_details.auth.id, {}) send_notification.delay(TASK_APPROVED, instance.guardian.user.id, GUARDIAN,
junior_details.auth.id, {'task_id': instance.id})
else: else:
# reject the task # reject the task
instance.task_status = str(NUMBER['three']) instance.task_status = str(NUMBER['three'])
instance.is_approved = False instance.is_approved = False
# update total task point
junior_data.total_points = junior_data.total_points - instance.points
# update reject time of task # update reject time of task
# instance.rejected_on = real_time() # instance.rejected_on = real_time()
instance.rejected_on = timezone.now().astimezone(pytz.utc) instance.rejected_on = timezone.now().astimezone(pytz.utc)
send_notification.delay(TASK_REJECTED, None, junior_details.auth.id, {}) send_notification.delay(TASK_REJECTED, instance.guardian.user.id, GUARDIAN,
junior_details.auth.id, {'task_id': instance.id})
instance.save() instance.save()
junior_data.save() junior_data.save()
return junior_details return junior_details
@ -403,11 +464,20 @@ class GuardianDetailListSerializer(serializers.ModelSerializer):
email = serializers.SerializerMethodField('get_email') email = serializers.SerializerMethodField('get_email')
image = serializers.SerializerMethodField('get_image') image = serializers.SerializerMethodField('get_image')
guardian_id = serializers.SerializerMethodField('get_guardian_id') 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): class Meta(object):
"""Meta info""" """Meta info"""
model = JuniorGuardianRelationship model = JuniorGuardianRelationship
fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'created_at', fields = ['guardian_id', 'first_name', 'last_name', 'email', 'relationship', 'image', 'dob',
'guardian_code', 'gender', 'phone', 'country_name', 'created_at', 'guardian_code_status',
'updated_at'] 'updated_at']
def get_guardian_id(self,obj): def get_guardian_id(self,obj):
@ -422,9 +492,36 @@ class GuardianDetailListSerializer(serializers.ModelSerializer):
return obj.guardian.user.last_name return obj.guardian.user.last_name
def get_email(self,obj): def get_email(self,obj):
"""emailof guardian""" """email of guardian"""
return obj.guardian.user.email return obj.guardian.user.email
def get_image(self,obj): def get_image(self,obj):
"""first name of guardian""" """guardian image"""
return 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,7 +1,12 @@
"""task files""" """task files"""
"""Django import"""
# Django import
import secrets import secrets
def generate_otp(): def generate_otp():
"""generate random otp""" """
generate random otp
"""
digits = "0123456789" digits = "0123456789"
return "".join(secrets.choice(digits) for _ in range(6)) return "".join(secrets.choice(digits) for _ in range(6))

View File

@ -1,7 +1,7 @@
""" Urls files""" """ Urls files"""
"""Django import""" """Django import"""
from django.urls import path, include from django.urls import path, include
from .views import (SignupViewset, UpdateGuardianProfile, AllTaskListAPIView, CreateTaskAPIView, TaskListAPIView, from .views import (SignupViewset, UpdateGuardianProfile, CreateTaskAPIView, TaskListAPIView,
SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView, SearchTaskListAPIView, TopJuniorListAPIView, ApproveJuniorAPIView, ApproveTaskAPIView,
GuardianListAPIView) GuardianListAPIView)
"""Third party import""" """Third party import"""
@ -25,8 +25,6 @@ router.register('sign-up', SignupViewset, basename='sign-up')
router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile') router.register('create-guardian-profile', UpdateGuardianProfile, basename='update-guardian-profile')
# Create Task API""" # Create Task API"""
router.register('create-task', CreateTaskAPIView, basename='create-task') 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""" # Task list bases on the status API"""
router.register('task-list', TaskListAPIView, basename='task-list') router.register('task-list', TaskListAPIView, basename='task-list')
# Leaderboard API""" # Leaderboard API"""

View File

@ -11,7 +11,7 @@ import tempfile
# Import date time module's function # Import date time module's function
from datetime import datetime, time from datetime import datetime, time
# import Number constant # import Number constant
from base.constants import NUMBER, time_url from base.constants import NUMBER
# Import Junior's model # Import Junior's model
from junior.models import Junior, JuniorPoints from junior.models import Junior, JuniorPoints
# Import guardian's model # Import guardian's model
@ -22,6 +22,8 @@ from zod_bank.celery import app
from notifications.constants import REFERRAL_POINTS from notifications.constants import REFERRAL_POINTS
# send notification function # send notification function
from notifications.utils import send_notification from notifications.utils import send_notification
# Define upload image on # Define upload image on
# ali baba cloud # ali baba cloud
# firstly save image # firstly save image
@ -41,18 +43,41 @@ def upload_image_to_alibaba(image, filename):
# Save the image object to a temporary file # Save the image object to a temporary file
with tempfile.NamedTemporaryFile(delete=False) as temp_file: with tempfile.NamedTemporaryFile(delete=False) as temp_file:
"""write image in temporary file""" """write image in temporary file"""
if type(image) == bytes: temp_file.write(image.read())
temp_file.write(image) return upload_file_to_alibaba(temp_file, filename)
else:
temp_file.write(image.read())
"""auth of bucket""" def upload_base64_image_to_alibaba(image, filename):
auth = oss2.Auth(settings.ALIYUN_OSS_ACCESS_KEY_ID, settings.ALIYUN_OSS_ACCESS_KEY_SECRET) """
"""fetch bucket details""" upload image on oss alibaba bucket
bucket = oss2.Bucket(auth, settings.ALIYUN_OSS_ENDPOINT, settings.ALIYUN_OSS_BUCKET_NAME) """
# Upload the temporary file to Alibaba OSS # Save the image object to a temporary file
bucket.put_object_from_file(filename, temp_file.name) with tempfile.NamedTemporaryFile(delete=False) as temp_file:
"""create perfect url for image""" # write image in temporary file
new_filename = filename.replace(' ', '%20') 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}" return f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{new_filename}"
@ -92,7 +117,7 @@ def update_referral_points(referral_code, referral_code_used):
junior_query.total_points = junior_query.total_points + NUMBER['five'] junior_query.total_points = junior_query.total_points + NUMBER['five']
junior_query.referral_points = junior_query.referral_points + NUMBER['five'] junior_query.referral_points = junior_query.referral_points + NUMBER['five']
junior_query.save() junior_query.save()
send_notification.delay(REFERRAL_POINTS, None, junior_queryset.auth.id, {}) send_notification.delay(REFERRAL_POINTS, None, None, junior_queryset.auth.id, {})
@ -102,7 +127,7 @@ def update_expired_task_status(data=None):
Update task of the status if due date is in past Update task of the status if due date is in past
""" """
try: try:
task_status = [str(NUMBER['one']), str(NUMBER['two']), str(NUMBER['four'])] task_status = [str(NUMBER['one']), str(NUMBER['two'])]
JuniorTask.objects.filter(due_date__lt=datetime.today().date(), JuniorTask.objects.filter(due_date__lt=datetime.today().date(),
task_status__in=task_status).update(task_status=str(NUMBER['six'])) task_status__in=task_status).update(task_status=str(NUMBER['six']))
except ObjectDoesNotExist as e: except ObjectDoesNotExist as e:

View File

@ -6,12 +6,14 @@
# Import PageNumberPagination # Import PageNumberPagination
# Import User # Import User
# Import timezone # 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.permissions import IsAuthenticated
from rest_framework import viewsets, status from rest_framework import viewsets, status
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from django.contrib.auth.models import User from django.contrib.auth.models import User
from base.constants import guardian_code_tuple
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from django.utils import timezone from django.utils import timezone
# Import guardian's model, # Import guardian's model,
@ -32,13 +34,13 @@ from .serializers import (UserSerializer, CreateGuardianSerializer, TaskSerializ
GuardianDetailListSerializer) GuardianDetailListSerializer)
from .models import Guardian, JuniorTask from .models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
from account.models import UserEmailOtp, UserNotification from account.models import UserEmailOtp, UserNotification, UserDeviceDetails
from .tasks import generate_otp from .tasks import generate_otp
from account.utils import custom_response, custom_error_response, OTP_EXPIRY, send_otp_email from account.utils import custom_response, custom_error_response, OTP_EXPIRY, send_otp_email
from base.messages import ERROR_CODE, SUCCESS_CODE 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 .utils import upload_image_to_alibaba
from notifications.constants import REGISTRATION, TASK_CREATED, LEADERBOARD_RANKING from notifications.constants import REGISTRATION, TASK_ASSIGNED, ASSOCIATE_APPROVED, ASSOCIATE_REJECTED
from notifications.utils import send_notification from notifications.utils import send_notification
""" Define APIs """ """ Define APIs """
@ -57,37 +59,36 @@ class SignupViewset(viewsets.ModelViewSet):
"""Signup view set""" """Signup view set"""
queryset = User.objects.all() queryset = User.objects.all()
serializer_class = UserSerializer serializer_class = UserSerializer
http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""Create user profile""" """Create user profile"""
try: device_id = request.META.get('HTTP_DEVICE_ID')
if request.data['user_type'] in [str(NUMBER['one']), str(NUMBER['two'])]: if request.data['user_type'] in [str(NUMBER['one']), str(NUMBER['two'])]:
serializer = UserSerializer(context=request.data['user_type'], data=request.data) serializer = UserSerializer(context=request.data['user_type'], data=request.data)
if serializer.is_valid(): if serializer.is_valid():
user = serializer.save() user = serializer.save()
"""Generate otp""" """Generate otp"""
otp = generate_otp() otp = generate_otp()
# expire otp after 1 day # expire otp after 1 day
expiry = OTP_EXPIRY expiry = timezone.now() + timezone.timedelta(days=1)
# create user email otp object # create user email otp object
UserEmailOtp.objects.create(email=request.data['email'], otp=otp, UserEmailOtp.objects.create(email=request.data['email'], otp=otp,
user_type=str(request.data['user_type']), expired_at=expiry) user_type=str(request.data['user_type']), expired_at=expiry)
"""Send email to the register user""" """Send email to the register user"""
send_otp_email(request.data['email'], otp) send_otp_email.delay(request.data['email'], otp)
# send push notification for registration UserDeviceDetails.objects.create(user=user, device_id=device_id)
send_notification.delay(REGISTRATION, None, user.id, {}) return custom_response(SUCCESS_CODE['3001'],
return custom_response(SUCCESS_CODE['3001'], response_status=status.HTTP_200_OK)
response_status=status.HTTP_200_OK) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) else:
else: return custom_error_response(ERROR_CODE['2028'], response_status=status.HTTP_400_BAD_REQUEST)
return custom_error_response(ERROR_CODE['2028'], 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 UpdateGuardianProfile(viewsets.ViewSet):
class UpdateGuardianProfile(viewsets.ModelViewSet):
"""Update guardian profile""" """Update guardian profile"""
queryset = Guardian.objects.all()
serializer_class = CreateGuardianSerializer serializer_class = CreateGuardianSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""Create guardian profile""" """Create guardian profile"""
@ -119,7 +120,6 @@ class UpdateGuardianProfile(viewsets.ViewSet):
class AllTaskListAPIView(viewsets.ModelViewSet): class AllTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile""" """Update guardian profile"""
serializer_class = TaskDetailsSerializer serializer_class = TaskDetailsSerializer
queryset = JuniorTask.objects.all()
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
@ -129,41 +129,13 @@ class AllTaskListAPIView(viewsets.ModelViewSet):
serializer = TaskDetailsSerializer(queryset, many=True) serializer = TaskDetailsSerializer(queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
# class TaskListAPIView(viewsets.ModelViewSet):
# """Update guardian profile"""
# serializer_class = TaskDetailsSerializer
# permission_classes = [IsAuthenticated]
# pagination_class = PageNumberPagination
# http_method_names = ('get',)
#
# def list(self, request, *args, **kwargs):
# """Create guardian profile"""
# try:
# status_value = self.request.GET.get('status')
# search = self.request.GET.get('search')
# if search and str(status_value) == '0':
# queryset = JuniorTask.objects.filter(guardian__user=request.user,
# task_name__icontains=search).order_by('due_date', 'created_at')
# elif search and str(status_value) != '0':
# queryset = JuniorTask.objects.filter(guardian__user=request.user,task_name__icontains=search,
# task_status=status_value).order_by('due_date', 'created_at')
# if search is None and str(status_value) == '0':
# queryset = JuniorTask.objects.filter(guardian__user=request.user).order_by('due_date', 'created_at')
# elif search is None and str(status_value) != '0':
# queryset = JuniorTask.objects.filter(guardian__user=request.user,
# task_status=status_value).order_by('due_date','created_at')
# paginator = self.pagination_class()
# # use Pagination
# paginated_queryset = paginator.paginate_queryset(queryset, request)
# # use TaskDetailsSerializer 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 TaskListAPIView(viewsets.ModelViewSet): class TaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile""" """Task list
Params
status
search
page"""
serializer_class = TaskDetailsSerializer serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,) filter_backends = (SearchFilter,)
@ -200,42 +172,59 @@ class CreateTaskAPIView(viewsets.ModelViewSet):
http_method_names = ('post', ) http_method_names = ('post', )
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""
image should be in form data
"""
try: try:
image = request.data['default_image'] image = request.data['default_image']
junior = request.data['junior'] juniors = request.data['junior'].split(',')
allowed_extensions = ['.jpg', '.jpeg', '.png'] for junior in juniors:
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 not junior.isnumeric():
"""junior value must be integer"""
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
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')
# use TaskSerializer serializer
serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data)
if serializer.is_valid():
# save serializer
serializer.save()
junior_id = Junior.objects.filter(id=junior).last() junior_id = Junior.objects.filter(id=junior).last()
send_notification.delay(TASK_CREATED, None, junior_id.auth.id, {}) if junior_id:
return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) guardian_data = Guardian.objects.filter(user=request.user).last()
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) index = junior_id.guardian_code.index(guardian_data.guardian_code)
status_index = junior_id.guardian_code_status[index]
if status_index == str(NUMBER['three']):
return custom_error_response(ERROR_CODE['2078'], 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 not junior.isnumeric():
"""junior value must be integer"""
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
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')
junior_data = data.pop('junior')
# use TaskSerializer serializer
serializer = TaskSerializer(context={"user":request.user, "image":image_data,
"junior_data":junior_data}, data=data)
if serializer.is_valid():
# save serializer
task = serializer.save()
send_notification.delay(TASK_ASSIGNED, request.auth.payload['user_id'], GUARDIAN,
junior_id.auth.id, {'task_id': task.id})
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)
else:
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class SearchTaskListAPIView(viewsets.ModelViewSet): class SearchTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile""" """Filter task"""
serializer_class = TaskDetailsSerializer serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination pagination_class = PageNumberPagination
http_method_names = ('get',)
def get_queryset(self): def get_queryset(self):
"""Get the queryset for the view""" """Get the queryset for the view"""
@ -246,7 +235,7 @@ class SearchTaskListAPIView(viewsets.ModelViewSet):
return junior_queryset return junior_queryset
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""Create guardian profile""" """Filter task"""
try: try:
queryset = self.get_queryset() queryset = self.get_queryset()
paginator = self.pagination_class() paginator = self.pagination_class()
@ -260,10 +249,11 @@ class SearchTaskListAPIView(viewsets.ModelViewSet):
class TopJuniorListAPIView(viewsets.ModelViewSet): class TopJuniorListAPIView(viewsets.ModelViewSet):
"""Top juniors list""" """Top juniors list
No Params"""
serializer_class = TopJuniorSerializer serializer_class = TopJuniorSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
queryset = JuniorPoints.objects.all() http_method_names = ('get',)
def get_serializer_context(self): def get_serializer_context(self):
# context list # context list
@ -271,98 +261,116 @@ class TopJuniorListAPIView(viewsets.ModelViewSet):
context.update({'view': self}) context.update({'view': self})
return context 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): def list(self, request, *args, **kwargs):
"""Fetch junior list of those who complete their tasks""" """Fetch junior list of those who complete their tasks"""
try: try:
junior_total_points = self.get_queryset().order_by('-total_points') junior_total_points = self.get_queryset()[:15]
# Update the position field for each JuniorPoints object
for index, junior in enumerate(junior_total_points):
junior.position = index + 1
send_notification.delay(LEADERBOARD_RANKING, None, junior.junior.auth.id, {})
junior.save()
serializer = self.get_serializer(junior_total_points, many=True) serializer = self.get_serializer(junior_total_points, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) 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""" """approve junior by guardian"""
serializer_class = ApproveJuniorSerializer serializer_class = ApproveJuniorSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def get_queryset(self):
"""Get the queryset for the view"""
guardian = Guardian.objects.filter(user__email=self.request.user).last()
# fetch junior query
junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last()
return guardian, junior_queryset
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" junior list""" """ Use below param
{"junior_id":"75",
"action":"1"}
"""
try: try:
queryset = self.get_queryset() guardian = Guardian.objects.filter(user__email=self.request.user).last()
# fetch junior query
junior_queryset = Junior.objects.filter(id=self.request.data.get('junior_id')).last()
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 # action 1 is use for approve and 2 for reject
if request.data['action'] == '1': if request.data['action'] == '1':
# use ApproveJuniorSerializer serializer # use ApproveJuniorSerializer serializer
serializer = ApproveJuniorSerializer(context={"guardian_code": queryset[0].guardian_code, serializer = ApproveJuniorSerializer(context={"guardian_code": guardian.guardian_code,
"junior": queryset[1], "action": request.data['action']}, "junior": junior_queryset,
"action": request.data['action']},
data=request.data) data=request.data)
if serializer.is_valid(): if serializer.is_valid():
# save serializer # save serializer
serializer.save() 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) return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK)
else: else:
queryset[1].guardian_code = None if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code):
queryset[1].save() 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, {})
return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK) return custom_response(SUCCESS_CODE['3024'], response_status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ApproveTaskAPIView(viewsets.ViewSet): class ApproveTaskAPIView(viewsets.ModelViewSet):
"""approve junior by guardian""" """approve junior by guardian"""
serializer_class = ApproveTaskSerializer serializer_class = ApproveTaskSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def get_queryset(self):
"""Get the queryset for the view"""
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()
return guardian, task_queryset
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" junior list""" """ 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 # action 1 is use for approve and 2 for reject
try: try:
queryset = self.get_queryset() 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 (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 # use ApproveJuniorSerializer serializer
serializer = ApproveTaskSerializer(context={"guardian_code": queryset[0].guardian_code, serializer = ApproveTaskSerializer(context={"guardian_code": guardian.guardian_code,
"task_instance": queryset[1], "task_instance": task_queryset,
"action": str(request.data['action']), "action": str(request.data['action']),
"junior": self.request.data['junior_id']}, "junior": self.request.data['junior_id']},
data=request.data) data=request.data)
unexpected_task_status = [str(NUMBER['five']), str(NUMBER['six'])] unexpected_task_status = [str(NUMBER['five']), str(NUMBER['six'])]
if (str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid() if (str(request.data['action']) == str(NUMBER['one']) and serializer.is_valid()
and queryset[1] and queryset[1].task_status not in unexpected_task_status): and task_queryset and task_queryset.task_status not in unexpected_task_status):
# save serializer # save serializer
serializer.save() serializer.save()
return custom_response(SUCCESS_CODE['3025'], 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() elif (str(request.data['action']) == str(NUMBER['two']) and serializer.is_valid()
and queryset[1] and queryset[1].task_status not in unexpected_task_status): and task_queryset and task_queryset.task_status not in unexpected_task_status):
# save serializer # save serializer
serializer.save() serializer.save()
return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK) return custom_response(SUCCESS_CODE['3026'], response_status=status.HTTP_200_OK)
else: else:
return custom_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
#
class GuardianListAPIView(viewsets.ModelViewSet): class GuardianListAPIView(viewsets.ModelViewSet):
"""Guardian list of assosicated junior""" """Guardian list of assosicated junior"""
@ -371,7 +379,8 @@ class GuardianListAPIView(viewsets.ModelViewSet):
http_method_names = ('get',) http_method_names = ('get',)
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
""" junior list""" """ Guardian list of assosicated junior
No Params"""
try: try:
guardian_data = JuniorGuardianRelationship.objects.filter(junior__auth__email=self.request.user) guardian_data = JuniorGuardianRelationship.objects.filter(junior__auth__email=self.request.user)
# fetch junior object # fetch junior object
@ -379,6 +388,6 @@ class GuardianListAPIView(viewsets.ModelViewSet):
# use GuardianDetailListSerializer serializer # use GuardianDetailListSerializer serializer
serializer = GuardianDetailListSerializer(guardian_data, many=True) serializer = GuardianDetailListSerializer(guardian_data, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(ERROR_CODE['2068'], 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: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)

View File

@ -2,8 +2,27 @@
"""Third party Django app""" """Third party Django app"""
from django.contrib import admin from django.contrib import admin
"""Import Django app""" """Import Django app"""
from .models import Junior, JuniorPoints, JuniorGuardianRelationship from .models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle,
JuniorArticleCard, FAQ)
# Register your models here. # 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) @admin.register(Junior)
class JuniorAdmin(admin.ModelAdmin): class JuniorAdmin(admin.ModelAdmin):
"""Junior Admin""" """Junior Admin"""
@ -27,3 +46,7 @@ class JuniorGuardianRelationshipAdmin(admin.ModelAdmin):
"""Junior Admin""" """Junior Admin"""
list_display = ['guardian', 'junior', 'relationship'] 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,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,10 +6,11 @@ from django.contrib.auth import get_user_model
"""Import ArrayField""" """Import ArrayField"""
from django.contrib.postgres.fields import ArrayField from django.contrib.postgres.fields import ArrayField
"""Import django app""" """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 # Import guardian's model
from guardian.models import Guardian from guardian.models import Guardian
# Import web admin's model
from web_admin.models import SurveyOption, ArticleSurvey, Article, ArticleCard
"""Define User model""" """Define User model"""
User = get_user_model() User = get_user_model()
# Create your models here. # Create your models here.
@ -67,12 +68,17 @@ class Junior(models.Model):
is_password_set = models.BooleanField(default=True) is_password_set = models.BooleanField(default=True)
# junior profile is complete or not""" # junior profile is complete or not"""
is_complete_profile = models.BooleanField(default=False) is_complete_profile = models.BooleanField(default=False)
# junior profile deleted or not"""
is_deleted = models.BooleanField(default=False)
# passcode of the junior profile""" # passcode of the junior profile"""
passcode = models.IntegerField(null=True, blank=True, default=None) passcode = models.IntegerField(null=True, blank=True, default=None)
# junior is verified or not""" # junior is verified or not"""
is_verified = models.BooleanField(default=False) is_verified = models.BooleanField(default=False)
"""guardian code is approved or not""" """guardian code is approved or not"""
guardian_code_approved = models.BooleanField(default=False) 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""" # Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
@ -137,3 +143,102 @@ class JuniorGuardianRelationship(models.Model):
return f'{self.guardian.user}' 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

@ -12,18 +12,19 @@ from rest_framework_simplejwt.tokens import RefreshToken
# local imports # local imports
from account.utils import send_otp_email, generate_code from account.utils import send_otp_email, generate_code
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, FAQ
from guardian.tasks import generate_otp from guardian.tasks import generate_otp
from base.messages import ERROR_CODE, SUCCESS_CODE from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED from base.constants import (PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, NUMBER, JUN, ZOD, EXPIRED,
GUARDIAN_CODE_STATUS, JUNIOR)
from guardian.models import Guardian, JuniorTask from guardian.models import Guardian, JuniorTask
from account.models import UserEmailOtp, UserNotification from account.models import UserEmailOtp, UserNotification
from junior.utils import junior_notification_email, junior_approval_mail 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 guardian.utils import real_time, update_referral_points, convert_timedelta_into_datetime
from notifications.utils import send_notification from notifications.utils import send_notification
from notifications.constants import (INVITED_GUARDIAN, APPROVED_JUNIOR, SKIPPED_PROFILE_SETUP, TASK_ACTION, from notifications.constants import (ASSOCIATE_REQUEST, ASSOCIATE_JUNIOR, TASK_ACTION,
TASK_SUBMITTED) )
from web_admin.models import ArticleCard
class ListCharField(serializers.ListField): class ListCharField(serializers.ListField):
"""Serializer for Array field""" """Serializer for Array field"""
@ -87,14 +88,27 @@ class CreateJuniorSerializer(serializers.ModelSerializer):
if junior: if junior:
"""update details according to the data get from request""" """update details according to the data get from request"""
junior.gender = validated_data.get('gender',junior.gender) junior.gender = validated_data.get('gender',junior.gender)
"""Update guardian code""" # Update guardian code"""
junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code) # condition for guardian code
"""condition for guardian code"""
if guardian_code: if guardian_code:
junior.guardian_code = guardian_code 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() guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last()
if guardian_data: if guardian_data:
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior) 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)
junior.dob = validated_data.get('dob', junior.dob) junior.dob = validated_data.get('dob', junior.dob)
junior.passcode = validated_data.get('passcode', junior.passcode) junior.passcode = validated_data.get('passcode', junior.passcode)
junior.country_name = validated_data.get('country_name', junior.country_name) junior.country_name = validated_data.get('country_name', junior.country_name)
@ -142,8 +156,8 @@ class JuniorDetailSerializer(serializers.ModelSerializer):
"""Meta info""" """Meta info"""
model = Junior model = Junior
fields = ['id', 'email', 'first_name', 'last_name', 'country_code', 'phone', 'gender', 'dob', 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', 'guardian_code', 'image', 'is_invited', 'referral_code','is_active', 'is_complete_profile',
'image', 'updated_at'] 'created_at', 'image', 'is_deleted', 'updated_at']
class JuniorDetailListSerializer(serializers.ModelSerializer): class JuniorDetailListSerializer(serializers.ModelSerializer):
"""junior serializer""" """junior serializer"""
@ -159,6 +173,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
rejected_task = serializers.SerializerMethodField('get_rejected_task') rejected_task = serializers.SerializerMethodField('get_rejected_task')
pending_task = serializers.SerializerMethodField('get_pending_task') pending_task = serializers.SerializerMethodField('get_pending_task')
position = serializers.SerializerMethodField('get_position') position = serializers.SerializerMethodField('get_position')
guardian_code_status = serializers.SerializerMethodField('get_guardian_code_status')
def get_auth(self, obj): def get_auth(self, obj):
@ -175,13 +190,13 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
return data return data
def get_position(self, obj): def get_position(self, obj):
return get_junior_leaderboard_rank(obj)
def get_points(self, obj):
data = JuniorPoints.objects.filter(junior=obj).last() data = JuniorPoints.objects.filter(junior=obj).last()
if data: if data:
return data.position return data.total_points
return 99999 return NUMBER['zero']
def get_points(self, obj):
data = sum(JuniorTask.objects.filter(junior=obj, task_status=COMPLETED).values_list('points', flat=True))
return data
def get_in_progress_task(self, obj): def get_in_progress_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=IN_PROGRESS).count() data = JuniorTask.objects.filter(junior=obj, task_status=IN_PROGRESS).count()
@ -203,13 +218,21 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
def get_pending_task(self, obj): def get_pending_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=PENDING).count() data = JuniorTask.objects.filter(junior=obj, task_status=PENDING).count()
return data 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): class Meta(object):
"""Meta info""" """Meta info"""
model = Junior 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', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'assigned_task','points', 'pending_task', 'in_progress_task', 'completed_task', '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): class JuniorProfileSerializer(serializers.ModelSerializer):
"""junior serializer""" """junior serializer"""
@ -252,15 +275,14 @@ class JuniorProfileSerializer(serializers.ModelSerializer):
fields = ['id', 'email', 'first_name', 'last_name', 'country_name', 'country_code', 'phone', 'gender', 'dob', 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', 'guardian_code', 'referral_code','is_active', 'is_complete_profile', 'created_at', 'image',
'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method', 'updated_at', 'notification_count', 'total_count', 'complete_field_count', 'signup_method',
'is_invited', 'passcode', 'guardian_code_approved'] 'is_invited', 'passcode', 'guardian_code_approved', 'is_deleted']
class AddJuniorSerializer(serializers.ModelSerializer): class AddJuniorSerializer(serializers.ModelSerializer):
"""Add junior serializer""" """Add junior serializer"""
class Meta(object): class Meta(object):
"""Meta info""" """Meta info"""
model = Junior model = Junior
fields = ['id', 'gender','dob', 'is_invited'] fields = ['id', 'gender', 'dob', 'is_invited']
def create(self, validated_data): def create(self, validated_data):
@ -269,6 +291,7 @@ class AddJuniorSerializer(serializers.ModelSerializer):
email = self.context['email'] email = self.context['email']
guardian = self.context['user'] guardian = self.context['user']
relationship = self.context['relationship'] relationship = self.context['relationship']
profile_image = self.context['image']
full_name = self.context['first_name'] + ' ' + self.context['last_name'] full_name = self.context['first_name'] + ' ' + self.context['last_name']
guardian_data = Guardian.objects.filter(user__username=guardian).last() guardian_data = Guardian.objects.filter(user__username=guardian).last()
user_data = User.objects.create(username=email, email=email, user_data = User.objects.create(username=email, email=email,
@ -278,14 +301,18 @@ class AddJuniorSerializer(serializers.ModelSerializer):
user_data.set_password(password) user_data.set_password(password)
user_data.save() user_data.save()
junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'), junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'),
image=profile_image,
dob=validated_data.get('dob'), is_invited=True, dob=validated_data.get('dob'), is_invited=True,
guardian_code=[guardian_data.guardian_code], guardian_code=[guardian_data.guardian_code],
junior_code=generate_code(JUN, user_data.id), junior_code=generate_code(JUN, user_data.id),
referral_code=generate_code(ZOD, 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) is_password_set=False, is_verified=True,
guardian_code_status=[str(NUMBER['two'])])
JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data, JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data,
relationship=relationship) relationship=relationship)
total_junior = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior_data, position=total_junior)
"""Generate otp""" """Generate otp"""
otp_value = generate_otp() otp_value = generate_otp()
expiry_time = timezone.now() + timezone.timedelta(days=1) expiry_time = timezone.now() + timezone.timedelta(days=1)
@ -294,9 +321,9 @@ class AddJuniorSerializer(serializers.ModelSerializer):
# add push notification # add push notification
UserNotification.objects.get_or_create(user=user_data) UserNotification.objects.get_or_create(user=user_data)
"""Notification email""" """Notification email"""
junior_notification_email(email, full_name, email, password) junior_notification_email.delay(email, full_name, email, password)
# push notification # push notification
send_notification.delay(SKIPPED_PROFILE_SETUP, None, junior_data.auth.id, {}) send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth.id, {})
return junior_data return junior_data
@ -309,8 +336,13 @@ class RemoveJuniorSerializer(serializers.ModelSerializer):
fields = ('id', 'is_invited') fields = ('id', 'is_invited')
def update(self, instance, validated_data): def update(self, instance, validated_data):
if instance: if instance:
guardian_code = self.context['guardian_code']
instance.is_invited = False 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() instance.save()
return instance return instance
@ -323,13 +355,12 @@ class CompleteTaskSerializer(serializers.ModelSerializer):
fields = ('id', 'image') fields = ('id', 'image')
def update(self, instance, validated_data): def update(self, instance, validated_data):
instance.image = validated_data.get('image', instance.image) instance.image = validated_data.get('image', instance.image)
# instance.requested_on = real_time()
instance.requested_on = timezone.now().astimezone(pytz.utc) instance.requested_on = timezone.now().astimezone(pytz.utc)
instance.task_status = str(NUMBER['four']) instance.task_status = str(NUMBER['four'])
instance.is_approved = False instance.is_approved = False
instance.save() instance.save()
send_notification.delay(TASK_SUBMITTED, None, instance.junior.auth.id, {}) send_notification.delay(TASK_ACTION, instance.junior.auth.id, JUNIOR,
send_notification.delay(TASK_ACTION, None, instance.guardian.user.id, {}) instance.guardian.user.id, {'task_id': instance.id})
return instance return instance
class JuniorPointsSerializer(serializers.ModelSerializer): class JuniorPointsSerializer(serializers.ModelSerializer):
@ -349,10 +380,7 @@ class JuniorPointsSerializer(serializers.ModelSerializer):
return obj.junior.id return obj.junior.id
def get_position(self, obj): def get_position(self, obj):
data = JuniorPoints.objects.filter(junior=obj.junior).last() return get_junior_leaderboard_rank(obj.junior)
if data:
return data.position
return 99999
def get_points(self, obj): def get_points(self, obj):
"""total points""" """total points"""
points = JuniorPoints.objects.filter(junior=obj.junior).last() points = JuniorPoints.objects.filter(junior=obj.junior).last()
@ -384,7 +412,7 @@ class JuniorPointsSerializer(serializers.ModelSerializer):
"""Meta info""" """Meta info"""
model = Junior model = Junior
fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task', fields = ['junior_id', 'total_points', 'position', 'pending_task', 'in_progress_task', 'completed_task',
'requested_task', 'rejected_task', 'expired_task'] 'requested_task', 'rejected_task', 'expired_task', 'is_deleted']
class AddGuardianSerializer(serializers.ModelSerializer): class AddGuardianSerializer(serializers.ModelSerializer):
"""Add guardian serializer""" """Add guardian serializer"""
@ -403,13 +431,15 @@ class AddGuardianSerializer(serializers.ModelSerializer):
relationship = self.context['relationship'] relationship = self.context['relationship']
full_name = self.context['first_name'] + ' ' + self.context['last_name'] full_name = self.context['first_name'] + ' ' + self.context['last_name']
junior_data = Junior.objects.filter(auth__username=junior).last() 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() instance = User.objects.filter(username=email).last()
if instance: if instance:
guardian_data = Guardian.objects.filter(user=instance).update(is_invited=True, guardian_data = Guardian.objects.filter(user=instance).update(is_invited=True,
referral_code=generate_code(ZOD, referral_code=generate_code(ZOD,
instance.id), instance.id),
referral_code_used=junior_data.referral_code, referral_code_used=junior_data.referral_code,
is_verified=True) is_verified=True)
UserNotification.objects.get_or_create(user=instance) UserNotification.objects.get_or_create(user=instance)
return guardian_data return guardian_data
else: else:
@ -436,9 +466,8 @@ class AddGuardianSerializer(serializers.ModelSerializer):
"""Notification email""" """Notification email"""
junior_notification_email(email, full_name, email, password) junior_notification_email(email, full_name, email, password)
junior_approval_mail(email, full_name) junior_approval_mail.delay(email, full_name)
send_notification.delay(INVITED_GUARDIAN, None, junior_data.auth.id, {}) send_notification.delay(ASSOCIATE_REQUEST, junior_data.auth.id, JUNIOR, guardian_data.user.id, {})
send_notification.delay(APPROVED_JUNIOR, None, guardian_data.user.id, {})
return guardian_data return guardian_data
class StartTaskSerializer(serializers.ModelSerializer): class StartTaskSerializer(serializers.ModelSerializer):
@ -481,3 +510,41 @@ class ReAssignTaskSerializer(serializers.ModelSerializer):
instance.requested_on = None instance.requested_on = None
instance.save() instance.save()
return instance 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

@ -4,7 +4,9 @@ from django.urls import path, include
from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView, from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView, AddJuniorAPIView,
InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView, InvitedJuniorAPIView, FilterJuniorAPIView, RemoveJuniorAPIView, JuniorTaskListAPIView,
CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode, CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode,
InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView) InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView,
StartAssessmentAPIView, CheckAnswerAPIView, CompleteArticleAPIView, ReadArticleCardAPIView,
CreateArticleCardAPIView, RemoveGuardianCodeAPIView, FAQViewSet)
"""Third party import""" """Third party import"""
from rest_framework import routers from rest_framework import routers
@ -41,6 +43,16 @@ router.register('junior-points', JuniorPointsListAPIView, basename='junior-point
router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code') router.register('validate-referral-code', ValidateReferralCode, basename='validate-referral-code')
# invite guardian API""" # invite guardian API"""
router.register('invite-guardian', InviteGuardianAPIView, basename='invite-guardian') 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""" # Define url pattern"""
urlpatterns = [ urlpatterns = [
path('api/v1/', include(router.urls)), path('api/v1/', include(router.urls)),
@ -48,4 +60,7 @@ urlpatterns = [
path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()), path('api/v1/complete-task/', CompleteJuniorTaskAPIView.as_view()),
path('api/v1/start-task/', StartTaskAPIView.as_view()), path('api/v1/start-task/', StartTaskAPIView.as_view()),
path('api/v1/reassign-task/', ReAssignJuniorTaskAPIView.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

@ -4,7 +4,9 @@ from django.conf import settings
"""Third party Django app""" """Third party Django app"""
from templated_email import send_templated_mail from templated_email import send_templated_mail
from .models import JuniorPoints from .models import JuniorPoints
from django.db.models import F from base.constants import NUMBER
from django.db.models import F, Window
from django.db.models.functions.window import Rank
# junior notification # junior notification
# email for sending email # email for sending email
# when guardian create junior profile # when guardian create junior profile
@ -13,6 +15,8 @@ from django.db.models import F
# being part of the zod bank and access the platform # being part of the zod bank and access the platform
# define junior notification email # define junior notification email
# junior approval email # junior approval email
from celery import shared_task
@shared_task()
def junior_notification_email(recipient_email, full_name, email, password): def junior_notification_email(recipient_email, full_name, email, password):
"""Notification email""" """Notification email"""
from_email = settings.EMAIL_FROM_ADDRESS from_email = settings.EMAIL_FROM_ADDRESS
@ -31,7 +35,7 @@ def junior_notification_email(recipient_email, full_name, email, password):
} }
) )
return full_name return full_name
@shared_task()
def junior_approval_mail(guardian, full_name): def junior_approval_mail(guardian, full_name):
"""junior approval mail""" """junior approval mail"""
from_email = settings.EMAIL_FROM_ADDRESS from_email = settings.EMAIL_FROM_ADDRESS
@ -50,11 +54,29 @@ def junior_approval_mail(guardian, full_name):
def update_positions_based_on_points(): def update_positions_based_on_points():
"""Update position of the junior""" """Update position of the junior"""
# First, retrieve all the JuniorPoints instances ordered by total_points in descending order. # First, retrieve all the JuniorPoints instances ordered by total_points in descending order.
juniors_points = JuniorPoints.objects.order_by('-total_points') juniors_points = JuniorPoints.objects.order_by('-total_points', 'created_at')
# Now, iterate through the queryset and update the position field based on the order. # Now, iterate through the queryset and update the position field based on the order.
position = 1 position = NUMBER['one']
for junior_point in juniors_points: for junior_point in juniors_points:
junior_point.position = position junior_point.position = position
junior_point.save() junior_point.save()
position += 1 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

@ -6,11 +6,16 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
from django.contrib.auth.models import User from django.contrib.auth.models import User
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from django.db.models import F
import datetime import datetime
import requests import requests
"""Django app import"""
from rest_framework.viewsets import GenericViewSet, mixins
"""Django app import"""
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
# Import guardian's model, # Import guardian's model,
# Import junior's model, # Import junior's model,
# Import account's model, # Import account's model,
@ -27,20 +32,25 @@ import requests
# Import upload_image_to_alibaba # Import upload_image_to_alibaba
# Import custom_response, custom_error_response # Import custom_response, custom_error_response
# Import constants # Import constants
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship from django.db.models import Sum
from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,\ from junior.models import (Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, JuniorArticle,
JuniorArticleCard, FAQ)
from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, AddJuniorSerializer,
RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer, RemoveJuniorSerializer, CompleteTaskSerializer, JuniorPointsSerializer,
AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer) AddGuardianSerializer, StartTaskSerializer, ReAssignTaskSerializer,
RemoveGuardianCodeSerializer, FAQSerializer, CreateArticleCardSerializer)
from guardian.models import Guardian, JuniorTask from guardian.models import Guardian, JuniorTask
from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER from base.constants import NUMBER, ARTICLE_STATUS, none, GUARDIAN
from account.utils import custom_response, custom_error_response from account.utils import custom_response, custom_error_response
from guardian.utils import upload_image_to_alibaba from guardian.utils import upload_image_to_alibaba
from .utils import update_positions_based_on_points from .utils import update_positions_based_on_points
from notifications.utils import send_notification from notifications.utils import send_notification
from notifications.constants import REMOVE_JUNIOR 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 APIs """
# Define validate guardian code API, # Define validate guardian code API,
# update junior profile, # update junior profile,
@ -57,14 +67,14 @@ from notifications.constants import REMOVE_JUNIOR
# Start task # Start task
# by junior API # by junior API
# Create your views here. # Create your views here.
class UpdateJuniorProfile(viewsets.ViewSet): class UpdateJuniorProfile(viewsets.ModelViewSet):
"""Update junior profile""" """Update junior profile"""
queryset = Junior.objects.all()
serializer_class = CreateJuniorSerializer serializer_class = CreateJuniorSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
"""Use CreateJuniorSerializer""" """Create Junior Profile"""
try: try:
request_data = request.data request_data = request.data
image = request.data.get('image') image = request.data.get('image')
@ -89,15 +99,22 @@ class UpdateJuniorProfile(viewsets.ViewSet):
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) 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) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) 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""" """Check guardian code exist or not"""
queryset = Guardian.objects.all()
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""check guardian code""" """check guardian code
Params
"guardian_code"
"""
try: try:
guardian_code = self.request.GET.get('guardian_code').split(',') guardian_code = self.request.GET.get('guardian_code').split(',')
for code in guardian_code: for code in guardian_code:
@ -128,14 +145,15 @@ class JuniorListAPIView(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
""" junior list""" """ junior list"""
try: try:
update_positions_based_on_points() # update_positions_based_on_points, function removed
guardian_data = Guardian.objects.filter(user__email=request.user).last() guardian_data = Guardian.objects.filter(user__email=request.user).last()
# fetch junior object # fetch junior object
if guardian_data: if guardian_data:
queryset = self.get_queryset() queryset = self.get_queryset()
queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code)) queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code))
# use JuniorDetailListSerializer serializer # use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(queryset, many=True) 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_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK) return custom_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
@ -149,12 +167,36 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
http_method_names = ('post',) http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" junior list""" """ add junior
{ "gender":"1",
"first_name":"abc",
"last_name":"xyz",
"dob":"2023-12-12",
"relationship":"2",
"email":"abc@yopmail.com"
}"""
try: try:
info_data = {'user': request.user, 'relationship': str(request.data['relationship']), 'email': request.data['email'], 'first_name': request.data['first_name'], info_data = {'user': request.user, 'relationship': str(request.data['relationship']),
'last_name': request.data['last_name']} '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})
if user := User.objects.filter(username=request.data['email']).first(): if user := User.objects.filter(username=request.data['email']).first():
self.associate_guardian(user) 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) return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK)
# use AddJuniorSerializer serializer # use AddJuniorSerializer serializer
serializer = AddJuniorSerializer(data=request.data, context=info_data) serializer = AddJuniorSerializer(data=request.data, context=info_data)
@ -162,25 +204,44 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
# save serializer # save serializer
serializer.save() serializer.save()
return custom_response(SUCCESS_CODE['3021'], serializer.data, response_status=status.HTTP_200_OK) 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) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
def associate_guardian(self, user): def associate_guardian(self, user):
junior = Junior.objects.filter(auth=user).first() junior = Junior.objects.filter(auth__email=self.request.data['email']).first()
guardian = Guardian.objects.filter(user=self.request.user).first() guardian = Guardian.objects.filter(user=self.request.user).first()
junior.guardian_code = [guardian.guardian_code] 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() junior.save()
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior, jun_data, created = JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior)
relationship=str(self.request.data['relationship'])) if jun_data:
jun_data.relationship = str(self.request.data['relationship'])
jun_data.save()
send_notification.delay(ASSOCIATE_EXISTING_JUNIOR, self.request.user.id, GUARDIAN, junior.auth.id, {})
return True return True
class InvitedJuniorAPIView(viewsets.ModelViewSet): class InvitedJuniorAPIView(viewsets.ModelViewSet):
"""Junior list of assosicated guardian""" """Invited Junior list of assosicated guardian"""
serializer_class = JuniorDetailListSerializer serializer_class = JuniorDetailListSerializer
queryset = Junior.objects.all()
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination pagination_class = PageNumberPagination
http_method_names = ('get',) http_method_names = ('get',)
@ -192,7 +253,8 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet):
is_invited=True) is_invited=True)
return junior_queryset return junior_queryset
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
""" junior list""" """ Invited Junior list of assosicated guardian
No Params"""
try: try:
queryset = self.get_queryset() queryset = self.get_queryset()
paginator = self.pagination_class() paginator = self.pagination_class()
@ -206,13 +268,25 @@ class InvitedJuniorAPIView(viewsets.ModelViewSet):
class FilterJuniorAPIView(viewsets.ModelViewSet): class FilterJuniorAPIView(viewsets.ModelViewSet):
"""Update guardian profile""" """filter junior profile"""
serializer_class = JuniorDetailListSerializer serializer_class = JuniorDetailListSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
pagination_class = PageNumberPagination pagination_class = PageNumberPagination
queryset = Junior.objects.all()
http_method_names = ('get',) 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): def get_queryset(self):
"""Get the queryset for the view""" """Get the queryset for the view"""
title = self.request.GET.get('title') title = self.request.GET.get('title')
@ -223,7 +297,7 @@ class FilterJuniorAPIView(viewsets.ModelViewSet):
return queryset return queryset
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""Create guardian profile""" """Filter junior"""
try: try:
queryset = self.get_queryset() queryset = self.get_queryset()
paginator = self.pagination_class() paginator = self.pagination_class()
@ -237,7 +311,9 @@ class FilterJuniorAPIView(viewsets.ModelViewSet):
class RemoveJuniorAPIView(views.APIView): class RemoveJuniorAPIView(views.APIView):
"""Remove junior API""" """Remove junior API
Params
id=37"""
serializer_class = RemoveJuniorSerializer serializer_class = RemoveJuniorSerializer
model = Junior model = Junior
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -247,14 +323,17 @@ class RemoveJuniorAPIView(views.APIView):
junior_id = self.request.GET.get('id') junior_id = self.request.GET.get('id')
guardian = Guardian.objects.filter(user__email=self.request.user).last() guardian = Guardian.objects.filter(user__email=self.request.user).last()
# fetch junior query # fetch junior query
junior_queryset = Junior.objects.filter(id=junior_id, guardian_code__icontains=str(guardian.guardian_code)).last() junior_queryset = Junior.objects.filter(id=junior_id,
guardian_code__icontains=str(guardian.guardian_code)).last()
if junior_queryset: if junior_queryset:
# use RemoveJuniorSerializer serializer # use RemoveJuniorSerializer serializer
serializer = RemoveJuniorSerializer(junior_queryset, data=request.data, partial=True) serializer = RemoveJuniorSerializer(junior_queryset, context={"guardian_code":guardian.guardian_code},
data=request.data, partial=True)
if serializer.is_valid(): if serializer.is_valid():
# save serializer # save serializer
serializer.save() serializer.save()
send_notification.delay(REMOVE_JUNIOR, None, junior_queryset.auth.id, {}) JuniorGuardianRelationship.objects.filter(guardian=guardian, junior=junior_queryset).delete()
send_notification.delay(REMOVE_JUNIOR, None, None, junior_queryset.auth.id, {})
return custom_response(SUCCESS_CODE['3022'], serializer.data, response_status=status.HTTP_200_OK) 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) return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
else: else:
@ -264,45 +343,46 @@ class RemoveJuniorAPIView(views.APIView):
class JuniorTaskListAPIView(viewsets.ModelViewSet): class JuniorTaskListAPIView(viewsets.ModelViewSet):
"""Update guardian profile""" """Junior task list"""
serializer_class = TaskDetailsjuniorSerializer serializer_class = TaskDetailsjuniorSerializer
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,)
search_fields = ['task_name', ]
pagination_class = PageNumberPagination pagination_class = PageNumberPagination
queryset = JuniorTask.objects.all()
http_method_names = ('get',) http_method_names = ('get',)
def get_queryset(self):
queryset = JuniorTask.objects.filter(junior__auth=self.request.user
).prefetch_related('junior', 'junior__auth'
).order_by('due_date', 'created_at')
queryset = self.filter_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""Create guardian profile""" """Junior task list
status=0
search='title'
page=1"""
try: try:
status_value = self.request.GET.get('status') status_value = self.request.GET.get('status')
search = self.request.GET.get('search') queryset = self.get_queryset()
if search and str(status_value) == '0': if status_value and status_value != '0':
# search with title and for all task list queryset = queryset.filter(task_status=status_value)
queryset = JuniorTask.objects.filter(junior__auth=request.user,
task_name__icontains=search).order_by('due_date', 'created_at')
elif search and str(status_value) != '0':
# search with title and fetch task list with status wise
queryset = JuniorTask.objects.filter(junior__auth=request.user, task_name__icontains=search,
task_status=status_value).order_by('due_date', 'created_at')
if search is None and str(status_value) == '0':
# fetch all task list
queryset = JuniorTask.objects.filter(junior__auth=request.user).order_by('due_date', 'created_at')
elif search is None and str(status_value) != '0':
# fetch task list with status wise
queryset = JuniorTask.objects.filter(junior__auth=request.user,
task_status=status_value).order_by('due_date','created_at')
paginator = self.pagination_class() paginator = self.pagination_class()
# use Pagination # use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request) paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskDetailsSerializer serializer # use TaskDetails juniorSerializer serializer
serializer = TaskDetailsjuniorSerializer(paginated_queryset, many=True) serializer = self.serializer_class(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CompleteJuniorTaskAPIView(views.APIView): class CompleteJuniorTaskAPIView(views.APIView):
"""Update junior task API""" """Payload
task_id
image"""
serializer_class = CompleteTaskSerializer serializer_class = CompleteTaskSerializer
model = JuniorTask model = JuniorTask
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -320,8 +400,11 @@ class CompleteJuniorTaskAPIView(views.APIView):
image_url = upload_image_to_alibaba(image, filename) image_url = upload_image_to_alibaba(image, filename)
# fetch junior query # fetch junior query
task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last() 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:
if 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 # use CompleteTaskSerializer serializer
if task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]: if task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]:
"""Already request send """ """Already request send """
@ -343,23 +426,20 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('get',) http_method_names = ('get',)
def get_queryset(self):
"""get queryset"""
return JuniorTask.objects.filter(junior__auth__email=self.request.user).last()
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
"""profile view""" """Junior Points
No Params"""
try: try:
queryset = self.get_queryset() # update_positions_based_on_points, function removed
queryset = JuniorPoints.objects.filter(junior__auth__email=self.request.user).last()
# update position of junior # update position of junior
update_positions_based_on_points()
serializer = JuniorPointsSerializer(queryset) serializer = JuniorPointsSerializer(queryset)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK) return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ValidateReferralCode(viewsets.ViewSet): class ValidateReferralCode(viewsets.ModelViewSet):
"""Check guardian code exist or not""" """Check guardian code exist or not"""
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('get',) http_method_names = ('get',)
@ -394,7 +474,13 @@ class InviteGuardianAPIView(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
http_method_names = ('post',) http_method_names = ('post',)
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" junior list""" """ add guardian
{
"first_name":"abc",
"last_name":"xyz",
"email":"abc@yopmail.com",
"relationship":2
}"""
try: try:
if request.data['email'] == '': if request.data['email'] == '':
return custom_error_response(ERROR_CODE['2062'], response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE['2062'], response_status=status.HTTP_400_BAD_REQUEST)
@ -412,7 +498,11 @@ class InviteGuardianAPIView(viewsets.ModelViewSet):
class StartTaskAPIView(views.APIView): class StartTaskAPIView(views.APIView):
"""Update junior task API""" """Update junior task API
Paylod
{
"task_id":28
}"""
serializer_class = StartTaskSerializer serializer_class = StartTaskSerializer
model = JuniorTask model = JuniorTask
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -436,7 +526,13 @@ class StartTaskAPIView(views.APIView):
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class ReAssignJuniorTaskAPIView(views.APIView): class ReAssignJuniorTaskAPIView(views.APIView):
"""Update junior task API""" """Update junior task API
Payload
{
"task_id":34,
"due_date":"2023-08-22"
}
"""
serializer_class = ReAssignTaskSerializer serializer_class = ReAssignTaskSerializer
model = JuniorTask model = JuniorTask
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
@ -458,3 +554,258 @@ class ReAssignJuniorTaskAPIView(views.APIView):
return custom_error_response(ERROR_CODE['2066'], response_status=status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE['2066'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e: except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) 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:
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}
send_notification.delay(ARTICLE_REWARD_POINTS, None, None,
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.all()
def create(self, request, *args, **kwargs):
"""
faq create api method
:param request:
:param args: question, description
:param kwargs:
:return: success message
"""
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

@ -10,3 +10,4 @@ from notifications.models import Notification
class NotificationAdmin(admin.ModelAdmin): class NotificationAdmin(admin.ModelAdmin):
"""Notification Admin""" """Notification Admin"""
list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read'] list_display = ['id', 'notification_type', 'notification_to', 'data', 'is_read']
list_filter = ['notification_type']

View File

@ -1,19 +1,26 @@
""" """
notification constants file notification constants file
""" """
from base.constants import NUMBER REGISTRATION = 1
REGISTRATION = NUMBER['one'] ASSOCIATE_REQUEST = 3
TASK_CREATED = NUMBER['two'] ASSOCIATE_REJECTED = 4
INVITED_GUARDIAN = NUMBER['three'] ASSOCIATE_APPROVED = 5
APPROVED_JUNIOR = NUMBER['four'] REFERRAL_POINTS = 6
REFERRAL_POINTS = NUMBER['five'] ASSOCIATE_JUNIOR = 7
TASK_POINTS = NUMBER['six'] ASSOCIATE_EXISTING_JUNIOR = 8
TASK_REJECTED = NUMBER['seven']
SKIPPED_PROFILE_SETUP = NUMBER['eight'] TASK_ASSIGNED = 9
TASK_SUBMITTED = NUMBER['nine'] TASK_ACTION = 10
TASK_ACTION = NUMBER['ten'] TASK_REJECTED = 11
LEADERBOARD_RANKING = NUMBER['eleven'] TASK_APPROVED = 12
REMOVE_JUNIOR = NUMBER['twelve'] 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 TEST_NOTIFICATION = 99
NOTIFICATION_DICT = { NOTIFICATION_DICT = {
@ -21,52 +28,102 @@ NOTIFICATION_DICT = {
"title": "Successfully registered!", "title": "Successfully registered!",
"body": "You have registered successfully. Now login and complete your profile." "body": "You have registered successfully. Now login and complete your profile."
}, },
TASK_CREATED: { # user will receive notification as soon junior
"title": "Task created!", # sign up application using their guardian code for association
"body": "Task created successfully." ASSOCIATE_REQUEST: {
"title": "Associate request!",
"body": "You have request from {from_user} to associate with you."
}, },
INVITED_GUARDIAN: { # Juniors will receive notification when
"title": "Invite guardian", # custodians reject their request for associate
"body": "Invite guardian successfully" ASSOCIATE_REJECTED: {
"title": "Associate request rejected!",
"body": "Your request to associate has been rejected by {from_user}."
}, },
APPROVED_JUNIOR: { # Juniors will receive notification when
"title": "Approve junior", # custodians approve their request for associate
"body": "You have request from junior to associate with you" 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: { REFERRAL_POINTS: {
"title": "Earn Referral points", "title": "Earn Referral points!",
"body": "You earn 5 points for referral." "body": "You earn 5 points for referral."
}, },
TASK_POINTS: { # Juniors will receive notification
"title": "Earn Task points!", # once any custodians add them in their account
"body": "You earn 5 points for task." ASSOCIATE_JUNIOR: {
"title": "Profile already setup!",
"body": "Your guardian has already setup your profile."
}, },
TASK_REJECTED: { ASSOCIATE_EXISTING_JUNIOR: {
"title": "Task rejected!", "title": "Associated to guardian",
"body": "Your task has been rejected." "body": "Your are associated to your guardian {from_user}."
}, },
SKIPPED_PROFILE_SETUP: { # Juniors will receive Notification
"title": "Skipped profile setup!", # for every Task Assign by Custodians
"body": "Your guardian has been setup your profile." TASK_ASSIGNED: {
}, "title": "New task assigned!",
TASK_SUBMITTED: { "body": "{from_user} has assigned you a new task."
"title": "Task submitted!",
"body": "Your task has been submitted successfully."
}, },
# Guardian will receive notification as soon
# as junior send task for approval
TASK_ACTION: { TASK_ACTION: {
"title": "Task approval!", "title": "Task completion approval!",
"body": "You have request for task approval." "body": "{from_user} completed their task {task_name}."
}, },
LEADERBOARD_RANKING: { # Juniors will receive notification as soon
"title": "Leader board rank!", # as their task is rejected by custodians
"body": "Your rank is ." 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: {
"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: {
"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: {
"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: {
"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: {
"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: {
"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: { REMOVE_JUNIOR: {
"title": "Disassociate by guardian!", "title": "Disassociate by guardian!",
"body": "Your guardian disassociate you ." "body": "Your guardian has disassociated you."
}, },
# Test notification
TEST_NOTIFICATION: { TEST_NOTIFICATION: {
"title": "Test Notification", "title": "Test Notification",
"body": "This notification is for testing purpose" "body": "This notification is for testing purpose from {from_user}."
} }
} }

View File

@ -35,10 +35,13 @@ class NotificationListSerializer(serializers.ModelSerializer):
class Meta(object): class Meta(object):
"""meta info""" """meta info"""
model = Notification model = Notification
fields = ['id', 'data', 'is_read'] fields = ['id', 'data', 'is_read', 'created_at']
class ReadNotificationSerializer(serializers.ModelSerializer): class ReadNotificationSerializer(serializers.ModelSerializer):
"""User task Serializer""" """User task Serializer"""
id = serializers.ListSerializer(child=serializers.IntegerField())
class Meta(object): class Meta(object):
"""Meta class""" """Meta class"""
model = Notification model = Notification

View File

@ -6,7 +6,7 @@ from django.urls import path, include
from rest_framework import routers from rest_framework import routers
# local imports # local imports
from notifications.views import NotificationViewSet, ReadNotification from notifications.views import NotificationViewSet
# initiate router # initiate router
router = routers.SimpleRouter() router = routers.SimpleRouter()
@ -15,5 +15,4 @@ router.register('notifications', NotificationViewSet, basename='notifications')
urlpatterns = [ urlpatterns = [
path('api/v1/', include(router.urls)), path('api/v1/', include(router.urls)),
path('api/v1/read-notification/', ReadNotification.as_view()),
] ]

View File

@ -8,21 +8,24 @@ from firebase_admin.messaging import Message, Notification as FirebaseNotificati
# django imports # django imports
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db.models import Q
from account.models import UserNotification
from notifications.constants import NOTIFICATION_DICT
from notifications.models import Notification
# local imports # 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() User = get_user_model()
def register_fcm_token(user_id, registration_id, device_id, device_type): def register_fcm_token(user_id, registration_id, device_id, device_type):
""" used to register the fcm device token""" """ used to register the fcm device token"""
device, _ = FCMDevice.objects.update_or_create(device_id=device_id, device, _ = FCMDevice.objects.update_or_create(user_id=user_id,
defaults={'user_id': user_id, 'type': device_type, defaults={'device_id': device_id, 'type': device_type,
'active': True, 'active': True,
'registration_id': registration_id}) 'registration_id': registration_id})
return device return device
@ -39,30 +42,140 @@ def remove_fcm_token(user_id: int, access_token: str, registration_id) -> None:
print(e) print(e)
def get_basic_detail(notification_type, from_user_id, to_user_id): def get_from_user_details(from_user_id, from_user_type):
""" used to get the basic details """ """
notification_data = NOTIFICATION_DICT[notification_type] used to get from user details
from_user = User.objects.get(id=from_user_id) if from_user_id else None """
to_user = User.objects.get(id=to_user_id) from_user = None
return notification_data, from_user, to_user 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)
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() @shared_task()
def send_notification(notification_type, from_user_id, to_user_id, extra_data): 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, from_user, to_user) = get_basic_detail(notification_type, from_user_id, to_user_id) 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() user_notification_type = UserNotification.objects.filter(user=to_user).first()
data = notification_data notification_data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()})
Notification.objects.create(notification_type=notification_type, notification_from=from_user, Notification.objects.create(notification_type=notification_type, notification_from=from_user,
notification_to=to_user, data=data) notification_to=to_user, data=notification_data)
if user_notification_type.push_notification: if user_notification_type and user_notification_type.push_notification:
data.update({'badge': Notification.objects.filter(notification_to=to_user, is_read=False).count()}) send_push(to_user, push_data)
send_push(to_user, data)
def send_push(user, data): def send_push(user, data):
""" used to send push notification to specific user """ """ used to send push notification to specific user """
notification_data = data.pop('data', None)
user.fcmdevice_set.filter(active=True).send_message( user.fcmdevice_set.filter(active=True).send_message(
Message(notification=FirebaseNotification(data['title'], data['body']), data=notification_data) Message(notification=FirebaseNotification(data['title'], data['body']), data=data)
) )
def send_multiple_push(queryset, data):
""" used to send same notification to multiple users """
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_copy_data = notification_data.copy()
notification_copy_data.update(
{'badge': Notification.objects.filter(notification_to=user, is_read=False).count()})
notification_list.append(Notification(notification_type=notification_type,
notification_to=user,
notification_from=from_user,
data=notification_copy_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)
@shared_task()
def send_notification_to_guardian(notification_type, from_user_id, to_user_id, extra_data):
"""
:param notification_type:
:param from_user_id:
:param to_user_id:
:param extra_data:
:return:
"""
if from_user_id:
from_user = Junior.objects.filter(auth_id=from_user_id).first()
extra_data['from_user_image'] = from_user.image
send_notification(notification_type, from_user_id, to_user_id, extra_data)
@shared_task()
def send_notification_to_junior(notification_type, from_user_id, to_user_id, extra_data):
"""
:param notification_type:
:param from_user_id:
:param to_user_id:
:param extra_data:
:return:
"""
if from_user_id:
from_user = Guardian.objects.filter(user_id=from_user_id).first()
extra_data['from_user_image'] = from_user.image
send_notification(notification_type, from_user_id, to_user_id, extra_data)

View File

@ -4,12 +4,15 @@ notifications views file
# django imports # django imports
from django.db.models import Q from django.db.models import Q
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework import viewsets, status, views from rest_framework import viewsets, status, views
# local imports # local imports
from account.utils import custom_response, custom_error_response from account.utils import custom_response, custom_error_response
from base.messages import SUCCESS_CODE, ERROR_CODE 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.constants import TEST_NOTIFICATION
from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer
from notifications.utils import send_notification from notifications.utils import send_notification
@ -17,25 +20,24 @@ from notifications.models import Notification
class NotificationViewSet(viewsets.GenericViewSet): class NotificationViewSet(viewsets.GenericViewSet):
""" used to do the notification actions """ """
used to do the notification actions
"""
serializer_class = NotificationListSerializer serializer_class = NotificationListSerializer
permission_classes = [IsAuthenticated, ] permission_classes = [IsAuthenticated, ]
def list(self, request, *args, **kwargs) -> Response: def list(self, request, *args, **kwargs) -> Response:
""" list the notifications """ """
to list user's notifications
:param request:
:return:
"""
queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id'] queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id']
).select_related('notification_to').order_by('-id') ).select_related('notification_to').order_by('-id')
paginator = self.pagination_class() paginator = CustomPageNumberPagination()
paginated_queryset = paginator.paginate_queryset(queryset, request) paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True) serializer = self.serializer_class(paginated_queryset, many=True)
self.mark_notifications_as_read(serializer.data) return paginator.get_paginated_response(serializer.data)
return custom_response(None, serializer.data)
@staticmethod
def mark_notifications_as_read(data):
""" used to mark notification queryset as read """
ids = [obj['id'] for obj in data]
Notification.objects.filter(id__in=ids).update(is_read=True)
@action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice) @action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice)
def fcm_registration(self, request): def fcm_registration(self, request):
@ -51,38 +53,20 @@ class NotificationViewSet(viewsets.GenericViewSet):
@action(methods=['get'], detail=False, url_path='test', url_name='test') @action(methods=['get'], detail=False, url_path='test', url_name='test')
def send_test_notification(self, request): def send_test_notification(self, request):
""" """
to send test notification to test send notification, task expiry, top junior
:return: :return:
""" """
send_notification.delay(TEST_NOTIFICATION, None, request.auth.payload['user_id'], {}) notify_task_expiry()
notify_top_junior()
send_notification(TEST_NOTIFICATION, None, None, request.auth.payload['user_id'],
{})
return custom_response(SUCCESS_CODE["3000"]) return custom_response(SUCCESS_CODE["3000"])
@action(methods=['get'], detail=False, url_path='list', url_name='list', @action(methods=['patch'], url_path='mark-as-read', url_name='mark-as-read', detail=False,
serializer_class=NotificationListSerializer) serializer_class=ReadNotificationSerializer)
def notification_list(self, request): def mark_as_read(self, request, *args, **kwargs):
""" """
notification list notification list
""" """
try: Notification.objects.filter(id__in=request.data.get('id')).update(is_read=True)
queryset = Notification.objects.filter(notification_to=request.user) return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK)
serializer = NotificationListSerializer(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 ReadNotification(views.APIView):
"""Update notification API"""
serializer_class = ReadNotificationSerializer
model = Notification
permission_classes = [IsAuthenticated]
def put(self, request, format=None):
try:
notification_id = self.request.data.get('notification_id')
notification_queryset = Notification.objects.filter(id__in=notification_id,
notification_to=self.request.user).update(is_read=True)
if notification_queryset:
return custom_response(SUCCESS_CODE['3039'], 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

@ -99,3 +99,6 @@ uritemplate==4.1.1
urllib3==1.26.16 urllib3==1.26.16
vine==5.0.0 vine==5.0.0
wcwidth==0.2.6 wcwidth==0.2.6
pandas==2.0.3
XlsxWriter==3.1.2

View File

@ -49,7 +49,7 @@ class ArticleSurvey(models.Model):
def __str__(self): def __str__(self):
"""Return title""" """Return title"""
return f'{self.id} | {self.article}' return f'{self.id} | {self.question}'
class SurveyOption(models.Model): class SurveyOption(models.Model):
@ -64,7 +64,7 @@ class SurveyOption(models.Model):
def __str__(self): def __str__(self):
"""Return title""" """Return title"""
return f'{self.id} | {self.survey}' return f'{self.id} | {self.option}'
class DefaultArticleCardImage(models.Model): class DefaultArticleCardImage(models.Model):

View File

@ -0,0 +1,150 @@
"""
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
else:
return 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').capitalize()
elif obj.junior_profile.all().first():
return dict(USER_TYPE).get('1').capitalize()
else:
return None
@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

@ -10,9 +10,11 @@ from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE
# local imports # local imports
from base.messages import ERROR_CODE from base.messages import ERROR_CODE
from guardian.utils import upload_image_to_alibaba 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.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage
from web_admin.utils import pop_id, get_image_url from web_admin.utils import pop_id, get_image_url
from junior.models import JuniorArticlePoints, JuniorArticle
USER = get_user_model() USER = get_user_model()
@ -79,7 +81,7 @@ class ArticleSerializer(serializers.ModelSerializer):
meta class meta class
""" """
model = Article model = Article
fields = ('id', 'title', 'description', 'article_cards', 'article_survey') fields = ('id', 'title', 'description', 'is_published', 'article_cards', 'article_survey')
def validate(self, attrs): def validate(self, attrs):
""" """
@ -119,11 +121,15 @@ class ArticleSerializer(serializers.ModelSerializer):
option = pop_id(option) option = pop_id(option)
SurveyOption.objects.create(survey=survey_obj, **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 return article
def update(self, instance, validated_data): def update(self, instance, validated_data):
""" """
to update article and related table to update article and related table
:param validated_data:
:param instance: article object, :param instance: article object,
:return: article object :return: article object
""" """
@ -219,17 +225,120 @@ class ArticleListSerializer(serializers.ModelSerializer):
""" """
serializer for article API serializer for article API
""" """
article_cards = ArticleCardSerializer(many=True) image = serializers.SerializerMethodField('get_image')
total_points = serializers.SerializerMethodField('get_total_points') total_points = serializers.SerializerMethodField('get_total_points')
is_completed = serializers.SerializerMethodField('get_is_completed')
class Meta: class Meta(object):
""" """
meta class meta class
""" """
model = Article model = Article
fields = ('id', 'title', 'description', 'article_cards', 'total_points') 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): def get_total_points(self, obj):
"""total points of article""" """total points of article"""
total_question = ArticleSurvey.objects.filter(article=obj).count() return obj.article_survey.all().count() * NUMBER['five']
return total_question * 5
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()
if data:
return data.current_card_page
return NUMBER['zero']
class Meta(object):
"""
meta class
"""
model = ArticleCard
fields = ('id', 'title', 'description', 'image_name', 'image_url', 'current_page')

View File

@ -14,7 +14,7 @@ from account.models import UserEmailOtp
from base.constants import USER_TYPE from base.constants import USER_TYPE
from base.messages import ERROR_CODE from base.messages import ERROR_CODE
from guardian.tasks import generate_otp from guardian.tasks import generate_otp
from base.tasks import send_email_otp from base.tasks import send_email
USER = get_user_model() USER = get_user_model()
@ -48,11 +48,13 @@ class AdminOTPSerializer(serializers.ModelSerializer):
:return: user_data :return: user_data
""" """
email = validated_data['email'] email = validated_data['email']
verification_code = generate_otp() verification_code = generate_otp()
template = 'email_reset_verification.email'
# Send the verification code to the user's email # Send the verification code to the user's email
send_email_otp.delay(email, verification_code) data = {
"verification_code": verification_code
}
send_email.delay([email], template, data)
expiry = timezone.now() + timezone.timedelta(days=1) expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.update_or_create(email=email, user_data, created = UserEmailOtp.objects.update_or_create(email=email,
@ -87,9 +89,9 @@ class AdminVerifyOTPSerializer(serializers.Serializer):
# fetch email otp object of the user # fetch email otp object of the user
user_otp_details = UserEmailOtp.objects.filter(email=email, otp=otp).last() user_otp_details = UserEmailOtp.objects.filter(email=email, otp=otp).last()
if not user_otp_details: if not user_otp_details:
raise serializers.ValidationError({'details': ERROR_CODE['2064']}) raise serializers.ValidationError({'details': ERROR_CODE['2008']})
if user_otp_details.user_type != dict(USER_TYPE).get('3'): if user_otp_details.user_type != dict(USER_TYPE).get('3'):
raise serializers.ValidationError({'details': ERROR_CODE['2063']}) raise serializers.ValidationError({'details': ERROR_CODE['2008']})
if user_otp_details.expired_at.replace(tzinfo=None) < datetime.utcnow(): if user_otp_details.expired_at.replace(tzinfo=None) < datetime.utcnow():
raise serializers.ValidationError({'details': ERROR_CODE['2029']}) raise serializers.ValidationError({'details': ERROR_CODE['2029']})
user_otp_details.is_verified = True user_otp_details.is_verified = True

View File

@ -5,7 +5,8 @@ web_admin user_management serializers file
from rest_framework import serializers from rest_framework import serializers
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from base.constants import USER_TYPE from account.utils import get_user_full_name
from base.constants import USER_TYPE, GUARDIAN, JUNIOR
# local imports # local imports
from base.messages import ERROR_CODE, SUCCESS_CODE from base.messages import ERROR_CODE, SUCCESS_CODE
from guardian.models import Guardian from guardian.models import Guardian
@ -37,7 +38,7 @@ class UserManagementListSerializer(serializers.ModelSerializer):
:param obj: user object :param obj: user object
:return: full name :return: full name
""" """
return f"{obj.first_name} {obj.last_name}" if obj.last_name else obj.first_name return get_user_full_name(obj)
@staticmethod @staticmethod
def get_country_code(obj): def get_country_code(obj):
@ -108,7 +109,7 @@ class GuardianSerializer(serializers.ModelSerializer):
""" """
model = Guardian model = Guardian
fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone', fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone',
'is_active', 'country_name', 'image', 'email') 'is_active', 'country_name', 'image', 'email', 'is_deleted')
def validate(self, attrs): def validate(self, attrs):
""" """
@ -144,7 +145,7 @@ class GuardianSerializer(serializers.ModelSerializer):
:param obj: guardian object :param obj: guardian object
:return: full name :return: full name
""" """
return f"{obj.user.first_name} {obj.user.last_name}" if obj.user.last_name else obj.user.first_name return get_user_full_name(obj.user)
@staticmethod @staticmethod
def get_first_name(obj): def get_first_name(obj):
@ -187,7 +188,7 @@ class JuniorSerializer(serializers.ModelSerializer):
""" """
model = Junior model = Junior
fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone', fields = ('id', 'name', 'first_name', 'last_name', 'username', 'dob', 'gender', 'country_code', 'phone',
'is_active', 'country_name', 'image', 'email') 'is_active', 'country_name', 'image', 'email', 'is_deleted')
def validate(self, attrs): def validate(self, attrs):
""" """
@ -210,10 +211,10 @@ class JuniorSerializer(serializers.ModelSerializer):
""" """
instance.auth.email = self.validated_data.get('email', instance.auth.email) instance.auth.email = self.validated_data.get('email', instance.auth.email)
instance.auth.username = self.validated_data.get('email', instance.auth.username) instance.auth.username = self.validated_data.get('email', instance.auth.username)
instance.auth.save() instance.auth.save(update_fields=['email', 'username'])
instance.country_code = validated_data.get('country_code', instance.country_code) instance.country_code = validated_data.get('country_code', instance.country_code)
instance.phone = validated_data.get('phone', instance.phone) instance.phone = validated_data.get('phone', instance.phone)
instance.save() instance.save(update_fields=['country_code', 'phone'])
return instance return instance
@staticmethod @staticmethod
@ -222,7 +223,7 @@ class JuniorSerializer(serializers.ModelSerializer):
:param obj: junior object :param obj: junior object
:return: full name :return: full name
""" """
return f"{obj.auth.first_name} {obj.auth.last_name}" if obj.auth.last_name else obj.auth.first_name return get_user_full_name(obj.auth)
@staticmethod @staticmethod
def get_first_name(obj): def get_first_name(obj):
@ -265,33 +266,30 @@ class UserManagementDetailSerializer(serializers.ModelSerializer):
model = USER model = USER
fields = ('id', 'user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users') fields = ('id', 'user_type', 'email', 'guardian_profile', 'junior_profile', 'associated_users')
@staticmethod def get_user_type(self, obj):
def get_user_type(obj):
""" """
:param obj: user object :param obj: user object
:return: user type :return: user type
""" """
if obj.guardian_profile.all().first(): return GUARDIAN if self.context['user_type'] == GUARDIAN else JUNIOR
return dict(USER_TYPE).get('2')
elif obj.junior_profile.all().first():
return dict(USER_TYPE).get('1')
else:
return None
@staticmethod def get_associated_users(self, obj):
def get_associated_users(obj):
""" """
:param obj: user object :param obj: user object
:return: associated user :return: associated user
""" """
if profile := obj.guardian_profile.all().first(): if self.context['user_type'] == GUARDIAN:
profile = obj.guardian_profile.all().only('user_id', 'guardian_code').first()
if profile.guardian_code: if profile.guardian_code:
junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code], is_verified=True) junior = Junior.objects.filter(guardian_code__contains=[profile.guardian_code],
is_verified=True).select_related('auth')
serializer = JuniorSerializer(junior, many=True) serializer = JuniorSerializer(junior, many=True)
return serializer.data return serializer.data
elif profile := obj.junior_profile.all().first(): elif self.context['user_type'] == JUNIOR:
profile = obj.junior_profile.all().only('auth_id', 'guardian_code').first()
if profile.guardian_code: if profile.guardian_code:
guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code, is_verified=True) guardian = Guardian.objects.filter(guardian_code__in=profile.guardian_code,
is_verified=True).select_related('user')
serializer = GuardianSerializer(guardian, many=True) serializer = GuardianSerializer(guardian, many=True)
return serializer.data return serializer.data
else: else:

View File

@ -6,7 +6,9 @@ from django.urls import path, include
from rest_framework import routers from rest_framework import routers
# local imports # local imports
from web_admin.views.article import ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet from web_admin.views.analytics import AnalyticsViewSet
from web_admin.views.article import (ArticleViewSet, DefaultArticleCardImagesViewSet, ArticleListViewSet,
ArticleCardListViewSet)
from web_admin.views.auth import ForgotAndResetPasswordViewSet from web_admin.views.auth import ForgotAndResetPasswordViewSet
from web_admin.views.user_management import UserManagementViewSet from web_admin.views.user_management import UserManagementViewSet
@ -16,7 +18,11 @@ router = routers.SimpleRouter()
router.register('article', ArticleViewSet, basename='article') router.register('article', ArticleViewSet, basename='article')
router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images') router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images')
router.register('user-management', UserManagementViewSet, basename='user') router.register('user-management', UserManagementViewSet, basename='user')
router.register('analytics', AnalyticsViewSet, basename='user-analytics')
router.register('article-list', ArticleListViewSet, basename='article-list') router.register('article-list', ArticleListViewSet, basename='article-list')
router.register('article-card-list', ArticleCardListViewSet, basename='article-card-list')
# forgot and reset password api for admin # forgot and reset password api for admin
router.register('admin', ForgotAndResetPasswordViewSet, basename='admin') router.register('admin', ForgotAndResetPasswordViewSet, basename='admin')

View File

@ -2,9 +2,10 @@
web_utils file web_utils file
""" """
import base64 import base64
import datetime
from base.constants import ARTICLE_CARD_IMAGE_FOLDER from base.constants import ARTICLE_CARD_IMAGE_FOLDER, DATE_FORMAT
from guardian.utils import upload_image_to_alibaba from guardian.utils import upload_image_to_alibaba, upload_base64_image_to_alibaba
def pop_id(data): def pop_id(data):
@ -29,10 +30,10 @@ def get_image_url(data):
return data['image_url'] return data['image_url']
elif 'image_url' in data and type(data['image_url']) == str and data['image_url'].startswith('data:image'): elif 'image_url' in data and type(data['image_url']) == str and data['image_url'].startswith('data:image'):
base64_image = base64.b64decode(data.get('image_url').split(',')[1]) base64_image = base64.b64decode(data.get('image_url').split(',')[1])
image_name = f"{data['title']} | {data.pop('image_name')}" if 'image_name' in data else data['title'] image_name = data.pop('image_name') if 'image_name' in data else f"{data['title']}.jpg"
filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image_name}" filename = f"{ARTICLE_CARD_IMAGE_FOLDER}/{image_name}"
# upload image on ali baba # upload image on ali baba
image_url = upload_image_to_alibaba(base64_image, filename) image_url = upload_base64_image_to_alibaba(base64_image, filename)
return image_url return image_url
elif 'image' in data and data['image'] is not None: elif 'image' in data and data['image'] is not None:
image = data.pop('image') image = data.pop('image')
@ -40,3 +41,21 @@ def get_image_url(data):
# upload image on ali baba # upload image on ali baba
image_url = upload_image_to_alibaba(image, filename) image_url = upload_image_to_alibaba(image, filename)
return image_url return image_url
def get_dates(start_date, end_date):
"""
to get start and end date
:param start_date: format (yyyy-mm-dd)
:param end_date: format (yyyy-mm-dd)
:return: start and end date
"""
if start_date and end_date:
start_date = datetime.datetime.strptime(start_date, DATE_FORMAT).date()
end_date = datetime.datetime.strptime(end_date, DATE_FORMAT).date()
else:
end_date = datetime.date.today()
start_date = end_date - datetime.timedelta(days=6)
return start_date, end_date

View File

@ -0,0 +1,247 @@
"""
web_admin analytics view file
"""
# python imports
import datetime
import io
import pandas as pd
import xlsxwriter
# third party imports
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
# django imports
from django.contrib.auth import get_user_model
from django.db.models import Q
from django.db.models import Count
from django.db.models.functions import TruncDate
from django.db.models import F, Window
from django.db.models.functions.window import Rank
from django.http import HttpResponse
# local imports
from account.utils import custom_response, get_user_full_name
from base.constants import PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED, EXPIRED, TASK_STATUS
from guardian.models import JuniorTask
from guardian.utils import upload_excel_file_to_alibaba
from junior.models import JuniorPoints
from base.pagination import CustomPageNumberPagination
from web_admin.permission import AdminPermission
from web_admin.serializers.analytics_serializer import LeaderboardSerializer, UserCSVReportSerializer
from web_admin.utils import get_dates
USER = get_user_model()
class AnalyticsViewSet(GenericViewSet):
"""
analytics api view
to get user report (active users, guardians and juniors counts)
to get new user sign up report
to get task report (completed, in-progress, requested and rejected tasks count)
to get junior leaderboard and ranking
"""
serializer_class = None
permission_classes = [IsAuthenticated, AdminPermission]
def get_queryset(self):
user_qs = USER.objects.filter(
(Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True)),
is_superuser=False
).prefetch_related('guardian_profile',
'junior_profile'
).exclude(junior_profile__isnull=True,
guardian_profile__isnull=True).order_by('-date_joined')
return user_qs
@action(methods=['get'], url_name='users-count', url_path='users-count', detail=False)
def total_users_count(self, request, *args, **kwargs):
"""
api method to get total users, guardians and juniors
:param request: start_date: date format (yyyy-mm-dd)
:param request: end_date: date format (yyyy-mm-dd)
:return:
"""
start_date, end_date = get_dates(request.query_params.get('start_date'),
request.query_params.get('end_date'))
user_qs = self.get_queryset()
queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1))))
data = {'total_users': queryset.count(),
'total_guardians': queryset.filter(junior_profile__isnull=True).count(),
'total_juniors': queryset.filter(guardian_profile__isnull=True).count()}
return custom_response(None, data)
@action(methods=['get'], url_name='new-signups', url_path='new-signups', detail=False)
def new_signups(self, request, *args, **kwargs):
"""
api method to get new signups
:param request: start_date: date format (yyyy-mm-dd)
:param request: end_date: date format (yyyy-mm-dd)
:return:
"""
start_date, end_date = get_dates(request.query_params.get('start_date'),
request.query_params.get('end_date'))
user_qs = self.get_queryset()
signup_data = user_qs.filter(date_joined__range=[start_date, (end_date + datetime.timedelta(days=1))]
).annotate(date=TruncDate('date_joined')
).values('date').annotate(signups=Count('id')).order_by('date')
return custom_response(None, signup_data)
@action(methods=['get'], url_name='assign-tasks', url_path='assign-tasks', detail=False)
def assign_tasks_report(self, request, *args, **kwargs):
"""
api method to get assign tasks count for (completed, in-progress, requested and rejected) task
:param request: start_date: date format (yyyy-mm-dd)
:param request: end_date: date format (yyyy-mm-dd)
:return:
"""
start_date, end_date = get_dates(request.query_params.get('start_date'),
request.query_params.get('end_date'))
assign_tasks = JuniorTask.objects.filter(
created_at__range=[start_date, (end_date + datetime.timedelta(days=1))]
)
data = {
'task_completed': assign_tasks.filter(task_status=COMPLETED).count(),
'task_pending': assign_tasks.filter(task_status=PENDING).count(),
'task_in_progress': assign_tasks.filter(task_status=IN_PROGRESS).count(),
'task_requested': assign_tasks.filter(task_status=REQUESTED).count(),
'task_rejected': assign_tasks.filter(task_status=REJECTED).count(),
'task_expired': assign_tasks.filter(task_status=EXPIRED).count(),
}
return custom_response(None, data)
@action(methods=['get'], url_name='junior-leaderboard', url_path='junior-leaderboard', detail=False,
serializer_class=LeaderboardSerializer)
def junior_leaderboard(self, request):
"""
to get junior leaderboard and rank
:param request:
:return:
"""
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')
paginator = CustomPageNumberPagination()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True)
return custom_response(None, serializer.data)
@action(methods=['get'], url_name='export-excel', url_path='export-excel', detail=False)
def export_excel(self, request):
"""
to export users count, task details and top juniors in csv/excel file
:param request: start_date: date format (yyyy-mm-dd)
:param request: end_date: date format (yyyy-mm-dd)
:return:
"""
response = HttpResponse(content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
response['Content-Disposition'] = 'attachment; filename="ZOD_Bank_Analytics.xlsx"'
start_date, end_date = get_dates(request.query_params.get('start_date'),
request.query_params.get('end_date'))
# Use BytesIO for binary data
buffer = io.BytesIO()
# Create an XlsxWriter Workbook object
workbook = xlsxwriter.Workbook(buffer)
# Add sheets
sheets = ['Users', 'Assign Tasks', 'Juniors Leaderboard']
for sheet_name in sheets:
worksheet = workbook.add_worksheet(name=sheet_name)
# sheet 1 for Total Users
if sheet_name == 'Users':
user_qs = self.get_queryset()
queryset = user_qs.filter(date_joined__range=(start_date, (end_date + datetime.timedelta(days=1))))
serializer = UserCSVReportSerializer(queryset, many=True)
df_users = pd.DataFrame([
{'Name': user['name'], 'Email': user['email'],
'Phone Number': user['phone_number'], 'User Type': user['user_type'],
'Status': user['is_active'], 'Date Joined': user['date_joined']}
for user in serializer.data
])
write_excel_worksheet(worksheet, df_users)
# sheet 2 for Assign Task
elif sheet_name == 'Assign Tasks':
assign_tasks = JuniorTask.objects.filter(
created_at__range=[start_date, (end_date + datetime.timedelta(days=1))]
).select_related('junior__auth', 'guardian__user').only('task_name', 'task_status',
'junior__auth__first_name',
'junior__auth__last_name',
'guardian__user__first_name',
'guardian__user__last_name',)
df_tasks = pd.DataFrame([
{'Task Name': task.task_name, 'Assign To': get_user_full_name(task.junior.auth),
'Assign By': get_user_full_name(task.guardian.user),
'Task Status': dict(TASK_STATUS).get(task.task_status).capitalize()}
for task in assign_tasks
])
write_excel_worksheet(worksheet, df_tasks)
# sheet 3 for Juniors Leaderboard and rank
elif sheet_name == 'Juniors Leaderboard':
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')[:15]
df_leaderboard = pd.DataFrame([
{
'Name': get_user_full_name(junior.junior.auth),
'Points': junior.total_points,
'Rank': junior.rank
}
for junior in queryset
])
write_excel_worksheet(worksheet, df_leaderboard)
# Close the workbook to save the content
workbook.close()
# Reset the buffer position and write the content to the response
buffer.seek(0)
response.write(buffer.getvalue())
buffer.close()
filename = f"{'analytics'}/{'ZOD_Bank_Analytics.xlsx'}"
file_link = upload_excel_file_to_alibaba(response, filename)
return custom_response(None, file_link)
def write_excel_worksheet(worksheet, dataframe):
"""
to perform write action on worksheets
:param worksheet:
:param dataframe:
:return: worksheet
"""
for idx, col in enumerate(dataframe.columns):
# Write header
worksheet.write(0, idx, col)
for row_num, row in enumerate(dataframe.values, start=1):
for col_num, value in enumerate(row):
worksheet.write(row_num, col_num, value)
return worksheet

View File

@ -4,7 +4,7 @@ web_admin views file
# django imports # django imports
from rest_framework.viewsets import GenericViewSet, mixins from rest_framework.viewsets import GenericViewSet, mixins
from rest_framework.filters import OrderingFilter, SearchFilter from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework import status from rest_framework import status, viewsets
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.permissions import IsAuthenticated, AllowAny
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -16,7 +16,8 @@ from base.messages import SUCCESS_CODE, ERROR_CODE
from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticleCardImage
from web_admin.permission import AdminPermission from web_admin.permission import AdminPermission
from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer, from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer,
DefaultArticleCardImageSerializer, ArticleListSerializer) DefaultArticleCardImageSerializer, ArticleListSerializer,
ArticleCardlistSerializer)
USER = get_user_model() USER = get_user_model()
@ -43,9 +44,20 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" """
article create api method article create api method
:param request: :param request: { "title": "string", "description": "string",
:param args: "article_cards": [
:param kwargs: { "title": "string",
"description": "string",
"image_name": "string",
"image_url": "string"
} ],
"article_survey": [
{ "question": "string",
"options": [
{ "option": "string",
"is_answer": true }
] }
] }
:return: success message :return: success message
""" """
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
@ -56,9 +68,24 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def update(self, request, *args, **kwargs): def update(self, request, *args, **kwargs):
""" """
article update api method article update api method
:param request: :param request: article_id,
:param args: { "title": "string", "description": "string",
:param kwargs: "article_cards": [
{ "id": 0,
"title": "string",
"description": "string",
"image_name": "string",
"image_url": "string"
} ],
"article_survey": [
{ "id": 0,
"question": "string",
"options": [
{ "id": 0,
"option": "string",
"is_answer": true
} ]
} ] }
:return: success message :return: success message
""" """
article = self.get_object() article = self.get_object()
@ -71,8 +98,6 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
""" """
article list api method article list api method
:param request: :param request:
:param args:
:param kwargs:
:return: list of article :return: list of article
""" """
queryset = self.get_queryset() queryset = self.get_queryset()
@ -85,9 +110,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def retrieve(self, request, *args, **kwargs): def retrieve(self, request, *args, **kwargs):
""" """
article detail api method article detail api method
:param request: :param request: article_id
:param args:
:param kwargs:
:return: article detail data :return: article detail data
""" """
queryset = self.get_object() queryset = self.get_object()
@ -97,9 +120,7 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
""" """
article delete (soft delete) api method article delete (soft delete) api method
:param request: :param request: article_id
:param args:
:param kwargs:
:return: success message :return: success message
""" """
article = self.get_object() article = self.get_object()
@ -109,6 +130,22 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
return custom_response(SUCCESS_CODE["3029"]) return custom_response(SUCCESS_CODE["3029"])
return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST)
@action(methods=['get'], url_name='status-change', url_path='status-change',
detail=True)
def article_status_change(self, request, *args, **kwargs):
"""
article un-publish or publish api method
:param request: article id
:return: success message
"""
try:
article = Article.objects.filter(id=kwargs['pk']).first()
article.is_published = False if article.is_published else True
article.save(update_fields=['is_published'])
return custom_response(SUCCESS_CODE["3038"])
except AttributeError:
return custom_error_response(ERROR_CODE["2041"], response_status=status.HTTP_400_BAD_REQUEST)
@action(methods=['get'], url_name='remove-card', url_path='remove-card', @action(methods=['get'], url_name='remove-card', url_path='remove-card',
detail=True) detail=True)
def remove_article_card(self, request, *args, **kwargs): def remove_article_card(self, request, *args, **kwargs):
@ -176,7 +213,10 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m
def create(self, request, *args, **kwargs): def create(self, request, *args, **kwargs):
""" """
api method to upload default article card images api method to upload default article card images
:param request: :param request: {
"image_name": "string",
"image": "image_file"
}
:return: success message :return: success message
""" """
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
@ -205,10 +245,7 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin):
http_method_names = ['get',] http_method_names = ['get',]
def get_queryset(self): def get_queryset(self):
article = self.queryset.objects.filter(is_deleted=False).prefetch_related( queryset = self.queryset.objects.filter(is_deleted=False, is_published=True).order_by('-created_at')
'article_cards', 'article_survey', 'article_survey__options'
).order_by('-created_at')
queryset = self.filter_queryset(article)
return queryset return queryset
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
@ -220,8 +257,29 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin):
:return: list of article :return: list of article
""" """
queryset = self.get_queryset() queryset = self.get_queryset()
count = queryset.count() serializer = self.serializer_class(queryset, context={"user": request.user}, many=True)
paginator = self.pagination_class() return custom_response(None, data=serializer.data)
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True) class ArticleCardListViewSet(viewsets.ModelViewSet):
return custom_response(None, data=serializer.data, count=count) """Article card list
use below query param
article_id"""
serializer_class = ArticleCardlistSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
def get_queryset(self):
"""get queryset"""
return ArticleCard.objects.filter(article=self.request.GET.get('article_id'))
def list(self, request, *args, **kwargs):
"""Article card list
use below query param
article_id"""
try:
queryset = self.get_queryset()
# article card list
serializer = ArticleCardlistSerializer(queryset, context={"user": self.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)

View File

@ -27,6 +27,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet):
def admin_otp(self, request): def admin_otp(self, request):
""" """
api method to send otp api method to send otp
:param request: {"email": "string"}
:return: success message :return: success message
""" """
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
@ -40,6 +41,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet):
def admin_verify_otp(self, request): def admin_verify_otp(self, request):
""" """
api method to verify otp api method to verify otp
:param request: {"email": "string", "otp": "otp"}
:return: success message :return: success message
""" """
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)
@ -52,6 +54,7 @@ class ForgotAndResetPasswordViewSet(GenericViewSet):
def admin_create_password(self, request): def admin_create_password(self, request):
""" """
api method to create new password api method to create new password
:param request: {"email": "string", "new_password": "string", "confirm_password": "string"}
:return: success message :return: success message
""" """
serializer = self.serializer_class(data=request.data) serializer = self.serializer_class(data=request.data)

View File

@ -12,8 +12,11 @@ from django.db.models import Q
# local imports # local imports
from account.utils import custom_response, custom_error_response from account.utils import custom_response, custom_error_response
from base.constants import USER_TYPE from base.constants import USER_TYPE, GUARDIAN, JUNIOR
from base.messages import SUCCESS_CODE, ERROR_CODE from base.messages import SUCCESS_CODE, ERROR_CODE
from base.tasks import send_email
from guardian.models import Guardian
from junior.models import Junior
from web_admin.permission import AdminPermission from web_admin.permission import AdminPermission
from web_admin.serializers.user_management_serializer import (UserManagementListSerializer, from web_admin.serializers.user_management_serializer import (UserManagementListSerializer,
UserManagementDetailSerializer, GuardianSerializer, UserManagementDetailSerializer, GuardianSerializer,
@ -29,11 +32,12 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
""" """
serializer_class = UserManagementListSerializer serializer_class = UserManagementListSerializer
permission_classes = [IsAuthenticated, AdminPermission] permission_classes = [IsAuthenticated, AdminPermission]
queryset = USER.objects.filter(is_superuser=False queryset = USER.objects.filter(
).prefetch_related('guardian_profile', (Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True)),
'junior_profile' is_superuser=False).prefetch_related('guardian_profile',
).exclude(junior_profile__isnull=True, 'junior_profile'
guardian_profile__isnull=True).order_by('date_joined') ).exclude(junior_profile__isnull=True,
guardian_profile__isnull=True).order_by('-date_joined')
filter_backends = (SearchFilter,) filter_backends = (SearchFilter,)
search_fields = ['first_name', 'last_name'] search_fields = ['first_name', 'last_name']
http_method_names = ['get', 'post', 'patch'] http_method_names = ['get', 'post', 'patch']
@ -55,8 +59,6 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
:return: :return:
""" """
queryset = self.get_queryset() queryset = self.get_queryset()
queryset = queryset.filter(
(Q(junior_profile__is_verified=True) | Q(guardian_profile__is_verified=True)))
paginator = self.pagination_class() paginator = self.pagination_class()
paginated_queryset = paginator.paginate_queryset(queryset, request) paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True) serializer = self.serializer_class(paginated_queryset, many=True)
@ -71,12 +73,14 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
""" """
if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]:
return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST)
queryset = self.queryset queryset = self.queryset
if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): queryset = queryset.filter(id=kwargs['pk'])
queryset = queryset.filter(guardian_profile__user__id=kwargs['pk'])
elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): serializer = UserManagementDetailSerializer(
queryset = queryset.filter(junior_profile__auth__id=kwargs['pk']) queryset, many=True,
serializer = UserManagementDetailSerializer(queryset, many=True) context={'user_type': self.request.query_params.get('user_type')}
)
return custom_response(None, data=serializer.data) return custom_response(None, data=serializer.data)
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
@ -88,15 +92,16 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
""" """
if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]:
return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST)
queryset = self.queryset
if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'):
user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first() guardian = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True
serializer = GuardianSerializer(user_obj.guardian_profile.all().first(), ).select_related('user').first()
serializer = GuardianSerializer(guardian,
request.data, context={'user_id': kwargs['pk']}) request.data, context={'user_id': kwargs['pk']})
elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'):
user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first() junior = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True
serializer = JuniorSerializer(user_obj.junior_profile.all().first(), ).select_related('auth').first()
serializer = JuniorSerializer(junior,
request.data, context={'user_id': kwargs['pk']}) request.data, context={'user_id': kwargs['pk']})
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
@ -111,17 +116,21 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
user_type {'guardian' for Guardian, 'junior' for Junior} mandatory user_type {'guardian' for Guardian, 'junior' for Junior} mandatory
:return: success message :return: success message
""" """
if self.request.query_params.get('user_type') not in [dict(USER_TYPE).get('1'), dict(USER_TYPE).get('2')]: user_type = self.request.query_params.get('user_type')
if user_type not in [GUARDIAN, JUNIOR]:
return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST) return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST)
queryset = self.queryset
if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'): email_template = 'user_deactivate.email'
user_obj = queryset.filter(guardian_profile__user__id=kwargs['pk']).first()
obj = user_obj.guardian_profile.all().first() if user_type == GUARDIAN:
obj.is_active = False if obj.is_active else True obj = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True).select_related('user').first()
obj.save() elif user_type == JUNIOR:
elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'): obj = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True).select_related('auth').first()
user_obj = queryset.filter(junior_profile__auth__id=kwargs['pk']).first()
obj = user_obj.junior_profile.all().first() if obj.is_active:
obj.is_active = False if obj.is_active else True obj.is_active = False
obj.save() send_email([obj.user.email if user_type == GUARDIAN else obj.auth.email], email_template)
else:
obj.is_active = True
obj.save()
return custom_response(SUCCESS_CODE['3038']) return custom_response(SUCCESS_CODE['3038'])

View File

@ -27,19 +27,24 @@ app.config_from_object('django.conf:settings')
# Load task modules from all registered Django apps. # Load task modules from all registered Django apps.
app.autodiscover_tasks() app.autodiscover_tasks()
# scheduled task
app.conf.beat_schedule = {
"expired_task": {
"task": "guardian.utils.update_expired_task_status",
"schedule": crontab(minute=0, hour=0),
},
'notify_task_expiry': {
'task': 'base.tasks.notify_task_expiry',
'schedule': crontab(minute='0', hour='18'),
},
'notify_top_junior': {
'task': 'base.tasks.notify_top_junior',
'schedule': crontab(minute='0', hour='*/2'),
},
}
@app.task(bind=True) @app.task(bind=True)
def debug_task(self): def debug_task(self):
""" celery debug task """ """ celery debug task """
print(f'Request: {self.request!r}') print(f'Request: {self.request!r}')
"""cron task"""
app.conf.beat_schedule = {
"expired_task": {
"task": "guardian.utils.update_expired_task_status",
"schedule": crontab(minute=0, hour=0),
},
}

View File

@ -37,7 +37,17 @@ SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = os.getenv('DEBUG') DEBUG = os.getenv('DEBUG')
# cors allow setting # cors allow setting
CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_ALLOW_ALL = False
# Allow specific origins
CORS_ALLOWED_ORIGINS = [
"https://dev-api.zodqaapp.com",
"https://qa-api.zodqaapp.com",
"https://stage-api.zodqaapp.com",
# Add more trusted origins as needed
]
# if DEBUG:
# CORS_ALLOWED_ORIGINS += ["http://localhost:3000"]
# allow all host # allow all host
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
@ -53,7 +63,7 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
# Add Django rest frame work apps here # Add Django rest framework apps here
'django_extensions', 'django_extensions',
'storages', 'storages',
'drf_yasg', 'drf_yasg',
@ -125,6 +135,7 @@ SIMPLE_JWT = {
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = { DATABASES = {
# default db setting
'default': { 'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis', 'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME':os.getenv('DB_NAME'), 'NAME':os.getenv('DB_NAME'),
@ -177,6 +188,35 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
# database query logs settings
# Allows us to check db hits
# useful to optimize db query and hit
LOGGING = {
"version": 1,
"filters": {
"require_debug_true": {
"()": "django.utils.log.RequireDebugTrue"
}
},
"handlers": {
"console": {
"level": "DEBUG",
"filters": [
"require_debug_true"
],
"class": "logging.StreamHandler"
}
},
# database logger
"loggers": {
"django.db.backends": {
"level": "DEBUG",
"handlers": [
"console"
]
}
}
}
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/ # https://docs.djangoproject.com/en/3.0/topics/i18n/
@ -217,6 +257,7 @@ CORS_ALLOW_HEADERS = (
'x-requested-with', 'x-requested-with',
) )
# CORS header settings
CORS_EXPOSE_HEADERS = ( CORS_EXPOSE_HEADERS = (
'Access-Control-Allow-Origin: *', 'Access-Control-Allow-Origin: *',
) )
@ -272,5 +313,7 @@ STATIC_URL = 'static/'
# define static root # define static root
STATIC_ROOT = 'static' STATIC_ROOT = 'static'
# media url
MEDIA_URL = "/media/" MEDIA_URL = "/media/"
# media path
MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'media') MEDIA_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'media')

View File

@ -20,12 +20,11 @@ from django.urls import path, include
from drf_yasg import openapi from drf_yasg import openapi
from drf_yasg.views import get_schema_view from drf_yasg.views import get_schema_view
from django.urls import path from django.urls import path
from django.conf import settings
schema_view = get_schema_view(openapi.Info(title="Zod Bank API", default_version='v1'), public=True, ) schema_view = get_schema_view(openapi.Info(title="Zod Bank API", default_version='v1'), public=True, )
urlpatterns = [ urlpatterns = [
path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('', include(('account.urls', 'account'), namespace='account')), path('', include(('account.urls', 'account'), namespace='account')),
path('', include('guardian.urls')), path('', include('guardian.urls')),
@ -33,3 +32,6 @@ urlpatterns = [
path('', include(('notifications.urls', 'notifications'), namespace='notifications')), path('', include(('notifications.urls', 'notifications'), namespace='notifications')),
path('', include(('web_admin.urls', 'web_admin'), namespace='web_admin')), path('', include(('web_admin.urls', 'web_admin'), namespace='web_admin')),
] ]
if settings.DEBUG:
urlpatterns += [(path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'))]