Exemple #1
0
class CourseListGetForm(UsernameValidatorMixin, Form):
    """
    A form to validate query parameters in the course list retrieval endpoint
    """
    username = CharField(required=False)
    org = CharField(required=False)

    # white list of all supported filter fields
    filter_type = namedtuple('filter_type', ['param_name', 'field_name'])
    supported_filters = [
        filter_type(param_name='mobile', field_name='mobile_available'),
        filter_type(param_name='include_hidden', field_name='include_hidden'),
    ]
    mobile = ExtendedNullBooleanField(required=False)
    include_hidden = ExtendedNullBooleanField(required=False)

    def clean(self):
        """
        Return cleaned data, including additional filters.
        """
        cleaned_data = super(CourseListGetForm, self).clean()

        # create a filter for all supported filter fields
        filter_ = dict()
        for supported_filter in self.supported_filters:
            if cleaned_data.get(supported_filter.param_name) is not None:
                filter_[supported_filter.field_name] = cleaned_data[supported_filter.param_name]
        cleaned_data['filter_'] = filter_ or None

        return cleaned_data
Exemple #2
0
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)
Exemple #3
0
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 CourseListGetForm(UsernameValidatorMixin, Form):
    """
    A form to validate query parameters in the course list retrieval endpoint
    """
    search_term = CharField(required=False)
    username = CharField(required=False)
    org = CharField(required=False)
    ids = CharField(required=False)

    # white list of all supported filter fields
    filter_type = namedtuple('filter_type', ['param_name', 'field_name'])
    supported_filters = [
        filter_type(param_name='mobile', field_name='mobile_available'),
        filter_type(param_name='ids', field_name='id__in'),
    ]
    mobile = ExtendedNullBooleanField(required=False)

    def clean_ids(self):
        ids = self.cleaned_data.get('ids', '')
        if not ids:
            return None

        ids = ids.replace(' ', '+')

        if ids.endswith(','):
            ids = ids[:-1]
        ids = ids.split(',')

        ids = list(map(lambda course_id: CourseKey.from_string(course_id),
                       ids))
        return ids

    def clean(self):
        """
        Return cleaned data, including additional filters.
        """
        cleaned_data = super(CourseListGetForm, self).clean()

        # create a filter for all supported filter fields
        filter_ = dict()
        for supported_filter in self.supported_filters:
            if cleaned_data.get(supported_filter.param_name) is not None:
                filter_[supported_filter.field_name] = cleaned_data[
                    supported_filter.param_name]
        cleaned_data['filter_'] = filter_ or None

        return cleaned_data
