From 6f4282d96ec2b5d1dd9f1f1be7a9b81c671c9935 Mon Sep 17 00:00:00 2001 From: "afanasyev.aa" Date: Tue, 15 Jul 2025 00:58:31 +0900 Subject: [PATCH] added endpoints for user profile --- api/serializers.py | 32 ++++++++++++++++ api/urls.py | 16 ++++++-- api/views.py | 93 ++++++++++++++++++++++++++++++++++++++++++++- testapi/settings.py | 2 +- 4 files changed, 138 insertions(+), 5 deletions(-) diff --git a/api/serializers.py b/api/serializers.py index 68f257d..2bf727d 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -1,6 +1,10 @@ from rest_framework import serializers from .models import Prediction, SavedPoint, SavedRateProfile, PreditctionTemplate from datetime import datetime +from django.contrib.auth.password_validation import validate_password +from django.core.validators import validate_email +from django.core.exceptions import ValidationError as DjangoValidationError +from django.contrib.auth import get_user_model from .validators import ( validate_custom_curve, rate_clip, _rfc3339_to_timestamp, base64_to_curve @@ -11,6 +15,7 @@ class PredictionSerializer(serializers.ModelSerializer): model = Prediction fields = ['id', 'created_at', 'updated_at', 'result'] +User = get_user_model() PROFILE_STANDARD = "standard_profile" PROFILE_FLOAT = "float_profile" @@ -151,3 +156,30 @@ class PreditctionTemplateSerializer(serializers.ModelSerializer): model = PreditctionTemplate fields = ['id', 'name', 'template_data', 'is_default'] read_only_fields = ['id'] + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ['username', 'email', 'first_name', 'last_name'] + extra_kwargs = { + 'username': {'read_only': True} + } + + def validate_email(self, value): + try: + validate_email(value) + except DjangoValidationError: + raise serializers.ValidationError("Invalid email format") + return value + +class ChangePasswordSerializer(serializers.Serializer): + old_password = serializers.CharField(required=True) + new_password = serializers.CharField(required=True) + + def validate_new_password(self, value): + validate_password(value) + return value + +class DeleteAccountSerializer(serializers.Serializer): + password = serializers.CharField(required=True) \ No newline at end of file diff --git a/api/urls.py b/api/urls.py index 6040495..3a438c5 100644 --- a/api/urls.py +++ b/api/urls.py @@ -10,7 +10,12 @@ from .views import ( login_view, logout_view, SessionView, - WhoAmIView + WhoAmIView, + UserProfileView, + ChangePasswordView, + TokenManagementView, + DeleteUserDataView, + DeleteAccountView ) @@ -32,7 +37,12 @@ urlpatterns = [ path('csrf/', get_csrf, name='api-csrf'), path('login/', login_view, name='api-login'), path('logout/', logout_view, name='api-logout'), - path('session/', SessionView.as_view(), name='api-session'), # new - path('whoami/', WhoAmIView.as_view(), name='api-whoami'), # new + path('session/', SessionView.as_view(), name='api-session'), + path('whoami/', WhoAmIView.as_view(), name='api-whoami'), + path("profile/", UserProfileView.as_view(), name='api-profile'), + path("profile/change-password/", ChangePasswordView.as_view(), name='api-change-password'), + path("profile/token/", TokenManagementView.as_view(), name='api-token'), + path("profile/delete-data/", DeleteUserDataView.as_view(), name='api-delete-data'), + path("profile/delete-account/", DeleteAccountView.as_view(), name='api-delete-account'), ] urlpatterns += router.urls diff --git a/api/views.py b/api/views.py index f0cb04a..319f2a1 100644 --- a/api/views.py +++ b/api/views.py @@ -9,6 +9,7 @@ from rest_framework.exceptions import APIException from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.authentication import SessionAuthentication, BasicAuthentication, TokenAuthentication from rest_framework.decorators import api_view, permission_classes, authentication_classes, action +from rest_framework.authtoken.models import Token from django.utils import timezone from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator @@ -18,7 +19,7 @@ from django.middleware.csrf import get_token from django.core.exceptions import ValidationError from django.utils.dateparse import parse_datetime from .models import Prediction, User, Satellite, SavedPoint, SavedRateProfile, PreditctionTemplate, TelemetryPacket -from .serializers import PredictionSerializer, TelemetryPacketSerializer, PredictionRequestSerializer, PredictionListSerializer, PredictionDetailSerializer, SavedPointSerializer, SavedRateProfileSerializer, PreditctionTemplateSerializer +from .serializers import PredictionSerializer, TelemetryPacketSerializer, PredictionRequestSerializer, PredictionListSerializer, PredictionDetailSerializer, SavedPointSerializer, SavedRateProfileSerializer, PreditctionTemplateSerializer, UserSerializer, ChangePasswordSerializer, DeleteAccountSerializer from .services.tawhiri import TawhiriClient from drf_spectacular.utils import extend_schema from .permissions import ReadOnlyOrAuthenticated, IsOwner @@ -248,6 +249,96 @@ class PreditctionTemplateViewset(ModelViewSet): def perform_create(self, serializer): serializer.save(user=self.request.user) + +class UserProfileView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + serializer = UserSerializer(request.user) + return Response(serializer.data) + + def patch(self, request): + user = request.user + serializer = UserSerializer(user, data=request.data, partial=True) + + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + serializer.save() + return Response(serializer.data) + + +class ChangePasswordView(APIView): + permission_classes = [IsAuthenticated] + + def post(self, request): + user = request.user + serializer = ChangePasswordSerializer(data=request.data) + + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + if not user.check_password(serializer.validated_data['old_password']): + return Response({'detail': 'Old password is incorrect'}, + status=status.HTTP_400_BAD_REQUEST) + + user.set_password(serializer.validated_data['new_password']) + user.save() + return Response({'detail': 'Password changed successfully'}) + + +class DeleteAccountView(APIView): + permission_classes = [IsAuthenticated] + + def delete(self, request): + user = request.user + serializer = DeleteAccountSerializer(data=request.data) + + if not serializer.is_valid(): + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + if not user.check_password(serializer.validated_data['password']): + return Response({'detail': 'Incorrect password'}, + status=status.HTTP_400_BAD_REQUEST) + + Prediction.objects.filter(user=user).delete() + SavedPoint.objects.filter(user=user).delete() + PreditctionTemplate.objects.filter(user=user).delete() + + user.delete() + + return Response({'detail': 'Account deleted successfully'}) + + +class DeleteUserDataView(APIView): + permission_classes = [IsAuthenticated] + + def delete(self, request): + user = request.user + + Prediction.objects.filter(user=user).delete() + SavedPoint.objects.filter(user=user).delete() + PreditctionTemplate.objects.filter(user=user).delete() + + return Response({'detail': 'All user data deleted successfully'}) + + +class TokenManagementView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request): + + token, created = Token.objects.get_or_create(user=request.user) + return Response({"token": token.key}) + + def post(self, request): + + Token.objects.filter(user=request.user).delete() + token = Token.objects.create(user=request.user) + return Response({"token": token.key}) + + + # class PredictionCreateView(APIView): # permission_classes = [IsAuthenticated] diff --git a/testapi/settings.py b/testapi/settings.py index 614122e..cd6c31d 100644 --- a/testapi/settings.py +++ b/testapi/settings.py @@ -57,7 +57,7 @@ INSTALLED_APPS = [ 'drf_spectacular', 'corsheaders', 'api.apps.ApiConfig', - + 'channels', ] MIDDLEWARE = [