Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions cms/djangoapps/contentstore/tests/test_libraries.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,99 @@ def test_change_after_first_sync(self):
html_block = modulestore().get_item(lc_block.children[0])
self.assertEqual(html_block.data, data_value)

def test_refreshes_children_if_libraries_change(self):
library2key = self._create_library("org2", "lib2", "Library2")
library2 = modulestore().get_library(library2key)
data1, data2 = "Hello world!", "Hello other world!"
ItemFactory.create(
category="html",
parent_location=self.library.location,
user_id=self.user.id,
publish_item=False,
display_name="Lib1: HTML BLock",
data=data1,
)

ItemFactory.create(
category="html",
parent_location=library2.location,
user_id=self.user.id,
publish_item=False,
display_name="Lib 2: HTML BLock",
data=data2,
)

# Create a course:
with modulestore().default_store(ModuleStoreEnum.Type.split):
course = CourseFactory.create()

# Add a LibraryContent block to the course:
lc_block = self._add_library_content_block(course, self.lib_key)
lc_block = self._refresh_children(lc_block)
self.assertEqual(len(lc_block.children), 1)

# Now, change the block settings to have an invalid library key:
resp = self._update_item(
lc_block.location,
{"source_libraries": [[str(library2key)]]},
)
self.assertEqual(resp.status_code, 200)
lc_block = modulestore().get_item(lc_block.location)

self.assertEqual(len(lc_block.children), 1)
html_block = modulestore().get_item(lc_block.children[0])
self.assertEqual(html_block.data, data2)

def test_refreshes_children_if_capa_type_change(self):
name1, name2 = "Option Problem", "Multiple Choice Problem"
ItemFactory.create(
category="problem",
parent_location=self.library.location,
user_id=self.user.id,
publish_item=False,
display_name=name1,
data="<problem><optionresponse></optionresponse></problem>",
)
ItemFactory.create(
category="problem",
parent_location=self.library.location,
user_id=self.user.id,
publish_item=False,
display_name=name2,
data="<problem><multiplechoiceresponse></multiplechoiceresponse></problem>",
)

# Create a course:
with modulestore().default_store(ModuleStoreEnum.Type.split):
course = CourseFactory.create()

# Add a LibraryContent block to the course:
lc_block = self._add_library_content_block(course, self.lib_key)
lc_block = self._refresh_children(lc_block)
self.assertEqual(len(lc_block.children), 2)

resp = self._update_item(
lc_block.location,
{"capa_type": 'optionresponse'},
)
self.assertEqual(resp.status_code, 200)
lc_block = modulestore().get_item(lc_block.location)

self.assertEqual(len(lc_block.children), 1)
html_block = modulestore().get_item(lc_block.children[0])
self.assertEqual(html_block.display_name, name1)

resp = self._update_item(
lc_block.location,
{"capa_type": 'multiplechoiceresponse'},
)
self.assertEqual(resp.status_code, 200)
lc_block = modulestore().get_item(lc_block.location)

self.assertEqual(len(lc_block.children), 1)
html_block = modulestore().get_item(lc_block.children[0])
self.assertEqual(html_block.display_name, name2)


@ddt.ddt
class TestLibraryAccess(LibraryTestCase):
Expand Down
18 changes: 18 additions & 0 deletions common/lib/capa/capa/responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@
CorrectMap = correctmap.CorrectMap # pylint: disable=invalid-name
CORRECTMAP_PY = None

# Make '_' a no-op so we can scrape strings
_ = lambda text: text

#-----------------------------------------------------------------------------
# Exceptions
Expand Down Expand Up @@ -439,6 +441,7 @@ class JavascriptResponse(LoncapaResponse):
Javascript using Node.js.
"""

human_name = _('JavaScript Input')
tags = ['javascriptresponse']
max_inputfields = 1
allowed_inputfields = ['javascriptinput']
Expand Down Expand Up @@ -684,6 +687,7 @@ class ChoiceResponse(LoncapaResponse):

"""

human_name = _('Checkboxes')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a TNL technical debt story to change our display of problem categories to use these human_names? The human_names are possibly now different from what Studio is showing in the Problem component button, correct?

If so, please create the technical debt story.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cahrens I don't think so, as it's not one-to-one relationship between problem categories and response types. E.g. "Custom Javascript/Python" and "Drag and Drop" problem templates are all processed by CustomResponse.

tags = ['choiceresponse']
max_inputfields = 1
allowed_inputfields = ['checkboxgroup', 'radiogroup']
Expand Down Expand Up @@ -754,6 +758,7 @@ class MultipleChoiceResponse(LoncapaResponse):
"""
# TODO: handle direction and randomize

human_name = _('Multiple Choice')
tags = ['multiplechoiceresponse']
max_inputfields = 1
allowed_inputfields = ['choicegroup']
Expand Down Expand Up @@ -1042,6 +1047,7 @@ def sample_from_answer_pool(self, choices, rng, num_pool):
@registry.register
class TrueFalseResponse(MultipleChoiceResponse):

human_name = _('True/False Choice')
tags = ['truefalseresponse']

def mc_setup_response(self):
Expand Down Expand Up @@ -1073,6 +1079,7 @@ class OptionResponse(LoncapaResponse):
TODO: handle direction and randomize
"""

