11from decimal import Decimal , InvalidOperation
22
33from django .contrib .auth import get_user_model
4+ from django .core .signing import BadSignature , SignatureExpired , TimestampSigner
45from django .db .models import Avg , Count , Max , Sum
6+ from django .http import JsonResponse
7+ from django .shortcuts import get_object_or_404
8+ from django_filters .rest_framework import DjangoFilterBackend
59from rest_framework import status
10+ from rest_framework .filters import OrderingFilter , SearchFilter
11+ from rest_framework .generics import ListAPIView
612from rest_framework .permissions import AllowAny , IsAuthenticated
713from rest_framework .response import Response
14+ from rest_framework .throttling import ScopedRateThrottle
815from rest_framework .views import APIView
916from rest_framework_simplejwt .tokens import RefreshToken
1017
1118from users .models import Booking , Product , ProductReview , Profile
1219from users .permissions import AdminUser
1320from users .serializers import (
21+ AdminProfileSerializer ,
1422 AdminUserDetailSerializer ,
1523 BookingDetailSerializer ,
1624 LoginSerializer ,
1725 ProductReviewSerializer ,
1826 ProductSerializer ,
1927 RegisterSerializer ,
28+ TechnicianProfileSerializer ,
2029 TechnicianSummarySerializer ,
2130 UserBookingHistorySerializer ,
31+ UserProfileSerializer ,
2232)
2333from users .utils import paginate , parse_date_range
2434
@@ -82,23 +92,15 @@ def post(self, request):
8292class LoginView (APIView ):
8393 permission_classes = [AllowAny ]
8494 serializer_class = LoginSerializer
95+ throttle_classes = [ScopedRateThrottle ]
96+ throttle_scope = "login"
8597
8698 def post (self , request ):
8799 # Validate input using serializer
88100 serializer = self .serializer_class (data = request .data )
89101 if not serializer .is_valid ():
90102 return Response (serializer .errors , status = status .HTTP_400_BAD_REQUEST )
91103
92- # Debug logging — visible in Django terminal
93- print ("=" * 60 )
94- print ("[LOGIN REQUEST] Received POST request!" )
95- print (f"[IP] { request .META .get ('REMOTE_ADDR' , 'Unknown' )} " )
96- print (f"[PATH] { request .path } " )
97- print (f"[METHOD] { request .method } " )
98- print (f"[CONTENT-TYPE] { request .content_type } " )
99- print (f"[DATA] { dict (request .data )} " )
100- print ("=" * 60 )
101-
102104 identifier = serializer .validated_data .get ("identifier" ).strip ()
103105 password = serializer .validated_data .get ("password" )
104106
@@ -155,7 +157,7 @@ def post(self, request):
155157 refresh = RefreshToken .for_user (user )
156158
157159 # Auto-redirect URL based on role
158- redirect_url = f"/{ user . role } /{ user .username } /"
160+ redirect_url = f"/profile /{ user .username } /"
159161
160162 return Response (
161163 {
@@ -173,29 +175,91 @@ def post(self, request):
173175 )
174176
175177
176- class ProductListView (APIView ):
178+ class ProfileView (APIView ):
177179 permission_classes = [IsAuthenticated ]
178180
179- def get (self , request ):
180- products = Product .objects .all ().prefetch_related ("images" , "tags" , "reviews" )
181- serializer = ProductSerializer (products , many = True )
182- return Response (serializer .data , status = status .HTTP_200_OK )
181+ def get_profile (self , username ):
182+ try :
183+ user = User .objects .get (username = username )
184+ profile = Profile .objects .filter (user = user ).first ()
185+ if not profile :
186+ return None , None
187+ return user , profile
188+ except User .DoesNotExist :
189+ return None , None
190+
191+ def get_serializer_class (self , request_user , profile_user ):
192+ if request_user .role == "admin" :
193+ return AdminProfileSerializer
194+
195+ if request_user .role == "technician" and request_user == profile_user :
196+ return TechnicianProfileSerializer
197+
198+ if request_user .role == "user" and request_user == profile_user :
199+ return UserProfileSerializer
200+
201+ return None
202+
203+ def get (self , request , username ):
204+ user , profile = self .get_profile (username )
205+ if not user :
206+ return Response ({"error" : "User not found" }, status = 404 )
207+
208+ serializer_class = self .get_serializer_class (request .user , user )
209+ if not serializer_class :
210+ return Response ({"error" : "Permission denied" }, status = 403 )
211+
212+ serializer = serializer_class (profile )
213+ return Response (serializer .data )
214+
215+ def put (self , request , username ):
216+ user , profile = self .get_profile (username )
217+ if not user :
218+ return Response ({"error" : "User not found" }, status = 404 )
219+
220+ serializer_class = self .get_serializer_class (request .user , user )
221+ if not serializer_class :
222+ return Response ({"error" : "Permission denied" }, status = 403 )
223+
224+ serializer = serializer_class (profile , data = request .data , partial = True )
225+
226+ if serializer .is_valid ():
227+ serializer .save ()
228+ return Response ({"message" : "Profile updated successfully" })
229+ return Response (serializer .errors , status = 400 )
230+
231+
232+ class ProductListView (ListAPIView ):
233+ permission_classes = [IsAuthenticated ]
234+ throttle_classes = [ScopedRateThrottle ]
235+ throttle_scope = "product_list"
236+ serializer_class = ProductSerializer
237+ queryset = Product .objects .all ().prefetch_related ("images" , "tags" , "reviews" )
238+
239+ filter_backends = [
240+ DjangoFilterBackend ,
241+ SearchFilter ,
242+ OrderingFilter ,
243+ ]
244+
245+ filterset_fields = ["brand" , "status" , "price" ]
246+ search_fields = ["product_name" , "description" ]
247+ ordering_fields = ["price" , "product_name" , "average_rating" ]
248+ ordering = ["price" ]
183249
184250
185251class ProductReviewCreateUpdateView (APIView ):
186252 permission_classes = [IsAuthenticated ]
253+ serializer_class = ProductReviewSerializer
187254
188255 def post (self , request , product_id ):
189- product = Product . objects . get ( id = product_id )
256+ product = get_object_or_404 ( Product , id = product_id )
190257
191258 try :
192259 review = ProductReview .objects .get (product = product , user = request .user )
193- serializer = ProductReviewSerializer (
194- review , data = request .data , partial = True
195- )
260+ serializer = self .serializer_class (review , data = request .data , partial = True )
196261 except ProductReview .DoesNotExist :
197- serializer = ProductReviewSerializer (data = request .data )
198-
262+ serializer = self .serializer_class (data = request .data )
199263 if serializer .is_valid ():
200264 serializer .save (product = product , user = request .user )
201265 return Response (
@@ -208,10 +272,11 @@ def post(self, request, product_id):
208272
209273class ProductDetailView (APIView ):
210274 permission_classes = [IsAuthenticated ]
275+ serializer_class = ProductSerializer
211276
212277 def get (self , request , product_id ):
213- product = Product . objects . get ( id = product_id )
214- serializer = ProductSerializer (product )
278+ product = get_object_or_404 ( Product , id = product_id )
279+ serializer = self . serializer_class (product )
215280 return Response (serializer .data , status = status .HTTP_200_OK )
216281
217282
@@ -237,7 +302,7 @@ def get(self, request):
237302 # ===== If technician detail is requested =====
238303 tech_id = request .GET .get ("technician_id" )
239304 if tech_id :
240- tech = Profile . objects . get ( id = tech_id )
305+ tech = get_object_or_404 ( Profile , id = tech_id )
241306 tech_bookings = Booking .objects .filter (technician = tech ).order_by (
242307 "-date_time_start"
243308 )
@@ -308,6 +373,8 @@ def get(self, request, user_id):
308373 )
309374 .first ()
310375 )
376+ if not user_stats :
377+ return Response ({"error" : "User not found" }, status = 404 )
311378
312379 summary_data = self .serializer_class (user_stats ).data
313380
@@ -328,3 +395,28 @@ def get(self, request, user_id):
328395 },
329396 }
330397 )
398+
399+
400+ signer = TimestampSigner ()
401+
402+
403+ class DeleteAccountView (APIView ):
404+ permission_classes = [AllowAny ]
405+
406+ def get (self , request ):
407+ token = request .GET .get ("token" )
408+ if not token :
409+ return JsonResponse ({"error" : "Token required" }, status = 400 )
410+
411+ try :
412+ # Verify and check expiration (max_age in seconds = 24h)
413+ unsigned = signer .unsign (token , max_age = 86400 )
414+ user = get_user_model ().objects .get (pk = unsigned )
415+ except SignatureExpired :
416+ return JsonResponse ({"error" : "Link expired" }, status = 400 )
417+ except (BadSignature , get_user_model ().DoesNotExist ):
418+ return JsonResponse ({"error" : "Invalid link" }, status = 400 )
419+
420+ # Delete user and profile
421+ user .delete ()
422+ return JsonResponse ({"message" : "Account deleted successfully." }, status = 200 )
0 commit comments