Skip to content

Commit

Permalink
feat: Send xqueue submissions to edx-submission
Browse files Browse the repository at this point in the history
fix: Restructuring to send course_id and score to edx submission
  • Loading branch information
gabrielC1409 committed Feb 26, 2025
1 parent 6d21b97 commit b1b18b5
Show file tree
Hide file tree
Showing 16 changed files with 736 additions and 52 deletions.
8 changes: 8 additions & 0 deletions lms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,14 @@
xqueue_callback,
name='xqueue_callback',
),

re_path(
r'^courses/{}/xqueue/(?P<userid>[^/]*)/(?P<mod_id>.*?)/(?P<dispatch>[^/]*)$'.format(
settings.COURSE_ID_PATTERN,
),
xqueue_callback,
name='callback_submission',
),

# TODO: These views need to be updated before they work
path('calculate', util_views.calculate),
Expand Down
5 changes: 3 additions & 2 deletions scripts/xsslint_thresholds.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"django-trans-escape-variable-mismatch": 0,
"django-trans-invalid-escape-filter": 0,
"django-trans-missing-escape": 0,
"django-trans-escape-filter-parse-error": 0,
"javascript-concat-html": 2,
"javascript-escape": 1,
"javascript-jquery-append": 2,
Expand Down Expand Up @@ -36,5 +37,5 @@
"python-wrap-html": 0,
"underscore-not-escaped": 2
},
"total": 64
}
"total": 65
}
4 changes: 4 additions & 0 deletions xmodule/capa/capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
import xmodule.capa.inputtypes as inputtypes
import xmodule.capa.responsetypes as responsetypes
import xmodule.capa.xqueue_interface as xqueue_interface
import xmodule.capa.xqueue_submission as xqueue_submission
from xmodule.capa.xqueue_interface import get_flag_by_name
from xmodule.capa.correctmap import CorrectMap
from xmodule.capa.safe_exec import safe_exec
from xmodule.capa.util import contextualize_text, convert_files_to_filenames, get_course_id_from_capa_block
Expand Down Expand Up @@ -96,6 +98,7 @@ class LoncapaSystem(object):
See :class:`DescriptorSystem` for documentation of other attributes.
"""

def __init__(
self,
ajax_url,
Expand Down Expand Up @@ -130,6 +133,7 @@ class LoncapaProblem(object):
"""
Main class for capa Problems.
"""

def __init__(self, problem_text, id, capa_system, capa_block, # pylint: disable=redefined-builtin
state=None, seed=None, minimal_init=False, extract_tree=True):
"""
Expand Down
22 changes: 10 additions & 12 deletions xmodule/capa/inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,18 +56,16 @@

from lxml import etree

from xmodule.capa.xqueue_interface import XQUEUE_TIMEOUT
from xmodule.capa.xqueue_interface import XQUEUE_TIMEOUT, get_flag_by_name
from openedx.core.djangolib.markup import HTML, Text
from xmodule.stringify import stringify_children

from . import xqueue_interface
from . import xqueue_interface, xqueue_submission
from .registry import TagRegistry
from .util import sanitize_html

log = logging.getLogger(__name__)

#########################################################################

registry = TagRegistry() # pylint: disable=invalid-name


Expand Down Expand Up @@ -408,7 +406,7 @@ class OptionInput(InputTypeBase):
Example:
<optioninput options="('Up','Down')" correct="Up"/><text>The location of the sky</text>
<optioninput options="('Up', 'Down')" correct="Up"/><text>The location of the sky</text>
# TODO: allow ordering to be randomized
"""
Expand Down Expand Up @@ -505,7 +503,7 @@ def setup(self):
raise Exception(msg)

self.choices = self.extract_choices(self.xml, i18n)
self._choices_map = dict(self.choices,)
self._choices_map = dict(self.choices, )

@classmethod
def get_attributes(cls):
Expand Down Expand Up @@ -602,16 +600,16 @@ def get_attributes(cls):
Register the attributes.
"""
return [
Attribute('params', None), # extra iframe params
Attribute('params', None), # extra iframe params
Attribute('html_file', None),
Attribute('gradefn', "gradefn"),
Attribute('get_statefn', None), # Function to call in iframe
# to get current state.
# to get current state.
Attribute('initial_state', None), # JSON string to be used as initial state
Attribute('set_statefn', None), # Function to call iframe to
# set state
Attribute('width', "400"), # iframe width
Attribute('height', "300"), # iframe height
# set state
Attribute('width', "400"), # iframe width
Attribute('height', "300"), # iframe height
# Title for the iframe, which should be supplied by the author of the problem. Not translated
# because we are in a class method and therefore do not have access to capa_system.i18n.
# Note that the default "display name" for the problem is also not translated.
Expand Down Expand Up @@ -1626,7 +1624,7 @@ class ChoiceTextGroup(InputTypeBase):
CheckboxProblem:
<problem>
<startouttext/>
A person randomly selects 100 times, with replacement, from the list of numbers \(\sqrt{2}\) , 2, 3, 4 ,5 ,6
A person randomly selects 100 times, with replacement, from the list of numbers \(\sqrt{2}\), 2, 3, 4, 5, 6
and records the results. The first number they pick is \(\sqrt{2}\) Given this information
select the correct choices and fill in numbers to make them accurate.
<endouttext/>
Expand Down
1 change: 1 addition & 0 deletions xmodule/capa/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class TagRegistry(object):
(A dictionary with some extra error checking.)
"""

def __init__(self):
self._mapping = {}

Expand Down
1 change: 1 addition & 0 deletions xmodule/capa/tests/test_answer_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

class CapaAnswerPoolTest(unittest.TestCase):
"""Capa Answer Pool Test"""

def setUp(self):
super(CapaAnswerPoolTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.system = test_capa_system()
Expand Down
22 changes: 11 additions & 11 deletions xmodule/capa/tests/test_capa_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def test_label_and_description_inside_responsetype(self, question):
""".format(question=question)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': question, 'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}}}
{'1_2_1': {'label': question, 'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}}}
assert len(problem.tree.xpath('//label')) == 0

@ddt.unpack
Expand Down Expand Up @@ -123,7 +123,7 @@ def test_neither_label_tag_nor_attribute(self, question1, question2):
""".format(question1, question2)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
for question in (question1, question2):
assert len(problem.tree.xpath('//label[text()="{}"]'.format(question))) == 0

Expand All @@ -146,8 +146,8 @@ def test_multiple_descriptions(self):
""".format(desc1, desc2)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': '___ requires sacrifices.',
'descriptions': {'description_1_1_1': desc1, 'description_1_1_2': desc2}}}
{'1_2_1': {'label': '___ requires sacrifices.',
'descriptions': {'description_1_1_1': desc1, 'description_1_1_2': desc2}}}

