mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-01-01 20:27:02 -05:00
feat: Implement initial schema and add various API, service, and management command enhancements across the application.
This commit is contained in:
@@ -69,8 +69,7 @@ logger = logging.getLogger(__name__)
|
||||
200: {
|
||||
"description": "User successfully deleted with submissions preserved",
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "User successfully deleted with submissions preserved",
|
||||
"detail": "User successfully deleted with submissions preserved",
|
||||
"deleted_user": {
|
||||
"username": "john_doe",
|
||||
"user_id": "1234",
|
||||
@@ -92,17 +91,16 @@ logger = logging.getLogger(__name__)
|
||||
400: {
|
||||
"description": "Bad request - user cannot be deleted",
|
||||
"example": {
|
||||
"success": False,
|
||||
"error": "Cannot delete user: Cannot delete superuser accounts",
|
||||
"detail": "Cannot delete user: Cannot delete superuser accounts",
|
||||
},
|
||||
},
|
||||
404: {
|
||||
"description": "User not found",
|
||||
"example": {"success": False, "error": "User not found"},
|
||||
"example": {"detail": "User not found"},
|
||||
},
|
||||
403: {
|
||||
"description": "Permission denied - admin access required",
|
||||
"example": {"success": False, "error": "Admin access required"},
|
||||
"example": {"detail": "Admin access required"},
|
||||
},
|
||||
},
|
||||
tags=["User Management"],
|
||||
@@ -137,7 +135,7 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
"is_superuser": user.is_superuser,
|
||||
"user_role": user.role,
|
||||
"rejection_reason": reason,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Determine error code based on reason
|
||||
@@ -151,8 +149,7 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Cannot delete user: {reason}",
|
||||
"detail": f"Cannot delete user: {reason}",
|
||||
"error_code": error_code,
|
||||
"user_info": {
|
||||
"username": user.username,
|
||||
@@ -174,7 +171,7 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
"target_user": user.username,
|
||||
"target_user_id": user_id,
|
||||
"action": "user_deletion",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Perform the deletion
|
||||
@@ -185,17 +182,16 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
f"Successfully deleted user {result['deleted_user']['username']} (ID: {user_id}) by admin {request.user.username}",
|
||||
extra={
|
||||
"admin_user": request.user.username,
|
||||
"deleted_user": result['deleted_user']['username'],
|
||||
"deleted_user": result["deleted_user"]["username"],
|
||||
"deleted_user_id": user_id,
|
||||
"preserved_submissions": result['preserved_submissions'],
|
||||
"preserved_submissions": result["preserved_submissions"],
|
||||
"action": "user_deletion_completed",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "User successfully deleted with submissions preserved",
|
||||
"detail": "User successfully deleted with submissions preserved",
|
||||
**result,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
@@ -208,16 +204,15 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
extra={
|
||||
"admin_user": request.user.username,
|
||||
"target_user_id": user_id,
|
||||
"error": str(e),
|
||||
"detail": str(e),
|
||||
"action": "user_deletion_error",
|
||||
},
|
||||
exc_info=True
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Error deleting user: {str(e)}",
|
||||
"detail": f"Error deleting user: {str(e)}",
|
||||
"error_code": "DELETION_ERROR",
|
||||
"help_text": "Please try again or contact system administrator if the problem persists.",
|
||||
},
|
||||
@@ -259,8 +254,7 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
},
|
||||
},
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "Avatar saved successfully",
|
||||
"detail": "Avatar saved successfully",
|
||||
"avatar_url": "https://imagedelivery.net/account-hash/image-id/avatar",
|
||||
"avatar_variants": {
|
||||
"thumbnail": "https://imagedelivery.net/account-hash/image-id/thumbnail",
|
||||
@@ -285,7 +279,7 @@ def save_avatar_image(request):
|
||||
|
||||
if not cloudflare_image_id:
|
||||
return Response(
|
||||
{"success": False, "error": "cloudflare_image_id is required"},
|
||||
{"detail": "cloudflare_image_id is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -299,26 +293,25 @@ def save_avatar_image(request):
|
||||
|
||||
if not image_data:
|
||||
return Response(
|
||||
{"success": False, "error": "Image not found in Cloudflare"},
|
||||
{"detail": "Image not found in Cloudflare"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Try to find existing CloudflareImage record by cloudflare_id
|
||||
cloudflare_image = None
|
||||
try:
|
||||
cloudflare_image = CloudflareImage.objects.get(
|
||||
cloudflare_id=cloudflare_image_id)
|
||||
cloudflare_image = CloudflareImage.objects.get(cloudflare_id=cloudflare_image_id)
|
||||
|
||||
# Update existing record with latest data from Cloudflare
|
||||
cloudflare_image.status = 'uploaded'
|
||||
cloudflare_image.status = "uploaded"
|
||||
cloudflare_image.uploaded_at = timezone.now()
|
||||
cloudflare_image.metadata = image_data.get('meta', {})
|
||||
cloudflare_image.metadata = image_data.get("meta", {})
|
||||
# Extract variants from nested result structure
|
||||
cloudflare_image.variants = image_data.get('result', {}).get('variants', [])
|
||||
cloudflare_image.variants = image_data.get("result", {}).get("variants", [])
|
||||
cloudflare_image.cloudflare_metadata = image_data
|
||||
cloudflare_image.width = image_data.get('width')
|
||||
cloudflare_image.height = image_data.get('height')
|
||||
cloudflare_image.format = image_data.get('format', '')
|
||||
cloudflare_image.width = image_data.get("width")
|
||||
cloudflare_image.height = image_data.get("height")
|
||||
cloudflare_image.format = image_data.get("format", "")
|
||||
cloudflare_image.save()
|
||||
|
||||
except CloudflareImage.DoesNotExist:
|
||||
@@ -326,25 +319,23 @@ def save_avatar_image(request):
|
||||
cloudflare_image = CloudflareImage.objects.create(
|
||||
cloudflare_id=cloudflare_image_id,
|
||||
user=user,
|
||||
status='uploaded',
|
||||
upload_url='', # Not needed for uploaded images
|
||||
status="uploaded",
|
||||
upload_url="", # Not needed for uploaded images
|
||||
expires_at=timezone.now() + timezone.timedelta(days=365), # Set far future expiry
|
||||
uploaded_at=timezone.now(),
|
||||
metadata=image_data.get('meta', {}),
|
||||
metadata=image_data.get("meta", {}),
|
||||
# Extract variants from nested result structure
|
||||
variants=image_data.get('result', {}).get('variants', []),
|
||||
variants=image_data.get("result", {}).get("variants", []),
|
||||
cloudflare_metadata=image_data,
|
||||
width=image_data.get('width'),
|
||||
height=image_data.get('height'),
|
||||
format=image_data.get('format', ''),
|
||||
width=image_data.get("width"),
|
||||
height=image_data.get("height"),
|
||||
format=image_data.get("format", ""),
|
||||
)
|
||||
|
||||
except Exception as api_error:
|
||||
logger.error(
|
||||
f"Error fetching image from Cloudflare API: {str(api_error)}", exc_info=True)
|
||||
logger.error(f"Error fetching image from Cloudflare API: {str(api_error)}", exc_info=True)
|
||||
return Response(
|
||||
{"success": False,
|
||||
"error": f"Failed to fetch image from Cloudflare: {str(api_error)}"},
|
||||
{"detail": f"Failed to fetch image from Cloudflare: {str(api_error)}"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -391,8 +382,7 @@ def save_avatar_image(request):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Avatar saved successfully",
|
||||
"detail": "Avatar saved successfully",
|
||||
"avatar_url": avatar_url,
|
||||
"avatar_variants": avatar_variants,
|
||||
},
|
||||
@@ -402,7 +392,7 @@ def save_avatar_image(request):
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving avatar image: {str(e)}", exc_info=True)
|
||||
return Response(
|
||||
{"success": False, "error": f"Failed to save avatar: {str(e)}"},
|
||||
{"detail": f"Failed to save avatar: {str(e)}"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -420,8 +410,7 @@ def save_avatar_image(request):
|
||||
"avatar_url": {"type": "string"},
|
||||
},
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "Avatar deleted successfully",
|
||||
"detail": "Avatar deleted successfully",
|
||||
"avatar_url": "https://ui-avatars.com/api/?name=J&size=200&background=random&color=fff&bold=true",
|
||||
},
|
||||
},
|
||||
@@ -447,6 +436,7 @@ def delete_avatar(request):
|
||||
# Delete from Cloudflare first, then from database
|
||||
try:
|
||||
from django_cloudflareimages_toolkit.services import CloudflareImagesService
|
||||
|
||||
service = CloudflareImagesService()
|
||||
service.delete_image(avatar_to_delete)
|
||||
logger.info(f"Successfully deleted avatar from Cloudflare: {avatar_to_delete.cloudflare_id}")
|
||||
@@ -461,8 +451,7 @@ def delete_avatar(request):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Avatar deleted successfully",
|
||||
"detail": "Avatar deleted successfully",
|
||||
"avatar_url": avatar_url,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
@@ -471,8 +460,7 @@ def delete_avatar(request):
|
||||
except UserProfile.DoesNotExist:
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "No avatar to delete",
|
||||
"detail": "No avatar to delete",
|
||||
"avatar_url": f"https://ui-avatars.com/api/?name={user.username[0].upper()}&size=200&background=random&color=fff&bold=true",
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
@@ -480,7 +468,7 @@ def delete_avatar(request):
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"success": False, "error": f"Failed to delete avatar: {str(e)}"},
|
||||
{"detail": f"Failed to delete avatar: {str(e)}"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -506,7 +494,7 @@ def request_account_deletion(request):
|
||||
can_delete, reason = UserDeletionService.can_delete_user(user)
|
||||
if not can_delete:
|
||||
return Response(
|
||||
{"success": False, "error": reason},
|
||||
{"detail": reason},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -515,8 +503,7 @@ def request_account_deletion(request):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Verification code sent to your email",
|
||||
"detail": "Verification code sent to your email",
|
||||
"expires_at": deletion_request.expires_at,
|
||||
"email": user.email,
|
||||
},
|
||||
@@ -534,7 +521,7 @@ def request_account_deletion(request):
|
||||
"user_role": request.user.role,
|
||||
"rejection_reason": str(e),
|
||||
"action": "self_deletion_rejected",
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
# Determine error code based on reason
|
||||
@@ -549,8 +536,7 @@ def request_account_deletion(request):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": False,
|
||||
"error": error_message,
|
||||
"detail": error_message,
|
||||
"error_code": error_code,
|
||||
"user_info": {
|
||||
"username": request.user.username,
|
||||
@@ -570,16 +556,15 @@ def request_account_deletion(request):
|
||||
extra={
|
||||
"user": request.user.username,
|
||||
"user_id": request.user.user_id,
|
||||
"error": str(e),
|
||||
"detail": str(e),
|
||||
"action": "self_deletion_error",
|
||||
},
|
||||
exc_info=True
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Error creating deletion request: {str(e)}",
|
||||
"detail": f"Error creating deletion request: {str(e)}",
|
||||
"error_code": "DELETION_REQUEST_ERROR",
|
||||
"help_text": "Please try again or contact support if the problem persists.",
|
||||
},
|
||||
@@ -611,8 +596,7 @@ def request_account_deletion(request):
|
||||
200: {
|
||||
"description": "Account successfully deleted",
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "Account successfully deleted with submissions preserved",
|
||||
"detail": "Account successfully deleted with submissions preserved",
|
||||
"deleted_user": {
|
||||
"username": "john_doe",
|
||||
"user_id": "1234",
|
||||
@@ -637,7 +621,7 @@ def request_account_deletion(request):
|
||||
},
|
||||
400: {
|
||||
"description": "Invalid or expired verification code",
|
||||
"example": {"success": False, "error": "Verification code has expired"},
|
||||
"example": {"detail": "Verification code has expired"},
|
||||
},
|
||||
},
|
||||
tags=["Self-Service Account Management"],
|
||||
@@ -663,7 +647,7 @@ def verify_account_deletion(request):
|
||||
|
||||
if not verification_code:
|
||||
return Response(
|
||||
{"success": False, "error": "Verification code is required"},
|
||||
{"detail": "Verification code is required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
@@ -672,20 +656,17 @@ def verify_account_deletion(request):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": "Account successfully deleted with submissions preserved",
|
||||
"detail": "Account successfully deleted with submissions preserved",
|
||||
**result,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
return Response(
|
||||
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"success": False, "error": f"Error verifying deletion: {str(e)}"},
|
||||
{"detail": f"Error verifying deletion: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
@@ -701,14 +682,13 @@ def verify_account_deletion(request):
|
||||
200: {
|
||||
"description": "Deletion request cancelled or no request found",
|
||||
"example": {
|
||||
"success": True,
|
||||
"message": "Deletion request cancelled",
|
||||
"detail": "Deletion request cancelled",
|
||||
"had_pending_request": True,
|
||||
},
|
||||
},
|
||||
401: {
|
||||
"description": "Authentication required",
|
||||
"example": {"success": False, "error": "Authentication required"},
|
||||
"example": {"detail": "Authentication required"},
|
||||
},
|
||||
},
|
||||
tags=["Self-Service Account Management"],
|
||||
@@ -732,12 +712,7 @@ def cancel_account_deletion(request):
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
"message": (
|
||||
"Deletion request cancelled"
|
||||
if had_request
|
||||
else "No pending deletion request found"
|
||||
),
|
||||
"detail": ("Deletion request cancelled" if had_request else "No pending deletion request found"),
|
||||
"had_pending_request": had_request,
|
||||
},
|
||||
status=status.HTTP_200_OK,
|
||||
@@ -745,7 +720,7 @@ def cancel_account_deletion(request):
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"success": False, "error": f"Error cancelling deletion request: {str(e)}"},
|
||||
{"detail": f"Error cancelling deletion request: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
@@ -753,10 +728,7 @@ def cancel_account_deletion(request):
|
||||
@extend_schema(
|
||||
operation_id="check_user_deletion_eligibility",
|
||||
summary="Check if user can be deleted",
|
||||
description=(
|
||||
"Check if a user can be safely deleted and get a preview of "
|
||||
"what submissions would be preserved."
|
||||
),
|
||||
description=("Check if a user can be safely deleted and get a preview of " "what submissions would be preserved."),
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name="user_id",
|
||||
@@ -792,11 +764,11 @@ def cancel_account_deletion(request):
|
||||
},
|
||||
404: {
|
||||
"description": "User not found",
|
||||
"example": {"success": False, "error": "User not found"},
|
||||
"example": {"detail": "User not found"},
|
||||
},
|
||||
403: {
|
||||
"description": "Permission denied - admin access required",
|
||||
"example": {"success": False, "error": "Admin access required"},
|
||||
"example": {"detail": "Admin access required"},
|
||||
},
|
||||
},
|
||||
tags=["User Management"],
|
||||
@@ -821,27 +793,13 @@ def check_user_deletion_eligibility(request, user_id):
|
||||
|
||||
# Count submissions
|
||||
submission_counts = {
|
||||
"park_reviews": getattr(
|
||||
user, "park_reviews", user.__class__.objects.none()
|
||||
).count(),
|
||||
"ride_reviews": getattr(
|
||||
user, "ride_reviews", user.__class__.objects.none()
|
||||
).count(),
|
||||
"uploaded_park_photos": getattr(
|
||||
user, "uploaded_park_photos", user.__class__.objects.none()
|
||||
).count(),
|
||||
"uploaded_ride_photos": getattr(
|
||||
user, "uploaded_ride_photos", user.__class__.objects.none()
|
||||
).count(),
|
||||
"top_lists": getattr(
|
||||
user, "user_lists", user.__class__.objects.none()
|
||||
).count(),
|
||||
"edit_submissions": getattr(
|
||||
user, "edit_submissions", user.__class__.objects.none()
|
||||
).count(),
|
||||
"photo_submissions": getattr(
|
||||
user, "photo_submissions", user.__class__.objects.none()
|
||||
).count(),
|
||||
"park_reviews": getattr(user, "park_reviews", user.__class__.objects.none()).count(),
|
||||
"ride_reviews": getattr(user, "ride_reviews", user.__class__.objects.none()).count(),
|
||||
"uploaded_park_photos": getattr(user, "uploaded_park_photos", user.__class__.objects.none()).count(),
|
||||
"uploaded_ride_photos": getattr(user, "uploaded_ride_photos", user.__class__.objects.none()).count(),
|
||||
"top_lists": getattr(user, "user_lists", user.__class__.objects.none()).count(),
|
||||
"edit_submissions": getattr(user, "edit_submissions", user.__class__.objects.none()).count(),
|
||||
"photo_submissions": getattr(user, "photo_submissions", user.__class__.objects.none()).count(),
|
||||
}
|
||||
|
||||
total_submissions = sum(submission_counts.values())
|
||||
@@ -865,7 +823,7 @@ def check_user_deletion_eligibility(request, user_id):
|
||||
|
||||
except Exception as e:
|
||||
return Response(
|
||||
{"success": False, "error": f"Error checking user: {str(e)}"},
|
||||
{"detail": f"Error checking user: {str(e)}"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
@@ -912,9 +870,7 @@ def get_user_profile(request):
|
||||
@permission_classes([IsAuthenticated])
|
||||
def update_user_account(request):
|
||||
"""Update basic account information."""
|
||||
serializer = AccountUpdateSerializer(
|
||||
request.user, data=request.data, partial=True, context={"request": request}
|
||||
)
|
||||
serializer = AccountUpdateSerializer(request.user, data=request.data, partial=True, context={"request": request})
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
@@ -944,9 +900,7 @@ def update_user_profile(request):
|
||||
"""Update user profile information."""
|
||||
profile, created = UserProfile.objects.get_or_create(user=request.user)
|
||||
|
||||
serializer = ProfileUpdateSerializer(
|
||||
profile, data=request.data, partial=True, context={"request": request}
|
||||
)
|
||||
serializer = ProfileUpdateSerializer(profile, data=request.data, partial=True, context={"request": request})
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
@@ -1046,9 +1000,7 @@ def update_user_preferences(request):
|
||||
@permission_classes([IsAuthenticated])
|
||||
def update_theme_preference(request):
|
||||
"""Update theme preference."""
|
||||
serializer = ThemePreferenceSerializer(
|
||||
request.user, data=request.data, partial=True
|
||||
)
|
||||
serializer = ThemePreferenceSerializer(request.user, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
@@ -1395,14 +1347,9 @@ def update_top_list(request, list_id):
|
||||
try:
|
||||
top_list = UserList.objects.get(id=list_id, user=request.user)
|
||||
except UserList.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Top list not found"},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"detail": "Top list not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
serializer = UserListSerializer(
|
||||
top_list, data=request.data, partial=True, context={"request": request}
|
||||
)
|
||||
serializer = UserListSerializer(top_list, data=request.data, partial=True, context={"request": request})
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
@@ -1430,10 +1377,7 @@ def delete_top_list(request, list_id):
|
||||
top_list.delete()
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
except UserList.DoesNotExist:
|
||||
return Response(
|
||||
{"error": "Top list not found"},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({"detail": "Top list not found"}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
# === NOTIFICATION ENDPOINTS ===
|
||||
@@ -1453,9 +1397,9 @@ def delete_top_list(request, list_id):
|
||||
@permission_classes([IsAuthenticated])
|
||||
def get_user_notifications(request):
|
||||
"""Get user notifications."""
|
||||
notifications = UserNotification.objects.filter(
|
||||
user=request.user
|
||||
).order_by("-created_at")[:50] # Limit to 50 most recent
|
||||
notifications = UserNotification.objects.filter(user=request.user).order_by("-created_at")[
|
||||
:50
|
||||
] # Limit to 50 most recent
|
||||
|
||||
serializer = UserNotificationSerializer(notifications, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
@@ -1483,19 +1427,16 @@ def mark_notifications_read(request):
|
||||
mark_all = serializer.validated_data.get("mark_all", False)
|
||||
|
||||
if mark_all:
|
||||
UserNotification.objects.filter(
|
||||
user=request.user, is_read=False
|
||||
).update(is_read=True, read_at=timezone.now())
|
||||
UserNotification.objects.filter(user=request.user, is_read=False).update(
|
||||
is_read=True, read_at=timezone.now()
|
||||
)
|
||||
count = UserNotification.objects.filter(user=request.user).count()
|
||||
else:
|
||||
count = UserNotification.objects.filter(
|
||||
id__in=notification_ids, user=request.user, is_read=False
|
||||
).update(is_read=True, read_at=timezone.now())
|
||||
count = UserNotification.objects.filter(id__in=notification_ids, user=request.user, is_read=False).update(
|
||||
is_read=True, read_at=timezone.now()
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"message": f"Marked {count} notifications as read"},
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"detail": f"Marked {count} notifications as read"}, status=status.HTTP_200_OK)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -1544,9 +1485,7 @@ def update_notification_preferences(request):
|
||||
except NotificationPreference.DoesNotExist:
|
||||
preferences = NotificationPreference.objects.create(user=request.user)
|
||||
|
||||
serializer = NotificationPreferenceSerializer(
|
||||
preferences, data=request.data, partial=True
|
||||
)
|
||||
serializer = NotificationPreferenceSerializer(preferences, data=request.data, partial=True)
|
||||
|
||||
if serializer.is_valid():
|
||||
serializer.save()
|
||||
@@ -1578,10 +1517,7 @@ def upload_avatar(request):
|
||||
if serializer.is_valid():
|
||||
# Handle avatar upload logic here
|
||||
# This would typically involve saving the file and updating the user profile
|
||||
return Response(
|
||||
{"message": "Avatar uploaded successfully"},
|
||||
status=status.HTTP_200_OK
|
||||
)
|
||||
return Response({"detail": "Avatar uploaded successfully"}, status=status.HTTP_200_OK)
|
||||
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@@ -1596,8 +1532,8 @@ def upload_avatar(request):
|
||||
"example": {
|
||||
"account": {"username": "user", "email": "user@example.com"},
|
||||
"profile": {"display_name": "User"},
|
||||
"content": {"park_reviews": [], "lists": []}
|
||||
}
|
||||
"content": {"park_reviews": [], "lists": []},
|
||||
},
|
||||
},
|
||||
401: {"description": "Authentication required"},
|
||||
},
|
||||
@@ -1612,10 +1548,7 @@ def export_user_data(request):
|
||||
return Response(export_data, status=status.HTTP_200_OK)
|
||||
except Exception as e:
|
||||
logger.error(f"Error exporting data for user {request.user.id}: {e}", exc_info=True)
|
||||
return Response(
|
||||
{"error": "Failed to generate data export"},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
return Response({"detail": "Failed to generate data export"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
@@ -1690,20 +1623,25 @@ def get_login_history(request):
|
||||
# Serialize
|
||||
results = []
|
||||
for entry in entries:
|
||||
results.append({
|
||||
"id": entry.id,
|
||||
"ip_address": entry.ip_address,
|
||||
"user_agent": entry.user_agent[:100] if entry.user_agent else None, # Truncate long user agents
|
||||
"login_method": entry.login_method,
|
||||
"login_method_display": dict(LoginHistory._meta.get_field('login_method').choices).get(entry.login_method, entry.login_method),
|
||||
"login_timestamp": entry.login_timestamp.isoformat(),
|
||||
"country": entry.country,
|
||||
"city": entry.city,
|
||||
"success": entry.success,
|
||||
})
|
||||
|
||||
return Response({
|
||||
"results": results,
|
||||
"count": len(results),
|
||||
})
|
||||
results.append(
|
||||
{
|
||||
"id": entry.id,
|
||||
"ip_address": entry.ip_address,
|
||||
"user_agent": entry.user_agent[:100] if entry.user_agent else None, # Truncate long user agents
|
||||
"login_method": entry.login_method,
|
||||
"login_method_display": dict(LoginHistory._meta.get_field("login_method").choices).get(
|
||||
entry.login_method, entry.login_method
|
||||
),
|
||||
"login_timestamp": entry.login_timestamp.isoformat(),
|
||||
"country": entry.country,
|
||||
"city": entry.city,
|
||||
"success": entry.success,
|
||||
}
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"results": results,
|
||||
"count": len(results),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user