Commit b395ba02 by Nimisha Asthagiri

fixup! course_api multiple value fields.

parent 691fed7a
...@@ -3,33 +3,27 @@ ...@@ -3,33 +3,27 @@
""" """
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.forms import Form, CharField, ChoiceField, Field, IntegerField, MultipleHiddenInput from django.forms import Form, CharField, ChoiceField, IntegerField
from django.http import Http404 from django.http import Http404
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from xmodule.modulestore.django import modulestore
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import UsageKey from opaque_keys.edx.keys import UsageKey
from openedx.core.djangoapps.util.forms import MultiValueField
from xmodule.modulestore.django import modulestore
from .permissions import can_access_other_users_blocks, can_access_users_blocks from .permissions import can_access_other_users_blocks, can_access_users_blocks
class ListField(Field):
"""
Field for a list of strings
"""
widget = MultipleHiddenInput
class BlockListGetForm(Form): class BlockListGetForm(Form):
""" """
A form to validate query parameters in the block list retrieval endpoint A form to validate query parameters in the block list retrieval endpoint
""" """
user = CharField(required=True) # TODO return all blocks if user is not specified by requesting staff user user = CharField(required=True) # TODO return all blocks if user is not specified by requesting staff user
usage_key = CharField(required=True) usage_key = CharField(required=True)
requested_fields = ListField(required=False) requested_fields = MultiValueField(required=False)
student_view_data = ListField(required=False) student_view_data = MultiValueField(required=False)
block_counts = ListField(required=False) block_counts = MultiValueField(required=False)
depth = CharField(required=False) depth = CharField(required=False)
nav_depth = IntegerField(required=False, min_value=0) nav_depth = IntegerField(required=False, min_value=0)
return_type = ChoiceField( return_type = ChoiceField(
...@@ -38,8 +32,10 @@ class BlockListGetForm(Form): ...@@ -38,8 +32,10 @@ class BlockListGetForm(Form):
) )
def clean_requested_fields(self): def clean_requested_fields(self):
requested_fields = self.cleaned_data['requested_fields']
# add default requested_fields # add default requested_fields
return set(self.cleaned_data['requested_fields'] or set()) | {'type', 'display_name'} return (requested_fields or set()) | {'type', 'display_name'}
def clean_depth(self): def clean_depth(self):
value = self.cleaned_data['depth'] value = self.cleaned_data['depth']
...@@ -112,7 +108,8 @@ class BlockListGetForm(Form): ...@@ -112,7 +108,8 @@ class BlockListGetForm(Form):
'nav_depth', 'nav_depth',
] ]
for additional_field in additional_requested_fields: for additional_field in additional_requested_fields:
if not cleaned_data.get(additional_field) in (None, [], {}): # allow 0 as a requested value 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) cleaned_data['requested_fields'].add(additional_field)
usage_key = cleaned_data.get('usage_key') usage_key = cleaned_data.get('usage_key')
......
...@@ -49,12 +49,12 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase): ...@@ -49,12 +49,12 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase):
mutable=True, mutable=True,
) )
self.cleaned_data = { self.cleaned_data = {
'block_counts': [], 'block_counts': set(),
'depth': 0, 'depth': 0,
'nav_depth': None, 'nav_depth': None,
'return_type': 'dict', 'return_type': 'dict',
'requested_fields': {'display_name', 'type'}, 'requested_fields': {'display_name', 'type'},
'student_view_data': [], 'student_view_data': set(),
'usage_key': usage_key, 'usage_key': usage_key,
'user': self.student, 'user': self.student,
} }
...@@ -181,7 +181,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase): ...@@ -181,7 +181,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase):
@ddt.data('block_counts', 'student_view_data') @ddt.data('block_counts', 'student_view_data')
def test_higher_order_field(self, field_name): def test_higher_order_field(self, field_name):
field_value = ['block_type1', 'block_type2'] field_value = {'block_type1', 'block_type2'}
self.form_data.setlist(field_name, field_value) self.form_data.setlist(field_name, field_value)
self.cleaned_data[field_name] = field_value self.cleaned_data[field_name] = field_value
self.cleaned_data['requested_fields'].add(field_name) self.cleaned_data['requested_fields'].add(field_name)
...@@ -192,7 +192,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase): ...@@ -192,7 +192,7 @@ class TestBlockListGetForm(FormTestMixin, SharedModuleStoreTestCase):
self.form_data.setlist('requested_fields', ['field1', 'field2']) self.form_data.setlist('requested_fields', ['field1', 'field2'])
# add higher order fields # add higher order fields
block_types_list = ['block_type1', 'block_type2'] block_types_list = {'block_type1', 'block_type2'}
for field_name in ['block_counts', 'student_view_data']: for field_name in ['block_counts', 'student_view_data']:
self.form_data.setlist(field_name, block_types_list) self.form_data.setlist(field_name, block_types_list)
self.cleaned_data[field_name] = block_types_list self.cleaned_data[field_name] = block_types_list
......
...@@ -3,6 +3,8 @@ Tests for Blocks Views ...@@ -3,6 +3,8 @@ Tests for Blocks Views
""" """
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from string import join
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from student.models import CourseEnrollment from student.models import CourseEnrollment
from student.tests.factories import CourseEnrollmentFactory, UserFactory from student.tests.factories import CourseEnrollmentFactory, UserFactory
...@@ -53,6 +55,26 @@ class TestBlocksViewMixin(object): ...@@ -53,6 +55,26 @@ class TestBlocksViewMixin(object):
self.non_orphaned_block_usage_keys, self.non_orphaned_block_usage_keys,
) )
requested_fields = ['graded', 'format', 'student_view_multi_device', 'children', 'not_a_field']
def verify_response_with_requested_fields(self, response):
self.verify_response_block_dict(response)
for block_key_string, block_data in response.data['blocks'].iteritems():
block_key = deserialize_usage_key(block_key_string, self.course_key)
xblock = self.store.get_item(block_key)
self.assert_in_iff('children', block_data, xblock.has_children)
self.assert_in_iff('graded', block_data, xblock.graded is not None)
self.assert_in_iff('format', block_data, xblock.format is not None)
self.assert_true_iff(block_data['student_view_multi_device'], block_data['type'] == 'html')
self.assertNotIn('not_a_field', block_data)
if xblock.has_children:
self.assertSetEqual(
set(unicode(child.location) for child in xblock.get_children()),
set(block_data['children']),
)
def assert_in_iff(self, member, container, predicate): def assert_in_iff(self, member, container, predicate):
if predicate: if predicate:
self.assertIn(member, container) self.assertIn(member, container)
...@@ -145,24 +167,9 @@ class TestBlocksView(TestBlocksViewMixin, SharedModuleStoreTestCase): ...@@ -145,24 +167,9 @@ class TestBlocksView(TestBlocksViewMixin, SharedModuleStoreTestCase):
def test_requested_fields_param(self): def test_requested_fields_param(self):
response = self.verify_response( response = self.verify_response(
params={'requested_fields': ['graded', 'format', 'student_view_multi_device', 'children', 'not_a_field']} params={'requested_fields': self.requested_fields}
) )
self.verify_response_block_dict(response) self.verify_response_with_requested_fields(response)
for block_key_string, block_data in response.data['blocks'].iteritems():
block_key = deserialize_usage_key(block_key_string, self.course_key)
xblock = self.store.get_item(block_key)
self.assert_in_iff('children', block_data, xblock.has_children)
self.assert_in_iff('graded', block_data, xblock.graded is not None)
self.assert_in_iff('format', block_data, xblock.format is not None)
self.assert_true_iff(block_data['student_view_multi_device'], block_data['type'] == 'html')
self.assertNotIn('not_a_field', block_data)
if xblock.has_children:
self.assertSetEqual(
set(unicode(child.location) for child in xblock.get_children()),
set(block_data['children']),
)
class TestBlocksInCourseView(TestBlocksViewMixin, SharedModuleStoreTestCase): class TestBlocksInCourseView(TestBlocksViewMixin, SharedModuleStoreTestCase):
...@@ -188,3 +195,18 @@ class TestBlocksInCourseView(TestBlocksViewMixin, SharedModuleStoreTestCase): ...@@ -188,3 +195,18 @@ class TestBlocksInCourseView(TestBlocksViewMixin, SharedModuleStoreTestCase):
def test_invalid_course_id(self): def test_invalid_course_id(self):
self.verify_response(400, params={'course_id': 'invalid_course_id'}) self.verify_response(400, params={'course_id': 'invalid_course_id'})
def test_with_list_field_url(self):
url = '{base_url}?course_id={course_id}&user={username}&depth=all'.format(
course_id=unicode(self.course_key),
base_url=self.url.format(),
username=self.user.username,
)
url += '&requested_fields={0}&requested_fields={1}&requested_fields={2}'.format(
self.requested_fields[0],
self.requested_fields[1],
join(self.requested_fields[1:], ','),
)
response = self.client.get(url)
self.assertEquals(response.status_code, 200)
self.verify_response_with_requested_fields(response)
...@@ -6,26 +6,14 @@ from django.forms import ( ...@@ -6,26 +6,14 @@ from django.forms import (
BooleanField, BooleanField,
CharField, CharField,
ChoiceField, ChoiceField,
Field,
Form, Form,
IntegerField, IntegerField,
MultipleHiddenInput,
NullBooleanField, NullBooleanField,
) )
from opaque_keys import InvalidKeyError from opaque_keys import InvalidKeyError
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from openedx.core.djangoapps.util.forms import MultiValueField
class TopicIdField(Field):
"""
Field for a list of topic_ids
"""
widget = MultipleHiddenInput
def validate(self, value):
if value and "" in value:
raise ValidationError("This field cannot be empty.")
class _PaginationForm(Form): class _PaginationForm(Form):
...@@ -49,7 +37,7 @@ class ThreadListGetForm(_PaginationForm): ...@@ -49,7 +37,7 @@ class ThreadListGetForm(_PaginationForm):
EXCLUSIVE_PARAMS = ["topic_id", "text_search", "following"] EXCLUSIVE_PARAMS = ["topic_id", "text_search", "following"]
course_id = CharField() course_id = CharField()
topic_id = TopicIdField(required=False) topic_id = MultiValueField(required=False)
text_search = CharField(required=False) text_search = CharField(required=False)
following = NullBooleanField(required=False) following = NullBooleanField(required=False)
view = ChoiceField( view = ChoiceField(
......
...@@ -63,7 +63,7 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase): ...@@ -63,7 +63,7 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase):
"course_id": CourseLocator.from_string("Foo/Bar/Baz"), "course_id": CourseLocator.from_string("Foo/Bar/Baz"),
"page": 2, "page": 2,
"page_size": 13, "page_size": 13,
"topic_id": [], "topic_id": set(),
"text_search": "", "text_search": "",
"following": None, "following": None,
"view": "", "view": "",
...@@ -77,7 +77,7 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase): ...@@ -77,7 +77,7 @@ class ThreadListGetFormTest(FormTestMixin, PaginationTestMixin, TestCase):
form = self.get_form(expected_valid=True) form = self.get_form(expected_valid=True)
self.assertEqual( self.assertEqual(
form.cleaned_data["topic_id"], form.cleaned_data["topic_id"],
["example topic_id", "example 2nd topic_id"], {"example topic_id", "example 2nd topic_id"},
) )
def test_text_search(self): def test_text_search(self):
......
from django.core.exceptions import ValidationError
from django.forms import Field, MultipleHiddenInput
class MultiValueField(Field):
"""
Field class that supports a list of values for a single form field.
The field values can be specified as:
1. a comma-separated-list (foo:bar1,bar2,bar3), or
2. a repeated field in a MultiValueDict (foo:bar1, foo:bar2, foo:bar3)
"""
widget = MultipleHiddenInput
def to_python(self, list_of_string_values):
values = super(MultiValueField, self).to_python(list_of_string_values) or set()
if values:
# combine all values if there were multiple specified individually
values = ','.join(values)
# parse them into a set
values = set(values.split(',')) if values else set()
return values
def validate(self, values):
if values and "" in values:
raise ValidationError("This field cannot be empty.")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment