forms.py 6.51 KB
Newer Older
Nimisha Asthagiri committed
1 2 3 4 5
"""
Course API Forms
"""
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
6
from django.forms import CharField, ChoiceField, Form, IntegerField
Nimisha Asthagiri committed
7 8 9
from django.http import Http404
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
10 11
from rest_framework.exceptions import PermissionDenied

12
from openedx.core.djangoapps.util.forms import ExtendedNullBooleanField, MultiValueField
Nimisha Asthagiri committed
13 14
from xmodule.modulestore.django import modulestore

15
from . import permissions
Nimisha Asthagiri committed
16 17 18 19 20 21


class BlockListGetForm(Form):
    """
    A form to validate query parameters in the block list retrieval endpoint
    """
22
    all_blocks = ExtendedNullBooleanField(required=False)
Nimisha Asthagiri committed
23 24 25
    block_counts = MultiValueField(required=False)
    depth = CharField(required=False)
    nav_depth = IntegerField(required=False, min_value=0)
26
    requested_fields = MultiValueField(required=False)
Nimisha Asthagiri committed
27 28 29 30
    return_type = ChoiceField(
        required=False,
        choices=[(choice, choice) for choice in ['dict', 'list']],
    )
31 32 33
    student_view_data = MultiValueField(required=False)
    usage_key = CharField(required=True)
    username = CharField(required=False)
lenacom committed
34
    block_types_filter = MultiValueField(required=False)
Nimisha Asthagiri committed
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50

    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))

51
    def clean_requested_fields(self):
Nimisha Asthagiri committed
52
        """
53 54
        Return a set of `requested_fields`, merged with defaults of `type`
        and `display_name`
Nimisha Asthagiri committed
55
        """
56
        requested_fields = self.cleaned_data['requested_fields']
Nimisha Asthagiri committed
57

58 59
        # add default requested_fields
        return (requested_fields or set()) | {'type', 'display_name'}
Nimisha Asthagiri committed
60 61 62 63 64 65 66

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

67
    def clean_usage_key(self):
Nimisha Asthagiri committed
68
        """
69
        Ensure a valid `usage_key` was provided.
Nimisha Asthagiri committed
70
        """
71
        usage_key = self.cleaned_data['usage_key']
Nimisha Asthagiri committed
72

73 74 75 76
        try:
            usage_key = UsageKey.from_string(usage_key)
        except InvalidKeyError:
            raise ValidationError("'{}' is not a valid usage key.".format(unicode(usage_key)))
Nimisha Asthagiri committed
77

78
        return usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))
Nimisha Asthagiri committed
79 80 81

    def clean(self):
        """
82
        Return cleaned data, including additional requested fields.
Nimisha Asthagiri committed
83 84 85
        """
        cleaned_data = super(BlockListGetForm, self).clean()

86 87
        # Add additional requested_fields that are specified as separate
        # parameters, if they were requested.
Nimisha Asthagiri committed
88 89 90 91
        additional_requested_fields = [
            'student_view_data',
            'block_counts',
            'nav_depth',
lenacom committed
92
            'block_types_filter',
Nimisha Asthagiri committed
93 94 95 96 97 98 99 100 101 102
        ]
        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

103
        cleaned_data['user'] = self._clean_requested_user(cleaned_data, usage_key.course_key)
Nimisha Asthagiri committed
104
        return cleaned_data
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171

    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)
            )