Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
102 changes: 78 additions & 24 deletions lms/djangoapps/discussion/rest_api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
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,
Expand Down Expand Up @@ -120,6 +121,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 +731,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