def test_additional_answer_is_skipped_from_resulting_html(self):
"""Tests that additional_answer element is not present in transformed HTML"""
Expand Down Expand Up @@ -236,10 +236,10 @@ def test_multiple_questions_problem(self):
"""
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': 'Select the correct synonym of paranoid?',
'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}},
'1_3_1': {'label': 'What Apple device competed with the portable CD player?',
'descriptions': {'description_1_2_1': 'Device looks like an egg plant.'}}}
{'1_2_1': {'label': 'Select the correct synonym of paranoid?',
'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}},
'1_3_1': {'label': 'What Apple device competed with the portable CD player?',
'descriptions': {'description_1_2_1': 'Device looks like an egg plant.'}}}
assert len(problem.tree.xpath('//label')) == 0

def test_question_title_not_removed_got_children(self):
Expand Down Expand Up @@ -291,8 +291,8 @@ def test_multiple_inputtypes(self, group_label):

problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'group_label': group_label, 'label': input1_label, 'descriptions': {}},
'1_2_2': {'group_label': group_label, 'label': input2_label, 'descriptions': {}}}
{'1_2_1': {'group_label': group_label, 'label': input1_label, 'descriptions': {}},
'1_2_2': {'group_label': group_label, 'label': input2_label, 'descriptions': {}}}

def test_single_inputtypes(self):
"""
Expand Down Expand Up @@ -355,7 +355,7 @@ def assert_question_tag(self, question1, question2, tag, label_attr=False):
)
problem = new_loncapa_problem(xml)
assert problem.problem_data ==\
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
{'1_2_1': {'label': question1, 'descriptions': {}}, '1_3_1': {'label': question2, 'descriptions': {}}}
assert len(problem.tree.xpath('//{}'.format(tag))) == 0

@ddt.unpack
Expand Down
1 change: 1 addition & 0 deletions xmodule/capa/tests/test_customrender.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class HelperTest(unittest.TestCase):
'''
Make sure that our helper function works!
'''

def check(self, d):
xml = etree.XML(test_capa_system().render_template('blah', d))
assert d == extract_context(xml)
Expand Down
45 changes: 45 additions & 0 deletions xmodule/capa/tests/test_inputtypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -476,10 +476,12 @@ def test_rendering(self):
assert context == expected


@pytest.mark.django_db
class MatlabTest(unittest.TestCase):
"""
Test Matlab input types
"""

