""" Discussion API forms """ import urllib from django.core.exceptions import ValidationError from django.forms import BooleanField, CharField, ChoiceField, Form, IntegerField from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import CourseLocator from six import text_type from courseware.courses import get_course_with_access from django_comment_common.models import Role, FORUM_ROLE_MODERATOR, FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_GROUP_MODERATOR from openedx.core.djangoapps.util.forms import ExtendedNullBooleanField, MultiValueField class _PaginationForm(Form): """A form that includes pagination fields""" page = IntegerField(required=False, min_value=1) page_size = IntegerField(required=False, min_value=1) def clean_page(self): """Return given valid page or default of 1""" return self.cleaned_data.get("page") or 1 def clean_page_size(self): """Return given valid page_size (capped at 100) or default of 10""" return min(self.cleaned_data.get("page_size") or 10, 100) 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() topic_id = MultiValueField(required=False) text_search = CharField(required=False) following = ExtendedNullBooleanField(required=False) view = ChoiceField( choices=[(choice, choice) for choice in ["unread", "unanswered"]], required=False, ) order_by = ChoiceField( 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 ) requested_fields = MultiValueField(required=False) def clean_order_by(self): """Return a default choice""" return self.cleaned_data.get("order_by") or "last_activity_at" def clean_order_direction(self): """Return a default choice""" return self.cleaned_data.get("order_direction") or "desc" def clean_course_id(self): """Validate course_id""" value = self.cleaned_data["course_id"] try: return CourseLocator.from_string(value) except InvalidKeyError: raise ValidationError(u"'{}' is not a valid course id".format(value)) def clean_following(self): """Validate following""" value = self.cleaned_data["following"] if value is False: raise ValidationError("The value of the 'following' parameter must be true.") else: return value def clean(self): cleaned_data = super(ThreadListGetForm, self).clean() exclusive_params_count = sum( 1 for param in self.EXCLUSIVE_PARAMS if cleaned_data.get(param) ) if exclusive_params_count > 1: raise ValidationError( u"The following query parameters are mutually exclusive: {}".format( ", ".join(self.EXCLUSIVE_PARAMS) ) ) return cleaned_data 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) read = BooleanField(required=False) class CommentListGetForm(_PaginationForm): """ A form to validate query parameters in the comment list retrieval endpoint """ thread_id = CharField() endorsed = ExtendedNullBooleanField(required=False) requested_fields = MultiValueField(required=False) 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) 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') super(CourseDiscussionSettingsForm, self).__init__(*args, **kwargs) def clean_course_id(self): """Validate the 'course_id' value""" 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, 'staff', course_key) self.cleaned_data['course_key'] = course_key return course_id except InvalidKeyError: raise ValidationError("'{}' is not a valid course key".format(text_type(course_id))) 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( ROLE_CHOICES, error_messages={"invalid_choice": "Role '%(value)s' does not exist"} ) def clean_rolename(self): """Validate the 'rolename' value.""" rolename = urllib.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: raise ValidationError("Role '{}' does not exist".format(rolename)) self.cleaned_data['role'] = role return rolename