Compare commits

..

117 Commits

Author SHA1 Message Date
08f54f28a4 added a test case, updated coverage file 2023-09-12 23:08:17 +05:30
af06dddbeb updated coverage file 2023-09-12 15:03:12 +05:30
7aee372606 test cases for admin user management, analytics and notification 2023-09-12 14:51:28 +05:30
2444b6f55e test cases for admin user management, analytics and notification 2023-09-12 14:40:50 +05:30
373c1b70ab make special password method modified 2023-09-11 16:40:07 +05:30
bf1004696a added test cases for auth 2023-09-11 14:39:01 +05:30
d937c1bb92 Merge branch 'dev' into ZBKADM-73-test-cases 2023-09-11 13:00:04 +05:30
0a877b410e Merge pull request #320 from KiwiTechLLC/ZBKBCK-52
modified add junior api for exsiting junior.
2023-09-11 12:22:31 +05:30
9d4e7b05e4 modified add junior api for exsiting junior. 2023-09-11 12:08:22 +05:30
b20e1cf516 Merge pull request #319 from KiwiTechLLC/ZBKBCK-52
api to check whether given user exist or not
2023-09-09 16:52:03 +05:30
6b0ea91742 api to check whether given user exist or not 2023-09-08 20:55:32 +05:30
859d26d073 test cases for web admin auth 2023-09-08 19:07:06 +05:30
154b9de32b Merge pull request #317 from KiwiTechLLC/ZBKBCK-52
check answer api fixed
2023-09-08 16:56:18 +05:30
4a554272a0 check answer api fixed 2023-09-08 16:54:37 +05:30
8533a27cb7 Merge pull request #316 from KiwiTechLLC/ZBKBCK-52
check answer api fixed
2023-09-08 16:54:31 +05:30
7db6502a89 check answer api fixed 2023-09-08 16:41:59 +05:30
a8e9a09d3f Merge pull request #315 from KiwiTechLLC/ZBKBCK-52
modified task description field, modified check answer api and create…
2023-09-08 16:17:20 +05:30
1e97d7bd6b modified task description field, modified check answer api and create task api 2023-09-08 16:14:29 +05:30
b084b255dc Merge pull request #314 from KiwiTechLLC/ZBKADM-73-test-cases
test cases for web admin article
2023-09-08 11:08:18 +05:30
441842df74 test cases for web admin article 2023-09-07 19:47:25 +05:30
78fb5f5650 Merge pull request #311 from KiwiTechLLC/ZBKBCK-50
modification in create task api
2023-09-07 15:13:33 +05:30
f3478c972e modification in create task api 2023-09-07 15:10:18 +05:30
f63d9ddea0 Merge pull request #310 from KiwiTechLLC/ZBKBCK-50
modified article publish and un-publish api, sonar issues, modificati…
2023-09-07 13:51:57 +05:30
bc18c67527 modified article publish and un-publish api, sonar issues, modification in create task api 2023-09-07 13:49:06 +05:30
0471a3d588 Merge pull request #308 from KiwiTechLLC/ZBKBCK-50
modified some success messages.
2023-09-06 17:41:49 +05:30
ffb99f5099 modified some success messages. 2023-09-06 15:26:10 +05:30
8183edf319 Merge pull request #305 from KiwiTechLLC/ZBKBCK-51
modified create task api, added badge count in notification list
2023-09-05 19:36:09 +05:30
8f214d11a7 modified create task api, added badge count in notification list 2023-09-05 19:29:57 +05:30
b5a89df59a Merge pull request #304 from KiwiTechLLC/ZBKBCK-50
modified pagination in task list and junior task list api
2023-09-05 14:02:16 +05:30
5524eeed64 modified pagination in task list and junior task list api 2023-09-05 13:57:28 +05:30
aeaa7d7ab8 Merge pull request #303 from KiwiTechLLC/sprint6-bugs
handle scenerio for task after disassociate
2023-09-05 13:14:01 +05:30
69be3bb2ac Merge pull request #302 from KiwiTechLLC/ZBKBCK-50
modified cors allowed origin values, added yml file for qa stage and …
2023-09-04 18:55:12 +05:30
be9f600bcc modified cord allowed origin values, added yml file for qa stage and prod 2023-09-04 18:52:23 +05:30
65d0932893 sonar issues 2023-09-04 16:52:50 +05:30
9b14eedb18 handle scenerio for task after disassociate 2023-09-04 16:49:18 +05:30
6c96ea0820 Merge pull request #301 from KiwiTechLLC/ZBKBCK-49
added cors Allow specific origins setting, unpublish article api, pag…
2023-09-04 16:08:22 +05:30
a211baa10a added cors Allow specific origins setting, unpublish article api, pagination in notification list 2023-09-04 15:46:36 +05:30
a93dc83bd1 Merge pull request #300 from KiwiTechLLC/sprint6-bugs
Sprint6 bugs
2023-09-04 15:27:42 +05:30
116fb00358 optimized task status code 2023-09-04 15:22:15 +05:30
20fa6e43da changes in task list API and add special character in password for added junior 2023-09-04 15:02:47 +05:30
2cffc4e128 Merge pull request #299 from KiwiTechLLC/ZBKBCK-49
added and modifid pagination
2023-09-04 13:16:15 +05:30
ec585d35f3 added and modifid pagination 2023-09-04 13:11:25 +05:30
d62efa2139 added and modifid pagination 2023-09-04 13:09:54 +05:30
7b75a3233c add current page and total page in task list API 2023-09-04 12:52:04 +05:30
e1ef289c69 Merge pull request #298 from KiwiTechLLC/sprint6-bugs
Sprint6 bugs
2023-09-01 17:05:50 +05:30
4f79a690c1 unrestrict logout and refresh token api while login in multiple device 2023-09-01 16:25:05 +05:30
0af2a35206 task assign to multiple junior 2023-09-01 12:14:15 +05:30
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
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
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
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
55 changed files with 8064 additions and 392 deletions

BIN
.coverage Normal file

Binary file not shown.

1
.gitignore vendored
View File

@ -6,7 +6,6 @@ media/
*.name
*.iml
*.log
*.xml
*.pyo
.DS_Store
.idea

View File

@ -17,6 +17,8 @@ from guardian.models import Guardian
# multiple devices only
# user can login in single
# device at a time"""
# force update
# use 308 status code for force update
def custom_response(custom_error, response_status = status.HTTP_404_NOT_FOUND):
"""custom response"""
@ -43,9 +45,14 @@ class CustomMiddleware(object):
device_type = str(request.META.get('HTTP_TYPE'))
api_endpoint = request.path
unrestricted_api = ('/api/v1/user/login/', '/api/v1/logout/', '/api/v1/generate-token/')
if request.user.is_authenticated:
# device details
device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
if device_id:
device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last()
if not device_details and api_endpoint not in unrestricted_api:
custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
response = custom_response(custom_error)
if user_type and str(user_type) == str(NUMBER['one']):
junior = Junior.objects.filter(auth=request.user, is_active=False).last()
if junior:
@ -56,13 +63,11 @@ class CustomMiddleware(object):
if guardian:
custom_error = custom_error_response(ERROR_CODE['2075'], response_status=status.HTTP_404_NOT_FOUND)
response = custom_response(custom_error)
if device_id and not device_details and api_endpoint != '/api/v1/user/login/':
custom_error = custom_error_response(ERROR_CODE['2037'], response_status=status.HTTP_404_NOT_FOUND)
response = custom_response(custom_error)
force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last()
api_endpoint_checks = not any(endpoint in api_endpoint for endpoint in ['/admin/', '/api/v1/admin/'])
if not force_update and version and device_type:
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)
if version and device_type:
force_update = ForceUpdate.objects.filter(version=version, device_type=device_type).last()
if not force_update:
custom_error = custom_error_response(ERROR_CODE['2079'],
response_status=status.HTTP_308_PERMANENT_REDIRECT)
response = custom_response(custom_error, status.HTTP_308_PERMANENT_REDIRECT)
return response

View File

@ -123,7 +123,7 @@ class ChangePasswordSerializer(serializers.Serializer):
def create(self, validated_data):
"""
change password
"""
new_password = validated_data.pop('new_password')
current_password = validated_data.pop('current_password')
@ -392,7 +392,8 @@ class UserPhoneOtpSerializer(serializers.ModelSerializer):
fields = '__all__'
class ForceUpdateSerializer(serializers.ModelSerializer):
# ForceUpdate Serializer
""" ForceUpdate Serializer
"""
class Meta(object):
""" meta info """

View File

@ -1,7 +1,7 @@
{% extends "templated_email/email_base.email" %}
{% block subject %}
{{subject}}
Support Mail
{% endblock %}
{% block plain %}

View File

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

View File

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

View File