def setUp(self):
super(MatlabTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.rows = '10'
Expand Down Expand Up @@ -928,6 +930,40 @@ def test_matlab_sanitize_msg(self):
expected = ""
assert self.the_input._get_render_context()['msg'] == expected # pylint: disable=protected-access

@patch('xmodule.capa.inputtypes.get_flag_by_name', return_value=True)
@patch('xmodule.capa.inputtypes.datetime')
def test_plot_data_with_flag_active(self, mock_datetime, mock_get_flag_by_name):
"""
Test that the correct date format is used when the flag is active.
"""
mock_datetime.utcnow.return_value.strftime.return_value = 'formatted_date_with_flag'
data = {'submission': 'x = 1234;'}
response = self.the_input.handle_ajax("plot", data)
self.the_input.capa_system.xqueue.interface.send_to_queue.assert_called_with(header=ANY, body=ANY)
assert response['success']
assert self.the_input.input_state['queuekey'] is not None
assert self.the_input.input_state['queuestate'] == 'queued'
assert 'formatted_date_with_flag' in self.the_input.capa_system.xqueue.interface.send_to_queue.call_args[1][
'body'
]

@patch('xmodule.capa.inputtypes.get_flag_by_name', return_value=False)
@patch('xmodule.capa.inputtypes.datetime')
def test_plot_data_with_flag_inactive(self, mock_datetime, mock_get_flag_by_name):
"""
Test that the correct date format is used when the flag is inactive.
"""
mock_datetime.utcnow.return_value.strftime.return_value = 'formatted_date_without_flag'
data = {'submission': 'x = 1234;'}
response = self.the_input.handle_ajax("plot", data)
self.the_input.capa_system.xqueue.interface.send_to_queue.assert_called_with(header=ANY, body=ANY)
assert response['success']
assert self.the_input.input_state['queuekey'] is not None
assert self.the_input.input_state['queuestate'] == 'queued'
assert 'formatted_date_without_flag' in self.the_input.capa_system.xqueue.interface.send_to_queue.call_args[1][
'body'
]


def html_tree_equal(received, expected):
"""
Expand All @@ -947,6 +983,7 @@ class SchematicTest(unittest.TestCase):
"""
Check that schematic inputs work
"""

def test_rendering(self):
height = '12'
width = '33'
Expand Down Expand Up @@ -1002,6 +1039,7 @@ class ImageInputTest(unittest.TestCase):
"""
Check that image inputs work
"""

def check(self, value, egx, egy): # lint-amnesty, pylint: disable=missing-function-docstring
height = '78'
width = '427'
Expand Down Expand Up @@ -1061,6 +1099,7 @@ class CrystallographyTest(unittest.TestCase):
"""
Check that crystallography inputs work
"""

def test_rendering(self):
height = '12'
width = '33'
Expand Down Expand Up @@ -1102,6 +1141,7 @@ class VseprTest(unittest.TestCase):
"""
Check that vsepr inputs work
"""

def test_rendering(self):
height = '12'
width = '33'
Expand Down Expand Up @@ -1149,6 +1189,7 @@ class ChemicalEquationTest(unittest.TestCase):
"""
Check that chemical equation inputs work.
"""

def setUp(self):
super(ChemicalEquationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.size = "42"
Expand Down Expand Up @@ -1244,6 +1285,7 @@ class FormulaEquationTest(unittest.TestCase):
"""
Check that formula equation inputs work.
"""

def setUp(self):
super(FormulaEquationTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments
self.size = "42"
Expand Down Expand Up @@ -1392,6 +1434,7 @@ class DragAndDropTest(unittest.TestCase):
"""
Check that drag and drop inputs work
"""

def test_rendering(self):
path_to_images = '/dummy-static/images/'

Expand Down Expand Up @@ -1466,6 +1509,7 @@ class AnnotationInputTest(unittest.TestCase):
"""
Make sure option inputs work
"""

def test_rendering(self):
xml_str = '''
<annotationinput>
Expand Down Expand Up @@ -1626,6 +1670,7 @@ class TestStatus(unittest.TestCase):
"""
Tests for Status class
"""

def test_str(self):
"""
Test stringifing Status objects
Expand Down
11 changes: 9 additions & 2 deletions xmodule/capa/tests/test_responsetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
)
from xmodule.capa.util import convert_files_to_filenames
from xmodule.capa.xqueue_interface import dateformat
import xmodule.capa.xqueue_submission as xqueue_submission
from xmodule.capa.xqueue_interface import get_flag_by_name


class ResponseTest(unittest.TestCase):
Expand Down Expand Up @@ -929,6 +931,7 @@ def test_empty_answer_graded_as_incorrect(self):
self.assert_grade(problem, " ", "incorrect")


@pytest.mark.django_db
class CodeResponseTest(ResponseTest): # pylint: disable=missing-class-docstring
xml_factory_class = CodeResponseXMLFactory

Expand All @@ -944,8 +947,12 @@ def setUp(self):
@staticmethod
def make_queuestate(key, time):
"""Create queuestate dict"""
timestr = datetime.strftime(time, dateformat)
return {'key': key, 'time': timestr}
if get_flag_by_name('send_to_submission_course.enable'):
timestr = datetime.strftime(time, xqueue_submission.dateformat)
return {'key': key, 'time': timestr}
else:
timestr = datetime.strftime(time, dateformat)
return {'key': key, 'time': timestr}

def test_is_queued(self):
"""
Expand Down
2 changes: 1 addition & 1 deletion xmodule/capa/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def test_remove_markup(self):
Test for markup removal with nh3.
"""
assert remove_markup('The <mark>Truth</mark> is <em>Out There</em> & you need to <strong>find</strong> it') ==\
'The Truth is Out There &amp; you need to find it'
'The Truth is Out There &amp; you need to find it'

@ddt.data(
'When the root level failš the whole hierarchy won’t work anymore.',
Expand Down
Loading

0 comments on commit b1b18b5

Please sign in to comment.