mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-20 11:11:10 -05:00
Refactor user account system and remove moderation integration
- Remove first_name and last_name fields from User model - Add user deletion and social provider services - Restructure auth serializers into separate directory - Update avatar upload functionality and API endpoints - Remove django-moderation integration documentation - Add mandatory compliance enforcement rules - Update frontend documentation with API usage examples
This commit is contained in:
@@ -6,23 +6,6 @@ user deletion while preserving submissions, profile management, settings,
|
||||
preferences, privacy, notifications, and security.
|
||||
"""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.permissions import AllowAny
|
||||
from django.utils import timezone
|
||||
from apps.accounts.models import (
|
||||
User,
|
||||
UserProfile,
|
||||
TopList,
|
||||
UserNotification,
|
||||
NotificationPreference,
|
||||
)
|
||||
from apps.accounts.services import UserDeletionService
|
||||
from apps.api.v1.serializers.accounts import (
|
||||
CompleteUserSerializer,
|
||||
UserPreferencesSerializer,
|
||||
@@ -39,6 +22,27 @@ from apps.api.v1.serializers.accounts import (
|
||||
MarkNotificationsReadSerializer,
|
||||
AvatarUploadSerializer,
|
||||
)
|
||||
from apps.accounts.services import UserDeletionService
|
||||
from apps.accounts.models import (
|
||||
User,
|
||||
UserProfile,
|
||||
TopList,
|
||||
UserNotification,
|
||||
NotificationPreference,
|
||||
)
|
||||
import logging
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import api_view, permission_classes
|
||||
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework.permissions import AllowAny
|
||||
from django.utils import timezone
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@extend_schema(
|
||||
@@ -106,7 +110,7 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
Delete a user while preserving all their submissions.
|
||||
|
||||
This endpoint allows administrators to delete user accounts while
|
||||
preserving all user-generated content (reviews, photos, top lists, etc.).
|
||||
preserving all user - generated content(reviews, photos, top lists, etc.).
|
||||
All submissions are transferred to a system "deleted_user" placeholder.
|
||||
|
||||
**Admin Only**: This endpoint requires admin permissions.
|
||||
@@ -119,14 +123,71 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
# Check if user can be deleted
|
||||
can_delete, reason = UserDeletionService.can_delete_user(user)
|
||||
if not can_delete:
|
||||
# Log the attempt for security monitoring
|
||||
logger.warning(
|
||||
f"Admin user {request.user.username} attempted to delete protected user {user.username} (ID: {user_id}). Reason: {reason}",
|
||||
extra={
|
||||
"admin_user": request.user.username,
|
||||
"target_user": user.username,
|
||||
"target_user_id": user_id,
|
||||
"is_superuser": user.is_superuser,
|
||||
"user_role": user.role,
|
||||
"rejection_reason": reason,
|
||||
}
|
||||
)
|
||||
|
||||
# Determine error code based on reason
|
||||
error_code = "DELETION_FORBIDDEN"
|
||||
if "superuser" in reason.lower():
|
||||
error_code = "SUPERUSER_DELETION_FORBIDDEN"
|
||||
elif "admin" in reason.lower():
|
||||
error_code = "ADMIN_DELETION_FORBIDDEN"
|
||||
elif "system" in reason.lower():
|
||||
error_code = "SYSTEM_USER_DELETION_FORBIDDEN"
|
||||
|
||||
return Response(
|
||||
{"success": False, "error": f"Cannot delete user: {reason}"},
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Cannot delete user: {reason}",
|
||||
"error_code": error_code,
|
||||
"user_info": {
|
||||
"username": user.username,
|
||||
"user_id": user.user_id,
|
||||
"role": user.role,
|
||||
"is_superuser": user.is_superuser,
|
||||
"is_staff": user.is_staff,
|
||||
},
|
||||
"help_text": "Contact system administrator if you need to delete this account type.",
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
# Log the successful deletion attempt
|
||||
logger.info(
|
||||
f"Admin user {request.user.username} is deleting user {user.username} (ID: {user_id})",
|
||||
extra={
|
||||
"admin_user": request.user.username,
|
||||
"target_user": user.username,
|
||||
"target_user_id": user_id,
|
||||
"action": "user_deletion",
|
||||
}
|
||||
)
|
||||
|
||||
# Perform the deletion
|
||||
result = UserDeletionService.delete_user_preserve_submissions(user)
|
||||
|
||||
# Log successful deletion
|
||||
logger.info(
|
||||
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_id": user_id,
|
||||
"preserved_submissions": result['preserved_submissions'],
|
||||
"action": "user_deletion_completed",
|
||||
}
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
@@ -137,8 +198,25 @@ def delete_user_preserve_submissions(request, user_id):
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
# Log the error for debugging
|
||||
logger.error(
|
||||
f"Error deleting user {user_id} by admin {request.user.username}: {str(e)}",
|
||||
extra={
|
||||
"admin_user": request.user.username,
|
||||
"target_user_id": user_id,
|
||||
"error": str(e),
|
||||
"action": "user_deletion_error",
|
||||
},
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"success": False, "error": f"Error deleting user: {str(e)}"},
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Error deleting user: {str(e)}",
|
||||
"error_code": "DELETION_ERROR",
|
||||
"help_text": "Please try again or contact system administrator if the problem persists.",
|
||||
},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
@@ -185,7 +263,7 @@ def request_account_deletion(request):
|
||||
account. A verification code will be sent to their email address, and the
|
||||
account will only be deleted after they provide the correct code.
|
||||
|
||||
**Authentication Required**: User must be logged in.
|
||||
**Authentication Required**: User must be logged in .
|
||||
|
||||
**Email Verification**: A verification code is sent to the user's email.
|
||||
|
||||
@@ -197,6 +275,17 @@ def request_account_deletion(request):
|
||||
# Create deletion request and send email
|
||||
deletion_request = UserDeletionService.request_user_deletion(user)
|
||||
|
||||
# Log the self-service deletion request
|
||||
logger.info(
|
||||
f"User {user.username} (ID: {user.user_id}) requested account deletion",
|
||||
extra={
|
||||
"user": user.username,
|
||||
"user_id": user.user_id,
|
||||
"email": user.email,
|
||||
"action": "self_deletion_request",
|
||||
}
|
||||
)
|
||||
|
||||
return Response(
|
||||
{
|
||||
"success": True,
|
||||
@@ -208,12 +297,65 @@ def request_account_deletion(request):
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
# Log the rejection for security monitoring
|
||||
logger.warning(
|
||||
f"User {request.user.username} (ID: {request.user.user_id}) attempted self-deletion but was rejected: {str(e)}",
|
||||
extra={
|
||||
"user": request.user.username,
|
||||
"user_id": request.user.user_id,
|
||||
"is_superuser": request.user.is_superuser,
|
||||
"user_role": request.user.role,
|
||||
"rejection_reason": str(e),
|
||||
"action": "self_deletion_rejected",
|
||||
}
|
||||
)
|
||||
|
||||
# Determine error code based on reason
|
||||
error_message = str(e)
|
||||
error_code = "DELETION_FORBIDDEN"
|
||||
if "superuser" in error_message.lower():
|
||||
error_code = "SUPERUSER_DELETION_FORBIDDEN"
|
||||
elif "admin" in error_message.lower():
|
||||
error_code = "ADMIN_DELETION_FORBIDDEN"
|
||||
elif "system" in error_message.lower():
|
||||
error_code = "SYSTEM_USER_DELETION_FORBIDDEN"
|
||||
|
||||
return Response(
|
||||
{"success": False, "error": str(e)}, status=status.HTTP_400_BAD_REQUEST
|
||||
{
|
||||
"success": False,
|
||||
"error": error_message,
|
||||
"error_code": error_code,
|
||||
"user_info": {
|
||||
"username": request.user.username,
|
||||
"user_id": request.user.user_id,
|
||||
"role": request.user.role,
|
||||
"is_superuser": request.user.is_superuser,
|
||||
"is_staff": request.user.is_staff,
|
||||
},
|
||||
"help_text": "Superuser and admin accounts cannot be self-deleted for security reasons. Contact system administrator if you need to delete this account.",
|
||||
},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
except Exception as e:
|
||||
# Log the error for debugging
|
||||
logger.error(
|
||||
f"Error creating deletion request for user {request.user.username} (ID: {request.user.user_id}): {str(e)}",
|
||||
extra={
|
||||
"user": request.user.username,
|
||||
"user_id": request.user.user_id,
|
||||
"error": str(e),
|
||||
"action": "self_deletion_error",
|
||||
},
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
return Response(
|
||||
{"success": False, "error": f"Error creating deletion request: {str(e)}"},
|
||||
{
|
||||
"success": False,
|
||||
"error": f"Error creating deletion request: {str(e)}",
|
||||
"error_code": "DELETION_REQUEST_ERROR",
|
||||
"help_text": "Please try again or contact support if the problem persists.",
|
||||
},
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
|
||||
@@ -1279,7 +1421,7 @@ def get_user_notifications(request):
|
||||
unread_count = UserNotification.objects.filter(user=user, is_read=False).count()
|
||||
|
||||
# Apply pagination
|
||||
notifications = queryset[offset : offset + limit]
|
||||
notifications = queryset[offset: offset + limit]
|
||||
|
||||
# Build pagination URLs
|
||||
request_url = request.build_absolute_uri().split("?")[0]
|
||||
@@ -1517,11 +1659,13 @@ def upload_avatar(request):
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Upload avatar - Error saving to profile: {e}")
|
||||
return Response(
|
||||
{"success": False, "error": f"Failed to upload avatar: {str(e)}"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
print(f"Upload avatar - Serializer errors: {serializer.errors}")
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user