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
11a112a0
Commit
11a112a0
authored
Nov 05, 2012
by
Victor Shnayder
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Mid-cleanup.
- remove all the html-module-specific cruft
parent
72265d7f
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
128 additions
and
248 deletions
+128
-248
common/lib/xmodule/xmodule/capa_module.py
+11
-9
common/lib/xmodule/xmodule/self_assessment_module.py
+117
-239
No files found.
common/lib/xmodule/xmodule/capa_module.py
View file @
11a112a0
...
@@ -30,15 +30,17 @@ TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?)
...
@@ -30,15 +30,17 @@ TIMEDELTA_REGEX = re.compile(r'^((?P<days>\d+?) day(?:s?))?(\s)?((?P<hours>\d+?)
def
only_one
(
lst
,
default
=
""
,
process
=
lambda
x
:
x
):
def
only_one
(
lst
,
default
=
""
,
process
=
lambda
x
:
x
):
"""
"""
If lst is empty, returns default
If lst is empty, returns default
If lst has a single element, applies process to that element and returns it
Otherwise, raises an exeception
If lst has a single element, applies process to that element and returns it.
Otherwise, raises an exception.
"""
"""
if
len
(
lst
)
==
0
:
if
len
(
lst
)
==
0
:
return
default
return
default
elif
len
(
lst
)
==
1
:
elif
len
(
lst
)
==
1
:
return
process
(
lst
[
0
])
return
process
(
lst
[
0
])
else
:
else
:
raise
Exception
(
'Malformed XML'
)
raise
Exception
(
'Malformed XML
: expected at most one element in list.
'
)
def
parse_timedelta
(
time_str
):
def
parse_timedelta
(
time_str
):
...
@@ -292,11 +294,11 @@ class CapaModule(XModule):
...
@@ -292,11 +294,11 @@ class CapaModule(XModule):
# check button is context-specific.
# check button is context-specific.
# Put a "Check" button if unlimited attempts or still some left
# Put a "Check" button if unlimited attempts or still some left
if
self
.
max_attempts
is
None
or
self
.
attempts
<
self
.
max_attempts
-
1
:
if
self
.
max_attempts
is
None
or
self
.
attempts
<
self
.
max_attempts
-
1
:
check_button
=
"Check"
check_button
=
"Check"
else
:
else
:
# Will be final check so let user know that
# Will be final check so let user know that
check_button
=
"Final Check"
check_button
=
"Final Check"
reset_button
=
True
reset_button
=
True
save_button
=
True
save_button
=
True
...
@@ -527,11 +529,11 @@ class CapaModule(XModule):
...
@@ -527,11 +529,11 @@ class CapaModule(XModule):
# Problem queued. Students must wait a specified waittime before they are allowed to submit
# Problem queued. Students must wait a specified waittime before they are allowed to submit
if
self
.
lcp
.
is_queued
():
if
self
.
lcp
.
is_queued
():
current_time
=
datetime
.
datetime
.
now
()
current_time
=
datetime
.
datetime
.
now
()
prev_submit_time
=
self
.
lcp
.
get_recentmost_queuetime
()
prev_submit_time
=
self
.
lcp
.
get_recentmost_queuetime
()
waittime_between_requests
=
self
.
system
.
xqueue
[
'waittime'
]
waittime_between_requests
=
self
.
system
.
xqueue
[
'waittime'
]
if
(
current_time
-
prev_submit_time
)
.
total_seconds
()
<
waittime_between_requests
:
if
(
current_time
-
prev_submit_time
)
.
total_seconds
()
<
waittime_between_requests
:
msg
=
'You must wait at least
%
d seconds between submissions'
%
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
return
{
'success'
:
msg
,
'html'
:
''
}
# Prompts a modal dialog in ajax callback
try
:
try
:
old_state
=
self
.
lcp
.
get_state
()
old_state
=
self
.
lcp
.
get_state
()
...
@@ -678,10 +680,10 @@ class CapaDescriptor(RawDescriptor):
...
@@ -678,10 +680,10 @@ class CapaDescriptor(RawDescriptor):
'problems/'
+
path
[
8
:],
'problems/'
+
path
[
8
:],
path
[
8
:],
path
[
8
:],
]
]
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
CapaDescriptor
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
super
(
CapaDescriptor
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
weight_string
=
self
.
metadata
.
get
(
'weight'
,
None
)
weight_string
=
self
.
metadata
.
get
(
'weight'
,
None
)
if
weight_string
:
if
weight_string
:
self
.
weight
=
float
(
weight_string
)
self
.
weight
=
float
(
weight_string
)
...
...
common/lib/xmodule/xmodule/self_assessment_module.py
View file @
11a112a0
...
@@ -14,12 +14,14 @@ from path import path
...
@@ -14,12 +14,14 @@ from path import path
import
json
import
json
from
progress
import
Progress
from
progress
import
Progress
from
.x_module
import
XModule
from
pkg_resources
import
resource_string
from
pkg_resources
import
resource_string
from
.xml_module
import
XmlDescriptor
,
name_to_pathname
from
.capa_module
import
only_one
,
ComplexEncoder
from
.editing_module
import
EditingDescriptor
from
.editing_module
import
EditingDescriptor
from
.stringify
import
stringify_children
from
.html_checker
import
check_html
from
.html_checker
import
check_html
from
.stringify
import
stringify_children
from
.x_module
import
XModule
from
.xml_module
import
XmlDescriptor
,
name_to_pathname
from
xmodule.modulestore
import
Location
from
xmodule.modulestore
import
Location
from
xmodule.contentstore.content
import
XASSET_SRCREF_PREFIX
,
StaticContent
from
xmodule.contentstore.content
import
XASSET_SRCREF_PREFIX
,
StaticContent
...
@@ -28,35 +30,11 @@ log = logging.getLogger("mitx.courseware")
...
@@ -28,35 +30,11 @@ log = logging.getLogger("mitx.courseware")
#Set the default number of max attempts. Should be 1 for production
#Set the default number of max attempts. Should be 1 for production
#Set higher for debugging/testing
#Set higher for debugging/testing
#maxattempts specified in xml definition overrides this
# attempts specified in xml definition overrides this.
max_attempts
=
1
MAX_ATTEMPTS
=
1
def
only_one
(
lst
,
default
=
""
,
process
=
lambda
x
:
x
):
"""
If lst is empty, returns default
If lst has a single element, applies process to that element and returns it
Otherwise, raises an exeception
"""
if
len
(
lst
)
==
0
:
return
default
elif
len
(
lst
)
==
1
:
return
process
(
lst
[
0
])
else
:
raise
Exception
(
'Malformed XML'
)
class
ComplexEncoder
(
json
.
JSONEncoder
):
def
default
(
self
,
obj
):
if
isinstance
(
obj
,
complex
):
return
"{real:.7g}{imag:+.7g}*j"
.
format
(
real
=
obj
.
real
,
imag
=
obj
.
imag
)
return
json
.
JSONEncoder
.
default
(
self
,
obj
)
class
SelfAssessmentModule
(
XModule
):
class
SelfAssessmentModule
(
XModule
):
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/javascript_loader.coffee'
),
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/selfassessment/display.coffee'
)]
resource_string
(
__name__
,
'js/src/collapsible.coffee'
),
resource_string
(
__name__
,
'js/src/selfassessment/display.coffee'
)
]
}
}
js_module_name
=
"SelfAssessment"
js_module_name
=
"SelfAssessment"
...
@@ -70,96 +48,67 @@ class SelfAssessmentModule(XModule):
...
@@ -70,96 +48,67 @@ class SelfAssessmentModule(XModule):
instance_state
,
shared_state
,
**
kwargs
)
instance_state
,
shared_state
,
**
kwargs
)
"""
"""
Definition file should have 4 blocks -- problem, rubric, submitmessage, and maxattempts
Definition file should have 3 blocks -- prompt, rubric, submitmessage, and one optional attribute, attempts,
which should be an integer that defaults to 1. If it's >1, the student will be able to re-submit after they see
the rubric. Note: all the submissions are stored.
Sample file:
Sample file:
<selfassessment>
<selfassessment
attempts="1"
>
<pro
blem
>
<pro
mpt
>
Insert pro
blem text here.
Insert pro
mpt text here. (arbitrary html)
</pro
blem
>
</pro
mpt
>
<rubric>
<rubric>
Insert grading rubric here.
Insert grading rubric here.
(arbitrary html)
</rubric>
</rubric>
<submitmessage>
<submitmessage>
Thanks for submitting!
Thanks for submitting!
(arbitrary html)
</submitmessage>
</submitmessage>
<maxattempts>
1
</maxattempts>
</selfassessment>
</selfassessment>
"""
"""
#Initialize variables
# Load instance state
self
.
answer
=
[]
if
instance_state
is
not
None
:
self
.
score
=
0
instance_state
=
json
.
loads
(
instance_state
)
self
.
top_score
=
1
else
:
self
.
attempts
=
0
instance_state
=
{}
self
.
correctness
=
[]
self
.
done
=
False
log
.
debug
(
'Instance state of self-assessment module {0}: {1}'
.
format
(
location
.
url
(),
instance_state
))
self
.
max_attempts
=
self
.
metadata
.
get
(
'attempts'
,
None
)
self
.
hint
=
[]
# Pull out state, or initialize variables
self
.
temp_answer
=
""
# lists of student answers, correctness responses ('incorrect'/'correct'), and suggested hints
self
.
student_answers
=
instance_state
.
get
(
'student_answers'
,
[])
self
.
correctness
=
instance_state
.
get
(
'correctness'
,
[])
self
.
hints
=
instance_state
.
get
(
'hints'
,
[])
# Used to keep track of a submitted answer for which we don't have a self-assessment and hint yet:
# this means that the answers, correctness, hints always stay in sync, and have the same number of elements.
self
.
temp_answer
=
instance_state
.
get
(
'temp_answer'
,
''
)
# Used for progress / grading. Currently get credit just for completion (doesn't matter if you self-assessed
# correct/incorrect).
self
.
score
=
instance_state
.
get
(
'score'
,
0
)
self
.
top_score
=
instance_state
.
get
(
'top_score'
,
1
)
# TODO: do we need this? True once everything is done
self
.
done
=
instance_state
.
get
(
'done'
,
False
)
self
.
attempts
=
instance_state
.
get
(
'attempts'
,
0
)
#Try setting maxattempts, use default if not available in metadata
#Try setting maxattempts, use default if not available in metadata
if
self
.
max_attempts
is
not
None
:
self
.
max_attempts
=
int
(
self
.
metadata
.
get
(
'attempts'
,
MAX_ATTEMPTS
))
self
.
max_attempts
=
int
(
self
.
max_attempts
)
else
:
self
.
max_attempts
=
max_attempts
#Load instance state
#Extract prompt, submission message and rubric from definition file
if
instance_state
is
not
None
:
self
.
rubric
=
definition
[
'rubric'
]
instance_state
=
json
.
loads
(
instance_state
)
self
.
prompt
=
definition
[
'prompt'
]
log
.
debug
(
instance_state
)
self
.
submit_message
=
definition
[
'submitmessage'
]
#Pull variables from instance state if available
#Forms to append to prompt and rubric that capture student responses.
if
instance_state
is
not
None
and
'attempts'
in
instance_state
:
self
.
attempts
=
instance_state
[
'attempts'
]
if
instance_state
is
not
None
and
'student_answers'
in
instance_state
:
if
(
type
(
instance_state
[
'student_answers'
])
in
[
type
(
u''
),
type
(
''
)]):
self
.
answer
.
append
(
instance_state
[
'student_answers'
])
elif
(
type
(
instance_state
[
'student_answers'
])
==
type
([])):
self
.
answer
=
instance_state
[
'student_answers'
]
if
instance_state
is
not
None
and
'done'
in
instance_state
:
self
.
done
=
instance_state
[
'done'
]
if
instance_state
is
not
None
and
'temp_answer'
in
instance_state
:
self
.
temp_answer
=
instance_state
[
'temp_answer'
]
if
instance_state
is
not
None
and
'hint'
in
instance_state
:
if
(
type
(
instance_state
[
'hint'
])
in
[
type
(
u''
),
type
(
''
)]):
self
.
hint
.
append
(
instance_state
[
'hint'
])
elif
(
type
(
instance_state
[
'hint'
])
==
type
([])):
self
.
hint
=
instance_state
[
'hint'
]
if
instance_state
is
not
None
and
'correct_map'
in
instance_state
:
if
'self_assess'
in
instance_state
[
'correct_map'
]:
self
.
score
=
instance_state
[
'correct_map'
][
'self_assess'
][
'npoints'
]
map_correctness
=
instance_state
[
'correct_map'
][
'self_assess'
][
'correctness'
]
if
(
type
(
map_correctness
)
in
[
type
(
u''
),
type
(
''
)]):
self
.
correctness
.
append
(
map_correctness
)
elif
(
type
(
map_correctness
)
==
type
([])):
self
.
correctness
=
map_correctness
#Parse definition file
dom2
=
etree
.
fromstring
(
"<selfassessment>"
+
self
.
definition
[
'data'
]
+
"</selfassessment>"
)
#Try setting max_attempts from definition xml
max_attempt_parsed
=
dom2
.
xpath
(
'maxattempts'
)[
0
]
.
text
try
:
self
.
max_attempts
=
int
(
max_attempt_parsed
)
except
:
pass
#Extract problem, submission message and rubric from definition file
self
.
rubric
=
"<br/>"
+
''
.
join
([
etree
.
tostring
(
child
)
for
child
in
only_one
(
dom2
.
xpath
(
'rubric'
))])
self
.
problem
=
''
.
join
([
etree
.
tostring
(
child
)
for
child
in
only_one
(
dom2
.
xpath
(
'problem'
))])
self
.
submit_message
=
etree
.
tostring
(
dom2
.
xpath
(
'submitmessage'
)[
0
])
#Forms to append to problem and rubric that capture student responses.
#Do not change ids and names, as javascript (selfassessment/display.coffee) depends on them
#Do not change ids and names, as javascript (selfassessment/display.coffee) depends on them
problem_form
=
(
'<section class="sa-wrapper"><textarea name="answer" '
# TODO: use templates -- system.render_template will pull them from the right place (lms/templates dir)
prompt_form
=
(
'<section class="sa-wrapper"><textarea name="answer" '
'id="answer" cols="50" rows="5"/><br/>'
'id="answer" cols="50" rows="5"/><br/>'
'<input type="button" value="Check" id ="show" name="show"/>'
'<input type="button" value="Check" id ="show" name="show"/>'
'<p id="rubric"></p><input type="hidden" '
'<p id="rubric"></p><input type="hidden" '
...
@@ -178,20 +127,24 @@ class SelfAssessmentModule(XModule):
...
@@ -178,20 +127,24 @@ class SelfAssessmentModule(XModule):
rubric_header
=
(
'<br/><br/><b>Rubric</b>'
)
rubric_header
=
(
'<br/><br/><b>Rubric</b>'
)
#Combine problem, rubric, and the forms
# TODO:
if
type
(
self
.
answer
)
==
type
([]):
#context = {rubric, ..., answer, etc}
if
len
(
self
.
answer
)
>
0
:
# self.html = self.system.render_template('selfassessment.html', context)
answer_html
=
"<br/>Previous answer: {0}<br/>"
.
format
(
self
.
answer
[
len
(
self
.
answer
)
-
1
])
self
.
problem
=
''
.
join
([
self
.
problem
,
answer_html
,
problem_form
])
#Combine prompt, rubric, and the forms
if
type
(
self
.
student_answers
)
==
type
([]):
if
len
(
self
.
student_answers
)
>
0
:
answer_html
=
"<br/>Previous answer: {0}<br/>"
.
format
(
self
.
student_answers
[
len
(
self
.
student_answers
)
-
1
])
self
.
prompt
=
''
.
join
([
self
.
prompt
,
answer_html
,
prompt_form
])
else
:
else
:
self
.
pro
blem
=
''
.
join
([
self
.
problem
,
problem
_form
])
self
.
pro
mpt
=
''
.
join
([
self
.
prompt
,
prompt
_form
])
else
:
else
:
self
.
pro
blem
=
''
.
join
([
self
.
problem
,
problem
_form
])
self
.
pro
mpt
=
''
.
join
([
self
.
prompt
,
prompt
_form
])
self
.
rubric
=
''
.
join
([
rubric_header
,
self
.
rubric
,
rubric_form
])
self
.
rubric
=
''
.
join
([
rubric_header
,
self
.
rubric
,
rubric_form
])
#Display the pro
blem
to the student to begin with
#Display the pro
mpt
to the student to begin with
self
.
html
=
self
.
pro
blem
self
.
html
=
self
.
pro
mpt
def
get_score
(
self
):
def
get_score
(
self
):
...
@@ -244,12 +197,14 @@ class SelfAssessmentModule(XModule):
...
@@ -244,12 +197,14 @@ class SelfAssessmentModule(XModule):
def
show_rubric
(
self
,
get
):
def
show_rubric
(
self
,
get
):
"""
"""
After the pro
blem
is submitted, show the rubric
After the pro
mpt
is submitted, show the rubric
"""
"""
#Check to see if attempts are less than max
#Check to see if attempts are less than max
if
(
self
.
attempts
<
self
.
max_attempts
):
if
(
self
.
attempts
<
self
.
max_attempts
):
#Dump to temp to keep answer in sync with correctness and hint
# Dump to temp to keep answer in sync with correctness and hint
self
.
temp_answer
=
get
.
keys
()[
0
]
# TODO: expecting something like get['answer']
self
.
temp_answer
=
get
.
keys
()[
0
]
log
.
debug
(
self
.
temp_answer
)
log
.
debug
(
self
.
temp_answer
)
return
{
'success'
:
True
,
'rubric'
:
self
.
rubric
}
return
{
'success'
:
True
,
'rubric'
:
self
.
rubric
}
else
:
else
:
...
@@ -263,25 +218,26 @@ class SelfAssessmentModule(XModule):
...
@@ -263,25 +218,26 @@ class SelfAssessmentModule(XModule):
'''
'''
#Temp answer check is to keep hints, correctness, and answer in sync
#Temp answer check is to keep hints, correctness, and answer in sync
points
=
0
points
=
0
log
.
debug
(
self
.
temp_answer
)
log
.
debug
(
self
.
temp_answer
)
if
self
.
temp_answer
is
not
""
:
if
self
.
temp_answer
is
not
""
:
#Extract correctness and hint from ajax and assign points
#Extract correctness and hint from ajax and assign points
self
.
hint
.
append
(
get
[
get
.
keys
()[
1
]])
self
.
hint
s
.
append
(
get
[
get
.
keys
()[
1
]])
curr_correctness
=
get
[
get
.
keys
()[
0
]]
.
lower
()
curr_correctness
=
get
[
get
.
keys
()[
0
]]
.
lower
()
if
curr_correctness
==
"correct"
:
if
curr_correctness
==
"correct"
:
points
=
1
points
=
1
self
.
correctness
.
append
(
curr_correctness
)
self
.
correctness
.
append
(
curr_correctness
)
self
.
answer
.
append
(
self
.
temp_answer
)
self
.
student_answers
.
append
(
self
.
temp_answer
)
#Student is done, and increment attempts
#Student is done, and increment attempts
self
.
done
=
True
self
.
done
=
True
self
.
attempts
=
self
.
attempts
+
1
self
.
attempts
=
self
.
attempts
+
1
# TODO: simplify tracking info to just log the relevant stuff
event_info
=
dict
()
event_info
=
dict
()
event_info
[
'state'
]
=
{
'seed'
:
1
,
event_info
[
'state'
]
=
{
'seed'
:
1
,
'student_answers'
:
self
.
answer
,
'student_answers'
:
self
.
student_answers
,
'hint'
:
self
.
hint
,
'hint'
:
self
.
hint
s
,
'correct_map'
:
{
'self_assess'
:
{
'correctness'
:
self
.
correctness
,
'correct_map'
:
{
'self_assess'
:
{
'correctness'
:
self
.
correctness
,
'npoints'
:
points
,
'npoints'
:
points
,
'msg'
:
""
,
'msg'
:
""
,
...
@@ -291,8 +247,9 @@ class SelfAssessmentModule(XModule):
...
@@ -291,8 +247,9 @@ class SelfAssessmentModule(XModule):
}},
}},
'done'
:
self
.
done
}
'done'
:
self
.
done
}
event_info
[
'problem_id'
]
=
self
.
location
.
url
()
# TODO: figure out how to identify self assessment. May not want to confuse with problems.
event_info
[
'answers'
]
=
self
.
answer
event_info
[
'selfassessment_id'
]
=
self
.
location
.
url
()
event_info
[
'answers'
]
=
self
.
student_answers
self
.
system
.
track_function
(
'save_problem_succeed'
,
event_info
)
self
.
system
.
track_function
(
'save_problem_succeed'
,
event_info
)
...
@@ -306,15 +263,17 @@ class SelfAssessmentModule(XModule):
...
@@ -306,15 +263,17 @@ class SelfAssessmentModule(XModule):
points
=
1
points
=
1
#This is a pointless if structure, but left in place in case points change from
#This is a pointless if structure, but left in place in case points change from
#being completion based to correctness based
#being completion based to correctness based
# TODO: clean up
if
type
(
self
.
correctness
)
==
type
([]):
if
type
(
self
.
correctness
)
==
type
([]):
if
(
len
(
self
.
correctness
)
>
0
):
if
(
len
(
self
.
correctness
)
>
0
):
if
self
.
correctness
[
len
(
self
.
correctness
)
-
1
]
==
"correct"
:
if
self
.
correctness
[
len
(
self
.
correctness
)
-
1
]
==
"correct"
:
points
=
1
points
=
1
state
=
{
'seed'
:
1
,
state
=
{
'seed'
:
1
,
'student_answers'
:
self
.
answer
,
'student_answers'
:
self
.
student_answers
,
'temp_answer'
:
self
.
temp_answer
,
'temp_answer'
:
self
.
temp_answer
,
'hint'
:
self
.
hint
,
'hint'
:
self
.
hint
s
,
'correct_map'
:
{
'self_assess'
:
{
'correctness'
:
self
.
correctness
,
'correct_map'
:
{
'self_assess'
:
{
'correctness'
:
self
.
correctness
,
'npoints'
:
points
,
'npoints'
:
points
,
'msg'
:
""
,
'msg'
:
""
,
...
@@ -342,123 +301,42 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
...
@@ -342,123 +301,42 @@ class SelfAssessmentDescriptor(XmlDescriptor, EditingDescriptor):
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/html/edit.coffee'
)]}
js
=
{
'coffee'
:
[
resource_string
(
__name__
,
'js/src/html/edit.coffee'
)]}
js_module_name
=
"HTMLEditingDescriptor"
js_module_name
=
"HTMLEditingDescriptor"
# VS[compat] TODO (cpennington): Delete this method once all fall 2012 course
# are being edited in the cms
@classmethod
def
backcompat_paths
(
cls
,
path
):
if
path
.
endswith
(
'.html.xml'
):
path
=
path
[:
-
9
]
+
'.html'
# backcompat--look for html instead of xml
if
path
.
endswith
(
'.html.html'
):
path
=
path
[:
-
5
]
# some people like to include .html in filenames..
candidates
=
[]
while
os
.
sep
in
path
:
candidates
.
append
(
path
)
_
,
_
,
path
=
path
.
partition
(
os
.
sep
)
# also look for .html versions instead of .xml
nc
=
[]
for
candidate
in
candidates
:
if
candidate
.
endswith
(
'.xml'
):
nc
.
append
(
candidate
[:
-
4
]
+
'.html'
)
return
candidates
+
nc
# NOTE: html descriptors are special. We do not want to parse and
# export them ourselves, because that can break things (e.g. lxml
# adds body tags when it exports, but they should just be html
# snippets that will be included in the middle of pages.
@classmethod
@classmethod
def
load_definition
(
cls
,
xml_object
,
system
,
location
):
def
definition_from_xml
(
cls
,
xml_object
,
system
):
'''Load a descriptor from the specified xml_object:
"""
Pull out the rubric, prompt, and submitmessage into a dictionary.
If there is a filename attribute, load it as a string, and
log a warning if it is not parseable by etree.HTMLParser.
If there is not a filename attribute, the definition is the body
Returns:
of the xml_object, without the root tag (do not want <html> in the
{
middle of a page)
'rubric' : 'some-html',
'''
'prompt' : 'some-html',
filename
=
xml_object
.
get
(
'filename'
)
'submitmessage' : 'some-html'
if
filename
is
None
:
}
definition_xml
=
copy
.
deepcopy
(
xml_object
)
"""
cls
.
clean_metadata_from_xml
(
definition_xml
)
expected_children
=
[
'rubric'
,
'prompt'
,
'submitmessage'
]
return
{
'data'
:
stringify_children
(
definition_xml
)}
for
child
in
expected_children
:
else
:
if
len
(
xml_object
.
xpath
(
child
))
!=
1
:
# html is special. cls.filename_extension is 'xml', but
raise
ValueError
(
"Self assessment definition must include exactly one '{0}' tag"
.
format
(
child
))
# if 'filename' is in the definition, that means to load
# from .html
# 'filename' in html pointers is a relative path
# (not same as 'html/blah.html' when the pointer is in a directory itself)
pointer_path
=
"{category}/{url_path}"
.
format
(
category
=
'html'
,
url_path
=
name_to_pathname
(
location
.
name
))
base
=
path
(
pointer_path
)
.
dirname
()
#log.debug("base = {0}, base.dirname={1}, filename={2}".format(base, base.dirname(), filename))
filepath
=
"{base}/{name}.html"
.
format
(
base
=
base
,
name
=
filename
)
#log.debug("looking for html file for {0} at {1}".format(location, filepath))
# VS[compat]
# TODO (cpennington): If the file doesn't exist at the right path,
# give the class a chance to fix it up. The file will be written out
# again in the correct format. This should go away once the CMS is
# online and has imported all current (fall 2012) courses from xml
if
not
system
.
resources_fs
.
exists
(
filepath
):
candidates
=
cls
.
backcompat_paths
(
filepath
)
#log.debug("candidates = {0}".format(candidates))
for
candidate
in
candidates
:
if
system
.
resources_fs
.
exists
(
candidate
):
filepath
=
candidate
break
try
:
def
parse
(
k
):
with
system
.
resources_fs
.
open
(
filepath
)
as
file
:
"""Assumes that xml_object has child k"""
html
=
file
.
read
()
return
stringify_children
(
xml_object
.
xpath
(
k
)[
0
])
# Log a warning if we can't parse the file, but don't error
if
not
check_html
(
html
):
msg
=
"Couldn't parse html in {0}."
.
format
(
filepath
)
log
.
warning
(
msg
)
system
.
error_tracker
(
"Warning: "
+
msg
)
definition
=
{
'data'
:
html
}
return
{
'rubric'
:
parse
(
'rubric'
),
'prompt'
:
parse
(
'prompt'
),
'submitmessage'
:
parse
(
'submitmessage'
),}
# TODO (ichuang): remove this after migration
# for Fall 2012 LMS migration: keep filename (and unmangled filename)
definition
[
'filename'
]
=
[
filepath
,
filename
]
return
definition
def
definition_to_xml
(
self
,
resource_fs
):
'''Return an xml element representing this definition.'''
elt
=
etree
.
Element
(
'selfassessment'
)
except
(
ResourceNotFoundError
)
as
err
:
def
add_child
(
k
):
msg
=
'Unable to load file contents at path {0}: {1} '
.
format
(
child_str
=
'<{tag}>{body}</{tag}>'
.
format
(
tag
=
k
,
body
=
self
.
definition
[
k
])
filepath
,
err
)
child_node
=
etree
.
fromstring
(
child_str
)
# add more info and re-raise
elt
.
append
(
child_node
)
raise
Exception
(
msg
),
None
,
sys
.
exc_info
()[
2
]
# TODO (vshnayder): make export put things in the right places.
for
child
in
[
'rubric'
,
'prompt'
,
'submitmessage'
]:
add_child
(
child
)
def
definition_to_xml
(
self
,
resource_fs
):
'''If the contents are valid xml, write them to filename.xml. Otherwise,
write just <html filename="" [meta-attrs="..."]> to filename.xml, and the html
string to filename.html.
'''
try
:
return
etree
.
fromstring
(
self
.
definition
[
'data'
])
except
etree
.
XMLSyntaxError
:
pass
# Not proper format. Write html to file, return an empty tag
pathname
=
name_to_pathname
(
self
.
url_name
)
pathdir
=
path
(
pathname
)
.
dirname
()
filepath
=
u'{category}/{pathname}.html'
.
format
(
category
=
self
.
category
,
pathname
=
pathname
)
resource_fs
.
makedir
(
os
.
path
.
dirname
(
filepath
),
allow_recreate
=
True
)
with
resource_fs
.
open
(
filepath
,
'w'
)
as
file
:
file
.
write
(
self
.
definition
[
'data'
])
# write out the relative name
relname
=
path
(
pathname
)
.
basename
()
elt
=
etree
.
Element
(
'html'
)
elt
.
set
(
"filename"
,
relname
)
return
elt
return
elt
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