@ -1,6 +1,6 @@
"""Account utils"""
from celery import shared_task
import random
"""Import django"""
from django.conf import settings
from rest_framework import viewsets, status
@ -93,8 +93,8 @@ def junior_account_update(user_tb):
# Update junior account
junior_data.is_active = False
junior_data.is_verified = False
junior_data.guardian_code = '{}'
junior_data.guardian_code_status = str(NUMBER['one'])
junior_data.guardian_code = None
junior_data.guardian_code_status = None
junior_data.is_deleted = True
junior_data.save()
JuniorPoints.objects.filter(junior=junior_data).delete()
@ -167,7 +167,7 @@ def user_device_details(user, device_id):
return False
def send_support_email(name, sender, subject, message):
def send_support_email(name, sender, message):
"""Send otp on email with template"""
to_email = [settings.EMAIL_FROM_ADDRESS]
from_email = settings.DEFAULT_ADDRESS
@ -179,7 +179,6 @@ def send_support_email(name, sender, subject, message):
context={
'name': name.title(),
'sender': sender,
'subject': subject,
'message': message
}
)
@ -191,7 +190,8 @@ def custom_response(detail, data=None, response_status=status.HTTP_200_OK, count
if not data:
"""when data is none"""
data = None
return Response({"data": data, "message": detail, "status": "success", "code": response_status, "count": count})
return Response({"data": data, "message": detail, "status": "success",
"code": response_status, "count": count})
def custom_error_response(detail, response_status):
@ -288,3 +288,39 @@ def get_user_full_name(user_obj):
to get user's full name
"""
return f"{user_obj.first_name} {user_obj.last_name}" if user_obj.first_name or user_obj.last_name else "User"
def make_special_password(length=10):
"""
to make secured password
:param length:
:return:
"""
# Define character sets
lowercase_letters = string.ascii_lowercase
uppercase_letters = string.ascii_uppercase
digits = string.digits
special_characters = '@#$%&*?'
# Combine character sets
alphabets = lowercase_letters + uppercase_letters
# Create a password with random characters
password = [
secrets.choice(uppercase_letters) +
secrets.choice(lowercase_letters) +
secrets.choice(digits) +
secrets.choice(special_characters) +
''.join(secrets.choice(alphabets) for _ in range(length - 4))
]
return ''.join(password)
def task_status_fun(status_value):
"""task status"""
task_status_value = ['1']
if str(status_value) == '2':
task_status_value = ['2', '4']
elif str(status_value) == '3':
task_status_value = ['3', '5', '6']
return task_status_value

View File

@ -322,7 +322,7 @@ class ForgotPasswordAPIView(views.APIView):
send_all_email.delay(
'email_reset_verification.email', email, verification_code
)
expiry = OTP_EXPIRY
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.get_or_create(
email=email
)
@ -546,7 +546,6 @@ class UserEmailVerification(viewsets.ModelViewSet):
class ReSendEmailOtp(viewsets.ModelViewSet):
"""Send otp on phone"""
serializer_class = EmailVerificationSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('post',)
def create(self, request, *args, **kwargs):
"""Param
@ -690,11 +689,10 @@ class SendSupportEmail(views.APIView):
def post(self, request):
name = request.data.get('name')
sender = request.data.get('email')
subject = request.data.get('subject')
message = request.data.get('message')
if name and sender and subject and message:
if name and sender and message:
try:
send_support_email(name, sender, subject, message)
send_support_email(name, sender, message)
return custom_response(SUCCESS_CODE['3019'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)

View File

@ -43,7 +43,8 @@ FILE_SIZE = 5 * 1024 * 1024
# String constant for configurable date for allocation lock period
ALLOCATION_LOCK_DATE = 1
# guardian code status tuple
guardian_code_tuple = ('1','3')
"""user type"""
USER_TYPE = (
('1', 'junior'),

View File

@ -101,12 +101,20 @@ ERROR_CODE = {
"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",
"2076": "This junior already associated with you",
"2077": "You can not add guardian",
"2078": "This junior is not associate with you",
"2078": "This junior is not associated with you",
# force update
"2079": "Please update your app version for enjoying uninterrupted services",
"2080": "Can not add App version"
"2080": "Can not add App version",
"2081": "A junior can only be associated with a maximum of 3 guardian",
# guardian code not exist
"2082": "Guardian code does not exist",
"2083": "You can not start this task because guardian is not associate with you",
"2084": "You can not complete this task because guardian is not associate with you",
"2085": "You can not take action on this task because junior is not associate with you"
}
"""Success message code"""
@ -117,11 +125,11 @@ SUCCESS_CODE = {
# Success code for Thank you
"3002": "Thank you for contacting us! Our Consumer Experience Team will reach out to you shortly.",
# Success code for account activation
"3003": "Log in successful",
"3003": "Log in successful.",
# Success code for password reset
"3004": "Password reset link has been sent to your email address",
"3004": "Password reset link has been sent to your email address.",
# Success code for link verified
"3005": "Your account is deleted successfully.",
"3005": "Your account has been deleted successfully.",
# Success code for password reset
"3006": "Password reset successful. You can now log in with your new password.",
# Success code for password update
@ -129,11 +137,11 @@ SUCCESS_CODE = {
# Success code for valid link
"3008": "You have a valid link.",
# Success code for logged out
"3009": "You have successfully logged out!",
"3009": "You have successfully logged out.",
# Success code for check all fields
"3010": "All fields are valid",
"3011": "Email OTP Verified successfully",
"3012": "Phone OTP Verified successfully",
"3010": "All fields are valid.",
"3011": "Email OTP has been verified successfully.",
"3012": "Phone OTP has been verified successfully.",
"3013": "Valid Guardian code",
"3014": "Password has been updated successfully.",
"3015": "Verification code has been sent on your email.",
@ -142,39 +150,39 @@ SUCCESS_CODE = {
"3018": "Task created successfully",
"3019": "Support Email sent successfully",
"3020": "Logged out successfully.",
"3021": "Add junior successfully",
"3022": "Remove junior successfully",
"3023": "Junior is approved successfully",
"3024": "Junior request is rejected successfully",
"3025": "Task is approved successfully",
"3026": "Task is rejected successfully",
"3021": "Junior has been added successfully.",
"3022": "Junior has been removed successfully.",
"3023": "Junior has been approved successfully.",
"3024": "Junior request is rejected successfully.",
"3025": "Task is approved successfully.",
"3026": "Task is rejected successfully.",
"3027": "Article has been created successfully.",
"3028": "Article has been updated successfully.",
"3029": "Article has been deleted successfully.",
"3030": "Article Card has been removed successfully.",
"3031": "Article Survey has been removed successfully.",
"3032": "Task request sent successfully",
"3033": "Valid Referral code",
"3034": "Invite guardian successfully",
"3035": "Task started successfully",
"3036": "Task reassign successfully",
"3032": "Task request sent successfully.",
"3033": "Valid Referral code.",
"3034": "Invite guardian successfully.",
"3035": "Task started successfully.",
"3036": "Task reassign successfully.",
"3037": "Profile has been updated successfully.",
"3038": "Status has been changed successfully.",
# notification read
"3039": "Notification read successfully",
"3039": "Notification read successfully.",
# start article
"3040": "Start article successfully",
"3040": "Start article successfully.",
# complete article
"3041": "Article completed successfully",
"3041": "Article completed successfully.",
# submit assessment successfully
"3042": "Assessment completed successfully",
"3042": "Assessment completed successfully.",
# read article
"3043": "Read article card successfully",
"3043": "Read article card successfully.",
# remove guardian code request
"3044": "Remove guardian code request successfully",
"3044": "Remove guardian code request successfully.",
# create faq
"3045": "Create FAQ data",
"3046": "Add App version successfully"
"3045": "Create FAQ data.",
"3046": "Add App version successfully."
}
"""status code error"""

34
base/pagination.py Normal file
View File

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

View File

@ -9,36 +9,20 @@ from templated_email import send_templated_mail
# django imports
from django.conf import settings
from django.db.models import F, Window
from django.db.models.functions.window import Rank
# local imports
from base.constants import PENDING, IN_PROGRESS, JUNIOR
from guardian.models import JuniorTask
from notifications.constants import PENDING_TASK_EXPIRING, IN_PROGRESS_TASK_EXPIRING
from notifications.utils import send_notification
from junior.models import JuniorPoints
from notifications.constants import PENDING_TASK_EXPIRING, IN_PROGRESS_TASK_EXPIRING, NOTIFICATION_DICT, TOP_JUNIOR
from notifications.models import Notification
from notifications.utils import send_notification, get_from_user_details, send_notification_multiple_user
@shared_task
def send_email_otp(email, verification_code):
"""
used to send otp on email
:param email: e-mail
:param verification_code: otp
"""
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = [email]
send_templated_mail(
template_name='email_reset_verification.email',
from_email=from_email,
recipient_list=recipient_list,
context={
'verification_code': verification_code
}
)
return True
@shared_task
def send_mail(recipient_list, template, context: dict = None):
def send_email(recipient_list, template, context: dict = None):
"""
used to send otp on email
:param context:
@ -48,7 +32,6 @@ def send_mail(recipient_list, template, context: dict = None):
if context is None:
context = {}
from_email = settings.EMAIL_FROM_ADDRESS
recipient_list = recipient_list
send_templated_mail(
template_name=template,
from_email=from_email,
@ -77,3 +60,29 @@ def notify_task_expiry():
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.

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

View File

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

View File

@ -97,7 +97,7 @@ class JuniorTask(models.Model):
"""task details"""
task_name = models.CharField(max_length=100)
"""task description"""
task_description = models.CharField(max_length=500)
task_description = models.CharField(max_length=500, null=True, blank=True)
"""points of the task"""
points = models.IntegerField(default=TASK_POINTS)
"""last date of the task"""

View File

@ -28,7 +28,7 @@ from base.constants import NUMBER, JUN, ZOD, GRD, Already_register_user, GUARDIA
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
from .utils import real_time, convert_timedelta_into_datetime, update_referral_points
# notification's constant
from notifications.constants import TASK_APPROVED, TASK_REJECTED
from notifications.constants import TASK_APPROVED, TASK_REJECTED, TASK_ASSIGNED
# send notification function
from notifications.utils import send_notification
from django.core.exceptions import ValidationError
@ -36,6 +36,7 @@ from django.utils.translation import gettext as _
# In this serializer file
# define user serializer,
# define password validation
# create guardian serializer,
# task serializer,
# guardian serializer,
@ -47,6 +48,7 @@ from django.utils.translation import gettext as _
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
@ -57,6 +59,7 @@ class PasswordValidator:
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(
@ -64,16 +67,20 @@ class PasswordValidator:
)
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."))
@ -211,7 +218,7 @@ class TaskSerializer(serializers.ModelSerializer):
class Meta(object):
"""Meta info"""
model = JuniorTask
fields = ['id', 'task_name','task_description','points', 'due_date', 'junior', 'default_image']
fields = ['id', 'task_name','task_description','points', 'due_date','default_image']
def validate_due_date(self, value):
"""validation on due date"""
@ -222,11 +229,22 @@ class TaskSerializer(serializers.ModelSerializer):
return value
def create(self, validated_data):
"""create default task image data"""
validated_data['guardian'] = Guardian.objects.filter(user=self.context['user']).last()
guardian = self.context['guardian']
# update image of the task
images = self.context['image']
validated_data['default_image'] = images
instance = JuniorTask.objects.create(**validated_data)
junior_data = self.context['junior_data']
tasks_created = []
for junior in junior_data:
# create task
task_data = validated_data.copy()
task_data['guardian'] = guardian
task_data['default_image'] = images
task_data['junior'] = junior
instance = JuniorTask.objects.create(**task_data)
tasks_created.append(instance)
send_notification.delay(TASK_ASSIGNED, guardian.user.id, GUARDIAN,
junior.auth.id, {'task_id': instance.id})
return instance
class GuardianDetailSerializer(serializers.ModelSerializer):
@ -316,10 +334,11 @@ class TaskDetailsjuniorSerializer(serializers.ModelSerializer):
'requested_on', 'rejected_on', 'completed_on',
'junior', 'task_status', 'is_active', 'remaining_time', 'created_at','updated_at']
class TopJuniorSerializer(serializers.ModelSerializer):
"""Top junior serializer"""
junior = JuniorDetailSerializer()
position = serializers.IntegerField()
position = serializers.SerializerMethodField()
class Meta(object):
"""Meta info"""
@ -329,9 +348,13 @@ class TopJuniorSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
"""Convert instance to representation"""
representation = super().to_representation(instance)
representation['position'] = instance.position
return representation
@staticmethod
def get_position(obj):
""" get position/rank """
return obj.rank
class GuardianProfileSerializer(serializers.ModelSerializer):
"""junior serializer"""
@ -390,9 +413,9 @@ class ApproveJuniorSerializer(serializers.ModelSerializer):
def create(self, validated_data):
"""update guardian code"""
instance = self.context['junior']
instance.guardian_code = [self.context['guardian_code']]
instance.guardian_code_approved = True
instance.guardian_code_status = str(NUMBER['two'])
guardian_code = self.context['guardian_code']
index = instance.guardian_code.index(guardian_code)
instance.guardian_code_status[index] = str(NUMBER['two'])
instance.save()
return instance
@ -500,4 +523,7 @@ class GuardianDetailListSerializer(serializers.ModelSerializer):
def get_guardian_code_status(self,obj):
"""guardian code status"""
return obj.junior.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

@ -127,7 +127,7 @@ def update_expired_task_status(data=None):
Update task of the status if due date is in past
"""
try:
task_status = [str(NUMBER['one']), str(NUMBER['two']), str(NUMBER['four'])]
task_status = [str(NUMBER['one']), str(NUMBER['two'])]
JuniorTask.objects.filter(due_date__lt=datetime.today().date(),
task_status__in=task_status).update(task_status=str(NUMBER['six']))
except ObjectDoesNotExist as e:

View File

@ -1,4 +1,5 @@
"""Views of Guardian"""
import math
# django imports
# Import IsAuthenticated
@ -6,14 +7,17 @@
# Import PageNumberPagination
# Import User
# Import timezone
from django.db.models import F, Window
from django.db.models.functions.window import Rank
from rest_framework.permissions import IsAuthenticated
from rest_framework import viewsets, status
from rest_framework.pagination import PageNumberPagination
from django.contrib.auth.models import User
from base.constants import guardian_code_tuple
from rest_framework.filters import SearchFilter
from django.utils import timezone
from base.pagination import CustomPageNumberPagination
# Import guardian's model,
# Import junior's model,
# Import account's model,
@ -34,7 +38,7 @@ from .models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship
from account.models import UserEmailOtp, UserNotification, UserDeviceDetails
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, send_otp_email, task_status_fun
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, GUARDIAN_CODE_STATUS, GUARDIAN
from .utils import upload_image_to_alibaba
@ -133,7 +137,8 @@ class TaskListAPIView(viewsets.ModelViewSet):
Params
status
search
page"""
page
junior"""
serializer_class = TaskDetailsSerializer
permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,)
@ -143,8 +148,8 @@ class TaskListAPIView(viewsets.ModelViewSet):
def get_queryset(self):
queryset = JuniorTask.objects.filter(guardian__user=self.request.user
).prefetch_related('junior', 'junior__auth'
).order_by('due_date', 'created_at')
).select_related('junior', 'junior__auth'
).order_by('due_date', 'created_at')
queryset = self.filter_queryset(queryset)
return queryset
@ -152,15 +157,19 @@ class TaskListAPIView(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs):
"""Create guardian profile"""
status_value = self.request.GET.get('status')
junior = self.request.GET.get('junior')
queryset = self.get_queryset()
if status_value and status_value != '0':
queryset = queryset.filter(task_status=status_value)
paginator = self.pagination_class()
task_status = task_status_fun(status_value)
if status_value:
queryset = queryset.filter(task_status__in=task_status)
if junior:
queryset = queryset.filter(junior=int(junior))
paginator = CustomPageNumberPagination()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskDetailsSerializer serializer
serializer = self.serializer_class(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return paginator.get_paginated_response(serializer.data)
class CreateTaskAPIView(viewsets.ModelViewSet):
@ -175,40 +184,51 @@ class CreateTaskAPIView(viewsets.ModelViewSet):
"""
try:
image = request.data['default_image']
junior = request.data['junior']
junior_id = Junior.objects.filter(id=junior).last()
guardian_data = Guardian.objects.filter(user=request.user).last()
if guardian_data.guardian_code not in junior_id.guardian_code:
return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST)
junior_ids = request.data['junior'].split(',')
invalid_junior_ids = [junior_id for junior_id in junior_ids if not junior_id.isnumeric()]
if invalid_junior_ids:
# At least one junior value is not an integer
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
allowed_extensions = ['.jpg', '.jpeg', '.png']
if not any(extension in str(image) for extension in allowed_extensions):
return custom_error_response(ERROR_CODE['2048'], response_status=status.HTTP_400_BAD_REQUEST)
if 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')
return custom_error_response(ERROR_CODE['2035'],
response_status=status.HTTP_400_BAD_REQUEST)
image_data = upload_image_to_alibaba(image, filename)
request.data.pop('default_image')
guardian = Guardian.objects.filter(user=request.user).select_related('user').last()
junior_data = Junior.objects.filter(id__in=junior_ids,
guardian_code__contains=[guardian.guardian_code]
).select_related('auth')
if not junior_data:
return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST)
for junior in junior_data:
index = junior.guardian_code.index(guardian.guardian_code)
status_index = junior.guardian_code_status[index]
if status_index == str(NUMBER['three']):
return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST)
# use TaskSerializer serializer
serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data)
serializer = TaskSerializer(context={"guardian": guardian, "image": image_data,
"junior_data": junior_data}, data=request.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'], serializer.data, response_status=status.HTTP_200_OK)
serializer.save()
# removed send notification method and used in serializer
return custom_response(SUCCESS_CODE['3018'], response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class SearchTaskListAPIView(viewsets.ModelViewSet):
"""Filter task"""
serializer_class = TaskDetailsSerializer
@ -241,7 +261,6 @@ class SearchTaskListAPIView(viewsets.ModelViewSet):
class TopJuniorListAPIView(viewsets.ModelViewSet):
"""Top juniors list
No Params"""
queryset = JuniorPoints.objects.all()
serializer_class = TopJuniorSerializer
permission_classes = [IsAuthenticated]
http_method_names = ('get',)
@ -252,15 +271,22 @@ class TopJuniorListAPIView(viewsets.ModelViewSet):
context.update({'view': self})
return context
def get_queryset(self):
queryset = JuniorPoints.objects.filter(
junior__is_verified=True
).select_related(
'junior', 'junior__auth'
).annotate(rank=Window(
expression=Rank(),
order_by=[F('total_points').desc(), 'junior__created_at'])
).order_by('-total_points', 'junior__created_at')
return queryset
def list(self, request, *args, **kwargs):
"""Fetch junior list of those who complete their tasks"""
try:
junior_total_points = self.get_queryset().order_by('-total_points')
# Update the position field for each JuniorPoints object
for index, junior in enumerate(junior_total_points):
junior.position = index + 1
junior.save()
serializer = self.get_serializer(junior_total_points[:NUMBER['fifteen']], many=True)
junior_total_points = self.get_queryset()[:15]
serializer = self.get_serializer(junior_total_points, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
@ -287,19 +313,25 @@ class ApproveJuniorAPIView(viewsets.ModelViewSet):
if request.data['action'] == '1':
# use ApproveJuniorSerializer serializer
serializer = ApproveJuniorSerializer(context={"guardian_code": guardian.guardian_code,
"junior": junior_queryset, "action": request.data['action']},
"junior": junior_queryset,
"action": request.data['action']},
data=request.data)
if serializer.is_valid():
# save serializer
serializer.save()
send_notification.delay(ASSOCIATE_APPROVED, guardian.user.id, GUARDIAN,
junior_queryset.auth.id)
junior_queryset.auth.id, {})
return custom_response(SUCCESS_CODE['3023'], serializer.data, response_status=status.HTTP_200_OK)
else:
junior_queryset.guardian_code = None
junior_queryset.guardian_code_status = str(NUMBER['one'])
if junior_queryset.guardian_code and ('-' in junior_queryset.guardian_code):
junior_queryset.guardian_code.remove('-')
if junior_queryset.guardian_code_status and ('-' in junior_queryset.guardian_code_status):
junior_queryset.guardian_code_status.remove('-')
index = junior_queryset.guardian_code.index(guardian.guardian_code)
junior_queryset.guardian_code.remove(guardian.guardian_code)
junior_queryset.guardian_code_status.pop(index)
junior_queryset.save()
send_notification.delay(ASSOCIATE_REJECTED, guardian.user.id, GUARDIAN, junior_queryset.auth.id)
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)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
@ -325,6 +357,8 @@ class ApproveTaskAPIView(viewsets.ModelViewSet):
task_queryset = JuniorTask.objects.filter(id=self.request.data.get('task_id'),
guardian=guardian,
junior=self.request.data.get('junior_id')).last()
if task_queryset and guardian.guardian_code not in task_queryset.junior.guardian_code:
return custom_error_response(ERROR_CODE['2084'], response_status=status.HTTP_400_BAD_REQUEST)
if task_queryset and (task_queryset.junior.is_deleted or not task_queryset.junior.is_active):
return custom_error_response(ERROR_CODE['2072'], response_status=status.HTTP_400_BAD_REQUEST)
# use ApproveJuniorSerializer serializer
@ -348,7 +382,7 @@ class ApproveTaskAPIView(viewsets.ModelViewSet):
return custom_error_response(ERROR_CODE['2038'], response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
#
class GuardianListAPIView(viewsets.ModelViewSet):
"""Guardian list of assosicated junior"""

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

@ -76,9 +76,9 @@ class Junior(models.Model):
is_verified = models.BooleanField(default=False)
"""guardian code is approved or not"""
guardian_code_approved = models.BooleanField(default=False)
# guardian code status"""
guardian_code_status = models.CharField(max_length=31, choices=GUARDIAN_CODE_STATUS, default='1',
null=True, blank=True)
# # guardian code status"""
guardian_code_status = ArrayField(models.CharField(max_length=10, null=True, blank=True, default=None), null=True,
)
# Profile created and updated time"""
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

View File

@ -11,7 +11,7 @@ from django.utils import timezone
from rest_framework_simplejwt.tokens import RefreshToken
# local imports
from account.utils import send_otp_email, generate_code
from account.utils import send_otp_email, generate_code, make_special_password
from junior.models import Junior, JuniorPoints, JuniorGuardianRelationship, JuniorArticlePoints, FAQ
from guardian.tasks import generate_otp
from base.messages import ERROR_CODE, SUCCESS_CODE
@ -19,10 +19,10 @@ from base.constants import (PENDING, IN_PROGRESS, REJECTED, REQUESTED, COMPLETED
GUARDIAN_CODE_STATUS, JUNIOR)
from guardian.models import Guardian, JuniorTask
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 notifications.utils import send_notification
from notifications.constants import (ASSOCIATE_REQUEST, JUNIOR_ADDED, TASK_ACTION,
from notifications.constants import (ASSOCIATE_REQUEST, ASSOCIATE_JUNIOR, TASK_ACTION,
)
from web_admin.models import ArticleCard
@ -88,18 +88,27 @@ class CreateJuniorSerializer(serializers.ModelSerializer):
if junior:
"""update details according to the data get from request"""
junior.gender = validated_data.get('gender',junior.gender)
"""Update guardian code"""
junior.guardian_code = validated_data.get('guardian_code', junior.guardian_code)
"""condition for guardian code"""
# Update guardian code"""
# condition for guardian code
if guardian_code:
junior.guardian_code = guardian_code
if junior.guardian_code and guardian_code:
if guardian_code[0] in junior.guardian_code:
raise serializers.ValidationError({"error":ERROR_CODE['2076'],"code":"400", "status":"failed"})
if not junior.guardian_code:
junior.guardian_code = []
junior.guardian_code_status = []
junior.guardian_code.extend(guardian_code)
junior.guardian_code_status.extend(str(NUMBER['three']))
elif len(junior.guardian_code) < 3 and len(guardian_code) < 3:
junior.guardian_code.extend(guardian_code)
junior.guardian_code_status.extend(str(NUMBER['three']))
else:
raise serializers.ValidationError({"error":ERROR_CODE['2081'],"code":"400", "status":"failed"})
guardian_data = Guardian.objects.filter(guardian_code=guardian_code[0]).last()
if guardian_data:
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior)
junior.guardian_code_status = str(NUMBER['three'])
send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {})
junior_approval_mail.delay(user.email, user.first_name)
send_notification.delay(ASSOCIATE_REQUEST, junior.auth.id, JUNIOR, guardian_data.user.id, {})
junior.dob = validated_data.get('dob', junior.dob)
junior.passcode = validated_data.get('passcode', junior.passcode)
junior.country_name = validated_data.get('country_name', junior.country_name)
@ -164,6 +173,7 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
rejected_task = serializers.SerializerMethodField('get_rejected_task')
pending_task = serializers.SerializerMethodField('get_pending_task')
position = serializers.SerializerMethodField('get_position')
guardian_code_status = serializers.SerializerMethodField('get_guardian_code_status')
def get_auth(self, obj):
@ -180,9 +190,8 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
return data
def get_position(self, obj):
data = JuniorPoints.objects.filter(junior=obj).last()
if data:
return data.position
return get_junior_leaderboard_rank(obj)
def get_points(self, obj):
data = JuniorPoints.objects.filter(junior=obj).last()
if data:
@ -209,6 +218,13 @@ class JuniorDetailListSerializer(serializers.ModelSerializer):
def get_pending_task(self, obj):
data = JuniorTask.objects.filter(junior=obj, task_status=PENDING).count()
return data
def get_guardian_code_status(self, obj):
if self.context['guardian_code'] in obj.guardian_code:
index = obj.guardian_code.index(self.context['guardian_code'])
if obj.guardian_code_status:
data = obj.guardian_code_status[index]
return data
class Meta(object):
"""Meta info"""
model = Junior
@ -281,8 +297,8 @@ class AddJuniorSerializer(serializers.ModelSerializer):
user_data = User.objects.create(username=email, email=email,
first_name=self.context['first_name'],
last_name=self.context['last_name'])
password = User.objects.make_random_password()
user_data.set_password(password)
special_password = make_special_password()
user_data.set_password(special_password)
user_data.save()
junior_data = Junior.objects.create(auth=user_data, gender=validated_data.get('gender'),
image=profile_image,
@ -292,8 +308,8 @@ class AddJuniorSerializer(serializers.ModelSerializer):
referral_code=generate_code(ZOD, user_data.id),
referral_code_used=guardian_data.referral_code,
is_password_set=False, is_verified=True,
guardian_code_status=GUARDIAN_CODE_STATUS[1][0])
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior_data,
guardian_code_status=[str(NUMBER['two'])])
JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data,
relationship=relationship)
total_junior = Junior.objects.all().count()
JuniorPoints.objects.create(junior=junior_data, position=total_junior)
@ -305,9 +321,9 @@ class AddJuniorSerializer(serializers.ModelSerializer):
# add push notification
UserNotification.objects.get_or_create(user=user_data)
"""Notification email"""
junior_notification_email.delay(email, full_name, email, password)
junior_notification_email.delay(email, full_name, email, special_password)
# push notification
send_notification.delay(JUNIOR_ADDED, None, None, junior_data.auth.id, {})
send_notification.delay(ASSOCIATE_JUNIOR, None, None, junior_data.auth.id, {})
return junior_data
@ -320,9 +336,13 @@ class RemoveJuniorSerializer(serializers.ModelSerializer):
fields = ('id', 'is_invited')
def update(self, instance, validated_data):
if instance:
guardian_code = self.context['guardian_code']
instance.is_invited = False
instance.guardian_code = '{}'
instance.guardian_code_status = str(NUMBER['one'])
if instance.guardian_code and ('-' in instance.guardian_code):
instance.guardian_code.remove('-')
index = instance.guardian_code.index(guardian_code)
instance.guardian_code.remove(guardian_code)
instance.guardian_code_status.pop(index)
instance.save()
return instance
@ -360,10 +380,7 @@ class JuniorPointsSerializer(serializers.ModelSerializer):
return obj.junior.id
def get_position(self, obj):
data = JuniorPoints.objects.filter(junior=obj.junior).last()
if data:
return data.position
return 99999
return get_junior_leaderboard_rank(obj.junior)
def get_points(self, obj):
"""total points"""
points = JuniorPoints.objects.filter(junior=obj.junior).last()
@ -444,7 +461,7 @@ class AddGuardianSerializer(serializers.ModelSerializer):
user_type=str(NUMBER['two']), expired_at=expiry_time,
is_verified=True)
UserNotification.objects.get_or_create(user=user)
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian_data, junior=junior_data,
JuniorGuardianRelationship.objects.create(guardian=guardian_data, junior=junior_data,
relationship=relationship)
"""Notification email"""
@ -494,8 +511,6 @@ class ReAssignTaskSerializer(serializers.ModelSerializer):
instance.save()
return instance
class RemoveGuardianCodeSerializer(serializers.ModelSerializer):
"""User task Serializer"""
class Meta(object):
@ -503,24 +518,33 @@ class RemoveGuardianCodeSerializer(serializers.ModelSerializer):
model = Junior
fields = ('id', )
def update(self, instance, validated_data):
instance.guardian_code = None
instance.guardian_code_status = str(NUMBER['one'])
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
"""FAQ Serializer"""
class Meta(object):
# meta info
"""meta info"""
model = FAQ
fields = ('id', 'question', 'description')
class CreateArticleCardSerializer(serializers.ModelSerializer):
# Article card Serializer
"""Article card Serializer"""
class Meta(object):
# meta info
"""meta info"""
model = ArticleCard
fields = ('id', 'article')

