diff --git a/codedigger/atcoder/scrapers.py b/codedigger/atcoder/scrapers.py index f69fba8..8e89bf7 100644 --- a/codedigger/atcoder/scrapers.py +++ b/codedigger/atcoder/scrapers.py @@ -1,5 +1,5 @@ import requests -from user.exception import ValidationException +from utils.exception import ValidationException BASEURL = "https://atcoder.jp" diff --git a/codedigger/atcoder/views.py b/codedigger/atcoder/views.py index 3ee0f5d..59a2918 100644 --- a/codedigger/atcoder/views.py +++ b/codedigger/atcoder/views.py @@ -8,8 +8,8 @@ # Serializer and Extra Utils Function from .serializers import AtcoderUpsolveContestSerializer from problem.utils import atcoder_status, get_page_number, get_upsolve_response_dict -from user.permissions import * -from user.exception import ValidationException +from utils.permissions import * +from utils.exception import ValidationException from lists.utils import get_total_page, getqs from .scrapers import get_user_profile # Create your views here. diff --git a/codedigger/blog/views.py b/codedigger/blog/views.py index 7a71c85..9e93dd3 100644 --- a/codedigger/blog/views.py +++ b/codedigger/blog/views.py @@ -7,7 +7,7 @@ from .models import Blog from .serializers import BlogSerializer, ABlogSerializer -from user.permissions import * +from utils.permissions import * class BlogAPIView(mixins.CreateModelMixin, generics.ListAPIView): diff --git a/codedigger/codechef/scraper.py b/codedigger/codechef/scraper.py index baf087f..60ede62 100644 --- a/codedigger/codechef/scraper.py +++ b/codedigger/codechef/scraper.py @@ -1,7 +1,7 @@ import requests from bs4 import BeautifulSoup -from user.exception import ValidationException +from utils.exception import ValidationException from problem.models import * from codechef.models import * diff --git a/codedigger/codechef/views.py b/codedigger/codechef/views.py index 01e2669..d4db4f4 100644 --- a/codedigger/codechef/views.py +++ b/codedigger/codechef/views.py @@ -2,7 +2,7 @@ from rest_framework.response import Response from rest_framework import generics, serializers -from user.exception import ValidationException +from utils.exception import ValidationException from .models import CodechefContest from .serializers import CodechefUpsolveSerializer from .scraper_utils import contestgivenScrapper, problems_solved diff --git a/codedigger/codeforces/api.py b/codedigger/codeforces/api.py index c5b048f..8fc4b6d 100644 --- a/codedigger/codeforces/api.py +++ b/codedigger/codeforces/api.py @@ -2,7 +2,7 @@ import requests # User App -from user.exception import ValidationException +from utils.exception import ValidationException def validated_response(response): diff --git a/codedigger/codeforces/cron.py b/codedigger/codeforces/cron.py index 8aceca5..f94dbf9 100644 --- a/codedigger/codeforces/cron.py +++ b/codedigger/codeforces/cron.py @@ -1,4 +1,4 @@ -from user.exception import ValidationException +from utils.exception import ValidationException from .api import (contest_list, contest_ratingChanges, contest_standings, user_ratedList) diff --git a/codedigger/codeforces/models_utils.py b/codedigger/codeforces/models_utils.py index 588a696..cc191f4 100644 --- a/codedigger/codeforces/models_utils.py +++ b/codedigger/codeforces/models_utils.py @@ -13,7 +13,7 @@ from problem.models import Problem # User App Import -from user.exception import ValidationException +from utils.exception import ValidationException def create_or_update_user(codeforces_user): diff --git a/codedigger/codeforces/tests/test_api.py b/codedigger/codeforces/tests/test_api.py index 066fdb6..9f8a05c 100644 --- a/codedigger/codeforces/tests/test_api.py +++ b/codedigger/codeforces/tests/test_api.py @@ -1,10 +1,8 @@ -from rest_framework import response from .test_setup import TestSetUp -from user.exception import ValidationException +from utils.exception import ValidationException from codeforces.api import (user_info, user_rating, contest_list, contest_standings, contest_ratingChanges, user_status) -from django.urls import reverse class TestAPI(TestSetUp): diff --git a/codedigger/codeforces/tests/test_views.py b/codedigger/codeforces/tests/test_views.py index ff579d5..eaa68b9 100644 --- a/codedigger/codeforces/tests/test_views.py +++ b/codedigger/codeforces/tests/test_views.py @@ -1,9 +1,4 @@ -from rest_framework import response from .test_setup import TestSetUp -from user.exception import ValidationException -from codeforces.api import (user_info, user_rating, contest_list, - contest_standings, contest_ratingChanges, - user_status) from django.urls import reverse diff --git a/codedigger/codeforces/views.py b/codedigger/codeforces/views.py index bb8b6b1..1fc817e 100644 --- a/codedigger/codeforces/views.py +++ b/codedigger/codeforces/views.py @@ -7,8 +7,8 @@ from rest_framework import generics, mixins -from user.permissions import AuthenticatedActivated, AuthenticatedOrReadOnly -from user.exception import ValidationException +from utils.permissions import AuthenticatedActivated, AuthenticatedOrReadOnly +from utils.exception import ValidationException from user.serializers import GuruSerializer from user.models import Profile diff --git a/codedigger/contest/views.py b/codedigger/contest/views.py index 815ab7d..500c3f5 100644 --- a/codedigger/contest/views.py +++ b/codedigger/contest/views.py @@ -4,8 +4,8 @@ from rest_framework import generics, mixins, status # Exceptions and Permissions -from user.exception import ValidationException -from user.permissions import * +from utils.exception import ValidationException +from utils.permissions import * # Models from user.models import Profile diff --git a/codedigger/lists/cron.py b/codedigger/lists/cron.py index 18da564..50122ee 100644 --- a/codedigger/lists/cron.py +++ b/codedigger/lists/cron.py @@ -11,7 +11,7 @@ from django.core.mail import send_mail from codedigger.settings import EMAIL_HOST_USER from codeforces.api import user_status -from user.exception import ValidationException +from utils.exception import ValidationException def cron_codeforces(user): diff --git a/codedigger/lists/serializers.py b/codedigger/lists/serializers.py index e595c54..cab1c55 100644 --- a/codedigger/lists/serializers.py +++ b/codedigger/lists/serializers.py @@ -7,7 +7,7 @@ from django.db.models import Q, fields from rest_framework.response import Response from .solved_update import * -from user.exception import ValidationException +from utils.exception import ValidationException from django.template.defaultfilters import slugify diff --git a/codedigger/lists/solved_update.py b/codedigger/lists/solved_update.py index c88ae09..9f20530 100644 --- a/codedigger/lists/solved_update.py +++ b/codedigger/lists/solved_update.py @@ -9,7 +9,7 @@ from user.models import Profile, User from problem.models import Problem from codeforces.api import user_status -from user.exception import ValidationException +from utils.exception import ValidationException def codechef(user, prob): diff --git a/codedigger/lists/tests/test_lists.py b/codedigger/lists/tests/test_lists.py index 2d00c67..0188552 100644 --- a/codedigger/lists/tests/test_lists.py +++ b/codedigger/lists/tests/test_lists.py @@ -1,6 +1,6 @@ from django.test import client -from user.exception import ValidationException +from utils.exception import ValidationException from .test_setup import TestSetUp from user.models import User, Profile from django.urls import reverse diff --git a/codedigger/lists/views.py b/codedigger/lists/views.py index 22b2ae9..1f70601 100644 --- a/codedigger/lists/views.py +++ b/codedigger/lists/views.py @@ -9,8 +9,8 @@ UserlistAddSerializer, AddProblemsAdminSerializer, EnrollInListSerializer) from django.db.models import Q, Subquery, Count -from user.permissions import * -from user.exception import * +from utils.permissions import * +from utils.exception import * from .utils import * from user.models import User, UserFriends diff --git a/codedigger/problem/utils.py b/codedigger/problem/utils.py index ba9afb7..4339127 100644 --- a/codedigger/problem/utils.py +++ b/codedigger/problem/utils.py @@ -7,7 +7,7 @@ from lists.utils import (sub_page_number, get_next_url, get_prev_url, get_total_page, getqs) -from user.exception import ValidationException +from utils.exception import ValidationException from codeforces.api import user_status from .serializers import ProbSerializer diff --git a/codedigger/problem/views.py b/codedigger/problem/views.py index a866384..1e1b485 100644 --- a/codedigger/problem/views.py +++ b/codedigger/problem/views.py @@ -3,8 +3,8 @@ from rest_framework import generics, mixins, status # Exceptions and Permissions -from user.exception import ValidationException -from user.permissions import * +from utils.exception import ValidationException +from utils.permissions import * # Models Stuff from user.models import User, Profile, UserFriends diff --git a/codedigger/user/exception.py b/codedigger/user/exception.py deleted file mode 100644 index e3951fb..0000000 --- a/codedigger/user/exception.py +++ /dev/null @@ -1,35 +0,0 @@ -from rest_framework.exceptions import PermissionDenied -from rest_framework import status - - -class ValidationException(PermissionDenied): - status_code = status.HTTP_400_BAD_REQUEST - default_detail = "Custom Exception Message" - default_code = 'invalid' - - def __init__(self, detail, status_code=None): - self.detail = {'status': "FAILED", 'error': detail} - if status_code is not None: - self.status_code = status_code - - -class AuthenticationException(PermissionDenied): - status_code = status.HTTP_401_UNAUTHORIZED - default_detail = "Custom Exception Message" - default_code = 'invalid' - - def __init__(self, detail, status_code=None): - self.detail = {'status': "FAILED", 'error': detail} - if status_code is not None: - self.status_code = status_code - - -class NotFoundException(PermissionDenied): - status_code = status.HTTP_404_NOT_FOUND - default_detail = "Custom Exception Message" - default_code = 'invalid' - - def __init__(self, detail, status_code=None): - self.detail = {'status': "FAILED", 'error': detail} - if status_code is not None: - self.status_code = status_code diff --git a/codedigger/user/param_validators.py b/codedigger/user/param_validators.py index 97a52db..b1cc88e 100644 --- a/codedigger/user/param_validators.py +++ b/codedigger/user/param_validators.py @@ -1,4 +1,4 @@ -from .exception import ValidationException +from utils.exception import ValidationException from . import validator_functions as validators diff --git a/codedigger/user/permissions.py b/codedigger/user/permissions.py deleted file mode 100644 index 7ee6293..0000000 --- a/codedigger/user/permissions.py +++ /dev/null @@ -1,71 +0,0 @@ -from rest_framework import permissions -from rest_framework.exceptions import APIException -from rest_framework import status -from rest_framework.permissions import BasePermission, SAFE_METHODS -from .models import Profile - - -class AuthenticatedOrReadOnly(BasePermission): - - def has_permission(self, request, view): - if request.user or request.user.is_authenticated or request.method in SAFE_METHODS: - return True - else: - raise Forbidden - - -class ForbiddenAdmin(APIException): - status_code = status.HTTP_403_FORBIDDEN - default_detail = { - 'status': "FAILED", - 'error': 'Only Admins can access this page' - } - - -class AuthenticatedAdmin(permissions.BasePermission): - - def has_permission(self, request, view): - if request.user and request.user.is_authenticated and request.user.is_staff: - return True - else: - raise ForbiddenAdmin - - -class IsOwner(permissions.BasePermission): - - def has_object_permission(self, request, view, obj): - return obj.owner == request.user - - -class Forbidden(APIException): - status_code = status.HTTP_401_UNAUTHORIZED - default_detail = { - 'status': "FAILED", - 'error': 'Authentication credentials were not provided' - } - - -class Authenticated(permissions.BasePermission): - - def has_permission(self, request, view): - if not request.user or not request.user.is_authenticated: - raise Forbidden - else: - return True - - -class ForbiddenActivation(APIException): - status_code = status.HTTP_400_BAD_REQUEST - default_detail = {'status': "FAILED", 'error': 'Account was not activated'} - - -class AuthenticatedActivated(permissions.BasePermission): - - def has_permission(self, request, view): - if request.user and request.user.is_authenticated: - if Profile.objects.get(owner=request.user).codeforces is not None: - return True - else: - raise ForbiddenActivation - else: - raise Forbidden diff --git a/codedigger/user/response.py b/codedigger/user/response.py deleted file mode 100644 index 2391968..0000000 --- a/codedigger/user/response.py +++ /dev/null @@ -1,7 +0,0 @@ -from rest_framework.response import Response -from rest_framework import status - - -def response(Data, Status=status.HTTP_200_OK): - - return Response(data={'status': 'OK', 'result': Data}, status=Status) diff --git a/codedigger/user/serializers.py b/codedigger/user/serializers.py index 3a8824d..42222ce 100644 --- a/codedigger/user/serializers.py +++ b/codedigger/user/serializers.py @@ -12,7 +12,7 @@ from .utils import Util import requests, json from lists.models import Solved -from .exception import * +from utils.exception import * import re @@ -159,14 +159,13 @@ def validate(self, attrs): user = auth.authenticate(username=user_obj_email.username, password=password) if user_obj_email.auth_provider != 'email': - raise AuthenticationException( + raise UnauthorizedException( 'Please continue your login using ' + user_obj_email.auth_provider) if not user: - raise AuthenticationException('Invalid credentials. Try again') + raise UnauthorizedException('Invalid credentials. Try again') if not user.is_active: - raise AuthenticationException( - 'Account disabled. contact admin') + raise UnauthorizedException('Account disabled. contact admin') if not user.is_verified: email = user.email token = RefreshToken.for_user(user).access_token @@ -184,7 +183,7 @@ def validate(self, attrs): 'to_email': user.email } Util.send_email(data) - raise AuthenticationException( + raise UnauthorizedException( 'Email is not verified, A Verification Email has been sent to your email address' ) return { @@ -196,10 +195,9 @@ def validate(self, attrs): if user_obj_username: user = auth.authenticate(username=username, password=password) if not user: - raise AuthenticationException('Invalid credentials. Try again') + raise UnauthorizedException('Invalid credentials. Try again') if not user.is_active: - raise AuthenticationException( - 'Account disabled. contact admin') + raise UnauthorizedException('Account disabled. contact admin') if not user.is_verified: email = user.email token = RefreshToken.for_user(user).access_token @@ -217,7 +215,7 @@ def validate(self, attrs): 'to_email': user.email } Util.send_email(data) - raise AuthenticationException( + raise UnauthorizedException( 'Email is not verified, A Verification Email has been sent to your email address' ) return { @@ -226,7 +224,7 @@ def validate(self, attrs): 'tokens': user.tokens } return super().validate(attrs) - raise AuthenticationException('Invalid credentials. Try again') + raise UnauthorizedException('Invalid credentials. Try again') class ProfileSerializer(serializers.ModelSerializer): @@ -357,14 +355,14 @@ def validate(self, attrs): id = force_str(urlsafe_base64_decode(uidb64)) user = User.objects.get(id=id) if not PasswordResetTokenGenerator().check_token(user, token): - raise AuthenticationException('The reset link is invalid') + raise UnauthorizedException('The reset link is invalid') user.set_password(password) user.save() return (user) except Exception as e: - raise AuthenticationException('The reset link is invalid') + raise UnauthorizedException('The reset link is invalid') return super().validate(attrs) diff --git a/codedigger/user/validator_functions.py b/codedigger/user/validator_functions.py index 292c10d..b1b1127 100644 --- a/codedigger/user/validator_functions.py +++ b/codedigger/user/validator_functions.py @@ -1,4 +1,4 @@ -from .exception import ValidationException +from utils.exception import ValidationException def numeric(param, key, *args, **kwargs): diff --git a/codedigger/user/views.py b/codedigger/user/views.py index 04e3baf..ae13909 100644 --- a/codedigger/user/views.py +++ b/codedigger/user/views.py @@ -1,4 +1,3 @@ -from django.shortcuts import render from rest_framework import generics, status, permissions, views from .serializers import ( RegisterSerializer, ProfileSerializer, EmailVerificationSerializer, @@ -14,7 +13,7 @@ from django.urls import reverse from django.conf import settings import jwt, json -from .permissions import * +from utils.permissions import * from rest_framework.generics import RetrieveAPIView from drf_yasg.utils import swagger_auto_schema from drf_yasg import openapi @@ -36,8 +35,8 @@ # Validations from .param_validators import isValidRequest # Return Response -from .response import response -from .exception import ValidationException +from utils.response import OKResponse +from utils.exception import ValidationException # Profile from .profile import get_atcoder_profile, get_spoj_profile, get_uva_profile, get_codechef_profile, get_codeforces_profile from codeforces.models import user as CodeforcesUser @@ -85,7 +84,7 @@ def post(self, request): 'to_email': user.email } Util.send_email(data) - return response(user_data, Status=status.HTTP_201_CREATED) + return OKResponse(user_data, status=status.HTTP_201_CREATED) class VerifyEmail(views.APIView): @@ -139,7 +138,7 @@ def get(self, request, *args, **kwargs): """ Endpoint for checking if user is authenticated or not by checking if the JWT token is valid or not. """ - return response(Data="Token is Valid") + return OKResponse("Token is Valid") class SendVerificationMail(generics.GenericAPIView): @@ -173,7 +172,7 @@ def post(self, request, *args, **kwargs): 'to_email': user.email } Util.send_email(data) - return response(Data="A Verification Email has been sent") + return OKResponse("A Verification Email has been sent") class ProfileGetView(ListAPIView): @@ -236,7 +235,7 @@ def post(self, request, *args, **kwargs): raise ValidationException("Wrong Password") user.set_password(new_pass) user.save() - return response(Data="Password Change Complete") + return OKResponse("Password Change Complete") class RequestPasswordResetEmail(generics.GenericAPIView): @@ -277,8 +276,7 @@ def post(self, request): 'email_subject': 'Codedigger - Password Reset' } Util.send_email(data) - return response( - Data="We have sent you a link to reset your password") + return OKResponse("We have sent you a link to reset your password") raise ValidationException('The given email does not exist') @@ -334,7 +332,7 @@ def patch(self, request): """ serializer = self.serializer_class(data=request.data) serializer.is_valid(raise_exception=True) - return response(Data="Password reset success") + return OKResponse("Password reset success") class SearchUser(generics.GenericAPIView): @@ -539,7 +537,7 @@ def get(self, request, username): else: raise ValidationException('Invalid GET Request') - return response(Data=data) + return OKResponse(data) # Friends Related View Start @@ -597,13 +595,13 @@ def post(self, request): raise ValidationException('You are already Friends.') else: status.status = True - return response(Data="You are now Friends") + return OKResponse("You are now Friends") except UserFriends.DoesNotExist: UserFriends.objects.create(from_user=request.user, to_user=to_user, status=False) - return response(Data="Friend Request Sent") + return OKResponse("Friend Request Sent") class RemoveFriend(generics.GenericAPIView): @@ -644,13 +642,13 @@ def post(self, request): except UserFriends.DoesNotExist: its_ok = True uf.delete() - return response(Data="Removed Successfully!") + return OKResponse("Removed Successfully!") except UserFriends.DoesNotExist: try: opp_status = UserFriends.objects.get(from_user=user, to_user=request.user) opp_status.delete() - return response(Data="Removed Successfully!") + return OKResponse("Removed Successfully!") except UserFriends.DoesNotExist: raise ValidationException('Already Deleted!') @@ -691,7 +689,7 @@ def put(self, request): raise ValidationException('You are already Friends!') uf.status = True uf.save() - return Response({'status': 'OK', 'result': 'You are now Friends!'}) + return OKResponse('You are now Friends!') except UserFriends.DoesNotExist: raise ValidationException( 'No Request Found! It seems User have removed Request.') @@ -728,7 +726,7 @@ def get(self, request): many=True).data friends = friendsbyrequest + friendsbyaccept - return response(Data=friends) + return OKResponse(friends) class FriendRequestShowView(generics.GenericAPIView): @@ -752,7 +750,7 @@ def get(self, request): 'by_to_user': False }, many=True).data - return response(Data=friendsbyaccept) + return OKResponse(friendsbyaccept) class RequestSendShowView(generics.GenericAPIView): @@ -776,7 +774,7 @@ def get(self, request): 'by_to_user': True }, many=True).data - return response(Data=friendsbyrequest) + return OKResponse(friendsbyrequest) # Friends Related View Ends diff --git a/codedigger/utils/exception.py b/codedigger/utils/exception.py new file mode 100644 index 0000000..54cc28f --- /dev/null +++ b/codedigger/utils/exception.py @@ -0,0 +1,66 @@ +from email.policy import default +from rest_framework import status +from rest_framework.exceptions import APIException + + +class ValidationException(APIException): + + status_code = status.HTTP_400_BAD_REQUEST + default_detail = {'status': 'FAILED', 'error': 'Bad Request.'} + default_code = 'invalid' + + def __init__(self, detail=None, status_code=None, code=None): + self.detail = self.default_detail if detail == None \ + else {'status': 'FAILED', 'error': detail} + self.code = self.default_code if code == None \ + else code + if status_code is not None: + self.status_code = status_code + + +class UnauthorizedException(APIException): + + status_code = status.HTTP_401_UNAUTHORIZED + default_code = 'not_authenticated' + default_detail = { + 'status': 'FAILED', + 'error': 'Authentication credentials were not provided.' + } + + def __init__(self, detail=None, status_code=None, code=None): + self.detail = self.default_detail if detail == None \ + else {'status': 'FAILED', 'error': detail} + self.code = self.default_code if code == None \ + else code + if status_code is not None: + self.status_code = status_code + + +class ForbiddenException(APIException): + + status_code = status.HTTP_403_FORBIDDEN + default_code = 'permission_denied' + default_detail = {'status': 'FAILED', 'error': 'Permission Denied.'} + + def __init__(self, detail=None, status_code=None, code=None): + self.detail = self.default_detail if detail == None \ + else {'status': 'FAILED', 'error': detail} + self.code = self.default_code if code == None \ + else code + if status_code is not None: + self.status_code = status_code + + +class NotFoundException(APIException): + + status_code = status.HTTP_404_NOT_FOUND + default_code = 'not_found' + default_detail = {'status': 'FAILED', 'error': 'Not Found.'} + + def __init__(self, detail=None, status_code=None, code=None): + self.detail = self.default_detail if detail == None \ + else {'status': 'FAILED', 'error': detail} + self.code = self.default_code if code == None \ + else code + if status_code is not None: + self.status_code = status_code diff --git a/codedigger/utils/permissions.py b/codedigger/utils/permissions.py new file mode 100644 index 0000000..0332380 --- /dev/null +++ b/codedigger/utils/permissions.py @@ -0,0 +1,51 @@ +from rest_framework.permissions import BasePermission, SAFE_METHODS +from user.models import Profile +from .exception import (ValidationException, UnauthorizedException, + ForbiddenException) + + +class AuthenticatedOrReadOnly(BasePermission): + + def has_permission(self, request, view): + if request.user or \ + request.user.is_authenticated or \ + request.method in SAFE_METHODS: + return True + else: + raise UnauthorizedException + + +class Authenticated(BasePermission): + + def has_permission(self, request, view): + if not request.user or not request.user.is_authenticated: + raise UnauthorizedException + else: + return True + + +class AuthenticatedAdmin(BasePermission): + + def has_permission(self, request, view): + if request.user and request.user.is_authenticated and request.user.is_staff: + return True + else: + raise ForbiddenException + + +class AuthenticatedActivated(BasePermission): + + def has_permission(self, request, view): + if request.user and request.user.is_authenticated: + if Profile.objects.get(owner=request.user).codeforces is not None: + return True + else: + raise ValidationException('Account was not activated.') + else: + raise UnauthorizedException + + +class IsOwner(BasePermission): + + def has_object_permission(self, request, view, obj): + return obj.owner == request.user diff --git a/codedigger/utils/response.py b/codedigger/utils/response.py new file mode 100644 index 0000000..7a46ae9 --- /dev/null +++ b/codedigger/utils/response.py @@ -0,0 +1,22 @@ +from rest_framework.response import Response + + +class OKResponse(Response): + + default_status = 200 + + def __init__(self, + data=None, + status=None, + template_name=None, + headers=None, + exception=False, + content_type=None): + if not isinstance(data, dict) or \ + not 'status' in data or \ + data['status'] != 'OK': + data = {'status': 'OK', 'result': data} + if status == None: + status = self.default_status + super().__init__(data, status, template_name, headers, exception, + content_type)