Exemple #5
0
class BlockListGetForm(Form):
    """
    A form to validate query parameters in the block list retrieval endpoint
    """
    all_blocks = ExtendedNullBooleanField(required=False)
    block_counts = MultiValueField(required=False)
    depth = CharField(required=False)
    nav_depth = IntegerField(required=False, min_value=0)
    requested_fields = MultiValueField(required=False)
    return_type = ChoiceField(
        required=False,
        choices=[(choice, choice) for choice in ['dict', 'list']],
    )
    student_view_data = MultiValueField(required=False)
    usage_key = CharField(required=True)
    username = CharField(required=False)
    block_types_filter = MultiValueField(required=False)

    def clean_depth(self):
        """
        Get the appropriate depth.  No provided value will be treated as a
        depth of 0, while a value of "all" will be treated as unlimited depth.
        """
        value = self.cleaned_data['depth']
        if not value:
            return 0
        elif value == "all":
            return None
        try:
            return int(value)
        except ValueError:
            raise ValidationError("'{}' is not a valid depth value.".format(value))

    def clean_requested_fields(self):
        """
        Return a set of `requested_fields`, merged with defaults of `type`
        and `display_name`
        """
        requested_fields = self.cleaned_data['requested_fields']

        # add default requested_fields
        return (requested_fields or set()) | {'type', 'display_name'}

    def clean_return_type(self):
        """
        Return valid 'return_type' or default value of 'dict'
        """
        return self.cleaned_data['return_type'] or 'dict'

    def clean_usage_key(self):
        """
        Ensure a valid `usage_key` was provided.
        """
        usage_key = self.cleaned_data['usage_key']

        try:
            usage_key = UsageKey.from_string(usage_key)
        except InvalidKeyError:
            raise ValidationError("'{}' is not a valid usage key.".format(str(usage_key)))

        return usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))

    def clean(self):
        """
        Return cleaned data, including additional requested fields.
        """
        cleaned_data = super(BlockListGetForm, self).clean()

        # Add additional requested_fields that are specified as separate
        # parameters, if they were requested.
        additional_requested_fields = [
            'student_view_data',
            'block_counts',
            'nav_depth',
            'block_types_filter',
        ]
        for additional_field in additional_requested_fields:
            field_value = cleaned_data.get(additional_field)
            if field_value or field_value == 0:  # allow 0 as a requested value
                cleaned_data['requested_fields'].add(additional_field)

        usage_key = cleaned_data.get('usage_key')
        if not usage_key:
            return

        cleaned_data['user'] = self._clean_requested_user(cleaned_data, usage_key.course_key)
        return cleaned_data

    def clean_username(self):
        """
        Return cleaned username.

        Overrides the default behaviour that maps an empty string to None. This
        allows us to differentiate between no username being provided (None) vs
        an empty username being provided ('').
        """
        # In case all_blocks is specified, ignore the username.
        if self.cleaned_data.get('all_blocks', False):
            return None

        # See if 'username' was provided as a parameter in the raw data.
        # If so, we return the already-cleaned version of that, otherwise we
        # return None
        if 'username' in self.data:
            return self.cleaned_data['username']
        return None

    def _clean_requested_user(self, cleaned_data, course_key):
        """
        Validates and returns the requested_user, while checking permissions.
        """
        requesting_user = self.initial['requesting_user']
        requested_username = cleaned_data.get('username', None)
        all_blocks = cleaned_data.get('all_blocks', False)

        if requested_username is None and not all_blocks:
            raise ValidationError({'username': ["This field is required unless all_blocks is requested."]})

        if requesting_user.is_anonymous:
            return self._verify_anonymous_user(requested_username, course_key, all_blocks)

        if all_blocks:
            return self._verify_all_blocks(requesting_user, course_key)
        elif requesting_user.username.lower() == requested_username.lower():
            return self._verify_requesting_user(requesting_user, course_key)
        else:
            return self._verify_other_user(requesting_user, requested_username, course_key)

    @staticmethod
    def _verify_anonymous_user(username, course_key, all_blocks):
        """
        Verifies form for when the requesting user is anonymous.
        """
        if all_blocks:
            raise PermissionDenied(
                "Anonymous users do not have permission to access all blocks in '{course_key}'.".format(
                    course_key=str(course_key),
                )
            )

        # Check for '' and explicitly '' since the only valid option for anonymous users is
        # an empty string that corresponds to an anonymous user.
        if username != '':
            raise PermissionDenied("Anonymous users cannot access another user's blocks.")

        if not permissions.is_course_public(course_key):
            raise PermissionDenied(
                "Course blocks for '{course_key}' cannot be accessed anonymously.".format(
                    course_key=course_key,
                )
            )

        return AnonymousUser()

    @staticmethod
    def _verify_all_blocks(requesting_user, course_key):  # pylint: disable=useless-return
        """
        Verifies form for when no username is specified, including permissions.
        """
        # Verify all blocks can be accessed for the course.
        if not permissions.can_access_all_blocks(requesting_user, course_key):
            raise PermissionDenied(
                "'{requesting_username}' does not have permission to access all blocks in '{course_key}'.".format(
                    requesting_username=requesting_user.username,
                    course_key=str(course_key),
                )
            )

        return None

    @staticmethod
    def _verify_requesting_user(requesting_user, course_key):
        """
        Verifies whether the requesting user can access blocks in the course.
        """
        if not permissions.can_access_self_blocks(requesting_user, course_key):
            raise PermissionDenied(
                "Course blocks for '{requesting_username}' cannot be accessed.".format(
                    requesting_username=requesting_user.username,
                )
            )
        return requesting_user

    @staticmethod
    def _verify_other_user(requesting_user, requested_username, course_key):
        """
        Verifies whether the requesting user can access another user's view of
        the blocks in the course.
        """
        # If accessing a public course, and requesting only content available publicly,
        # we can allow the request.
        if requested_username == '' and permissions.is_course_public(course_key):
            return AnonymousUser()

        # Verify requesting user can access the user's blocks.
        if not permissions.can_access_others_blocks(requesting_user, course_key):
            raise PermissionDenied(
                "'{requesting_username}' does not have permission to access view for '{requested_username}'.".format(
                    requesting_username=requesting_user.username,
                    requested_username=requested_username,
                )
            )

        # Verify user exists.
        try:
            return User.objects.get(username=requested_username)
        except User.DoesNotExist:
            raise Http404(
                "Requested user '{requested_username}' does not exist.".format(
                    requested_username=requested_username,
                )
            )
