Files
edx-platform/lms/djangoapps/course_api/blocks/forms.py
2019-07-16 15:38:50 +05:00

175 lines
6.6 KiB
Python

"""
Course API Forms
"""
from __future__ import absolute_import
import six
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.forms import CharField, ChoiceField, Form, IntegerField
from django.http import Http404
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey
from rest_framework.exceptions import PermissionDenied
from openedx.core.djangoapps.util.forms import ExtendedNullBooleanField, MultiValueField
from xmodule.modulestore.django import modulestore
from . import permissions
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(u"'{}' 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(u"'{}' is not a valid usage key.".format(six.text_type(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_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(
u"'{requesting_username}' does not have permission to access all blocks in '{course_key}'."
.format(requesting_username=requesting_user.username, course_key=six.text_type(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(
u"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(
u"'{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(
u"Requested user '{requested_username}' does not exist.".format(requested_username=requested_username)
)