View File

@ -6,7 +6,7 @@ from .views import (UpdateJuniorProfile, ValidateGuardianCode, JuniorListAPIView
CompleteJuniorTaskAPIView, JuniorPointsListAPIView, ValidateReferralCode,
InviteGuardianAPIView, StartTaskAPIView, ReAssignJuniorTaskAPIView, StartArticleAPIView,
StartAssessmentAPIView, CheckAnswerAPIView, CompleteArticleAPIView, ReadArticleCardAPIView,
CreateArticleCardAPIView, RemoveGuardianCodeAPIView, FAQViewSet)
CreateArticleCardAPIView, RemoveGuardianCodeAPIView, FAQViewSet, CheckJuniorApiViewSet)
"""Third party import"""
from rest_framework import routers
@ -29,6 +29,8 @@ router.register('create-junior-profile', UpdateJuniorProfile, basename='profile-
router.register('validate-guardian-code', ValidateGuardianCode, basename='validate-guardian-code')
# junior list API"""
router.register('junior-list', JuniorListAPIView, basename='junior-list')
router.register('check-junior', CheckJuniorApiViewSet, basename='check-junior')
# Add junior list API"""
router.register('add-junior', AddJuniorAPIView, basename='add-junior')
# Invited junior list API"""
@ -62,5 +64,5 @@ urlpatterns = [
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()),
path('api/v1/remove-guardian-code-request/', RemoveGuardianCodeAPIView.as_view())
]

