from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.decorators import login_required from django.contrib.auth.views import LoginView from django.http import Http404 from django.shortcuts import render, redirect from django.urls import reverse from django.utils import timezone from django.utils.decorators import method_decorator from django.views.generic import DetailView, UpdateView from django_filters.rest_framework import DjangoFilterBackend from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope from rest_framework import generics, status, permissions, mixins from rest_framework.decorators import action from rest_framework.generics import get_object_or_404 from rest_framework.permissions import AllowAny from rest_framework.response import Response from rest_framework.views import APIView from rest_framework.viewsets import GenericViewSet from apps.gooyal_oauth2.rest_framework import IsAuthenticatedOrTokenMatchesOASRequirements, TokenMatchesViewSetActions from apps.gooyal_oauth2.throttling import get_application from apps.users.filters import UserFilter from apps.users.forms import OTPAuthenticationForm, ProfileUpdateForm from apps.users.models import User from apps.users.provinces_and_cities import State from apps.users.serializers import PublicUserSerializer, AccountSerializer, RequestOTPSerializer, RequestOTTSerializer, \ ChangePasswordSerializer, UserInquirySerializer, SessionSerializer, ReferralSetSerializer from utils.throttles import RequestOTPDayRateThrottle, RequestOTPMinRateThrottle, NumberedRequestOTPDayRateThrottle, NumberedRequestOTPMinRateThrottle from utils.clients.promotions_client import promote_user from utils.throttles import RequestOTPDayRateThrottle, RequestOTPMinRateThrottle from utils import exceptions UserModel = get_user_model() class BaseUserListView(generics.ListAPIView): permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] queryset = User.objects.all() serializer_class = PublicUserSerializer filter_backends = (DjangoFilterBackend,) # Make filterset_class abstract - must be defined in subclasses filterset_class = None class UserProfileView(BaseUserListView): filterset_class = UserFilter required_alternate_scopes = { "GET": [["accounts.profile:list"]], } class UserListView(BaseUserListView): # filterset_class = UserFilter required_alternate_scopes = { "GET": [["accounts.users:list"]], } class UserPublicRetrieveView(generics.RetrieveAPIView): permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] queryset = User.objects.all() serializer_class = PublicUserSerializer lookup_field = 'uuid' required_alternate_scopes = { "GET": [["accounts.profile:retrieve"]], } class UserDetailedRetrieveView(generics.RetrieveAPIView): permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] queryset = User.objects.all() serializer_class = AccountSerializer lookup_field = 'uuid' required_alternate_scopes = { "GET": [["accounts.profile:detailed_retrieve"]], } class UserCurrentAvatarUrlView(generics.RetrieveAPIView): permission_classes = [AllowAny] queryset = User.objects.all() lookup_field = 'uuid' def retrieve(self, request, *args, **kwargs): user: User = self.get_object() if user.avatar: return redirect(user.avatar.url) else: raise Http404() class AccountView(generics.RetrieveUpdateAPIView): serializer_class = AccountSerializer permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] required_alternate_scopes = { "GET": [["accounts.account:retrieve"]], "POST": [["accounts.account:update"]], "PUT": [["accounts.account:update"]], "PATCH": [["accounts.account:update"]], } def get_object(self): return self.request.user def perform_update(self, serializer): serializer.save(last_update=timezone.now()) class RequestOTPView(generics.CreateAPIView): permission_classes = [] serializer_class = RequestOTPSerializer required_scopes = [] throttle_classes = [RequestOTPMinRateThrottle, RequestOTPDayRateThrottle, NumberedRequestOTPDayRateThrottle, NumberedRequestOTPMinRateThrottle] class RequestOTTView(generics.CreateAPIView): permission_classes = [IsAuthenticatedOrTokenHasScope] required_scopes = ['accounts.account:request_ott'] serializer_class = RequestOTTSerializer def get_object(self): return self.request.user def create(self, request, *args, **kwargs): instance: User = self.get_object() serializer = self.get_serializer(instance, data=request.data) serializer.is_valid(raise_exception=True) instance.set_ott() instance.save() return Response(serializer.data) class UserInquiryView(generics.CreateAPIView): serializer_class = UserInquirySerializer permission_classes = [IsAuthenticatedOrTokenHasScope] required_scopes = ['accounts.profile:inquiry'] class ChangePasswordView(generics.UpdateAPIView): permission_classes = [IsAuthenticatedOrTokenHasScope] serializer_class = ChangePasswordSerializer required_scopes = ["accounts.account:change_password"] model = User def get_object(self, queryset=None): obj = self.request.user return obj def update(self, request, *args, **kwargs): self.object = self.get_object() serializer = self.get_serializer(data=request.data) if serializer.is_valid(): pass_field = serializer.data.get("old_password_field") old_password = serializer.data.get("old_password") new_password = serializer.data.get("new_password") if not self.object.check_auth(pass_field, old_password): return Response({"old_password": ["Wrong password/otp."]}, status=status.HTTP_400_BAD_REQUEST) self.object.set_password(new_password) self.object.last_update = timezone.now() self.object.save() return Response({"state": 'success'}, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) class OTPLoginView(LoginView): template_name = 'users/login.html' """ Display the login form and handle the login action. """ form_class = OTPAuthenticationForm def post(self, request, *args, **kwargs): return super().post(request, *args, **kwargs) @method_decorator(login_required, name='dispatch') class ProfileDetailView(DetailView): model = User template_name = 'users/profile_detail.html' pk_url_kwarg = 'uuid' def get_object(self, queryset=None): return self.request.user @method_decorator(login_required, name='dispatch') class ProfileUpdateView(UpdateView): model = User template_name = 'users/profile_update.html' pk_url_kwarg = 'uuid' form_class = ProfileUpdateForm def get_success_url(self): return reverse('users:profile_update') def get_object(self, queryset=None): return self.request.user def form_valid(self, form): return super().form_valid(form) class UserSessionListView(generics.ListAPIView): permission_classes = [IsAuthenticatedOrTokenMatchesOASRequirements] serializer_class = SessionSerializer filter_backends = (DjangoFilterBackend,) max_allowed_session = 0 required_alternate_scopes = { "GET": [["accounts.account:retrieve"]], } def get_queryset(self): from apps.gooyal_oauth2.models import AccessToken return AccessToken.objects.filter(user=self.request.user).all() class UserViewSet( # mixins.CreateModelMixin, GenericViewSet ): serializer_class = AccountSerializer permission_classes = [TokenMatchesViewSetActions] required_action_scopes = { "submit_referral": [["accounts.users.user:submit_referral"]], } # TODO: better solution # this is a proxy api for submit event in promotion service @action(detail=False, methods=['POST'], serializer_class=ReferralSetSerializer) def submit_referral(self, request, *args, **kwargs): user = self.request.user application = get_application(self.request) serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) referral = serializer.validated_data.get("referral") if referral == self.request.user.pk: raise exceptions.Conflict('SAME AS USER') referral_user = get_object_or_404(User.objects.all(), pk=referral) data = { "referral": referral, "application": application.pk, "user": user.pk, } plan = settings.PROMOTIONS_REFERRAL_PLAN result = promote_user(user, plan, 'accounts::submit_referral', data) # result_json = { # "promotions": [ # { # "uuid": "3fa85f64-5717-4562-b3fc-2c963f66afa6", # "event": { # "label": "string", # "data": "string", # "user": "3fa85f64-5717-4562-b3fc-2c963f66afa6", # "application": "3fa85f64-5717-4562-b3fc-2c963f66afa6" # }, # "promotion_amount": 2147483647, # "state": 1, # "user": "3fa85f64-5717-4562-b3fc-2c963f66afa6" # } # ] # } if not result: raise exceptions.Conflict('can not set referral') return Response(serializer.validated_data)