class BlockListGetForm(Form):
    """
    A form to validate query parameters in the block list retrieval endpoint
    """
    all_blocks = ExtendedNullBooleanField(required=False)
    block_counts = MultiValueField(required=False)
    depth = CharField(required=False)
    nav_depth = IntegerField(required=False, min_value=0)
    requested_fields = MultiValueField(required=False)
    return_type = ChoiceField(
        required=False,
        choices=[(choice, choice) for choice in ['dict', 'list']],
    )
    student_view_data = MultiValueField(required=False)
    usage_key = CharField(required=True)
    username = CharField(required=False)

    def clean_depth(self):
        """
        Get the appropriate depth.  No provided value will be treated as a
        depth of 0, while a value of "all" will be treated as unlimited depth.
        """
        value = self.cleaned_data['depth']
        if not value:
            return 0
        elif value == "all":
            return None
        try:
            return int(value)
        except ValueError:
            raise ValidationError(
                "'{}' is not a valid depth value.".format(value))

    def clean_requested_fields(self):
        """
        Return a set of `requested_fields`, merged with defaults of `type`
        and `display_name`
        """
        requested_fields = self.cleaned_data['requested_fields']

        # add default requested_fields
        return (requested_fields or set()) | {'type', 'display_name'}

    def clean_return_type(self):
        """
        Return valid 'return_type' or default value of 'dict'
        """
        return self.cleaned_data['return_type'] or 'dict'

    def clean_usage_key(self):
        """
        Ensure a valid `usage_key` was provided.
        """
        usage_key = self.cleaned_data['usage_key']

        try:
            usage_key = UsageKey.from_string(usage_key)
        except InvalidKeyError:
            raise ValidationError("'{}' is not a valid usage key.".format(
                unicode(usage_key)))

        return usage_key.replace(
            course_key=modulestore().fill_in_run(usage_key.course_key))

    def clean(self):
        """
        Return cleaned data, including additional requested fields.
        """
        cleaned_data = super(BlockListGetForm, self).clean()

        # Add additional requested_fields that are specified as separate
        # parameters, if they were requested.
        additional_requested_fields = [
            'student_view_data',
            'block_counts',
            'nav_depth',
        ]
        for additional_field in additional_requested_fields:
            field_value = cleaned_data.get(additional_field)
            if field_value or field_value == 0:  # allow 0 as a requested value
                cleaned_data['requested_fields'].add(additional_field)

        usage_key = cleaned_data.get('usage_key')
        if not usage_key:
            return

        cleaned_data['user'] = self._clean_requested_user(
            cleaned_data, usage_key.course_key)
        return cleaned_data

    def _clean_requested_user(self, cleaned_data, course_key):
        """
        Validates and returns the requested_user, while checking permissions.
        """
        requesting_user = self.initial['requesting_user']
        requested_username = cleaned_data.get('username', None)

        if not requested_username:
            return self._verify_no_user(requesting_user, cleaned_data,
                                        course_key)
        elif requesting_user.username.lower() == requested_username.lower():
            return self._verify_requesting_user(requesting_user, course_key)
        else:
            return self._verify_other_user(requesting_user, requested_username,
                                           course_key)

    @staticmethod
    def _verify_no_user(requesting_user, cleaned_data, course_key):
        """
        Verifies form for when no username is specified, including permissions.
        """
        # Verify that access to all blocks is requested
        # (and not unintentionally requested).
        if not cleaned_data.get('all_blocks', None):
            raise ValidationError({
                'username':
                ['This field is required unless all_blocks is requested.']
            })

        # Verify all blocks can be accessed for the course.
        if not permissions.can_access_all_blocks(requesting_user, course_key):
            raise PermissionDenied(
                "'{requesting_username}' does not have permission to access all blocks in '{course_key}'."
                .format(requesting_username=requesting_user.username,
                        course_key=unicode(course_key)))

        # return None for user
        return None

    @staticmethod
    def _verify_requesting_user(requesting_user, course_key):
        """
        Verifies whether the requesting user can access blocks in the course.
        """
        if not permissions.can_access_self_blocks(requesting_user, course_key):
            raise PermissionDenied(
                "Course blocks for '{requesting_username}' cannot be accessed."
                .format(requesting_username=requesting_user.username))
        return requesting_user

    @staticmethod
    def _verify_other_user(requesting_user, requested_username, course_key):
        """
        Verifies whether the requesting user can access another user's view of
        the blocks in the course.
        """
        # Verify requesting user can access the user's blocks.
        if not permissions.can_access_others_blocks(requesting_user,
                                                    course_key):
            raise PermissionDenied(
                "'{requesting_username}' does not have permission to access view for '{requested_username}'."
                .format(requesting_username=requesting_user.username,
                        requested_username=requested_username))

        # Verify user exists.
        try:
            return User.objects.get(username=requested_username)
        except User.DoesNotExist:
            raise Http404(
                "Requested user '{requested_username}' does not exist.".format(
                    requested_username=requested_username))