View File

@ -5,7 +5,8 @@ from django.conf import settings
from templated_email import send_templated_mail
from .models import JuniorPoints
from base.constants import NUMBER
from django.db.models import F
from django.db.models import F, Window
from django.db.models.functions.window import Rank
# junior notification
# email for sending email
# when guardian create junior profile
@ -61,3 +62,21 @@ def update_positions_based_on_points():
junior_point.position = position
junior_point.save()
position += NUMBER['one']
def get_junior_leaderboard_rank(junior_obj):
"""
to get junior's position/rank
:param junior_obj:
:return: junior's position/rank
"""
queryset = JuniorPoints.objects.filter(
junior__is_verified=True
).select_related('junior', 'junior__auth').annotate(rank=Window(
expression=Rank(),
order_by=[F('total_points').desc(), 'junior__created_at']
)).order_by('-total_points', 'junior__created_at')
junior = next((query for query in queryset if query.junior == junior_obj), None)
return junior.rank if junior else None

View File

@ -12,6 +12,10 @@ import datetime
import requests
from rest_framework.viewsets import GenericViewSet, mixins
import math
from base.pagination import CustomPageNumberPagination
"""Django app import"""
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
@ -42,12 +46,12 @@ from .serializers import (CreateJuniorSerializer, JuniorDetailListSerializer, Ad
from guardian.models import Guardian, JuniorTask
from guardian.serializers import TaskDetailsSerializer, TaskDetailsjuniorSerializer
from base.messages import ERROR_CODE, SUCCESS_CODE
from base.constants import NUMBER, ARTICLE_STATUS, none
from account.utils import custom_response, custom_error_response
from base.constants import NUMBER, ARTICLE_STATUS, none, GUARDIAN
from account.utils import custom_response, custom_error_response, task_status_fun
from guardian.utils import upload_image_to_alibaba
from .utils import update_positions_based_on_points
from notifications.utils import send_notification
from notifications.constants import REMOVE_JUNIOR
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)
@ -99,7 +103,11 @@ class UpdateJuniorProfile(viewsets.ModelViewSet):
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
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.ModelViewSet):
"""Check guardian code exist or not"""
@ -141,20 +149,44 @@ class JuniorListAPIView(viewsets.ModelViewSet):
def list(self, request, *args, **kwargs):
""" junior list"""
try:
update_positions_based_on_points()
# update_positions_based_on_points, function removed
guardian_data = Guardian.objects.filter(user__email=request.user).last()
# fetch junior object
if guardian_data:
queryset = self.get_queryset()
queryset = queryset.filter(guardian_code__icontains=str(guardian_data.guardian_code))
# use JuniorDetailListSerializer serializer
serializer = JuniorDetailListSerializer(queryset, 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_error_response(ERROR_CODE['2045'], response_status=status.HTTP_200_OK)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
class CheckJuniorApiViewSet(viewsets.GenericViewSet):
"""
api to check whether given user exist or not
"""
serializer_class = None
permission_classes = [IsAuthenticated]
def get_queryset(self):
junior = Junior.objects.filter(auth__email=self.request.data.get('email')).first()
return junior
def create(self, request, *args, **kwargs):
"""
:param request:
:return:
"""
junior = self.get_queryset()
data = {
'junior_exist': True if junior else False
}
return custom_response(None, data)
class AddJuniorAPIView(viewsets.ModelViewSet):
"""Add Junior by guardian"""
serializer_class = AddJuniorSerializer
@ -171,6 +203,16 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
"email":"abc@yopmail.com"
}"""
try:
if user := User.objects.filter(username=request.data['email']).first():
data = self.associate_guardian(user)
if data == none:
return custom_error_response(ERROR_CODE['2077'], response_status=status.HTTP_400_BAD_REQUEST)
elif not data:
return custom_error_response(ERROR_CODE['2076'], response_status=status.HTTP_400_BAD_REQUEST)
elif data == "Max":
return custom_error_response(ERROR_CODE['2081'], response_status=status.HTTP_400_BAD_REQUEST)
return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK)
info_data = {'user': request.user, 'relationship': str(request.data['relationship']),
'email': request.data['email'], 'first_name': request.data['first_name'],
'last_name': request.data['last_name'], 'image':None}
@ -184,13 +226,7 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
# 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():
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)
return custom_response(SUCCESS_CODE['3021'], response_status=status.HTTP_200_OK)
# use AddJuniorSerializer serializer
serializer = AddJuniorSerializer(data=request.data, context=info_data)
if serializer.is_valid():
@ -204,18 +240,30 @@ class AddJuniorAPIView(viewsets.ModelViewSet):
def associate_guardian(self, user):
junior = Junior.objects.filter(auth__email=self.request.data['email']).first()
guardian = Guardian.objects.filter(user=self.request.user).first()
if not junior:
if junior is None:
return none
if junior.guardian_code and (guardian.guardian_code in junior.guardian_code):
return False
if type(junior.guardian_code) is list:
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:
junior.guardian_code = [guardian.guardian_code]
junior.guardian_code_status = str(NUMBER['two'])
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()
JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior,
relationship=str(self.request.data['relationship']))
jun_data, created = JuniorGuardianRelationship.objects.get_or_create(guardian=guardian, junior=junior)
if jun_data:
jun_data.relationship = str(self.request.data['relationship'])
jun_data.save()
send_notification.delay(ASSOCIATE_EXISTING_JUNIOR, self.request.user.id, GUARDIAN, junior.auth.id, {})
return True
@ -259,10 +307,10 @@ class FilterJuniorAPIView(viewsets.ModelViewSet):
manual_parameters=[
# Example of a query parameter
openapi.Parameter(
'title', # Query parameter name
openapi.IN_QUERY, # Parameter location
'title',
openapi.IN_QUERY,
description='title of the name',
type=openapi.TYPE_STRING, # Parameter type
type=openapi.TYPE_STRING,
),
# Add more parameters as needed
]
@ -308,10 +356,12 @@ class RemoveJuniorAPIView(views.APIView):
guardian_code__icontains=str(guardian.guardian_code)).last()
if junior_queryset:
# 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():
# save serializer
serializer.save()
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_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST)
@ -325,9 +375,19 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet):
"""Junior task list"""
serializer_class = TaskDetailsjuniorSerializer
permission_classes = [IsAuthenticated]
filter_backends = (SearchFilter,)
search_fields = ['task_name', ]
pagination_class = PageNumberPagination
http_method_names = ('get',)
def get_queryset(self):
queryset = JuniorTask.objects.filter(junior__auth=self.request.user
).select_related('junior', 'junior__auth'
).order_by('due_date', 'created_at')
queryset = self.filter_queryset(queryset)
return queryset
def list(self, request, *args, **kwargs):
"""Junior task list
status=0
@ -335,28 +395,16 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet):
page=1"""
try:
status_value = self.request.GET.get('status')
search = self.request.GET.get('search')
if search and str(status_value) == '0':
# search with title and for all task list
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()
queryset = self.get_queryset()
task_status = task_status_fun(status_value)
if status_value:
queryset = queryset.filter(task_status__in=task_status)
paginator = CustomPageNumberPagination()
# use Pagination
paginated_queryset = paginator.paginate_queryset(queryset, request)
# use TaskDetailsSerializer serializer
serializer = TaskDetailsjuniorSerializer(paginated_queryset, many=True)
return custom_response(None, serializer.data, response_status=status.HTTP_200_OK)
# use TaskDetails juniorSerializer serializer
serializer = self.serializer_class(paginated_queryset, many=True)
return paginator.get_paginated_response(serializer.data)
except Exception as e:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
@ -385,10 +433,12 @@ class CompleteJuniorTaskAPIView(views.APIView):
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.junior.is_deleted or not task_queryset.junior.is_active:
if task_queryset.guardian.guardian_code not in task_queryset.junior.guardian_code:
return custom_error_response(ERROR_CODE['2085'], response_status=status.HTTP_400_BAD_REQUEST)
elif task_queryset.junior.is_deleted or not task_queryset.junior.is_active:
return custom_error_response(ERROR_CODE['2074'], response_status=status.HTTP_400_BAD_REQUEST)
# use CompleteTaskSerializer serializer
if task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]:
elif task_queryset.task_status in [str(NUMBER['four']), str(NUMBER['five'])]:
"""Already request send """
return custom_error_response(ERROR_CODE['2049'], response_status=status.HTTP_400_BAD_REQUEST)
serializer = CompleteTaskSerializer(task_queryset, data={'image': image_url}, partial=True)
@ -412,7 +462,7 @@ class JuniorPointsListAPIView(viewsets.ModelViewSet):
"""Junior Points
No Params"""
try:
update_positions_based_on_points()
# update_positions_based_on_points, function removed
queryset = JuniorPoints.objects.filter(junior__auth__email=self.request.user).last()
# update position of junior
serializer = JuniorPointsSerializer(queryset)
@ -493,7 +543,10 @@ class StartTaskAPIView(views.APIView):
try:
task_id = self.request.data.get('task_id')
task_queryset = JuniorTask.objects.filter(id=task_id, junior__auth__email=self.request.user).last()
print("task_queryset==>",task_queryset)
if task_queryset and task_queryset.task_status == str(NUMBER['one']):
if task_queryset.guardian.guardian_code not in task_queryset.junior.guardian_code:
return custom_error_response(ERROR_CODE['2083'], response_status=status.HTTP_400_BAD_REQUEST)
# use StartTaskSerializer serializer
serializer = StartTaskSerializer(task_queryset, data=request.data, partial=True)
if serializer.is_valid():
@ -577,8 +630,8 @@ class StartAssessmentAPIView(viewsets.ModelViewSet):
article_id = self.request.GET.get('article_id')
# if referral_code:
article = Article.objects.filter(id=article_id, is_deleted=False).prefetch_related(
'article_cards', 'article_survey', 'article_survey__options'
).order_by('-created_at')
'article_survey'
)
return article
def list(self, request, *args, **kwargs):
"""Params
@ -615,10 +668,10 @@ class CheckAnswerAPIView(viewsets.ModelViewSet):
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, is_answer=True).last()
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:
if submit_ans.is_answer:
junior_article_points.update(submitted_answer=submit_ans, is_attempt=True, is_answer_correct=True)
JuniorPoints.objects.filter(junior__auth=self.request.user).update(total_points=
F('total_points') + queryset.points)
@ -658,6 +711,8 @@ class CompleteArticleAPIView(views.APIView):
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)
@ -718,26 +773,34 @@ class CreateArticleCardAPIView(viewsets.ModelViewSet):
class RemoveGuardianCodeAPIView(views.APIView):
"""Remove guardian code request API
No Payload"""
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, data=request.data, partial=True)
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:
return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST)
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,

View File

@ -6,60 +6,76 @@ ASSOCIATE_REQUEST = 3
ASSOCIATE_REJECTED = 4
ASSOCIATE_APPROVED = 5
REFERRAL_POINTS = 6
JUNIOR_ADDED = 7
ASSOCIATE_JUNIOR = 7
ASSOCIATE_EXISTING_JUNIOR = 8
TASK_ASSIGNED = 8
TASK_ACTION = 9
TASK_REJECTED = 10
TASK_APPROVED = 11
PENDING_TASK_EXPIRING = 12
IN_PROGRESS_TASK_EXPIRING = 13
REMOVE_JUNIOR = 15
TASK_ASSIGNED = 9
TASK_ACTION = 10
TASK_REJECTED = 11
TASK_APPROVED = 12
PENDING_TASK_EXPIRING = 13
IN_PROGRESS_TASK_EXPIRING = 14
TOP_JUNIOR = 15
NEW_ARTICLE_PUBLISHED = 16
ARTICLE_REWARD_POINTS = 17
REMOVE_JUNIOR = 18
TEST_NOTIFICATION = 99
# notification dictionary
NOTIFICATION_DICT = {
REGISTRATION: {
"title": "Successfully registered!",
"body": "You have registered successfully. Now login and complete your profile."
},
# user will receive notification as soon junior sign up application using their guardian code
# for association
# user will receive notification as soon junior
# sign up application using their guardian code for association
ASSOCIATE_REQUEST: {
"title": "Associate request!",
"body": "You have request from {from_user} to associate with you."
},
# Juniors will receive notification when custodians reject their request for associate
# Juniors will receive notification when
# custodians reject their request for associate
ASSOCIATE_REJECTED: {
"title": "Associate request rejected!",
"body": "Your request to associate has been rejected by {from_user}."
},
# Juniors will receive notification when custodians approve their request for associate
# Juniors will receive notification when
# custodians approve their request for associate
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
# Juniors will receive Notifications
# for every Points earned by referrals
REFERRAL_POINTS: {
"title": "Earn Referral points!",
"body": "You earn 5 points for referral."
},
# Juniors will receive notification once any custodians add them in their account
JUNIOR_ADDED: {
# Juniors will receive notification
# once any custodians add them in their account
ASSOCIATE_JUNIOR: {
"title": "Profile already setup!",
"body": "Your guardian has already setup your profile."
},
# Juniors will receive Notification for every Task Assign by Custodians
ASSOCIATE_EXISTING_JUNIOR: {
"title": "Associated to guardian",
"body": "Your are associated to your guardian {from_user}."
},
# Juniors will receive Notification
# for every Task Assign by Custodians
TASK_ASSIGNED: {
"title": "New task assigned!",
"body": "{from_user} has assigned you a new task."
},
# Guardian will receive notification as soon as junior send task for approval
# Guardian will receive notification as soon
# as junior send task for approval
TASK_ACTION: {
"title": "Task completion approval!",
"body": "{from_user} completed her task {task_name}."
"body": "{from_user} completed their task {task_name}."
},
# Juniors will receive notification as soon as their task is rejected by custodians
# Juniors will receive notification as soon
# as their task is rejected by custodians
TASK_REJECTED: {
"title": "Task completion rejected!",
"body": "Your task completion request has been rejected by {from_user}."
@ -83,6 +99,23 @@ NOTIFICATION_DICT = {
"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: {
"title": "Disassociate by guardian!",

View File

@ -31,11 +31,16 @@ class RegisterDevice(serializers.Serializer):
class NotificationListSerializer(serializers.ModelSerializer):
"""List of notification"""
badge = serializers.SerializerMethodField()
class Meta(object):
"""meta info"""
model = Notification
fields = ['id', 'data', 'is_read', 'created_at']
fields = ['id', 'data', 'badge', 'is_read', 'created_at']
@staticmethod
def get_badge(obj):
return Notification.objects.filter(notification_to=obj.notification_to, is_read=False).count()
class ReadNotificationSerializer(serializers.ModelSerializer):

View File

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

View File

@ -8,6 +8,7 @@ from firebase_admin.messaging import Message, Notification as FirebaseNotificati
# django imports
from django.contrib.auth import get_user_model
from django.db.models import Q
# local imports
from account.models import UserNotification
@ -18,14 +19,13 @@ from junior.models import Junior
from notifications.constants import NOTIFICATION_DICT
from notifications.models import Notification
User = get_user_model()
def register_fcm_token(user_id, registration_id, device_id, device_type):
""" used to register the fcm device token"""
device, _ = FCMDevice.objects.update_or_create(device_id=device_id,
defaults={'user_id': user_id, 'type': device_type,
device, _ = FCMDevice.objects.update_or_create(user_id=user_id,
defaults={'device_id': device_id, 'type': device_type,
'active': True,
'registration_id': registration_id})
return device
@ -76,20 +76,23 @@ def get_notification_data(notification_type, from_user_id, from_user_type, to_us
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)
notification_data['body'] = notification_data['body'].format(from_user=from_user_name, task_name=task_name)
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.get(id=to_user_id)
to_user = User.objects.filter(id=to_user_id).first()
return notification_data, push_data, from_user, to_user
@ -101,7 +104,6 @@ def send_notification(notification_type, from_user_id, from_user_type, to_user_i
notification_data, push_data, from_user, to_user = get_notification_data(notification_type, from_user_id,
from_user_type, to_user_id, extra_data)
user_notification_type = UserNotification.objects.filter(user=to_user).first()
notification_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_to=to_user, data=notification_data)
if user_notification_type and user_notification_type.push_notification:
@ -115,33 +117,31 @@ def send_push(user, 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)
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_to_junior(notification_type, from_user_id, to_user_id, extra_data):
def send_notification_multiple_user(notification_type, from_user_id, from_user_type,
extra_data: dict = {}):
"""
:param notification_type:
:param from_user_id:
:param to_user_id:
:param extra_data:
:return:
used to send notification to multiple user for the given notification type
"""
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)
to_user_list = User.objects.filter(junior_profile__is_verified=True, is_superuser=False
).exclude(junior_profile__isnull=True, guardian_profile__isnull=True)
notification_data, push_data, from_user, _ = get_notification_data(notification_type, from_user_id,
from_user_type, None, extra_data)
notification_list = []
for user in to_user_list:
notification_list.append(Notification(notification_type=notification_type,
notification_to=user,
notification_from=from_user,
data=notification_data))
Notification.objects.bulk_create(notification_list)
to_user_list = to_user_list.filter(user_notification__push_notification=True)
send_multiple_push(to_user_list, push_data)

View File

@ -11,7 +11,8 @@ from rest_framework import viewsets, status, views
# local imports
from account.utils import custom_response, custom_error_response
from base.messages import SUCCESS_CODE, ERROR_CODE
from base.tasks import notify_task_expiry
from base.pagination import CustomPageNumberPagination
from base.tasks import notify_task_expiry, notify_top_junior
from notifications.constants import TEST_NOTIFICATION
from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer
from notifications.utils import send_notification
@ -33,10 +34,10 @@ class NotificationViewSet(viewsets.GenericViewSet):
"""
queryset = Notification.objects.filter(notification_to_id=request.auth.payload['user_id']
).select_related('notification_to').order_by('-id')
paginator = self.pagination_class()
paginator = CustomPageNumberPagination()
paginated_queryset = paginator.paginate_queryset(queryset, request)
serializer = self.serializer_class(paginated_queryset, many=True)
return custom_response(None, serializer.data)
return paginator.get_paginated_response(serializer.data)
@action(methods=['post'], detail=False, url_path='device', url_name='device', serializer_class=RegisterDevice)
def fcm_registration(self, request):
@ -52,9 +53,11 @@ class NotificationViewSet(viewsets.GenericViewSet):
@action(methods=['get'], detail=False, url_path='test', url_name='test')
def send_test_notification(self, request):
"""
to send test notification
to test send notification, task expiry, top junior
:return:
"""
notify_task_expiry()
notify_top_junior()
send_notification(TEST_NOTIFICATION, None, None, request.auth.payload['user_id'],
{})
return custom_response(SUCCESS_CODE["3000"])
@ -67,12 +70,3 @@ class NotificationViewSet(viewsets.GenericViewSet):
"""
Notification.objects.filter(id__in=request.data.get('id')).update(is_read=True)
return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK)
@action(methods=['get'], url_path='task', url_name='task', detail=False,
permission_classes=[AllowAny])
def task(self, request, *args, **kwargs):
"""
notification list
"""
notify_task_expiry()
return custom_response(SUCCESS_CODE['3039'], response_status=status.HTTP_200_OK)

View File

@ -101,4 +101,5 @@ vine==5.0.0
wcwidth==0.2.6
pandas==2.0.3
XlsxWriter==3.1.2
XlsxWriter==3.1.2
coverage==7.3.1

View File

@ -1,18 +0,0 @@
"""
web_admin pagination file
"""
# third party imports
from rest_framework.pagination import PageNumberPagination
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']

View File

@ -9,7 +9,7 @@ from django.contrib.auth import get_user_model
from account.utils import get_user_full_name
# local imports
from base.constants import USER_TYPE
from base.constants import USER_TYPE, JUNIOR
from junior.models import JuniorPoints, Junior
@ -37,7 +37,7 @@ class JuniorLeaderboardSerializer(serializers.ModelSerializer):
:param obj: junior object
: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
def get_first_name(obj):
@ -60,6 +60,8 @@ class LeaderboardSerializer(serializers.ModelSerializer):
"""
leaderboard serializer
"""
user_id = serializers.SerializerMethodField()
user_type = serializers.SerializerMethodField()
junior = JuniorLeaderboardSerializer()
rank = serializers.IntegerField()
@ -68,7 +70,15 @@ class LeaderboardSerializer(serializers.ModelSerializer):
meta class
"""
model = JuniorPoints
fields = ('total_points', 'rank', 'junior')
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):
@ -118,8 +128,6 @@ class UserCSVReportSerializer(serializers.ModelSerializer):
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):

View File

@ -10,9 +10,12 @@ from base.constants import (ARTICLE_SURVEY_POINTS, MAX_ARTICLE_CARD, MIN_ARTICLE
# local imports
from base.messages import ERROR_CODE
from guardian.utils import upload_image_to_alibaba
from notifications.constants import NEW_ARTICLE_PUBLISHED
from notifications.utils import send_notification_multiple_user
from web_admin.models import Article, ArticleCard, SurveyOption, ArticleSurvey, DefaultArticleCardImage
from web_admin.utils import pop_id, get_image_url
from junior.models import JuniorArticlePoints, JuniorArticle
USER = get_user_model()
@ -79,7 +82,7 @@ class ArticleSerializer(serializers.ModelSerializer):
meta class
"""
model = Article
fields = ('id', 'title', 'description', 'article_cards', 'article_survey')
fields = ('id', 'title', 'description', 'is_published', 'article_cards', 'article_survey')
def validate(self, attrs):
"""
@ -88,10 +91,9 @@ class ArticleSerializer(serializers.ModelSerializer):
"""
article_cards = attrs.get('article_cards', None)
article_survey = attrs.get('article_survey', None)
if article_cards is None or len(article_cards) > int(MAX_ARTICLE_CARD):
if not 0 < len(article_cards) <= int(MAX_ARTICLE_CARD):
raise serializers.ValidationError({'details': ERROR_CODE['2039']})
if article_survey is None or len(article_survey) < int(MIN_ARTICLE_SURVEY) or int(
MAX_ARTICLE_SURVEY) < len(article_survey):
if not int(MIN_ARTICLE_SURVEY) <= len(article_survey) <= int(MAX_ARTICLE_SURVEY):
raise serializers.ValidationError({'details': ERROR_CODE['2040']})
return attrs
@ -119,11 +121,15 @@ class ArticleSerializer(serializers.ModelSerializer):
option = pop_id(option)
SurveyOption.objects.create(survey=survey_obj, **option)
# All juniors will receive notification when admin add any new financial learnings/article
send_notification_multiple_user.delay(NEW_ARTICLE_PUBLISHED, None, None, {})
return article
def update(self, instance, validated_data):
"""
to update article and related table
:param validated_data:
:param instance: article object,
:return: article object
"""
@ -179,6 +185,28 @@ class ArticleSerializer(serializers.ModelSerializer):
return instance
class ArticleStatusChangeSerializer(serializers.ModelSerializer):
"""
Article status change serializer
"""
class Meta:
"""
meta class
"""
model = Article
fields = ('is_published', )
def update(self, instance, validated_data):
"""
:param instance: article object
:param validated_data:
:return:
"""
instance.is_published = validated_data['is_published']
instance.save()
return instance
class DefaultArticleCardImageSerializer(serializers.ModelSerializer):
"""
Article Card serializer
@ -215,12 +243,12 @@ class DefaultArticleCardImageSerializer(serializers.ModelSerializer):
card_image = DefaultArticleCardImage.objects.create(**validated_data)
return card_image
class ArticleListSerializer(serializers.ModelSerializer):
"""
serializer for article API
"""
article_cards = ArticleCardSerializer(many=True)
article_survey = ArticleSurveySerializer(many=True)
image = serializers.SerializerMethodField('get_image')
total_points = serializers.SerializerMethodField('get_total_points')
is_completed = serializers.SerializerMethodField('get_is_completed')
@ -229,12 +257,17 @@ class ArticleListSerializer(serializers.ModelSerializer):
meta class
"""
model = Article
fields = ('id', 'title', 'description', 'article_cards', 'article_survey', 'total_points', 'is_completed')
fields = ('id', 'title', 'description', 'image', 'total_points', 'is_completed')
def get_image(self, obj):
"""article image"""
if obj.article_cards.first():
return obj.article_cards.first().image_url
return None
def get_total_points(self, obj):
"""total points of article"""
total_question = ArticleSurvey.objects.filter(article=obj).count()
return total_question * NUMBER['five']
return obj.article_survey.all().count() * NUMBER['five']
def get_is_completed(self, obj):
"""complete all question"""
@ -244,6 +277,7 @@ class ArticleListSerializer(serializers.ModelSerializer):
return junior_article.is_completed
return False
class ArticleQuestionSerializer(serializers.ModelSerializer):
"""
article survey serializer
@ -254,7 +288,6 @@ class ArticleQuestionSerializer(serializers.ModelSerializer):
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')
@ -268,14 +301,14 @@ class ArticleQuestionSerializer(serializers.ModelSerializer):
ans_obj = SurveyOption.objects.filter(survey=obj, is_answer=True).last()
if ans_obj:
return ans_obj.id
return str("None")
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, is_answer_correct=True).last()
if junior_article_obj:
question=obj).last()
if junior_article_obj and junior_article_obj.submitted_answer:
return junior_article_obj.submitted_answer.id
return None
@ -286,6 +319,7 @@ class ArticleQuestionSerializer(serializers.ModelSerializer):
model = ArticleSurvey
fields = ('id', 'question', 'options', 'points', 'is_attempt', 'correct_answer', 'attempted_answer')
class StartAssessmentSerializer(serializers.ModelSerializer):
"""
serializer for article API
@ -297,9 +331,11 @@ class StartAssessmentSerializer(serializers.ModelSerializer):
"""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
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
@ -308,7 +344,6 @@ class StartAssessmentSerializer(serializers.ModelSerializer):
fields = ('article_survey', 'current_page')
class ArticleCardlistSerializer(serializers.ModelSerializer):
"""
Article Card serializer

View File

@ -14,7 +14,7 @@ from account.models import UserEmailOtp
from base.constants import USER_TYPE
from base.messages import ERROR_CODE
from guardian.tasks import generate_otp
from base.tasks import send_mail
from base.tasks import send_email
USER = get_user_model()
@ -54,7 +54,7 @@ class AdminOTPSerializer(serializers.ModelSerializer):
data = {
"verification_code": verification_code
}
send_mail.delay([email], template, data)
send_email.delay([email], template, data)
expiry = timezone.now() + timezone.timedelta(days=1)
user_data, created = UserEmailOtp.objects.update_or_create(email=email,

View File

@ -1,6 +0,0 @@
"""
web_admin test file
"""
from django.test import TestCase
# Create your tests here.

View File

View File

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

View File

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

View File

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

View File

@ -0,0 +1,407 @@
"""
web_admin test set up file
"""
# django imports
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.conf import settings
from rest_framework.test import APITestCase
from rest_framework.test import APIClient
# local imports
from guardian.models import Guardian, JuniorTask
from junior.models import Junior, JuniorPoints
from web_admin.models import Article, ArticleCard, ArticleSurvey, SurveyOption
# user model
User = get_user_model()
# image data in base 64 string
base64_image = ("data:image/jpeg;base64,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")
# export excel path and
# export excel url
export_excel_path = 'analytics/ZOD_Bank_Analytics.xlsx'
export_excel_url = f"https://{settings.ALIYUN_OSS_BUCKET_NAME}.{settings.ALIYUN_OSS_ENDPOINT}/{export_excel_path}"
class BaseSetUp(APITestCase):
"""
basic setup
"""
def setUp(self) -> None:
"""
user data
:return:
"""
# user and admin email
self.user_email = 'user@example.com'
self.admin_email = 'admin@example.com'
self.client = APIClient()
# create user
self.user = User.objects.create_user(username=self.user_email, email=self.user_email)
self.user.set_password('user@1234')
self.user.save()
# create admin
self.admin_user = User.objects.create_user(username=self.admin_email, email=self.admin_email,
is_staff=True, is_superuser=True)
self.admin_user.set_password('admin@1234')
self.admin_user.save()
class ArticleTestSetUp(BaseSetUp):
"""
test cases data set up
for article create, update, list, retrieve and
remove card, survey and add test card, list test card and
default image upload and list
"""
def setUp(self):
"""
set up data for test
create user and admin
create article, article card and article survey and survey options
:return:
"""
super(ArticleTestSetUp, self).setUp()
# create article
self.article = Article.objects.create(title="Existing Article", description="Existing Description",
is_published=True)
# create article card
self.article_card = ArticleCard.objects.create(article=self.article, title="Existing Card 1",
description="Existing Card 1 Description")
# create article survey
self.article_survey = ArticleSurvey.objects.create(article=self.article, points=5,
question="Existing Survey Question 1")
# create article survey options
SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 1", is_answer=True)
SurveyOption.objects.create(survey=self.article_survey, option="Existing Option 2", is_answer=False)
# article api url used for get api
self.article_list_url = 'web_admin:article-list'
# article api url used for post api
self.article_detail_url = 'web_admin:article-detail'
# article card data with default card image
self.article_card_data_with_default_card_image = {
"title": "Card 1",
"description": "Card 1 Description",
"image_name": "card1.jpg",
"image_url": "https://example.com/card1.jpg"
}
# article card data with base64 image
self.article_card_data_with_base64_image = {
"title": "Card base64",
"description": "Card base64 Description",
"image_name": "base64_image.jpg",
"image_url": base64_image
}
# article survey option data
self.article_survey_option_data = [
{"option": "Option 1", "is_answer": True},
{"option": "Option 2", "is_answer": False}
]
# article survey data
self.article_survey_data = [
{
"question": "Survey Question 1",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 2",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 3",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 4",
"options": self.article_survey_option_data
},
{
"question": "Survey Question 5",
"options": self.article_survey_option_data
},
]
# article data with default card image
self.article_data_with_default_card_image = {
"title": "Test Article",
"description": "Test Description",
"article_cards": [
self.article_card_data_with_default_card_image
],
# minimum 5 article survey needed
"article_survey": self.article_survey_data
}
# article data with base64 card image
self.article_data_with_base64_card_image = {
"title": "Test Article",
"description": "Test Description",
"article_cards": [
self.article_card_data_with_base64_image
],
# minimum 5 article survey needed
"article_survey": self.article_survey_data
}
# article update data
self.article_update_data = {
"title": "Updated Article",
"description": "Updated Description",
# updated article card
"article_cards": [
{
"id": self.article_card.id,
"title": "Updated Card 1",
"description": "Updated Card 1 Description",
"image_name": "updated_card1.jpg",
"image_url": "https://example.com/updated_card1.jpg"
}
],
# updated article survey
"article_survey": [
# updated article survey
{
"id": self.article_survey.id,
"question": "Updated Survey Question 1",
"options": [
{"id": self.article_survey.options.first().id,
"option": "Updated Option 1", "is_answer": False},
# New option
{"option": "New Option 3", "is_answer": True}
]
# added new articles
}] + self.article_survey_data
}
class UserManagementSetUp(BaseSetUp):
"""
test cases for user management
users count, new sign-ups,
"""
def setUp(self) -> None:
"""
data setup
create new guardian and junior
:return:
"""
super(UserManagementSetUp, self).setUp()
# guardian codes
self.guardian_code_1 = 'GRD123'
self.guardian_code_2 = 'GRD456'
# guardian 1
self.guardian = Guardian.objects.create(user=self.user, country_code=91, phone='8765876565',
country_name='India', gender=2, is_verified=True,
guardian_code=self.guardian_code_1)
# user 2 email
self.user_email_2 = 'user2@yopmail.com'
# create user 2
self.user_2 = User.objects.create_user(username=self.user_email_2, email=self.user_email_2)
self.user_2.set_password('user2@1234')
self.user_2.save()
# guardian 2
self.guardian_2 = Guardian.objects.create(user=self.user_2, country_code=92, phone='8765876575',
country_name='India', gender=1, is_verified=True,
guardian_code=self.guardian_code_2)
# user 3 email
self.user_email_3 = 'user3@yopmail.com'
# create user 3
self.user_3 = User.objects.create_user(username=self.user_email_3, email=self.user_email_3)
self.user_3.set_password('user3@1234')
self.user_3.save()
# junior 1
self.junior = Junior.objects.create(auth=self.user_3, country_name='India', gender=2,
is_verified=True, guardian_code=[self.guardian_code_1])
# user 4 email
self.user_email_4 = 'user4@yopmail.com'
# create user 4
self.user_4 = User.objects.create_user(username=self.user_email_4, email=self.user_email_4)
self.user_4.set_password('user4@1234')
self.user_4.save()
# junior 2
self.junior_2 = Junior.objects.create(auth=self.user_4, country_code=92, phone='8768763443',
country_name='India', gender=1, is_verified=True,
guardian_code=[self.guardian_code_2])
class AnalyticsSetUp(UserManagementSetUp):
"""
test analytics
task assign report, junior leaderboard
"""
def setUp(self) -> None:
"""
test data set up
create task and assigned to junior
create junior points data
:return:
"""
super(AnalyticsSetUp, self).setUp()
# pending tasks 1
self.pending_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Pending Task 1', task_status=1,
due_date='2023-09-12')
# pending tasks 2
self.pending_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Pending Task 2', task_status=1,
due_date='2023-09-12')
# in progress tasks 1
self.in_progress_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='In progress Task 1', task_status=2,
due_date='2023-09-12')
# in progress tasks 2
self.in_progress_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='In progress Task 2', task_status=2,
due_date='2023-09-12')
# rejected tasks 1
self.rejected_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Rejected Task 1', task_status=3,
due_date='2023-09-12')
# rejected tasks 2
self.rejected_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Rejected Task 2', task_status=3,
due_date='2023-09-12')
# requested task 1
self.requested_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Requested Task 1', task_status=4,
due_date='2023-09-12')
# requested task 2
self.requested_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Requested Task 2', task_status=4,
due_date='2023-09-12')
# completed task 1
self.completed_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Completed Task 1', task_status=5,
due_date='2023-09-12')
# completed task 2
self.completed_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Completed Task 2', task_status=5,
due_date='2023-09-12')
# expired task 1
self.expired_task_1 = JuniorTask.objects.create(guardian=self.guardian, junior=self.junior,
task_name='Expired Task 1', task_status=6,
due_date='2023-09-11')
# expired task 2
self.expired_task_2 = JuniorTask.objects.create(guardian=self.guardian_2, junior=self.junior_2,
task_name='Expired Task 2', task_status=6,
due_date='2023-09-11')
# junior point table data
JuniorPoints.objects.create(junior=self.junior_2, total_points=50)
JuniorPoints.objects.create(junior=self.junior, total_points=40)
# export excel url
self.export_excel_url = export_excel_url

View File

@ -0,0 +1,255 @@
"""
web admin test user management file
"""
# django imports
from django.contrib.auth import get_user_model
from rest_framework import status
# local imports
from base.constants import GUARDIAN, JUNIOR
from web_admin.tests.test_set_up import UserManagementSetUp
# user model
User = get_user_model()
class UserManagementViewSetTestCase(UserManagementSetUp):
"""
test cases for user management
"""
def setUp(self) -> None:
super(UserManagementViewSetTestCase, self).setUp()
self.update_data = {
'email': 'user5@yopmail.com',
'country_code': 93,
'phone': '8765454235'
}
self.user_management_endpoint = "/api/v1/user-management"
def test_user_management_list_all_users(self):
"""
test user management list all users
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/"
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming four user exists in the database
self.assertEqual(len(response.data['data']), 4)
def test_user_management_list_guardians(self):
"""
test user management list guardians
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/?user_type={GUARDIAN}"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming two guardians exists in the database
self.assertEqual(len(response.data['data']), 2)
def test_user_management_list_juniors(self):
"""
test user management list juniors
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/?user_type={JUNIOR}"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Assuming two juniors exists in the database
self.assertEqual(len(response.data['data']), 2)
def test_user_management_list_with_unauthorised_user(self):
"""
test user management list with unauthorised user
:return:
"""
# user unauthorised access
self.client.force_authenticate(user=self.user)
url = f"{self.user_management_endpoint}/"
response = self.client.get(url, format='json')
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
def test_user_management_retrieve_guardian(self):
"""
test user management retrieve guardian
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['data']), 1)
def test_user_management_retrieve_junior(self):
"""
test user management retrieve junior
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['data']), 1)
def test_user_management_retrieve_without_user_type(self):
"""
test user management retrieve without user type
user status is mandatory
API will throw error
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user.id}/"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_user_management_update_guardian(self):
"""
test user management update guardian
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}"
response = self.client.patch(url, self.update_data, format='json',)
self.user.refresh_from_db()
self.guardian.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.user.email, self.update_data['email'])
self.assertEqual(self.guardian.country_code, self.update_data['country_code'])
self.assertEqual(self.guardian.phone, self.update_data['phone'])
def test_user_management_update_guardian_with_existing_email(self):
"""
test user management update guardian with existing email
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}"
data = {
'email': self.user_email_2
}
response = self.client.patch(url, data, format='json',)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_user_management_update_guardian_with_existing_phone(self):
"""
test user management update guardian with existing phone
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user.id}/?user_type={GUARDIAN}"
data = {
'phone': self.guardian_2.phone
}
response = self.client.patch(url, data, format='json',)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_user_management_update_junior(self):
"""
test user management update junior
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}"
response = self.client.patch(url, self.update_data, format='json',)
self.user_3.refresh_from_db()
self.junior.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.user_3.email, self.update_data['email'])
self.assertEqual(self.junior.country_code, self.update_data['country_code'])
self.assertEqual(self.junior.phone, self.update_data['phone'])
def test_user_management_update_junior_with_existing_email(self):
"""
test user management update guardian with existing phone
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}"
data = {
'email': self.user_email_4
}
response = self.client.patch(url, data, format='json',)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_user_management_update_junior_with_existing_phone(self):
"""
test user management update junior with existing phone
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user_3.id}/?user_type={JUNIOR}"
data = {
'phone': self.junior_2.phone
}
response = self.client.patch(url, data, format='json',)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_user_management_update_without_user_type(self):
"""
test user management update without user type
user status is mandatory
API will throw error
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user_3.id}/"
response = self.client.patch(url, self.update_data, format='json',)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
def test_user_management_change_status_guardian(self):
"""
test user management change status guardian
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user.id}/change-status/?user_type={GUARDIAN}"
response = self.client.get(url)
self.guardian.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.guardian.is_active, False)
def test_user_management_change_status_junior(self):
"""
test user management change status junior
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user_3.id}/change-status/?user_type={JUNIOR}"
response = self.client.get(url)
self.junior.refresh_from_db()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(self.junior.is_active, False)
def test_user_management_change_status_without_user_type(self):
"""
test user management change status without user type
user status is mandatory
API will throw error
:return:
"""
# admin user authentication
self.client.force_authenticate(user=self.admin_user)
url = f"{self.user_management_endpoint}/{self.user.id}/"
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

View File

@ -18,7 +18,7 @@ router = routers.SimpleRouter()
router.register('article', ArticleViewSet, basename='article')
router.register('default-card-images', DefaultArticleCardImagesViewSet, basename='default-card-images')
router.register('user-management', UserManagementViewSet, basename='user')
router.register('analytics', AnalyticsViewSet, basename='user-analytics')
router.register('analytics', AnalyticsViewSet, basename='analytics')
router.register('article-list', ArticleListViewSet, basename='article-list')
router.register('article-card-list', ArticleCardListViewSet, basename='article-card-list')

View File

@ -23,11 +23,11 @@ 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, DATE_FORMAT, TASK_STATUS
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 web_admin.pagination import CustomPageNumberPagination
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
@ -128,10 +128,12 @@ class AnalyticsViewSet(GenericViewSet):
:param request:
:return:
"""
queryset = JuniorPoints.objects.prefetch_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')
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)
@ -199,10 +201,12 @@ class AnalyticsViewSet(GenericViewSet):
# sheet 3 for Juniors Leaderboard and rank
elif sheet_name == 'Juniors Leaderboard':
queryset = JuniorPoints.objects.prefetch_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]
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),

