diff --git a/account/custom_middleware.py b/account/custom_middleware.py index b3cc750..705cf49 100644 --- a/account/custom_middleware.py +++ b/account/custom_middleware.py @@ -45,11 +45,12 @@ 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 if device_id: device_details = UserDeviceDetails.objects.filter(user=request.user, device_id=device_id).last() - if not device_details and api_endpoint != '/api/v1/user/login/': + 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']): diff --git a/account/templates/templated_email/support_mail.email b/account/templates/templated_email/support_mail.email index 34d6156..20fcdb1 100644 --- a/account/templates/templated_email/support_mail.email +++ b/account/templates/templated_email/support_mail.email @@ -1,7 +1,7 @@ {% extends "templated_email/email_base.email" %} {% block subject %} - {{subject}} + Support Mail {% endblock %} {% block plain %} diff --git a/account/utils.py b/account/utils.py index 60a5c44..9122477 100644 --- a/account/utils.py +++ b/account/utils.py @@ -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 @@ -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,36 @@ 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): + # Define character sets + lowercase_letters = string.ascii_lowercase + uppercase_letters = string.ascii_uppercase + digits = string.digits + special_characters = '!@#$%^&*()_-+=<>?/[]{}|' + + # Combine character sets + all_characters = lowercase_letters + uppercase_letters + digits + special_characters + + # Create a password with random characters + password = ( + random.choice(lowercase_letters) + + random.choice(uppercase_letters) + + random.choice(digits) + + random.choice(special_characters) + + ''.join(random.choice(all_characters) for _ in range(length - 4)) + ) + + # Shuffle the characters to make it more random + password_list = list(password) + random.shuffle(password_list) + return ''.join(password_list) + +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 diff --git a/account/views.py b/account/views.py index ff0a6e6..a76693e 100644 --- a/account/views.py +++ b/account/views.py @@ -689,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) diff --git a/base/messages.py b/base/messages.py index 54c427f..0af15a1 100644 --- a/base/messages.py +++ b/base/messages.py @@ -111,7 +111,10 @@ ERROR_CODE = { "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" + "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""" @@ -147,8 +150,8 @@ SUCCESS_CODE = { "3018": "Task created successfully", "3019": "Support Email sent successfully", "3020": "Logged out successfully.", - "3021": "Add junior successfully", - "3022": "Remove junior successfully", + "3021": "Added junior successfully", + "3022": "Removed junior successfully", "3023": "Junior is approved successfully", "3024": "Junior request is rejected successfully", "3025": "Task is approved successfully", diff --git a/base/pagination.py b/base/pagination.py new file mode 100644 index 0000000..e060447 --- /dev/null +++ b/base/pagination.py @@ -0,0 +1,46 @@ +""" +web_admin pagination file +""" +# third party imports +from collections import OrderedDict +from rest_framework.pagination import PageNumberPagination + +from account.utils import custom_response +from base.constants import NUMBER + + +class CustomPageNumberPagination(PageNumberPagination): + """ + custom paginator class + """ + # Set the desired page size + page_size = NUMBER['ten'] + page_size_query_param = 'page_size' + # Set a maximum page size if needed + max_page_size = NUMBER['hundred'] + + def get_paginated_response(self, data): + """ + :param data: queryset to be paginated + :return: return a OrderedDict + """ + return custom_response(None, OrderedDict([ + ('count', self.page.paginator.count), + ('data', data), + ('current_page', self.page.number), + ('total_pages', self.page.paginator.num_pages), + + + ])) + + def get_paginated_dict_response(self, data): + """ + :param data: queryset to be paginated + :return: return a simple dict obj + """ + return { + 'count': self.page.paginator.count, + 'data': data, + 'current_page': self.page.number, + 'total_pages': self.page.paginator.num_pages, + } diff --git a/celerybeat-schedule b/celerybeat-schedule index f457bb5..be892ed 100644 Binary files a/celerybeat-schedule and b/celerybeat-schedule differ diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml new file mode 100644 index 0000000..436833b --- /dev/null +++ b/docker-compose-prod.yml @@ -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 diff --git a/docker-compose-qa.yml b/docker-compose-qa.yml new file mode 100644 index 0000000..6e3f4d5 --- /dev/null +++ b/docker-compose-qa.yml @@ -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 diff --git a/docker-compose-stage.yml b/docker-compose-stage.yml new file mode 100644 index 0000000..39db221 --- /dev/null +++ b/docker-compose-stage.yml @@ -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 diff --git a/guardian/serializers.py b/guardian/serializers.py index 4206d7a..f78c237 100644 --- a/guardian/serializers.py +++ b/guardian/serializers.py @@ -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 @@ -218,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""" @@ -229,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): diff --git a/guardian/views.py b/guardian/views.py index e120681..4a8a804 100644 --- a/guardian/views.py +++ b/guardian/views.py @@ -1,4 +1,5 @@ """Views of Guardian""" +import math # django imports # Import IsAuthenticated @@ -16,6 +17,7 @@ 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, @@ -36,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 @@ -135,7 +137,8 @@ class TaskListAPIView(viewsets.ModelViewSet): Params status search - page""" + page + junior""" serializer_class = TaskDetailsSerializer permission_classes = [IsAuthenticated] filter_backends = (SearchFilter,) @@ -145,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 @@ -154,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): @@ -177,45 +184,48 @@ class CreateTaskAPIView(viewsets.ModelViewSet): """ try: image = request.data['default_image'] - junior = request.data['junior'] - junior_id = Junior.objects.filter(id=junior).last() - if junior_id: - guardian_data = Guardian.objects.filter(user=request.user).last() - index = junior_id.guardian_code.index(guardian_data.guardian_code) - status_index = junior_id.guardian_code_status[index] - if status_index == str(NUMBER['three']): - return custom_error_response(ERROR_CODE['2078'], response_status=status.HTTP_400_BAD_REQUEST) - allowed_extensions = ['.jpg', '.jpeg', '.png'] - if not any(extension in str(image) for extension in allowed_extensions): - return custom_error_response(ERROR_CODE['2048'], response_status=status.HTTP_400_BAD_REQUEST) - if not junior.isnumeric(): - """junior value must be integer""" - return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) - data = request.data - if 'https' in str(image): - image_data = image - else: - filename = f"images/{image}" - if image and image.size == NUMBER['zero']: - return custom_error_response(ERROR_CODE['2035'], response_status=status.HTTP_400_BAD_REQUEST) - image_url = upload_image_to_alibaba(image, filename) - image_data = image_url - data.pop('default_image') - # use TaskSerializer serializer - serializer = TaskSerializer(context={"user":request.user, "image":image_data}, data=data) - if serializer.is_valid(): - # save serializer - task = serializer.save() - - send_notification.delay(TASK_ASSIGNED, request.auth.payload['user_id'], GUARDIAN, - junior_id.auth.id, {'task_id': task.id}) - return custom_response(SUCCESS_CODE['3018'], serializer.data, response_status=status.HTTP_200_OK) - return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) + junior_ids = request.data['junior'].split(',') + # if not junior.isnumeric(): + # """junior value must be integer""" + # return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) + allowed_extensions = ['.jpg', '.jpeg', '.png'] + if not any(extension in str(image) for extension in allowed_extensions): + return custom_error_response(ERROR_CODE['2048'], response_status=status.HTTP_400_BAD_REQUEST) + if 'https' in str(image): + image_data = image else: - return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) + filename = f"images/{image}" + if image and image.size == NUMBER['zero']: + return custom_error_response(ERROR_CODE['2035'], + response_status=status.HTTP_400_BAD_REQUEST) + image_data = upload_image_to_alibaba(image, filename) + request.data.pop('default_image') + + guardian = Guardian.objects.filter(user=request.user).select_related('user').last() + junior_data = Junior.objects.filter(id__in=junior_ids).select_related('auth') + + for junior in junior_data: + if junior: + 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) + else: + return custom_error_response(ERROR_CODE['2047'], response_status=status.HTTP_400_BAD_REQUEST) + + # use TaskSerializer serializer + serializer = TaskSerializer(context={"guardian": guardian, "image": image_data, + "junior_data": junior_data}, data=request.data) + if serializer.is_valid(): + # save serializer + serializer.save() + # removed send notification method and used in serializer + return custom_response(SUCCESS_CODE['3018'], response_status=status.HTTP_200_OK) + return custom_error_response(serializer.errors, response_status=status.HTTP_400_BAD_REQUEST) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) + class SearchTaskListAPIView(viewsets.ModelViewSet): """Filter task""" serializer_class = TaskDetailsSerializer @@ -344,6 +354,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 diff --git a/junior/serializers.py b/junior/serializers.py index de5f671..aba9b12 100644 --- a/junior/serializers.py +++ b/junior/serializers.py @@ -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 @@ -297,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, @@ -321,7 +321,7 @@ 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(ASSOCIATE_JUNIOR, None, None, junior_data.auth.id, {}) return junior_data diff --git a/junior/views.py b/junior/views.py index 3e9eeed..fb4771d 100644 --- a/junior/views.py +++ b/junior/views.py @@ -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 @@ -43,7 +47,7 @@ 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, GUARDIAN -from account.utils import custom_response, custom_error_response +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 @@ -353,8 +357,8 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): def get_queryset(self): queryset = JuniorTask.objects.filter(junior__auth=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 @@ -367,14 +371,15 @@ class JuniorTaskListAPIView(viewsets.ModelViewSet): try: status_value = self.request.GET.get('status') 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) + paginator = CustomPageNumberPagination() # use Pagination paginated_queryset = paginator.paginate_queryset(queryset, request) # use TaskDetails juniorSerializer 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) except Exception as e: return custom_error_response(str(e), response_status=status.HTTP_400_BAD_REQUEST) @@ -403,10 +408,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) @@ -511,7 +518,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(): diff --git a/notifications/constants.py b/notifications/constants.py index 85b0d2d..c22f246 100644 --- a/notifications/constants.py +++ b/notifications/constants.py @@ -22,7 +22,7 @@ ARTICLE_REWARD_POINTS = 17 REMOVE_JUNIOR = 18 TEST_NOTIFICATION = 99 - +# notification dictionary NOTIFICATION_DICT = { REGISTRATION: { "title": "Successfully registered!", diff --git a/notifications/serializers.py b/notifications/serializers.py index 2f0222f..e4fdb05 100644 --- a/notifications/serializers.py +++ b/notifications/serializers.py @@ -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): diff --git a/notifications/views.py b/notifications/views.py index 812502e..c3a6751 100644 --- a/notifications/views.py +++ b/notifications/views.py @@ -11,6 +11,7 @@ from rest_framework import viewsets, status, views # local imports from account.utils import custom_response, custom_error_response from base.messages import SUCCESS_CODE, ERROR_CODE +from base.pagination import CustomPageNumberPagination from base.tasks import notify_task_expiry, notify_top_junior from notifications.constants import TEST_NOTIFICATION from notifications.serializers import RegisterDevice, NotificationListSerializer, ReadNotificationSerializer @@ -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, count=queryset.count()) + 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): diff --git a/web_admin/pagination.py b/web_admin/pagination.py deleted file mode 100644 index c2eed5e..0000000 --- a/web_admin/pagination.py +++ /dev/null @@ -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'] diff --git a/web_admin/serializers/article_serializer.py b/web_admin/serializers/article_serializer.py index 7e94e9b..d030d41 100644 --- a/web_admin/serializers/article_serializer.py +++ b/web_admin/serializers/article_serializer.py @@ -81,7 +81,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): """ diff --git a/web_admin/views/analytics.py b/web_admin/views/analytics.py index 926cd47..b4f228f 100644 --- a/web_admin/views/analytics.py +++ b/web_admin/views/analytics.py @@ -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 diff --git a/web_admin/views/article.py b/web_admin/views/article.py index 902f579..ab55d16 100644 --- a/web_admin/views/article.py +++ b/web_admin/views/article.py @@ -130,6 +130,22 @@ 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=['get'], url_name='status-change', url_path='status-change', + detail=True) + def article_status_change(self, request, *args, **kwargs): + """ + article un-publish or publish api method + :param request: article id + :return: success message + """ + try: + article = Article.objects.filter(id=kwargs['pk']).first() + article.is_published = False if article.is_published else True + article.save(update_fields=['is_published']) + return custom_response(SUCCESS_CODE["3038"]) + except AttributeError: + return custom_error_response(ERROR_CODE["2041"], response_status=status.HTTP_400_BAD_REQUEST) + @action(methods=['get'], url_name='remove-card', url_path='remove-card', 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) diff --git a/zod_bank/settings.py b/zod_bank/settings.py index 781df80..cde1918 100644 --- a/zod_bank/settings.py +++ b/zod_bank/settings.py @@ -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', diff --git a/zod_bank/urls.py b/zod_bank/urls.py index a34ef93..ba78b8f 100644 --- a/zod_bank/urls.py +++ b/zod_bank/urls.py @@ -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'))]