Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 121 additions & 3 deletions forum/views/bans.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"""

import logging
from typing import Any

from django.contrib.auth import get_user_model
from rest_framework import status
Expand All @@ -19,11 +20,25 @@
BanUserSerializer,
UnbanUserSerializer,
)
from forum.views.telemetry import (
forum_error_type,
set_custom_attribute,
set_forum_trace_context,
set_forum_trace_outcome,
)

User = get_user_model()
log = logging.getLogger(__name__)


def _get_request_value(data: Any, key: str) -> str:
"""Return request data value safely for telemetry context."""
if not hasattr(data, "get"):
return ""
value = data.get(key)
return str(value) if value is not None else ""


class BanUserAPIView(APIView):
"""
API View to ban a user from discussions.
Expand Down Expand Up @@ -58,9 +73,26 @@ class BanUserAPIView(APIView):

def post(self, request: Request) -> Response:
"""Ban a user from discussions."""
request_data = request.data
set_forum_trace_context(
request,
"moderation.ban_user",
"user",
entity_id=_get_request_value(request_data, "user_id"),
course_id=_get_request_value(request_data, "course_id"),
actor_id=_get_request_value(request_data, "banned_by_id"),
data=request_data,
)
scope = _get_request_value(request_data, "scope")
if scope:
set_custom_attribute("forum.scope", scope)
serializer = BanUserSerializer(data=request.data)

if not serializer.is_valid():
set_forum_trace_outcome(
status.HTTP_400_BAD_REQUEST,
"validation_error",
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

try:
Expand All @@ -72,15 +104,33 @@ def post(self, request: Request) -> Response:
banned_by = User.objects.get(id=banned_by_id)

ban_data = ban_user(user=user, banned_by=banned_by, **validated_data)
set_custom_attribute("forum.entity_id", str(user_id))
set_custom_attribute(
"forum.course_id", str(ban_data.get("course_id") or "")
)
set_custom_attribute("forum.scope", str(ban_data.get("scope") or ""))
set_forum_trace_outcome(status.HTTP_201_CREATED)
return Response(ban_data, status=status.HTTP_201_CREATED)
except (ValueError, TypeError) as e:
set_forum_trace_outcome(
status.HTTP_400_BAD_REQUEST,
forum_error_type(e),
)
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except User.DoesNotExist:
except User.DoesNotExist as error:
set_forum_trace_outcome(
status.HTTP_404_NOT_FOUND,
forum_error_type(error),
)
return Response(
{"error": "User not found"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e: # pylint: disable=broad-exception-caught
log.exception("Error banning user: %s", str(e))
set_forum_trace_outcome(
status.HTTP_500_INTERNAL_SERVER_ERROR,
forum_error_type(e),
)
return Response(
{"error": "Failed to ban user"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
Expand Down Expand Up @@ -114,9 +164,22 @@ class UnbanUserAPIView(APIView):

def post(self, request: Request, ban_id: int) -> Response:
"""Unban a user from discussions."""
request_data = request.data
set_forum_trace_context(
request,
"moderation.unban_user",
"user",
course_id=_get_request_value(request_data, "course_id"),
actor_id=_get_request_value(request_data, "unbanned_by_id"),
data=request_data,
)
serializer = UnbanUserSerializer(data=request.data)

if not serializer.is_valid():
set_forum_trace_outcome(
status.HTTP_400_BAD_REQUEST,
"validation_error",
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

try:
Expand All @@ -128,24 +191,56 @@ def post(self, request: Request, ban_id: int) -> Response:
unban_data = unban_user(
ban_id=ban_id, unbanned_by=unbanned_by, **validated_data
)
ban_data = unban_data.get("ban") or {}
user_data = ban_data.get("user") or {}
set_custom_attribute("forum.entity_id", str(user_data.get("id", "")))
set_custom_attribute(
"forum.course_id", str(ban_data.get("course_id") or "")
)
set_custom_attribute("forum.scope", str(ban_data.get("scope") or ""))
set_forum_trace_outcome(status.HTTP_200_OK)
return Response(unban_data, status=status.HTTP_200_OK)
except ValueError as e:
if "not found" in str(e).lower():
set_forum_trace_outcome(
status.HTTP_404_NOT_FOUND,
forum_error_type(e),
)
return Response({"error": str(e)}, status=status.HTTP_404_NOT_FOUND)
set_forum_trace_outcome(
status.HTTP_400_BAD_REQUEST,
forum_error_type(e),
)
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except TypeError as e:
set_forum_trace_outcome(
status.HTTP_400_BAD_REQUEST,
forum_error_type(e),
)
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except DiscussionBan.DoesNotExist:
except DiscussionBan.DoesNotExist as error:
set_forum_trace_outcome(
status.HTTP_404_NOT_FOUND,
forum_error_type(error),
)
return Response(
{"error": f"Active ban with id {ban_id} not found"},
status=status.HTTP_404_NOT_FOUND,
)
except User.DoesNotExist:
except User.DoesNotExist as error:
set_forum_trace_outcome(
status.HTTP_404_NOT_FOUND,
forum_error_type(error),
)
return Response(
{"error": "Moderator user not found"}, status=status.HTTP_404_NOT_FOUND
)
except Exception as e: # pylint: disable=broad-exception-caught
log.exception("Error unbanning user: %s", str(e))
set_forum_trace_outcome(
status.HTTP_500_INTERNAL_SERVER_ERROR,
forum_error_type(e),
)
return Response(
{"error": "Failed to unban user"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
Expand Down Expand Up @@ -185,19 +280,42 @@ class BannedUsersAPIView(APIView):

def get(self, request: Request) -> Response:
"""Get list of banned users."""
request_params = request.query_params.dict()
set_forum_trace_context(
request,
"moderation.list_banned_users",
"user",
course_id=request_params.get("course_id", ""),
data=request_params,
)
if request_params.get("scope") is not None:
set_custom_attribute("forum.scope", str(request_params.get("scope")))
serializer = BannedUsersListSerializer(data=request.query_params)

if not serializer.is_valid():
set_forum_trace_outcome(
status.HTTP_400_BAD_REQUEST,
"validation_error",
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

try:
banned_users = get_banned_users(**serializer.validated_data)
response_serializer = BannedUserResponseSerializer(banned_users, many=True)
set_forum_trace_outcome(status.HTTP_200_OK)
return Response(response_serializer.data, status=status.HTTP_200_OK)
except (ValueError, TypeError) as e:
set_forum_trace_outcome(
status.HTTP_400_BAD_REQUEST,
forum_error_type(e),
)
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e: # pylint: disable=broad-exception-caught
log.exception("Error fetching banned users: %s", str(e))
set_forum_trace_outcome(
status.HTTP_500_INTERNAL_SERVER_ERROR,
forum_error_type(e),
)
return Response(
{"error": "Failed to fetch banned users"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
Expand Down
Loading
Loading