View File

@ -17,7 +17,7 @@ from web_admin.models import Article, ArticleCard, ArticleSurvey, DefaultArticle
from web_admin.permission import AdminPermission
from web_admin.serializers.article_serializer import (ArticleSerializer, ArticleCardSerializer,
DefaultArticleCardImageSerializer, ArticleListSerializer,
ArticleCardlistSerializer)
ArticleCardlistSerializer, ArticleStatusChangeSerializer)
USER = get_user_model()
@ -32,7 +32,6 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
queryset = Article
filter_backends = (SearchFilter,)
search_fields = ['title']
http_method_names = ['get', 'post', 'put', 'delete']
def get_queryset(self):
article = self.queryset.objects.filter(is_deleted=False).prefetch_related(
@ -130,6 +129,23 @@ class ArticleViewSet(GenericViewSet, mixins.CreateModelMixin, mixins.UpdateModel
return custom_response(SUCCESS_CODE["3029"])
return custom_error_response(ERROR_CODE["2041"], status.HTTP_400_BAD_REQUEST)
@action(methods=['patch'], url_name='status-change', url_path='status-change',
detail=True, serializer_class=ArticleStatusChangeSerializer)
def article_status_change(self, request, *args, **kwargs):
"""
article un-publish or publish api method
:param request: article id and
{
"is_published": true/false
}
:return: success message
"""
article = Article.objects.filter(id=kwargs['pk']).first()
serializer = self.serializer_class(article, data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return custom_response(SUCCESS_CODE["3038"])
@action(methods=['get'], url_name='remove-card', url_path='remove-card',
detail=True)
def remove_article_card(self, request, *args, **kwargs):
@ -214,7 +230,7 @@ class DefaultArticleCardImagesViewSet(GenericViewSet, mixins.CreateModelMixin, m
:param request:
:return: default article card images
"""
queryset = self.queryset
queryset = self.get_queryset()
serializer = self.serializer_class(queryset, many=True)
return custom_response(None, data=serializer.data)
@ -229,10 +245,7 @@ class ArticleListViewSet(GenericViewSet, mixins.ListModelMixin):
http_method_names = ['get',]
def get_queryset(self):
article = self.queryset.objects.filter(is_deleted=False, is_published=True).prefetch_related(
'article_cards', 'article_survey', 'article_survey__options'
).order_by('-created_at')
queryset = self.filter_queryset(article)
queryset = self.queryset.objects.filter(is_deleted=False, is_published=True).order_by('-created_at')
return queryset
def list(self, request, *args, **kwargs):

View File

@ -4,7 +4,7 @@ web_admin auth views file
# django imports
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action
from rest_framework import status
from rest_framework import status
from django.contrib.auth import get_user_model
# local imports

View File

@ -12,8 +12,9 @@ from django.db.models import Q
# local imports
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.tasks import send_email
from guardian.models import Guardian
from junior.models import Junior
from web_admin.permission import AdminPermission
@ -92,12 +93,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')]:
return custom_error_response(ERROR_CODE['2067'], status.HTTP_400_BAD_REQUEST)
if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'):
guardian = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True).first()
guardian = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True
).select_related('user').first()
serializer = GuardianSerializer(guardian,
request.data, context={'user_id': kwargs['pk']})
elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'):
junior = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True).select_related('auth').first()
junior = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True
).select_related('auth').first()
serializer = JuniorSerializer(junior,
request.data, context={'user_id': kwargs['pk']})
@ -113,17 +116,21 @@ class UserManagementViewSet(GenericViewSet, mixins.ListModelMixin,
user_type {'guardian' for Guardian, 'junior' for Junior} mandatory
: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)
queryset = self.queryset
if self.request.query_params.get('user_type') == dict(USER_TYPE).get('2'):
user_obj = queryset.filter(id=kwargs['pk']).first()
obj = user_obj.guardian_profile.all().first()
obj.is_active = False if obj.is_active else True
obj.save()
elif self.request.query_params.get('user_type') == dict(USER_TYPE).get('1'):
user_obj = queryset.filter(id=kwargs['pk']).first()
obj = user_obj.junior_profile.all().first()
obj.is_active = False if obj.is_active else True
obj.save()
email_template = 'user_deactivate.email'
if user_type == GUARDIAN:
obj = Guardian.objects.filter(user_id=kwargs['pk'], is_verified=True).select_related('user').first()
elif user_type == JUNIOR:
obj = Junior.objects.filter(auth_id=kwargs['pk'], is_verified=True).select_related('auth').first()
if obj.is_active:
obj.is_active = False
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'])

View File

@ -27,10 +27,19 @@ app.config_from_object('django.conf:settings')
# Load task modules from all registered Django apps.
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='*/1'),
'schedule': crontab(minute='0', hour='18'),
},
'notify_top_junior': {
'task': 'base.tasks.notify_top_junior',
'schedule': crontab(minute='0', hour='*/2'),
},
}
@ -39,14 +48,3 @@ app.conf.beat_schedule = {
def debug_task(self):
""" celery debug task """
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

@ -35,9 +35,28 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = os.getenv('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG')
ENV = os.getenv('ENV')
# cors allow setting
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_ALLOW_ALL = False
# Allow specific origins
if ENV in ['dev', 'qa', 'stage']:
CORS_ALLOWED_ORIGINS = [
# backend base url
"https://dev-api.zodqaapp.com",
"https://qa-api.zodqaapp.com",
"https://stage-api.zodqaapp.com",
# frontend url
"http://localhost:3000",
"https://zod-dev.zodqaapp.com",
"https://zod-qa.zodqaapp.com",
"https://zod-stage.zodqaapp.com",
# Add more trusted origins as needed
]
if ENV == "prod":
CORS_ALLOWED_ORIGINS = []
# allow all host
ALLOWED_HOSTS = ['*']
@ -53,7 +72,7 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# Add Django rest frame work apps here
# Add Django rest framework apps here
'django_extensions',
'storages',
'drf_yasg',

View File

@ -20,12 +20,11 @@ from django.urls import path, include
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
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, )
urlpatterns = [
path('apidoc/', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
path('admin/', admin.site.urls),
path('', include(('account.urls', 'account'), namespace='account')),
path('', include('guardian.urls')),
@ -33,3 +32,6 @@ urlpatterns = [
path('', include(('notifications.urls', 'notifications'), namespace='notifications')),
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'))]