human_name = _('Dropdown')
tags = ['optionresponse']
hint_tag = 'optionhint'
allowed_inputfields = ['optioninput']
Expand Down Expand Up @@ -1108,6 +1115,7 @@ class NumericalResponse(LoncapaResponse):
to a number (e.g. `4+5/2^2`), and accepts with a tolerance.
"""

human_name = _('Numerical Input')
tags = ['numericalresponse']
hint_tag = 'numericalhint'
allowed_inputfields = ['textline', 'formulaequationinput']
Expand Down Expand Up @@ -1308,6 +1316,7 @@ class StringResponse(LoncapaResponse):
</hintgroup>
</stringresponse>
"""
human_name = _('Text Input')
tags = ['stringresponse']
hint_tag = 'stringhint'
allowed_inputfields = ['textline']
Expand Down Expand Up @@ -1426,6 +1435,7 @@ class CustomResponse(LoncapaResponse):
or in a <script>...</script>
"""

human_name = _('Custom Evaluated Script')
tags = ['customresponse']

allowed_inputfields = ['textline', 'textbox', 'crystallography',
Expand Down Expand Up @@ -1797,6 +1807,7 @@ class SymbolicResponse(CustomResponse):
Symbolic math response checking, using symmath library.
"""

human_name = _('Symbolic Math Input')
tags = ['symbolicresponse']
max_inputfields = 1

Expand Down Expand Up @@ -1865,6 +1876,7 @@ class CodeResponse(LoncapaResponse):

"""

human_name = _('Code Input')
tags = ['coderesponse']
allowed_inputfields = ['textbox', 'filesubmission', 'matlabinput']
max_inputfields = 1
Expand Down Expand Up @@ -2142,6 +2154,7 @@ class ExternalResponse(LoncapaResponse):

"""

human_name = _('External Grader')
tags = ['externalresponse']
allowed_inputfields = ['textline', 'textbox']
awdmap = {
Expand Down Expand Up @@ -2299,6 +2312,7 @@ class FormulaResponse(LoncapaResponse):
Checking of symbolic math response using numerical sampling.
"""

human_name = _('Math Expression Input')
tags = ['formularesponse']
hint_tag = 'formulahint'
allowed_inputfields = ['textline', 'formulaequationinput']
Expand Down Expand Up @@ -2511,6 +2525,7 @@ class SchematicResponse(LoncapaResponse):
"""
Circuit schematic response type.
"""
human_name = _('Circuit Schematic Builder')
tags = ['schematicresponse']
allowed_inputfields = ['schematic']

Expand Down Expand Up @@ -2589,6 +2604,7 @@ class ImageResponse(LoncapaResponse):
True, if click is inside any region or rectangle. Otherwise False.
"""

human_name = _('Image Mapped Input')
tags = ['imageresponse']
allowed_inputfields = ['imageinput']

Expand Down Expand Up @@ -2707,6 +2723,7 @@ class AnnotationResponse(LoncapaResponse):
The response contains both a comment (student commentary) and an option (student tag).
Only the tag is currently graded. Answers may be incorrect, partially correct, or correct.
"""
human_name = _('Annotation Input')
tags = ['annotationresponse']
allowed_inputfields = ['annotationinput']
max_inputfields = 1
Expand Down Expand Up @@ -2831,6 +2848,7 @@ class ChoiceTextResponse(LoncapaResponse):
ChoiceResponse.
"""

human_name = _('Checkboxes With Text Input')
tags = ['choicetextresponse']
max_inputfields = 1
allowed_inputfields = ['choicetextgroup',
Expand Down
9 changes: 9 additions & 0 deletions common/lib/xmodule/xmodule/capa_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
import json
import logging
import sys
from lxml import etree

from pkg_resources import resource_string

from .capa_base import CapaMixin, CapaFields, ComplexEncoder
from capa import responsetypes
from .progress import Progress
from xmodule.x_module import XModule, module_attr
from xmodule.raw_module import RawDescriptor
Expand Down Expand Up @@ -172,6 +174,13 @@ def non_editable_metadata_fields(self):
])
return non_editable_fields

@property
def problem_types(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure there is unit test coverage for this.

Also, I'm confused about how this works. How are registered_tags specified? Is there some documentation that I can read about xblocks registry/registered_tags?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cahrens there's @registry.register all over the common/lib/capa/capa/responsetypes.py and it just builds up a dictionary in common/lib/capa/capa/registry.py

""" Low-level problem type introspection for content libraries filtering by problem type """
tree = etree.XML(self.data)
registered_tags = responsetypes.registry.registered_tags()
return set([node.tag for node in tree.iter() if node.tag in registered_tags])

# Proxy to CapaModule for access to any of its attributes
answer_available = module_attr('answer_available')
check_button_name = module_attr('check_button_name')
Expand Down
Loading