Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
231b8c6a
Commit
231b8c6a
authored
Aug 28, 2012
by
David Ormsbee
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #548 from MITx/kimth/limit-queue-dos
Kimth/limit queue dos
parents
18127cf9
377b09e1
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
195 additions
and
109 deletions
+195
-109
common/lib/capa/capa/capa_problem.py
+21
-5
common/lib/capa/capa/correctmap.py
+27
-11
common/lib/capa/capa/inputtypes.py
+2
-2
common/lib/capa/capa/responsetypes.py
+14
-7
common/lib/capa/capa/templates/filesubmission.html
+1
-1
common/lib/capa/capa/templates/textbox.html
+1
-1
common/lib/capa/capa/xqueue_interface.py
+1
-1
common/lib/xmodule/xmodule/capa_module.py
+9
-0
common/lib/xmodule/xmodule/tests/__init__.py
+0
-0
common/lib/xmodule/xmodule/tests/test_files/coderesponse.xml
+11
-79
common/lib/xmodule/xmodule/tests/test_files/coderesponse_externalresponseformat.xml
+101
-0
lms/djangoapps/courseware/module_render.py
+3
-1
lms/envs/common.py
+3
-0
lms/envs/test.py
+1
-1
No files found.
common/lib/capa/capa/capa_problem.py
View file @
231b8c6a
...
...
@@ -14,6 +14,8 @@ This is used by capa_module.
from
__future__
import
division
from
datetime
import
datetime
import
json
import
logging
import
math
import
numpy
...
...
@@ -32,6 +34,7 @@ from correctmap import CorrectMap
import
eia
import
inputtypes
from
util
import
contextualize_text
,
convert_files_to_filenames
import
xqueue_interface
# to be replaced with auto-registering
import
responsetypes
...
...
@@ -202,11 +205,24 @@ class LoncapaProblem(object):
'''
Returns True if any part of the problem has been submitted to an external queue
'''
queued
=
False
for
answer_id
in
self
.
correct_map
:
if
self
.
correct_map
.
is_queued
(
answer_id
):
queued
=
True
return
queued
return
any
(
self
.
correct_map
.
is_queued
(
answer_id
)
for
answer_id
in
self
.
correct_map
)
def
get_recentmost_queuetime
(
self
):
'''
Returns a DateTime object that represents the timestamp of the most recent queueing request, or None if not queued
'''
if
not
self
.
is_queued
():
return
None
# Get a list of timestamps of all queueing requests, then convert it to a DateTime object
queuetime_strs
=
[
self
.
correct_map
.
get_queuetime_str
(
answer_id
)
for
answer_id
in
self
.
correct_map
if
self
.
correct_map
.
is_queued
(
answer_id
)]
queuetimes
=
[
datetime
.
strptime
(
qt_str
,
xqueue_interface
.
dateformat
)
for
qt_str
in
queuetime_strs
]
return
max
(
queuetimes
)
def
grade_answers
(
self
,
answers
):
'''
...
...
common/lib/capa/capa/correctmap.py
View file @
231b8c6a
...
...
@@ -15,7 +15,8 @@ class CorrectMap(object):
- msg : string (may have HTML) giving extra message response (displayed below textline or textbox)
- hint : string (may have HTML) giving optional hint (displayed below textline or textbox, above msg)
- hintmode : one of (None,'on_request','always') criteria for displaying hint
- queuekey : a random integer for xqueue_callback verification
- queuestate : Dict {key:'', time:''} where key is a secret string, and time is a string dump
of a DateTime object in the format '
%
Y
%
m
%
d
%
H
%
M
%
S'. Is None when not queued
Behaves as a dict.
'''
...
...
@@ -31,14 +32,15 @@ class CorrectMap(object):
def
__iter__
(
self
):
return
self
.
cmap
.
__iter__
()
def
set
(
self
,
answer_id
=
None
,
correctness
=
None
,
npoints
=
None
,
msg
=
''
,
hint
=
''
,
hintmode
=
None
,
queuekey
=
None
):
# See the documentation for 'set_dict' for the use of kwargs
def
set
(
self
,
answer_id
=
None
,
correctness
=
None
,
npoints
=
None
,
msg
=
''
,
hint
=
''
,
hintmode
=
None
,
queuestate
=
None
,
**
kwargs
):
if
answer_id
is
not
None
:
self
.
cmap
[
answer_id
]
=
{
'correctness'
:
correctness
,
'npoints'
:
npoints
,
'msg'
:
msg
,
'hint'
:
hint
,
'hintmode'
:
hintmode
,
'queue
key'
:
queuekey
,
'queue
state'
:
queuestate
,
}
def
__repr__
(
self
):
...
...
@@ -52,25 +54,39 @@ class CorrectMap(object):
def
set_dict
(
self
,
correct_map
):
'''
set internal dict to provided correct_map dict
for graceful migration, if correct_map is a one-level dict, then convert it to the new
dict of dicts format.
Set internal dict of CorrectMap to provided correct_map dict
correct_map is saved by LMS as a plaintext JSON dump of the correctmap dict. This means that
when the definition of CorrectMap (e.g. its properties) are altered, existing correct_map dict
not coincide with the newest CorrectMap format as defined by self.set.
For graceful migration, feed the contents of each correct map to self.set, rather than
making a direct copy of the given correct_map dict. This way, the common keys between
the incoming correct_map dict and the new CorrectMap instance will be written, while
mismatched keys will be gracefully ignored.
Special migration case:
If correct_map is a one-level dict, then convert it to the new dict of dicts format.
'''
if
correct_map
and
not
(
type
(
correct_map
[
correct_map
.
keys
()[
0
]])
==
dict
):
self
.
__init__
()
# empty current dict
for
k
in
correct_map
:
self
.
set
(
k
,
correct_map
[
k
])
# create new dict entries
self
.
__init__
()
# empty current dict
for
k
in
correct_map
:
self
.
set
(
k
,
correct_map
[
k
])
# create new dict entries
else
:
self
.
cmap
=
correct_map
self
.
__init__
()
for
k
in
correct_map
:
self
.
set
(
k
,
**
correct_map
[
k
])
def
is_correct
(
self
,
answer_id
):
if
answer_id
in
self
.
cmap
:
return
self
.
cmap
[
answer_id
][
'correctness'
]
==
'correct'
return
None
def
is_queued
(
self
,
answer_id
):
return
answer_id
in
self
.
cmap
and
self
.
cmap
[
answer_id
][
'queue
key
'
]
is
not
None
return
answer_id
in
self
.
cmap
and
self
.
cmap
[
answer_id
][
'queue
state
'
]
is
not
None
def
is_right_queuekey
(
self
,
answer_id
,
test_key
):
return
answer_id
in
self
.
cmap
and
self
.
cmap
[
answer_id
][
'queuekey'
]
==
test_key
return
self
.
is_queued
(
answer_id
)
and
self
.
cmap
[
answer_id
][
'queuestate'
][
'key'
]
==
test_key
def
get_queuetime_str
(
self
,
answer_id
):
return
self
.
cmap
[
answer_id
][
'queuestate'
][
'time'
]
def
get_npoints
(
self
,
answer_id
):
npoints
=
self
.
get_property
(
answer_id
,
'npoints'
)
...
...
common/lib/capa/capa/inputtypes.py
View file @
231b8c6a
...
...
@@ -351,7 +351,7 @@ def filesubmission(element, value, status, render_template, msg=''):
if
status
==
'incomplete'
:
# Flag indicating that the problem has been queued, 'msg' is length of queue
status
=
'queued'
queue_len
=
msg
msg
=
'Submitted to grader.
(Queue length:
%
s)'
%
queue_len
msg
=
'Submitted to grader.
'
context
=
{
'id'
:
eid
,
'state'
:
status
,
'msg'
:
msg
,
'value'
:
value
,
'queue_len'
:
queue_len
,
'allowed_files'
:
allowed_files
,
...
...
@@ -384,7 +384,7 @@ def textbox(element, value, status, render_template, msg=''):
if
status
==
'incomplete'
:
# Flag indicating that the problem has been queued, 'msg' is length of queue
status
=
'queued'
queue_len
=
msg
msg
=
'Submitted to grader.
(Queue length:
%
s)'
%
queue_len
msg
=
'Submitted to grader.
'
# For CodeMirror
mode
=
element
.
get
(
'mode'
,
'python'
)
...
...
common/lib/capa/capa/responsetypes.py
View file @
231b8c6a
...
...
@@ -26,6 +26,7 @@ import xml.sax.saxutils as saxutils
# specific library imports
from
calc
import
evaluator
,
UndefinedVariable
from
correctmap
import
CorrectMap
from
datetime
import
datetime
from
util
import
*
from
lxml
import
etree
from
lxml.html.soupparser
import
fromstring
as
fromstring_bs
# uses Beautiful Soup!!! FIXME?
...
...
@@ -1026,7 +1027,7 @@ class CodeResponse(LoncapaResponse):
TODO: Determines whether in synchronous or asynchronous (queued) mode
'''
xml
=
self
.
xml
self
.
url
=
xml
.
get
(
'url'
,
None
)
# XML can override external resource (grader/queue) URL
self
.
url
=
xml
.
get
(
'url'
,
None
)
#
TODO:
XML can override external resource (grader/queue) URL
self
.
queue_name
=
xml
.
get
(
'queuename'
,
self
.
system
.
xqueue
[
'default_queuename'
])
# VS[compat]:
...
...
@@ -1128,7 +1129,7 @@ class CodeResponse(LoncapaResponse):
xheader
=
xqueue_interface
.
make_xheader
(
lms_callback_url
=
self
.
system
.
xqueue
[
'callback_url'
],
lms_key
=
queuekey
,
queue_name
=
self
.
queue_name
)
# Generate body
if
is_list_of_files
(
submission
):
self
.
context
.
update
({
'submission'
:
queuekey
})
# For tracking. TODO: May want to record something else here
...
...
@@ -1148,16 +1149,22 @@ class CodeResponse(LoncapaResponse):
(
error
,
msg
)
=
qinterface
.
send_to_queue
(
header
=
xheader
,
body
=
json
.
dumps
(
contents
))
# State associated with the queueing request
qtime
=
datetime
.
strftime
(
datetime
.
now
(),
xqueue_interface
.
dateformat
)
queuestate
=
{
'key'
:
queuekey
,
'time'
:
qtime
,
}
cmap
=
CorrectMap
()
if
error
:
cmap
.
set
(
self
.
answer_id
,
queue
key
=
None
,
cmap
.
set
(
self
.
answer_id
,
queue
state
=
None
,
msg
=
'Unable to deliver your submission to grader. (Reason:
%
s.) Please try again later.'
%
msg
)
else
:
# Queueing mechanism flags:
# 1) Backend: Non-null CorrectMap['queue
key
'] indicates that the problem has been queued
# 1) Backend: Non-null CorrectMap['queue
state
'] indicates that the problem has been queued
# 2) Frontend: correctness='incomplete' eventually trickles down through inputtypes.textbox
# and .filesubmission to inform the browser to poll the LMS
cmap
.
set
(
self
.
answer_id
,
queue
key
=
queuekey
,
correctness
=
'incomplete'
,
msg
=
msg
)
cmap
.
set
(
self
.
answer_id
,
queue
state
=
queuestate
,
correctness
=
'incomplete'
,
msg
=
msg
)
return
cmap
...
...
@@ -1180,7 +1187,7 @@ class CodeResponse(LoncapaResponse):
points
=
0
elif
points
>
self
.
maxpoints
[
self
.
answer_id
]:
points
=
self
.
maxpoints
[
self
.
answer_id
]
oldcmap
.
set
(
self
.
answer_id
,
npoints
=
points
,
correctness
=
correctness
,
msg
=
msg
.
replace
(
' '
,
' '
),
queue
key
=
None
)
# Queuekey
is consumed
oldcmap
.
set
(
self
.
answer_id
,
npoints
=
points
,
correctness
=
correctness
,
msg
=
msg
.
replace
(
' '
,
' '
),
queue
state
=
None
)
# Queuestate
is consumed
else
:
log
.
debug
(
'CodeResponse: queuekey
%
s does not match for answer_id=
%
s.'
%
(
queuekey
,
self
.
answer_id
))
...
...
@@ -1197,7 +1204,7 @@ class CodeResponse(LoncapaResponse):
'''
Grader reply is a JSON-dump of the following dict
{ 'correct': True/False,
'score':
# TODO -- Partial grading
'score':
Numeric value (floating point is okay) to assign to answer
'msg': grader_msg }
Returns (valid_score_msg, correct, score, msg):
...
...
common/lib/capa/capa/templates/filesubmission.html
View file @
231b8c6a
...
...
@@ -10,7 +10,7 @@
<span
class=
"processing"
id=
"status_${id}"
></span>
<span
style=
"display:none;"
class=
"xqueue"
id=
"${id}"
>
${queue_len}
</span>
% endif
<span
class=
"debug"
>
(${state})
</span>
<span
style=
"display:none;"
class=
"debug"
>
(${state})
</span>
<br/>
<span
class=
"message"
>
${msg|n}
</span>
<br/>
...
...
common/lib/capa/capa/templates/textbox.html
View file @
231b8c6a
...
...
@@ -21,7 +21,7 @@
<div
style=
"display:none;"
name=
"${hidden}"
inputid=
"input_${id}"
/>
% endif
<br/>
<span
class=
"debug"
>
(${state})
</span>
<span
style=
"display:none;"
class=
"debug"
>
(${state})
</span>
<br/>
<span
class=
"message"
>
${msg|n}
</span>
<br/>
...
...
common/lib/capa/capa/xqueue_interface.py
View file @
231b8c6a
...
...
@@ -9,7 +9,7 @@ import time
log
=
logging
.
getLogger
(
'mitx.'
+
__name__
)
dateformat
=
'
%
Y
%
m
%
d
%
H
%
M
%
S'
def
make_hashkey
(
seed
=
None
):
'''
...
...
common/lib/xmodule/xmodule/capa_module.py
View file @
231b8c6a
...
...
@@ -462,6 +462,15 @@ class CapaModule(XModule):
self
.
system
.
track_function
(
'save_problem_check_fail'
,
event_info
)
raise
NotFoundError
(
'Problem must be reset before it can be checked again'
)
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if
self
.
lcp
.
is_queued
():
current_time
=
datetime
.
datetime
.
now
()
prev_submit_time
=
self
.
lcp
.
get_recentmost_queuetime
()
waittime_between_requests
=
self
.
system
.
xqueue
[
'waittime'
]
if
(
current_time
-
prev_submit_time
)
.
total_seconds
()
<
waittime_between_requests
:
msg
=
'You must wait at least
%
d seconds between submissions'
%
waittime_between_requests
return
{
'success'
:
msg
,
'html'
:
''
}
# Prompts a modal dialog in ajax callback
try
:
old_state
=
self
.
lcp
.
get_state
()
lcp_id
=
self
.
lcp
.
problem_id
...
...
common/lib/xmodule/xmodule/tests/__init__.py
View file @
231b8c6a
This diff is collapsed.
Click to expand it.
common/lib/xmodule/xmodule/tests/test_files/coderesponse.xml
View file @
231b8c6a
...
...
@@ -9,91 +9,23 @@
Write a program to compute the square of a number
<coderesponse
tests=
"repeat:2,generate"
>
<textbox
rows=
"10"
cols=
"70"
mode=
"python"
/>
<answer>
<![CDATA[
initial_display = """
def square(n):
"""
answer = """
def square(n):
return n**2
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testSquare(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: square(%d)'%n
return str(square(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testSquare(0))
elif test == 2: f.write(testSquare(1))
else: f.write(testSquare())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
<codeparam>
<initial_display>
def square(x):
</initial_display>
<answer_display>
answer
</answer_display>
<grader_payload>
grader stuff
</grader_payload>
</codeparam>
</coderesponse>
</text>
<text>
Write a program to compute the
cub
e of a number
Write a program to compute the
squar
e of a number
<coderesponse
tests=
"repeat:2,generate"
>
<textbox
rows=
"10"
cols=
"70"
mode=
"python"
/>
<answer>
<![CDATA[
initial_display = """
def cube(n):
"""
answer = """
def cube(n):
return n**3
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testCube(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: cube(%d)'%n
return str(cube(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testCube(0))
elif test == 2: f.write(testCube(1))
else: f.write(testCube())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
<codeparam>
<initial_display>
def square(x):
</initial_display>
<answer_display>
answer
</answer_display>
<grader_payload>
grader stuff
</grader_payload>
</codeparam>
</coderesponse>
</text>
...
...
common/lib/xmodule/xmodule/tests/test_files/coderesponse_externalresponseformat.xml
0 → 100644
View file @
231b8c6a
<problem>
<text>
<h2>
Code response
</h2>
<p>
</p>
<text>
Write a program to compute the square of a number
<coderesponse
tests=
"repeat:2,generate"
>
<textbox
rows=
"10"
cols=
"70"
mode=
"python"
/>
<answer>
<![CDATA[
initial_display = """
def square(n):
"""
answer = """
def square(n):
return n**2
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testSquare(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: square(%d)'%n
return str(square(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testSquare(0))
elif test == 2: f.write(testSquare(1))
else: f.write(testSquare())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
<text>
Write a program to compute the cube of a number
<coderesponse
tests=
"repeat:2,generate"
>
<textbox
rows=
"10"
cols=
"70"
mode=
"python"
/>
<answer>
<![CDATA[
initial_display = """
def cube(n):
"""
answer = """
def cube(n):
return n**3
"""
preamble = """
import sys, time
"""
test_program = """
import random
import operator
def testCube(n = None):
if n is None:
n = random.randint(2, 20)
print 'Test is: cube(%d)'%n
return str(cube(n))
def main():
f = os.fdopen(3,'w')
test = int(sys.argv[1])
rndlist = map(int,os.getenv('rndlist').split(','))
random.seed(rndlist[0])
if test == 1: f.write(testCube(0))
elif test == 2: f.write(testCube(1))
else: f.write(testCube())
f.close()
main()
sys.exit(0)
"""
]]>
</answer>
</coderesponse>
</text>
</text>
</problem>
lms/djangoapps/courseware/module_render.py
View file @
231b8c6a
...
...
@@ -220,7 +220,9 @@ def _get_module(user, request, location, student_module_cache, course_id, positi
xqueue
=
{
'interface'
:
xqueue_interface
,
'callback_url'
:
xqueue_callback_url
,
'default_queuename'
:
xqueue_default_queuename
.
replace
(
' '
,
'_'
)}
'default_queuename'
:
xqueue_default_queuename
.
replace
(
' '
,
'_'
),
'waittime'
:
settings
.
XQUEUE_WAITTIME_BETWEEN_REQUESTS
}
def
inner_get_module
(
location
):
"""
...
...
lms/envs/common.py
View file @
231b8c6a
...
...
@@ -86,6 +86,9 @@ DEFAULT_GROUPS = []
# If this is true, random scores will be generated for the purpose of debugging the profile graphs
GENERATE_PROFILE_SCORES
=
False
# Used with XQueue
XQUEUE_WAITTIME_BETWEEN_REQUESTS
=
5
# seconds
############################# SET PATH INFORMATION #############################
PROJECT_ROOT
=
path
(
__file__
)
.
abspath
()
.
dirname
()
.
dirname
()
# /mitx/lms
REPO_ROOT
=
PROJECT_ROOT
.
dirname
()
...
...
lms/envs/test.py
View file @
231b8c6a
...
...
@@ -58,7 +58,7 @@ XQUEUE_INTERFACE = {
},
"basic_auth"
:
(
'anant'
,
'agarwal'
),
}
XQUEUE_WAITTIME_BETWEEN_REQUESTS
=
5
# seconds
# TODO (cpennington): We need to figure out how envs/test.py can inject things
# into common.py so that we don't have to repeat this sort of thing
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment