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
3 changes: 2 additions & 1 deletion lms/djangoapps/discussion/rest_api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from django.http import Http404
from django.urls import reverse
from django.utils.html import strip_tags
from edx_django_utils.monitoring import function_trace
from edx_django_utils.monitoring import function_trace, set_custom_attribute
from opaque_keys import InvalidKeyError
from opaque_keys.edx.locator import CourseKey
from pytz import UTC
Expand Down Expand Up @@ -1905,6 +1905,7 @@ def create_thread(request, thread_data):
)
serializer.save()
cc_thread = serializer.instance
set_custom_attribute("forum.entity_id", str(cc_thread.id))
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.
# Use send_signal_after_commit() to ensure the signal is sent only after the transaction commits.
send_signal_after_commit(
lambda: thread_created.send(sender=None, user=user, post=cc_thread, notify_all_learners=notify_all_learners)
Expand Down
108 changes: 83 additions & 25 deletions lms/djangoapps/discussion/rest_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@
from django.core.exceptions import BadRequest, ValidationError
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from edx_django_utils.monitoring import set_custom_attribute
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import (
SessionAuthenticationAllowInactiveUser,
)
from opaque_keys.edx.keys import CourseKey
from rest_framework import permissions, status
from rest_framework.authentication import SessionAuthentication
from rest_framework.exceptions import ParseError, UnsupportedMediaType
from rest_framework.exceptions import (
ParseError,
PermissionDenied,
UnsupportedMediaType,
)
from rest_framework.parsers import JSONParser
from rest_framework.response import Response
from rest_framework.views import APIView
Expand Down Expand Up @@ -120,6 +125,23 @@
User = get_user_model()


def _discussion_error_type(exc):
"""Map common discussion exceptions to a stable Datadog error type."""
if isinstance(exc, PermissionError):
return "permission_denied"
if isinstance(exc, PermissionDenied):
return "permission_denied"
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.
if isinstance(exc, InvalidKeyError):
return "validation_error"
if isinstance(exc, ValidationError):
return "validation_error"
if isinstance(exc, ParseError):
return "validation_error"
if isinstance(exc, UnsupportedMediaType):
return "validation_error"
return "backend_error"


@view_auth_classes()
class CourseView(DeveloperErrorViewMixin, APIView):
"""
Expand Down Expand Up @@ -713,35 +735,71 @@ def create(self, request):
Implements the POST method for the list endpoint as described in the
class docstring.
"""
if not request.data.get("course_id"):
raise ValidationError({"course_id": ["This field is required."]})
course_key_str = request.data.get("course_id")
course_key = CourseKey.from_string(course_key_str)
set_custom_attribute("forum.operation", "thread.create")
set_custom_attribute("forum.course_id", request.data.get("course_id", ""))
set_custom_attribute("forum.entity_type", "thread")
set_custom_attribute("forum.actor_id", str(getattr(request.user, "id", "")))

if is_content_creation_rate_limited(request, course_key=course_key):
return Response(
"Too many requests", status=status.HTTP_429_TOO_MANY_REQUESTS
)
if request.data.get("type"):
set_custom_attribute("forum.thread_type", request.data.get("type"))
if request.data.get("topic_id"):
set_custom_attribute("forum.commentable_id", request.data.get("topic_id"))
if request.data.get("group_id") is not None:
set_custom_attribute("forum.group_id", str(request.data.get("group_id")))
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.

if is_captcha_enabled(course_key) and is_only_student(course_key, request.user):
captcha_token = request.data.get("captcha_token")
if not captcha_token:
raise ValidationError({"captcha_token": "This field is required."})
try:
if not request.data.get("course_id"):
raise ValidationError({"course_id": ["This field is required."]})
course_key_str = request.data.get("course_id")
course_key = CourseKey.from_string(course_key_str)

if is_content_creation_rate_limited(request, course_key=course_key):
set_custom_attribute("forum.result", "error")
set_custom_attribute(
"forum.http_status", str(status.HTTP_429_TOO_MANY_REQUESTS)
)
set_custom_attribute("forum.error_type", "rate_limited")
return Response(
"Too many requests", status=status.HTTP_429_TOO_MANY_REQUESTS
)

if not verify_recaptcha_token(captcha_token):
return Response({"error": "CAPTCHA verification failed."}, status=400)
if is_captcha_enabled(course_key) and is_only_student(
course_key, request.user
):
captcha_token = request.data.get("captcha_token")
if not captcha_token:
raise ValidationError({"captcha_token": "This field is required."})

if not verify_recaptcha_token(captcha_token):
set_custom_attribute("forum.result", "error")
set_custom_attribute(
"forum.http_status", str(status.HTTP_400_BAD_REQUEST)
)
set_custom_attribute("forum.error_type", "validation_error")
return Response(
{"error": "CAPTCHA verification failed."},
status=status.HTTP_400_BAD_REQUEST,
)

if (
ONLY_VERIFIED_USERS_CAN_POST.is_enabled(course_key)
and not request.user.is_active
):
raise ValidationError(
{"detail": "Only verified users can post in discussions."}
)
if (
ONLY_VERIFIED_USERS_CAN_POST.is_enabled(course_key)
and not request.user.is_active
):
raise ValidationError(
{"detail": "Only verified users can post in discussions."}
)

data = request.data.copy()
data.pop("captcha_token", None)
return Response(create_thread(request, data))
data = request.data.copy()
data.pop("captcha_token", None)
response = Response(create_thread(request, data))
set_custom_attribute("forum.result", "success")
set_custom_attribute("forum.http_status", str(response.status_code))
return response
except Exception as exc:
set_custom_attribute("forum.result", "error")
set_custom_attribute("forum.http_status", str(status.HTTP_400_BAD_REQUEST))
set_custom_attribute("forum.error_type", _discussion_error_type(exc))
Comment thread
santhosh-apphelix-2u marked this conversation as resolved.
raise

def partial_update(self, request, thread_id):
"""
Expand Down
Loading