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
1,272 changes: 966 additions & 306 deletions lms/djangoapps/discussion/rest_api/api.py

Large diffs are not rendered by default.

73 changes: 49 additions & 24 deletions lms/djangoapps/discussion/rest_api/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Discussion API forms
"""

import urllib.parse

from django.core.exceptions import ValidationError
Expand All @@ -22,13 +23,15 @@


class UserOrdering(TextChoices):
BY_ACTIVITY = 'activity'
BY_FLAGS = 'flagged'
BY_RECENT_ACTIVITY = 'recency'
BY_ACTIVITY = "activity"
BY_FLAGS = "flagged"
BY_RECENT_ACTIVITY = "recency"
BY_DELETED = "deleted"


class _PaginationForm(Form):
"""A form that includes pagination fields"""

page = IntegerField(required=False, min_value=1)
page_size = IntegerField(required=False, min_value=1)

Expand All @@ -45,6 +48,7 @@ class ThreadListGetForm(_PaginationForm):
"""
A form to validate query parameters in the thread list retrieval endpoint
"""

EXCLUSIVE_PARAMS = ["topic_id", "text_search", "following"]

course_id = CharField()
Expand All @@ -58,17 +62,22 @@ class ThreadListGetForm(_PaginationForm):
)
count_flagged = ExtendedNullBooleanField(required=False)
flagged = ExtendedNullBooleanField(required=False)
show_deleted = ExtendedNullBooleanField(required=False)
view = ChoiceField(
choices=[(choice, choice) for choice in ["unread", "unanswered", "unresponded"]],
choices=[
(choice, choice) for choice in ["unread", "unanswered", "unresponded"]
],
required=False,
)
order_by = ChoiceField(
choices=[(choice, choice) for choice in ["last_activity_at", "comment_count", "vote_count"]],
required=False
choices=[
(choice, choice)
for choice in ["last_activity_at", "comment_count", "vote_count"]
],
required=False,
)
order_direction = ChoiceField(
choices=[(choice, choice) for choice in ["desc"]],
required=False
choices=[(choice, choice) for choice in ["desc"]], required=False
)
requested_fields = MultiValueField(required=False)

Expand All @@ -85,14 +94,16 @@ def clean_course_id(self):
value = self.cleaned_data["course_id"]
try:
return CourseLocator.from_string(value)
except InvalidKeyError:
raise ValidationError(f"'{value}' is not a valid course id") # lint-amnesty, pylint: disable=raise-missing-from
except InvalidKeyError as e:
raise ValidationError(f"'{value}' is not a valid course id") from e

def clean_following(self):
"""Validate following"""
value = self.cleaned_data["following"]
if value is False: # lint-amnesty, pylint: disable=no-else-raise
raise ValidationError("The value of the 'following' parameter must be true.")
raise ValidationError(
"The value of the 'following' parameter must be true."
)
else:
return value

Expand All @@ -115,6 +126,7 @@ class ThreadActionsForm(Form):
A form to handle fields in thread creation/update that require separate
interactions with the comments service.
"""

following = BooleanField(required=False)
voted = BooleanField(required=False)
abuse_flagged = BooleanField(required=False)
Expand All @@ -126,17 +138,20 @@ class CommentListGetForm(_PaginationForm):
"""
A form to validate query parameters in the comment list retrieval endpoint
"""

thread_id = CharField()
flagged = BooleanField(required=False)
endorsed = ExtendedNullBooleanField(required=False)
requested_fields = MultiValueField(required=False)
merge_question_type_responses = BooleanField(required=False)
show_deleted = ExtendedNullBooleanField(required=False)


class UserCommentListGetForm(_PaginationForm):
"""
A form to validate query parameters in the comment list retrieval endpoint
"""

course_id = CharField()
flagged = BooleanField(required=False)
requested_fields = MultiValueField(required=False)
Expand All @@ -146,15 +161,16 @@ def clean_course_id(self):
value = self.cleaned_data["course_id"]
try:
return CourseLocator.from_string(value)
except InvalidKeyError:
raise ValidationError(f"'{value}' is not a valid course id") # lint-amnesty, pylint: disable=raise-missing-from
except InvalidKeyError as e:
raise ValidationError(f"'{value}' is not a valid course id") from e


class CommentActionsForm(Form):
"""
A form to handle fields in comment creation/update that require separate
interactions with the comments service.
"""

voted = BooleanField(required=False)
abuse_flagged = BooleanField(required=False)

Expand All @@ -163,72 +179,81 @@ class CommentGetForm(_PaginationForm):
"""
A form to validate query parameters in the comment retrieval endpoint
"""

requested_fields = MultiValueField(required=False)


class CourseDiscussionSettingsForm(Form):
"""
A form to validate the fields in the course discussion settings requests.
"""

course_id = CharField()

def __init__(self, *args, **kwargs):
self.request_user = kwargs.pop('request_user')
self.request_user = kwargs.pop("request_user")
super().__init__(*args, **kwargs)

def clean_course_id(self):
"""Validate the 'course_id' value"""
course_id = self.cleaned_data['course_id']
course_id = self.cleaned_data["course_id"]
try:
course_key = CourseKey.from_string(course_id)
self.cleaned_data['course'] = get_course_with_access(self.request_user, 'load', course_key)
self.cleaned_data['course_key'] = course_key
self.cleaned_data["course"] = get_course_with_access(
self.request_user, "load", course_key
)
self.cleaned_data["course_key"] = course_key
return course_id
except InvalidKeyError:
raise ValidationError(f"'{str(course_id)}' is not a valid course key") # lint-amnesty, pylint: disable=raise-missing-from
except InvalidKeyError as e:
raise ValidationError(
f"'{str(course_id)}' is not a valid course key"
) from e


class CourseDiscussionRolesForm(CourseDiscussionSettingsForm):
"""
A form to validate the fields in the course discussion roles requests.
"""

ROLE_CHOICES = (
(FORUM_ROLE_MODERATOR, FORUM_ROLE_MODERATOR),
(FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_MODERATOR),
(FORUM_ROLE_GROUP_MODERATOR, FORUM_ROLE_GROUP_MODERATOR),
)
rolename = ChoiceField(
choices=ROLE_CHOICES,
error_messages={"invalid_choice": "Role '%(value)s' does not exist"}
error_messages={"invalid_choice": "Role '%(value)s' does not exist"},
)

def clean_rolename(self):
"""Validate the 'rolename' value."""
rolename = urllib.parse.unquote(self.cleaned_data.get('rolename'))
course_id = self.cleaned_data.get('course_key')
rolename = urllib.parse.unquote(self.cleaned_data.get("rolename"))
course_id = self.cleaned_data.get("course_key")
if course_id and rolename:
try:
role = Role.objects.get(name=rolename, course_id=course_id)
except Role.DoesNotExist as err:
raise ValidationError(f"Role '{rolename}' does not exist") from err

self.cleaned_data['role'] = role
self.cleaned_data["role"] = role
return rolename


class TopicListGetForm(Form):
"""
Form for the topics API get query parameters.
"""

topic_id = CharField(required=False)
order_by = ChoiceField(choices=TopicOrdering.choices, required=False)

def clean_topic_id(self):
topic_ids = self.cleaned_data.get("topic_id", None)
return set(topic_ids.strip(',').split(',')) if topic_ids else None
return set(topic_ids.strip(",").split(",")) if topic_ids else None


class CourseActivityStatsForm(_PaginationForm):
"""Form for validating course activity stats API query parameters"""

order_by = ChoiceField(choices=UserOrdering.choices, required=False)
username = CharField(required=False)
Loading
Loading