Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-ora2
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-ora2
Commits
a73a0638
Commit
a73a0638
authored
Jun 16, 2014
by
gradyward
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
A completed first draft of the authoring changes.
parent
dfb745c4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
1009 additions
and
199 deletions
+1009
-199
openassessment/templates/openassessmentblock/oa_edit.html
+0
-0
openassessment/xblock/static/css/openassessment.css
+96
-39
openassessment/xblock/static/js/openassessment.min.js
+0
-0
openassessment/xblock/static/js/spec/oa_edit.js
+60
-12
openassessment/xblock/static/js/spec/oa_server.js
+41
-18
openassessment/xblock/static/js/src/oa_base.js
+3
-5
openassessment/xblock/static/js/src/oa_edit.js
+149
-16
openassessment/xblock/static/js/src/oa_server.js
+10
-7
openassessment/xblock/static/sass/oa/utilities/_developer.scss
+103
-6
openassessment/xblock/studio_mixin.py
+41
-27
openassessment/xblock/test/data/invalid_rubric.json
+18
-11
openassessment/xblock/test/data/invalid_update_xblock.json
+105
-34
openassessment/xblock/test/data/parse_assessment_dicts.json
+173
-0
openassessment/xblock/test/data/parse_assessment_dicts_error.json
+99
-0
openassessment/xblock/test/data/update_xblock.json
+18
-11
openassessment/xblock/test/test_studio.py
+7
-9
openassessment/xblock/test/test_xml.py
+17
-1
openassessment/xblock/validation.py
+1
-1
openassessment/xblock/xml.py
+68
-2
No files found.
openassessment/templates/openassessmentblock/oa_edit.html
View file @
a73a0638
This diff is collapsed.
Click to expand it.
openassessment/xblock/static/css/openassessment.css
View file @
a73a0638
...
...
@@ -2133,47 +2133,104 @@ hr.divider,
.step--student-training
.message--incorrect.is--hidden
.step__header
{
border-bottom
:
none
;
}
#openassessment-editor
.openassessment-editor-content-and-tabs
{
width
:
100%
;
height
:
370px
;
}
#openassessment-editor
.openassessment-editor-header
{
background-color
:
#e5e5e5
;
width
:
100%
;
top
:
0
;
}
#openassessment-editor
#oa-editor-window-title
{
float
:
left
;
}
#openassessment-editor
.oa-editor-tab
{
float
:
right
;
padding
:
2.5px
5px
;
margin
:
2.5px
5px
;
}
#openassessment-editor
.oa-editor-content-wrapper
{
height
:
100%
;
width
:
100%
;
padding
:
5px
10px
;
}
#openassessment-editor
.openassessment-prompt-editor
{
width
:
100%
;
height
:
100%
;
resize
:
none
;
}
#openassessment-editor
.openassessment-rubric-editor
{
width
:
100%
;
height
:
100%
;
}
#openassessment-editor
.openassessment-assessments-editor
{
width
:
100%
;
}
#openassessment-editor
#oa-settings-editor-text-fields
{
float
:
left
;
width
:
30%
;
}
#openassessment-editor
#oa-settings-assessments
{
float
:
right
;
width
:
70%
;
height
:
100%
;
}
#openassessment-editor
.xblock-actions
{
background-color
:
#e5e5e5
;
position
:
absolute
;
width
:
100%
;
bottom
:
0
;
}
#openassessment-editor
{
margin-bottom
:
0
;
}
#openassessment-editor
.openassessment-editor-content-and-tabs
{
width
:
100%
;
height
:
370px
;
}
#openassessment-editor
.openassessment-editor-header
{
background-color
:
#e5e5e5
;
width
:
100%
;
top
:
0
;
}
#openassessment-editor
#oa-editor-window-title
{
float
:
left
;
}
#openassessment-editor
.oa-editor-tab
{
float
:
right
;
padding
:
2.5px
5px
;
margin
:
2.5px
5px
;
border-radius
:
2.5px
;
box-shadow
:
none
;
border
:
0
;
}
#openassessment-editor
.oa-editor-content-wrapper
{
height
:
100%
;
width
:
100%
;
padding
:
5px
10px
;
}
#openassessment-editor
.openassessment-prompt-editor
{
width
:
100%
;
height
:
100%
;
resize
:
none
;
}
#openassessment-editor
.openassessment-rubric-editor
{
width
:
100%
;
height
:
100%
;
}
#openassessment-editor
.openassessment-assessments-editor
{
width
:
100%
;
}
#openassessment-editor
#oa-settings-editor-wrapper
{
overflow-y
:
scroll
;
}
#openassessment-editor
#openassessment-title-editor
{
width
:
300px
;
margin-left
:
50px
;
}
#openassessment-editor
.openassessment-number-field
{
width
:
25px
;
}
#openassessment-editor
.openassessment-date-field
{
width
:
130px
;
}
#openassessment-editor
.openassessment-description
{
font-size
:
75%
;
}
#openassessment-editor
.openassessment-text-field-wrapper
{
width
:
50%
;
text-align
:
center
;
}
#openassessment-editor
.right-text-field-wrapper
{
float
:
right
;
}
#openassessment-editor
.left-text-field-wrapper
{
float
:
left
;
}
#openassessment-editor
.openassessment-due-date-editor
{
height
:
30px
;
}
#openassessment-editor
.openassessment-inclusion-wrapper
{
background-color
:
#dadbdc
;
padding
:
2.5px
5px
;
margin
:
2.5px
5px
;
border-radius
:
2.5px
;
}
#openassessment-editor
label
{
padding-right
:
10px
;
}
#openassessment-editor
.xblock-actions
{
background-color
:
#e5e5e5
;
position
:
absolute
;
width
:
100%
;
bottom
:
0
;
}
#openassessment-editor
.peer-number-constraints
{
margin-bottom
:
10px
;
}
#openassessment-editor
.ui-widget-header
.ui-state-default
{
background
:
#e5e5e5
;
}
#openassessment-editor
.ui-widget-header
.ui-state-default
a
{
color
:
#202021
;
text-transform
:
uppercase
;
outline-color
:
transparent
;
}
#openassessment-editor
.ui-widget-header
.ui-state-active
{
background
:
#414243
;
color
:
whitesmoke
;
}
#openassessment-editor
.ui-widget-header
.ui-state-active
a
{
color
:
whitesmoke
;
text-transform
:
uppercase
;
outline-color
:
transparent
;
}
#openassessment-editor
input
[
type
=
"checkbox"
]
{
display
:
none
;
}
#openassessment-editor
input
[
type
=
"checkbox"
]
+
label
:before
{
font-family
:
"FontAwesome"
;
display
:
inline-block
;
margin-right
:
10px
;
width
:
auto
;
height
:
auto
;
content
:
"\f096"
;
}
#openassessment-editor
input
[
type
=
"checkbox"
]
:checked
+
label
:before
{
content
:
"\f046"
;
}
#openassessment-editor
hr
{
background-color
:
#d4d4d4
;
color
:
#414243
;
height
:
1px
;
border
:
0px
;
clear
:
both
;
}
.modal-content
{
height
:
50
0px
!important
;
}
height
:
47
0px
!important
;
}
.openassessment
.self-assessment__display__header
,
.openassessment
.peer-assessment__display__header
,
.openassessment
.step__header
{
margin-bottom
:
0
!important
;
...
...
openassessment/xblock/static/js/openassessment.min.js
View file @
a73a0638
This diff is collapsed.
Click to expand it.
openassessment/xblock/static/js/spec/oa_edit.js
View file @
a73a0638
...
...
@@ -17,7 +17,21 @@ describe("OpenAssessment.StudioView", function() {
this
.
titleField
=
""
;
this
.
submissionStartField
=
""
;
this
.
submissionDueField
=
""
;
this
.
assessmentsXmlBox
=
""
;
this
.
hasPeer
=
true
;
this
.
hasSelf
=
true
;
this
.
hasTraining
=
true
;
this
.
hasAI
=
false
;
this
.
peerMustGrade
=
2
;
this
.
peerGradedBy
=
3
;
this
.
peerStart
=
''
;
this
.
peerDue
=
''
;
this
.
selfStart
=
''
;
this
.
selfDue
=
''
;
this
.
aiTrainingExamplesCodeBox
=
""
;
this
.
studentTrainingExamplesCodeBox
=
""
;
this
.
isReleased
=
false
;
...
...
@@ -28,16 +42,27 @@ describe("OpenAssessment.StudioView", function() {
this
.
loadEditorContext
=
function
()
{
var
prompt
=
this
.
promptBox
;
var
rubric
=
this
.
rubricXmlBox
;
var
settings
=
{
title
:
this
.
titleField
,
submission_start
:
this
.
submissionStartField
,
submission_due
:
this
.
submissionDueField
,
assessments
:
this
.
assessmentsXmlBox
};
var
title
=
this
.
titleField
;
var
submission_start
=
this
.
submissionStartField
;
var
submission_due
=
this
.
submissionDueField
;
var
assessments
=
[
{
name
:
"peer"
,
must_grade
:
this
.
peerMustGrade
,
must_be_graded_by
:
this
.
peerGradedBy
,
start
:
this
.
peerStart
,
due
:
this
.
peerDue
},
{
name
:
"self"
,
start
:
this
.
selfStart
,
due
:
this
.
selfDue
}
];
if
(
!
this
.
loadError
)
{
return
$
.
Deferred
(
function
(
defer
)
{
defer
.
resolveWith
(
this
,
[
prompt
,
rubric
,
setting
s
]);
defer
.
resolveWith
(
this
,
[
prompt
,
rubric
,
title
,
submission_start
,
submission_due
,
assessment
s
]);
}).
promise
();
}
else
{
...
...
@@ -45,14 +70,39 @@ describe("OpenAssessment.StudioView", function() {
}
};
this
.
updateEditorContext
=
function
(
prompt
,
rubricXml
,
title
,
sub_start
,
sub_due
,
assessments
Xml
)
{
this
.
updateEditorContext
=
function
(
prompt
,
rubricXml
,
title
,
sub_start
,
sub_due
,
assessments
)
{
if
(
!
this
.
updateError
)
{
this
.
promptBox
=
prompt
;
this
.
rubricXmlBox
=
rubricXml
;
this
.
titleField
=
title
;
this
.
submissionStartField
=
sub_start
;
this
.
submissionDueField
=
sub_due
;
this
.
assessmentsXmlBox
=
assessmentsXml
;
this
.
hasPeer
=
false
;
this
.
hasSelf
=
false
;
this
.
hasAI
=
false
;
this
.
hasTraining
=
false
;
for
(
var
i
=
0
;
i
<
assessments
.
length
;
i
++
)
{
var
assessment
=
assessments
[
i
];
if
(
assessment
.
name
==
'peer-assessment'
)
{
this
.
hasPeer
=
true
;
this
.
peerMustGrade
=
assessment
.
must_grade
;
this
.
peerGradedBy
=
assessment
.
must_be_graded_by
;
this
.
peerStart
=
assessment
.
start
;
this
.
peerDue
=
assessment
.
due
;
}
else
if
(
assessment
.
name
==
'self-assessment'
)
{
this
.
hasSelf
=
true
;
this
.
selfStart
=
assessment
.
start
;
this
.
selfDue
=
assessment
.
due
;
}
else
if
(
assessment
.
name
==
'example-based-assessment'
)
{
this
.
hasAI
=
true
;
this
.
aiTrainingExamplesCodeBox
=
assessment
.
examples
;
}
else
if
(
assessment
.
name
==
'student-training'
)
{
this
.
hasTraining
=
true
;
this
.
studentTrainingExamplesCodeBox
=
assessment
.
examples
;
}
}
return
$
.
Deferred
(
function
(
defer
)
{
defer
.
resolve
();
}).
promise
();
...
...
@@ -97,11 +147,9 @@ describe("OpenAssessment.StudioView", function() {
// Expect that the XML definition(s) were loaded
var
rubric
=
view
.
rubricXmlBox
.
getValue
();
var
prompt
=
view
.
promptBox
.
value
;
var
assessments
=
view
.
assessmentsXmlBox
.
getValue
()
expect
(
prompt
).
toEqual
(
''
);
expect
(
rubric
).
toEqual
(
''
);
expect
(
assessments
).
toEqual
(
''
);
});
it
(
"saves the Editor Context definition"
,
function
()
{
...
...
openassessment/xblock/static/js/spec/oa_server.js
View file @
a73a0638
...
...
@@ -51,17 +51,24 @@ describe("OpenAssessment.Server", function() {
'</criterion>'
+
'</rubric>'
;
var
assessments
=
'<assessments>'
+
'<assessment name="peer-assessment" must_grade="1" must_be_graded_by="1" due="2000-01-02"/>'
+
'<assessment name="self-assessment" due="2000-01-8"/>'
+
'</assessments>'
;
var
SETTINGS
=
{
title
:
'This is the title.'
,
submission_start
:
'2012-10-09T00:00:00'
,
submission_due
:
'2015-10-10T00:00:00'
,
assessments
:
assessments
};
var
ASSESSMENTS
=
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
"4014-03-10T00:00:00"
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
""
}
];
var
TITLE
=
'This is the title.'
;
var
SUBMISSION_START
=
'2012-10-09T00:00:00'
;
var
SUBMISSION_DUE
=
'2015-10-10T00:00:00'
;
beforeEach
(
function
()
{
// Create the server
...
...
@@ -184,20 +191,33 @@ describe("OpenAssessment.Server", function() {
});
it
(
"loads the XBlock's Context definition"
,
function
()
{
stubAjax
(
true
,
{
success
:
true
,
prompt
:
PROMPT
,
rubric
:
RUBRIC
,
settings
:
SETTINGS
});
stubAjax
(
true
,
{
success
:
true
,
prompt
:
PROMPT
,
rubric
:
RUBRIC
,
title
:
TITLE
,
submission_start
:
SUBMISSION_START
,
submission_due
:
SUBMISSION_DUE
,
assessments
:
ASSESSMENTS
});
var
loadedPrompt
=
""
;
var
loadedRubric
=
""
;
var
loadedSettings
=
""
;
server
.
loadEditorContext
().
done
(
function
(
prompt
,
rubric
,
settings
)
{
var
loadedAssessments
=
[];
var
loadedTitle
=
""
;
var
loadedStart
=
""
;
var
loadedDue
=
""
;
server
.
loadEditorContext
().
done
(
function
(
prompt
,
rubric
,
title
,
sub_start
,
sub_due
,
assessments
)
{
loadedPrompt
=
prompt
;
loadedRubric
=
rubric
;
loadedSettings
=
settings
;
loadedTitle
=
title
;
loadedStart
=
sub_start
;
loadedDue
=
sub_due
;
loadedAssessments
=
assessments
;
});
expect
(
loadedPrompt
).
toEqual
(
PROMPT
);
expect
(
loadedRubric
).
toEqual
(
RUBRIC
);
expect
(
loadedSettings
).
toEqual
(
SETTINGS
);
expect
(
loadedTitle
).
toEqual
(
TITLE
);
expect
(
loadedStart
).
toEqual
(
SUBMISSION_START
);
expect
(
loadedDue
).
toEqual
(
SUBMISSION_DUE
);
expect
(
loadedAssessments
).
toEqual
(
ASSESSMENTS
);
expect
(
$
.
ajax
).
toHaveBeenCalledWith
({
url
:
'/editor_context'
,
type
:
"POST"
,
data
:
'""'
});
...
...
@@ -207,11 +227,14 @@ describe("OpenAssessment.Server", function() {
stubAjax
(
true
,
{
success
:
true
});
server
.
updateEditorContext
(
PROMPT
,
RUBRIC
,
SETTINGS
.
title
,
SETTINGS
.
submission_start
,
SETTINGS
.
submission_due
,
SETTINGS
.
assessments
PROMPT
,
RUBRIC
,
TITLE
,
SUBMISSION_START
,
SUBMISSION_DUE
,
ASSESSMENTS
);
expect
(
$
.
ajax
).
toHaveBeenCalledWith
({
type
:
"POST"
,
url
:
'/update_editor_context'
,
data
:
JSON
.
stringify
({
prompt
:
PROMPT
,
rubric
:
RUBRIC
,
settings
:
SETTINGS
})
data
:
JSON
.
stringify
({
prompt
:
PROMPT
,
rubric
:
RUBRIC
,
title
:
TITLE
,
submission_start
:
SUBMISSION_START
,
submission_due
:
SUBMISSION_DUE
,
assessments
:
ASSESSMENTS
})
});
});
...
...
openassessment/xblock/static/js/src/oa_base.js
View file @
a73a0638
...
...
@@ -157,9 +157,7 @@ function OpenAssessmentBlock(runtime, element) {
/**
Render views within the base view on page load.
**/
$
(
function
(
$
)
{
var
server
=
new
OpenAssessment
.
Server
(
runtime
,
element
);
var
view
=
new
OpenAssessment
.
BaseView
(
runtime
,
element
,
server
);
view
.
load
();
});
var
server
=
new
OpenAssessment
.
Server
(
runtime
,
element
);
var
view
=
new
OpenAssessment
.
BaseView
(
runtime
,
element
,
server
);
view
.
load
();
}
openassessment/xblock/static/js/src/oa_edit.js
View file @
a73a0638
...
...
@@ -32,8 +32,27 @@ OpenAssessment.StudioView = function(runtime, element, server) {
this
.
submissionDueField
=
live_element
.
find
(
'.openassessment-submission-due-editor'
).
first
().
get
(
0
);
this
.
assessmentsXmlBox
=
CodeMirror
.
fromTextArea
(
live_element
.
find
(
'.openassessment-assessments-editor'
).
first
().
get
(
0
),
// Finds our boolean checkboxes that indicate the assessment definition
this
.
hasPeer
=
live_element
.
find
(
'#include-peer-assessment'
);
this
.
hasSelf
=
live_element
.
find
(
'#include-self-assessment'
);
this
.
hasAI
=
live_element
.
find
(
'#include-ai-assessment'
);
this
.
hasTraining
=
live_element
.
find
(
'#include-student-training'
);
this
.
peerMustGrade
=
live_element
.
find
(
'#peer-assessment-must-grade'
);
this
.
peerGradedBy
=
live_element
.
find
(
'#peer-assessment-graded-by'
);
this
.
peerStart
=
live_element
.
find
(
'#peer-assessment-start-date'
);
this
.
peerDue
=
live_element
.
find
(
'#peer-assessment-due-date'
);
this
.
selfStart
=
live_element
.
find
(
'#self-assessment-start-date'
);
this
.
selfDue
=
live_element
.
find
(
'#self-assessment-due-date'
);
this
.
aiTrainingExamplesCodeBox
=
CodeMirror
.
fromTextArea
(
live_element
.
find
(
'#ai-training-examples'
).
get
(
0
),
{
mode
:
"xml"
,
lineNumbers
:
true
,
lineWrapping
:
true
}
);
this
.
studentTrainingExamplesCodeBox
=
CodeMirror
.
fromTextArea
(
live_element
.
find
(
'#student-training-examples'
).
get
(
0
),
{
mode
:
"xml"
,
lineNumbers
:
true
,
lineWrapping
:
true
}
);
...
...
@@ -52,9 +71,49 @@ OpenAssessment.StudioView = function(runtime, element, server) {
live_element
.
find
(
'.openassessment-editor-content-and-tabs'
).
tabs
({
activate
:
function
(
event
,
ui
){
view
.
rubricXmlBox
.
refresh
();
view
.
assessmentsXmlBox
.
refresh
();
}
});
live_element
.
find
(
'#include-peer-assessment'
).
change
(
function
()
{
if
(
this
.
checked
){
$
(
"#peer-assessment-description-closed"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#peer-assessment-settings-editor"
,
live_element
).
fadeIn
();
}
else
{
$
(
"#peer-assessment-settings-editor"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#peer-assessment-description-closed"
,
live_element
).
fadeIn
();
}
});
live_element
.
find
(
'#include-self-assessment'
).
change
(
function
()
{
if
(
this
.
checked
){
$
(
"#self-assessment-description-closed"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#self-assessment-settings-editor"
,
live_element
).
fadeIn
();
}
else
{
$
(
"#self-assessment-settings-editor"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#self-assessment-description-closed"
,
live_element
).
fadeIn
();
}
});
live_element
.
find
(
'#include-ai-assessment'
).
change
(
function
()
{
if
(
this
.
checked
){
$
(
"#ai-assessment-description-closed"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#ai-assessment-settings-editor"
,
live_element
).
fadeIn
();
}
else
{
$
(
"#ai-assessment-settings-editor"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#ai-assessment-description-closed"
,
live_element
).
fadeIn
();
}
});
live_element
.
find
(
'#include-student-training'
).
change
(
function
()
{
if
(
this
.
checked
){
$
(
"#student-training-description-closed"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#student-training-settings-editor"
,
live_element
).
fadeIn
();
}
else
{
$
(
"#student-training-settings-editor"
,
live_element
).
fadeOut
(
'fast'
);
$
(
"#student-training-description-closed"
,
live_element
).
fadeIn
();
}
});
};
OpenAssessment
.
StudioView
.
prototype
=
{
...
...
@@ -65,13 +124,38 @@ OpenAssessment.StudioView.prototype = {
load
:
function
()
{
var
view
=
this
;
this
.
server
.
loadEditorContext
().
done
(
function
(
prompt
,
rubricXml
,
setting
s
)
{
function
(
prompt
,
rubricXml
,
title
,
sub_start
,
sub_due
,
assessment
s
)
{
view
.
rubricXmlBox
.
setValue
(
rubricXml
);
view
.
assessmentsXmlBox
.
setValue
(
settings
.
assessments
);
view
.
submissionStartField
.
value
=
settings
.
submission_start
;
view
.
submissionDueField
.
value
=
settings
.
submission_due
;
view
.
submissionStartField
.
value
=
sub_start
;
view
.
submissionDueField
.
value
=
sub_due
;
view
.
promptBox
.
value
=
prompt
;
view
.
titleField
.
value
=
settings
.
title
;
view
.
titleField
.
value
=
title
;
view
.
hasPeer
.
prop
(
'checked'
,
false
).
change
();
view
.
hasSelf
.
prop
(
'checked'
,
false
).
change
();
view
.
hasTraining
.
prop
(
'checked'
,
false
).
change
();
view
.
hasAI
.
prop
(
'checked'
,
false
).
change
();
for
(
var
i
=
0
;
i
<
assessments
.
length
;
i
++
)
{
var
assessment
=
assessments
[
i
];
if
(
assessment
.
name
==
'peer-assessment'
)
{
view
.
hasPeer
.
prop
(
'checked'
,
true
).
change
();
view
.
peerMustGrade
.
prop
(
'value'
,
assessment
.
must_grade
);
view
.
peerGradedBy
.
prop
(
'value'
,
assessment
.
must_be_graded_by
);
view
.
peerStart
.
prop
(
'value'
,
assessment
.
start
);
view
.
peerDue
.
prop
(
'value'
,
assessment
.
due
);
}
else
if
(
assessment
.
name
==
'self-assessment'
)
{
view
.
hasSelf
.
prop
(
'checked'
,
true
).
change
();
view
.
selfStart
.
prop
(
'value'
,
assessment
.
start
);
view
.
selfDue
.
prop
(
'value'
,
assessment
.
due
);
}
else
if
(
assessment
.
name
==
'example-based-assessment'
)
{
view
.
hasAI
.
prop
(
'checked'
,
true
).
change
();
view
.
aiTrainingExamplesCodeBox
.
setValue
(
assessment
.
examples
);
}
else
if
(
assessment
.
name
==
'student-training'
)
{
view
.
hasTraining
.
prop
(
'checked'
,
true
).
change
();
view
.
studentTrainingExamplesCodeBox
.
setValue
(
assessment
.
examples
);
}
else
{
}
}
}).
fail
(
function
(
msg
)
{
view
.
showError
(
msg
);
}
...
...
@@ -98,7 +182,7 @@ OpenAssessment.StudioView.prototype = {
}
).
fail
(
function
(
errMsg
)
{
view
.
showError
(
msg
);
});
});
},
/**
...
...
@@ -131,10 +215,58 @@ OpenAssessment.StudioView.prototype = {
var
title
=
this
.
titleField
.
value
;
var
sub_start
=
this
.
submissionStartField
.
value
;
var
sub_due
=
this
.
submissionDueField
.
value
;
var
assessmentsXml
=
this
.
assessmentsXmlBox
.
getValue
();
var
assessments
=
[];
if
(
this
.
hasTraining
.
prop
(
'checked'
)){
assessments
[
assessments
.
length
]
=
{
"name"
:
"student-training"
,
"examples"
:
this
.
studentTrainingExamplesCodeBox
.
getValue
()
};
}
if
(
this
.
hasPeer
.
prop
(
'checked'
))
{
var
assessment
=
{
"name"
:
"peer-assessment"
,
"must_grade"
:
parseInt
(
this
.
peerMustGrade
.
prop
(
'value'
)),
"must_be_graded_by"
:
parseInt
(
this
.
peerGradedBy
.
prop
(
'value'
))
};
var
start_str
=
this
.
peerStart
.
prop
(
'value'
);
var
due_str
=
this
.
peerDue
.
prop
(
'value'
);
if
(
start_str
){
assessment
=
$
.
extend
(
assessment
,
{
"start"
:
start_str
})
}
if
(
due_str
){
assessment
=
$
.
extend
(
assessment
,
{
"due"
:
due_str
})
}
assessments
[
assessments
.
length
]
=
assessment
;
}
if
(
this
.
hasSelf
.
prop
(
'checked'
))
{
assessment
=
{
"name"
:
"self-assessment"
};
start_str
=
this
.
selfStart
.
prop
(
'value'
);
due_str
=
this
.
selfDue
.
prop
(
'value'
);
if
(
start_str
){
assessment
=
$
.
extend
(
assessment
,
{
"start"
:
start_str
})
}
if
(
due_str
){
assessment
=
$
.
extend
(
assessment
,
{
"due"
:
due_str
})
}
assessments
[
assessments
.
length
]
=
assessment
;
}
if
(
this
.
hasAI
.
prop
(
'checked'
))
{
assessments
[
assessments
.
length
]
=
{
"name"
:
"example-based-assessment"
,
"algorithm_id"
:
"ease"
,
"examples"
:
this
.
aiTrainingExamplesCodeBox
.
getValue
()
};
}
var
view
=
this
;
this
.
server
.
updateEditorContext
(
prompt
,
rubricXml
,
title
,
sub_start
,
sub_due
,
assessments
Xml
).
done
(
function
()
{
this
.
server
.
updateEditorContext
(
prompt
,
rubricXml
,
title
,
sub_start
,
sub_due
,
assessments
).
done
(
function
()
{
// Notify the client-side runtime that we finished saving
// so it can hide the "Saving..." notification.
view
.
runtime
.
notify
(
'save'
,
{
state
:
'end'
});
...
...
@@ -172,9 +304,9 @@ function OpenAssessmentEditor(runtime, element) {
/**
Initialize the editing interface on page load.
**/
$
(
function
(
$
)
{
var
server
=
new
OpenAssessment
.
Server
(
runtime
,
element
);
var
view
=
new
OpenAssessment
.
StudioView
(
runtime
,
element
,
server
);
view
.
load
();
});
var
server
=
new
OpenAssessment
.
Server
(
runtime
,
element
);
var
view
=
new
OpenAssessment
.
StudioView
(
runtime
,
element
,
server
);
view
.
load
();
};
\ No newline at end of file
openassessment/xblock/static/js/src/oa_server.js
View file @
a73a0638
...
...
@@ -356,7 +356,9 @@ OpenAssessment.Server.prototype = {
$
.
ajax
({
type
:
"POST"
,
url
:
url
,
data
:
"
\"\"
"
}).
done
(
function
(
data
)
{
if
(
data
.
success
)
{
defer
.
resolveWith
(
this
,
[
data
.
prompt
,
data
.
rubric
,
data
.
settings
]);
}
if
(
data
.
success
)
{
defer
.
resolveWith
(
this
,
[
data
.
prompt
,
data
.
rubric
,
data
.
title
,
data
.
submission_start
,
data
.
submission_due
,
data
.
assessments
]);
}
else
{
defer
.
rejectWith
(
this
,
[
data
.
msg
]);
}
}).
fail
(
function
(
data
)
{
defer
.
rejectWith
(
this
,
[
gettext
(
'This problem could not be loaded.'
)]);
...
...
@@ -367,7 +369,7 @@ OpenAssessment.Server.prototype = {
/**
Update the XBlock's XML definition on the server.
Return
s:
Return
A JQuery promise, which resolves with no arguments
and fails with an error message.
...
...
@@ -378,15 +380,16 @@ OpenAssessment.Server.prototype = {
function(err) { console.log(err); }
);
**/
updateEditorContext
:
function
(
prompt
,
rubricXml
,
title
,
sub_start
,
sub_due
,
assessments
Xml
)
{
updateEditorContext
:
function
(
prompt
,
rubricXml
,
title
,
sub_start
,
sub_due
,
assessments
)
{
var
url
=
this
.
url
(
'update_editor_context'
);
var
settings
=
{
var
payload
=
JSON
.
stringify
({
'prompt'
:
prompt
,
'rubric'
:
rubricXml
,
'title'
:
title
,
'submission_start'
:
sub_start
,
'submission_due'
:
sub_due
,
'assessments'
:
assessmentsXml
};
var
payload
=
JSON
.
stringify
({
'prompt'
:
prompt
,
'rubric'
:
rubricXml
,
'settings'
:
settings
});
'assessments'
:
assessments
});
return
$
.
Deferred
(
function
(
defer
)
{
$
.
ajax
({
type
:
"POST"
,
url
:
url
,
data
:
payload
...
...
openassessment/xblock/static/sass/oa/utilities/_developer.scss
View file @
a73a0638
...
...
@@ -171,6 +171,7 @@
// --------------------
#openassessment-editor
{
margin-bottom
:
0
;
.openassessment-editor-content-and-tabs
{
width
:
100%
;
...
...
@@ -191,6 +192,9 @@
float
:
right
;
padding
:
(
$baseline-v
/
8
)
(
$baseline-h
/
8
);
margin
:
(
$baseline-v
/
8
)
(
$baseline-h
/
8
);
border-radius
:
(
$baseline-v
/
8
);
box-shadow
:
none
;
border
:
0
;
}
.oa-editor-content-wrapper
{
...
...
@@ -215,14 +219,57 @@
}
#oa-settings-editor-text-fields
{
float
:
left
;
width
:
30%
;
}
#oa-settings-assessments
{
#oa-settings-editor-wrapper
{
overflow-y
:
scroll
;
}
#openassessment-title-editor
{
width
:
300px
;
margin-left
:
50px
;
}
.openassessment-number-field
{
width
:
25px
;
}
.openassessment-date-field
{
width
:
130px
;
}
.openassessment-description
{
font-size
:
75%
;
}
.openassessment-text-field-wrapper
{
width
:
50%
;
text-align
:
center
;
}
.right-text-field-wrapper
{
float
:
right
;
width
:
70%
;
height
:
100%
;
}
.left-text-field-wrapper
{
float
:
left
;
}
.openassessment-due-date-editor
{
height
:
30px
;
}
.openassessment-inclusion-wrapper
{
background-color
:
$edx-gray-l3
;
input
[
type
=
"checkbox"
]
{
}
padding
:
(
$baseline-v
/
8
)
(
$baseline-h
/
8
);
margin
:
(
$baseline-v
/
8
)
(
$baseline-h
/
8
);
border-radius
:
(
$baseline-v
)
/
8
;
}
label
{
padding-right
:
10px
;
}
.xblock-actions
{
...
...
@@ -231,8 +278,58 @@
width
:
100%
;
bottom
:
0
;
}
.peer-number-constraints
{
margin-bottom
:
10px
;
}
.ui-widget-header
.ui-state-default
{
background
:
#e5e5e5
;
a
{
color
:
$edx-gray-d4
;
text-transform
:
uppercase
;
outline-color
:
transparent
;
}
}
.ui-widget-header
.ui-state-active
{
background
:
$edx-gray-d3
;
color
:
$white
;
a
{
color
:
$white
;
text-transform
:
uppercase
;
outline-color
:
transparent
;
}
}
input
[
type
=
"checkbox"
]
{
display
:
none
;
}
input
[
type
=
"checkbox"
]
+
label
:before
{
font-family
:
"FontAwesome"
;
display
:
inline-block
;
margin-right
:
(
$baseline-h
/
4
);
width
:
auto
;
height
:
auto
;
content
:
"\f096"
;
}
input
[
type
=
"checkbox"
]
:checked
+
label
:before
{
content
:
"\f046"
;
}
hr
{
background-color
:
#d4d4d4
;
color
:
$edx-gray-d3
;
height
:
1px
;
border
:
0px
;
clear
:
both
;
}
}
.modal-content
{
height
:
50
0px
!
important
;
height
:
47
0px
!
important
;
}
openassessment/xblock/studio_mixin.py
View file @
a73a0638
...
...
@@ -2,6 +2,7 @@
Studio editing view for OpenAssessment XBlock.
"""
import
pkg_resources
import
copy
import
logging
from
django.template.context
import
Context
from
django.template.loader
import
get_template
...
...
@@ -42,12 +43,14 @@ class StudioMixin(object):
Update the XBlock's configuration.
Args:
data (dict): Data from the request; should have a value for the keys
'rubric', 'settings' and 'prompt'. The 'rubric' should be an XML
representation of the new rubric. The 'prompt' should be a plain
text prompt. The 'settings' should be a dict of 'title',
'submission_due', 'submission_start' and the XML configuration for
all 'assessments'.
data (dict): Data from the request; should have a value for the keys: 'rubric', 'prompt',
'title', 'submission_start', 'submission_due', and 'assessments'.
-- The 'rubric' should be an XML representation of the new rubric.
-- The 'prompt' and 'title' should be plain text.
-- The dates 'submission_start' and 'submission_due' are both ISO strings
-- The 'assessments' is a list of asessment dictionaries (much like self.rubric_assessments)
with the notable exception that all examples (for Student Training and eventually AI)
are in XML string format and need to be parsed into dictionaries.
Kwargs:
suffix (str): Not used
...
...
@@ -55,18 +58,20 @@ class StudioMixin(object):
Returns:
dict with keys 'success' (bool) and 'msg' (str)
"""
missing_keys
=
list
({
'rubric'
,
'settings'
,
'prompt'
}
-
set
(
data
.
keys
()))
missing_keys
=
list
(
{
'rubric'
,
'prompt'
,
'title'
,
'assessments'
,
'submission_start'
,
'submission_due'
}
-
set
(
data
.
keys
())
)
if
missing_keys
:
logger
.
warn
(
'Must specify the following keys in request JSON dict: {}'
.
format
(
missing_keys
)
'Must specify the following
missing
keys in request JSON dict: {}'
.
format
(
missing_keys
)
)
return
{
'success'
:
False
,
'msg'
:
_
(
'Error updating XBlock configuration'
)}
settings
=
data
[
'settings'
]
try
:
rubric
=
xml
.
parse_rubric_xml_str
(
data
[
'rubric'
])
assessments
=
xml
.
parse_assessments_xml_str
(
settings
[
'assessments'
])
submission_
due
=
xml
.
parse_date
(
settings
[
"submission_due
"
])
submission_start
=
xml
.
parse_date
(
settings
[
"submission_start
"
])
rubric
=
xml
.
parse_rubric_xml_str
(
data
[
"rubric"
])
submission_due
=
xml
.
parse_date
(
data
[
"submission_due"
])
submission_
start
=
xml
.
parse_date
(
data
[
"submission_start
"
])
assessments
=
xml
.
parse_assessment_dictionaries
(
data
[
"assessments
"
])
except
xml
.
UpdateFromXmlError
as
ex
:
return
{
'success'
:
False
,
'msg'
:
_
(
'An error occurred while saving: {error}'
)
.
format
(
error
=
ex
)}
...
...
@@ -81,7 +86,7 @@ class StudioMixin(object):
assessments
,
submission_due
,
submission_start
,
settings
[
"title"
],
data
[
"title"
],
data
[
"prompt"
]
)
return
{
'success'
:
True
,
'msg'
:
'Successfully updated OpenAssessment XBlock'
}
...
...
@@ -100,15 +105,27 @@ class StudioMixin(object):
suffix (str): Not used
Returns:
dict with keys 'success' (bool), 'message' (unicode),
'rubric' (unicode), 'prompt' (unicode), and 'settings' (dict)
dict with keys
'success' (bool), 'message' (unicode), 'rubric' (unicode), 'prompt' (unicode),
'title' (unicode), 'submission_start' (unicode), 'submission_due' (unicode), 'assessments (dict)
"""
try
:
assessments
=
xml
.
serialize_assessments_to_xml_str
(
self
)
rubric
=
xml
.
serialize_rubric_to_xml_str
(
self
)
# We do not expect serialization to raise an exception,
# but if it does, handle it gracefully.
# Copies the rubric assessments so that we can change student training examples from dict -> str without
# negatively modifying the openassessmentblock definition.
assessment_list
=
copy
.
deepcopy
(
self
.
rubric_assessments
)
# Finds the student training dictionary, if it exists, and replaces the examples with their XML definition
student_training_dictionary
=
[
d
for
d
in
assessment_list
if
d
[
"name"
]
==
"student-training"
]
if
student_training_dictionary
:
# Our for loop will return a list. Select the first element of that list if it exists.
student_training_dictionary
=
student_training_dictionary
[
0
]
examples
=
xml
.
serialize_examples_to_xml_str
(
student_training_dictionary
)
student_training_dictionary
[
"examples"
]
=
examples
# We do not expect serialization to raise an exception, but if it does, handle it gracefully.
except
Exception
as
ex
:
msg
=
_
(
'An unexpected error occurred while loading the problem: {error}'
)
.
format
(
error
=
ex
)
logger
.
error
(
msg
)
...
...
@@ -122,19 +139,15 @@ class StudioMixin(object):
submission_start
=
self
.
submission_start
if
self
.
submission_start
else
''
settings
=
{
'submission_due'
:
submission_due
,
'submission_start'
:
submission_start
,
'title'
:
self
.
title
,
'assessments'
:
assessments
}
return
{
'success'
:
True
,
'msg'
:
''
,
'rubric'
:
rubric
,
'prompt'
:
self
.
prompt
,
'settings'
:
settings
'submission_due'
:
submission_due
,
'submission_start'
:
submission_start
,
'title'
:
self
.
title
,
'assessments'
:
assessment_list
}
@XBlock.json_handler
...
...
@@ -157,3 +170,4 @@ class StudioMixin(object):
'success'
:
True
,
'msg'
:
u''
,
'is_released'
:
self
.
is_released
()
}
openassessment/xblock/test/data/invalid_rubric.json
View file @
a73a0638
...
...
@@ -11,17 +11,24 @@
"</rubric>"
],
"prompt"
:
"My new prompt."
,
"settings"
:
{
"title"
:
"My new title."
,
"assessments"
:
[
"<assessments>"
,
"<assessment name=
\"
peer-assessment
\"
must_grade=
\"
5
\"
must_be_graded_by=
\"
3
\"
/>"
,
"<assessment name=
\"
self-assessment
\"
/>"
,
"</assessments>"
],
"submission_due"
:
"2014-02-27T09:46:28"
,
"submission_start"
:
"2014-02-10T09:46:28"
},
"submission_due"
:
"4014-02-27T09:46:28"
,
"submission_start"
:
"4014-02-10T09:46:28"
,
"title"
:
"My new title."
,
"assessments"
:
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
"4014-03-10T00:00:00"
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
""
}
],
"expected-assessment"
:
"peer-assessment"
,
"expected-criterion-prompt"
:
"Test criterion prompt"
}
...
...
openassessment/xblock/test/data/invalid_update_xblock.json
View file @
a73a0638
{
"no_rubric"
:
{
"prompt"
:
"My new prompt."
,
"settings"
:
{
"title"
:
"My new title."
,
"assessments"
:
[
"<assessments>"
,
"<assessment name=
\"
peer-assessment
\"
must_grade=
\"
5
\"
must_be_graded_by=
\"
3
\"
/>"
,
"<assessment name=
\"
self-assessment
\"
/>"
,
"</assessments>"
],
"submission_due"
:
"2014-02-27T09:46:28"
,
"submission_start"
:
"2014-02-10T09:46:28"
},
"title"
:
"My new title."
,
"assessments"
:
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
""
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
""
}
],
"submission_due"
:
"2014-02-27T09:46:28"
,
"submission_start"
:
"2014-02-10T09:46:28"
,
"expected_error"
:
"error"
},
"no_prompt"
:
{
...
...
@@ -26,20 +32,26 @@
"</criterion>"
,
"</rubric>"
],
"settings"
:
{
"title"
:
"My new title."
,
"assessments"
:
[
"<assessments>"
,
"<assessment name=
\"
peer-assessment
\"
must_grade=
\"
5
\"
must_be_graded_by=
\"
3
\"
/>"
,
"<assessment name=
\"
self-assessment
\"
/>"
,
"</assessments>"
],
"submission_due"
:
"2014-02-27T09:46:28"
,
"submission_start"
:
"2014-02-10T09:46:28"
},
"title"
:
"My new title."
,
"assessments"
:
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
""
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
""
}
],
"submission_due"
:
"2014-02-27T09:46:28"
,
"submission_start"
:
"2014-02-10T09:46:28"
,
"expected_error"
:
"error"
},
"no_s
ettings
"
:
{
"no_s
ubmission_due
"
:
{
"rubric"
:
[
"<rubric>"
,
"<prompt>Test prompt</prompt>"
,
...
...
@@ -52,9 +64,59 @@
"</rubric>"
],
"prompt"
:
"My new prompt."
,
"title"
:
"My new title."
,
"assessments"
:
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
""
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
""
}
],
"submission_start"
:
"2014-02-10T09:46:28"
,
"expected_error"
:
"error"
},
"invalid_dates"
:
{
"invalid_dates_one"
:
{
"rubric"
:
[
"<rubric>"
,
"<prompt>Test prompt</prompt>"
,
"<criterion>"
,
"<name>Test criterion</name>"
,
"<prompt>Test criterion prompt</prompt>"
,
"<option points=
\"
0
\"
><name>No</name><explanation>No explanation</explanation></option>"
,
"<option points=
\"
2
\"
><name>Yes</name><explanation>Yes explanation</explanation></option>"
,
"</criterion>"
,
"</rubric>"
],
"prompt"
:
"My new prompt."
,
"title"
:
"My new title."
,
"assessments"
:
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
""
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
""
}
],
"submission_due"
:
"2012-02-27T09:46:28"
,
"submission_start"
:
"2015-02-10T09:46:28"
,
"expected_error"
:
"cannot be later"
},
"invalid_dates_two"
:
{
"rubric"
:
[
"<rubric>"
,
"<prompt>Test prompt</prompt>"
,
...
...
@@ -67,17 +129,25 @@
"</rubric>"
],
"prompt"
:
"My new prompt."
,
"settings"
:
{
"title"
:
"My new title."
,
"assessments"
:
[
"<assessments>"
,
"<assessment name=
\"
peer-assessment
\"
must_grade=
\"
5
\"
must_be_graded_by=
\"
3
\"
/>"
,
"<assessment name=
\"
self-assessment
\"
start=
\"
2010-01-01
\"
due=
\"
2003-01-01
\"
/>"
,
"</assessments>"
"title"
:
"My new title."
,
"assessments"
:
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
""
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
"2003-01-02T00:00:00"
}
],
"submission_due"
:
"2012-02-27T09:46:28"
,
"submission_start"
:
"
2015-02-10T09:46:28"
},
"expected_error"
:
"cannot be earlier"
"submission_start"
:
"
"
,
"expected_error"
:
"cannot be later"
}
}
\ No newline at end of file
openassessment/xblock/test/data/parse_assessment_dicts.json
0 → 100644
View file @
a73a0638
{
"no-dates"
:
{
"assessments_list"
:
[
{
"name"
:
"peer-assessment"
,
"start"
:
""
,
"due"
:
""
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
""
,
"start"
:
""
}
],
"results"
:
[
{
"name"
:
"peer-assessment"
,
"start"
:
null
,
"due"
:
null
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
null
,
"start"
:
null
}
]
},
"student-training"
:
{
"assessments_list"
:
[
{
"name"
:
"student-training"
,
"start"
:
""
,
"due"
:
""
,
"examples"
:
"<example><answer>ẗëṡẗ äṅṡẅëṛ</answer><select criterion=
\"
Test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Another test criterion
\"
option=
\"
No
\"
/></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=
\"
Another test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Test criterion
\"
option=
\"
No
\"
/></example>"
},
{
"name"
:
"peer-assessment"
,
"start"
:
""
,
"due"
:
""
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
""
,
"start"
:
""
}
],
"results"
:
[
{
"name"
:
"student-training"
,
"due"
:
null
,
"start"
:
null
,
"examples"
:
[
{
"answer"
:
"ẗëṡẗ äṅṡẅëṛ"
,
"options_selected"
:
[
{
"criterion"
:
"Test criterion"
,
"option"
:
"Yes"
},
{
"criterion"
:
"Another test criterion"
,
"option"
:
"No"
}
]
},
{
"answer"
:
"äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ"
,
"options_selected"
:
[
{
"criterion"
:
"Another test criterion"
,
"option"
:
"Yes"
},
{
"criterion"
:
"Test criterion"
,
"option"
:
"No"
}
]
}
]
},
{
"name"
:
"peer-assessment"
,
"start"
:
null
,
"due"
:
null
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
null
,
"start"
:
null
}
]
},
"date-parsing"
:
{
"assessments_list"
:
[
{
"name"
:
"student-training"
,
"start"
:
"2014-10-10T01:00:01"
,
"due"
:
""
,
"examples"
:
"<example><answer>ẗëṡẗ äṅṡẅëṛ</answer><select criterion=
\"
Test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Another test criterion
\"
option=
\"
No
\"
/></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=
\"
Another test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Test criterion
\"
option=
\"
No
\"
/></example>"
},
{
"name"
:
"peer-assessment"
,
"start"
:
""
,
"due"
:
"2015-01-01T00:00:00"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
"2015-01-01T00:00:00"
,
"start"
:
""
}
],
"results"
:
[
{
"name"
:
"student-training"
,
"due"
:
null
,
"start"
:
"2014-10-10T01:00:01"
,
"examples"
:
[
{
"answer"
:
"ẗëṡẗ äṅṡẅëṛ"
,
"options_selected"
:
[
{
"criterion"
:
"Test criterion"
,
"option"
:
"Yes"
},
{
"criterion"
:
"Another test criterion"
,
"option"
:
"No"
}
]
},
{
"answer"
:
"äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ"
,
"options_selected"
:
[
{
"criterion"
:
"Another test criterion"
,
"option"
:
"Yes"
},
{
"criterion"
:
"Test criterion"
,
"option"
:
"No"
}
]
}
]
},
{
"name"
:
"peer-assessment"
,
"start"
:
null
,
"due"
:
"2015-01-01T00:00:00"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
"2015-01-01T00:00:00"
,
"start"
:
null
}
]
}
}
\ No newline at end of file
openassessment/xblock/test/data/parse_assessment_dicts_error.json
0 → 100644
View file @
a73a0638
{
"date-parsing-due"
:
{
"assessments_list"
:
[
{
"name"
:
"student-training"
,
"start"
:
"2014-10-10T01:00:01"
,
"due"
:
""
,
"examples"
:
"<examples><example><answer>ẗëṡẗ äṅṡẅëṛ</answer><select criterion=
\"
Test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Another test criterion
\"
option=
\"
No
\"
/></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=
\"
Another test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Test criterion
\"
option=
\"
No
\"
/></example></examples>"
},
{
"name"
:
"peer-assessment"
,
"start"
:
""
,
"due"
:
"2015-01-01T00:00:HI"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
"2015-014-01"
,
"start"
:
""
}
]
},
"date-parsing-start"
:
{
"assessments_list"
:
[
{
"name"
:
"peer-assessment"
,
"start"
:
"2014-13-13T00:00:00"
,
"due"
:
""
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
""
,
"start"
:
""
}
]
},
"no-answers-in-examples"
:
{
"assessments_list"
:
[
{
"name"
:
"student-training"
,
"start"
:
""
,
"due"
:
""
,
"examples"
:
"<example><select criterion=
\"
Test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Another test criterion
\"
option=
\"
No
\"
/></example><example><answer>äṅöẗḧëṛ ẗëṡẗ äṅṡẅëṛ</answer><select criterion=
\"
Another test criterion
\"
option=
\"
Yes
\"
/><select criterion=
\"
Test criterion
\"
option=
\"
No
\"
/></example>"
},
{
"name"
:
"peer-assessment"
,
"start"
:
""
,
"due"
:
""
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
""
,
"start"
:
""
}
]
},
"must_grade"
:
{
"assessments_list"
:
[
{
"name"
:
"peer-assessment"
,
"start"
:
""
,
"due"
:
""
,
"must_grade"
:
"Not a number fool!"
,
"must_be_graded_by"
:
3
},
{
"name"
:
"self-assessment"
,
"due"
:
""
,
"start"
:
""
}
]
},
"must_be_graded_by"
:
{
"assessments_list"
:
[
{
"name"
:
"peer-assessment"
,
"start"
:
""
,
"due"
:
""
,
"must_grade"
:
3
,
"must_be_graded_by"
:
"Not a number fool!"
},
{
"name"
:
"self-assessment"
,
"due"
:
""
,
"start"
:
""
}
]
}
}
\ No newline at end of file
openassessment/xblock/test/data/update_xblock.json
View file @
a73a0638
...
...
@@ -12,17 +12,24 @@
"</rubric>"
],
"prompt"
:
"My new prompt."
,
"settings"
:
{
"title"
:
"My new title."
,
"assessments"
:
[
"<assessments>"
,
"<assessment name=
\"
peer-assessment
\"
must_grade=
\"
5
\"
must_be_graded_by=
\"
3
\"
/>"
,
"<assessment name=
\"
self-assessment
\"
/>"
,
"</assessments>"
],
"submission_due"
:
"4014-02-27T09:46:28"
,
"submission_start"
:
"4014-02-10T09:46:28"
},
"submission_due"
:
"4014-02-27T09:46:28"
,
"submission_start"
:
"4014-02-10T09:46:28"
,
"title"
:
"My new title."
,
"assessments"
:
[
{
"name"
:
"peer-assessment"
,
"must_grade"
:
5
,
"must_be_graded_by"
:
3
,
"start"
:
""
,
"due"
:
"4014-03-10T00:00:00"
},
{
"name"
:
"self-assessment"
,
"start"
:
""
,
"due"
:
""
}
],
"expected-assessment"
:
"peer-assessment"
,
"expected-criterion-prompt"
:
"Test criterion prompt"
}
...
...
openassessment/xblock/test/test_studio.py
View file @
a73a0638
...
...
@@ -33,8 +33,12 @@ class StudioViewTest(XBlockHandlerTestCase):
rubric
=
etree
.
fromstring
(
resp
[
'rubric'
])
self
.
assertEqual
(
rubric
.
tag
,
'rubric'
)
assessments
=
etree
.
fromstring
(
resp
[
'settings'
][
'assessments'
])
self
.
assertEqual
(
assessments
.
tag
,
'assessments'
)
# Verify that every assessment in the list of assessments has a name.
for
assessment_dict
in
resp
[
'assessments'
]:
self
.
assertTrue
(
assessment_dict
.
get
(
'name'
,
False
))
if
assessment_dict
.
get
(
'name'
)
==
'studnet-training'
:
examples
=
etree
.
fromstring
(
assessment_dict
[
'examples'
])
self
.
assertEqual
(
examples
.
tag
,
'examples'
)
@mock.patch
(
'openassessment.xblock.xml.serialize_rubric_to_xml_str'
)
@scenario
(
'data/basic_scenario.xml'
)
...
...
@@ -52,7 +56,6 @@ class StudioViewTest(XBlockHandlerTestCase):
def
test_update_xblock
(
self
,
xblock
,
data
):
# First, parse XML data into a single string.
data
[
'rubric'
]
=
""
.
join
(
data
[
'rubric'
])
data
[
'settings'
][
'assessments'
]
=
""
.
join
(
data
[
'settings'
][
'assessments'
])
xblock
.
published_date
=
None
# Test that we can update the xblock with the expected configuration.
request
=
json
.
dumps
(
data
)
...
...
@@ -66,7 +69,7 @@ class StudioViewTest(XBlockHandlerTestCase):
# Check that the XBlock fields were updated
# We don't need to be exhaustive here, because we have other unit tests
# that verify this extensively.
self
.
assertEqual
(
xblock
.
title
,
data
[
'
settings'
][
'
title'
])
self
.
assertEqual
(
xblock
.
title
,
data
[
'title'
])
self
.
assertEqual
(
xblock
.
prompt
,
data
[
'prompt'
])
self
.
assertEqual
(
xblock
.
rubric_assessments
[
0
][
'name'
],
data
[
'expected-assessment'
])
self
.
assertEqual
(
xblock
.
rubric_criteria
[
0
][
'prompt'
],
data
[
'expected-criterion-prompt'
])
...
...
@@ -76,7 +79,6 @@ class StudioViewTest(XBlockHandlerTestCase):
def
test_update_context_post_release
(
self
,
xblock
,
data
):
# First, parse XML data into a single string.
data
[
'rubric'
]
=
""
.
join
(
data
[
'rubric'
])
data
[
'settings'
][
'assessments'
]
=
""
.
join
(
data
[
'settings'
][
'assessments'
])
# XBlock start date defaults to already open,
# so we should get an error when trying to update anything that change the number of points
...
...
@@ -93,9 +95,6 @@ class StudioViewTest(XBlockHandlerTestCase):
if
'rubric'
in
data
:
data
[
'rubric'
]
=
""
.
join
(
data
[
'rubric'
])
if
'settings'
in
data
and
'assessments'
in
data
[
'settings'
]:
data
[
'settings'
][
'assessments'
]
=
""
.
join
(
data
[
'settings'
][
'assessments'
])
xblock
.
published_date
=
None
resp
=
self
.
request
(
xblock
,
'update_editor_context'
,
json
.
dumps
(
data
),
response_format
=
'json'
)
...
...
@@ -107,7 +106,6 @@ class StudioViewTest(XBlockHandlerTestCase):
def
test_update_rubric_invalid
(
self
,
xblock
,
data
):
# First, parse XML data into a single string.
data
[
'rubric'
]
=
""
.
join
(
data
[
'rubric'
])
data
[
'settings'
][
'assessments'
]
=
""
.
join
(
data
[
'settings'
][
'assessments'
])
request
=
json
.
dumps
(
data
)
...
...
openassessment/xblock/test/test_xml.py
View file @
a73a0638
...
...
@@ -15,7 +15,7 @@ from openassessment.xblock.xml import (
serialize_content
,
parse_from_xml_str
,
parse_rubric_xml_str
,
parse_examples_xml_str
,
parse_assessments_xml_str
,
serialize_rubric_to_xml_str
,
serialize_examples_to_xml_str
,
serialize_assessments_to_xml_str
,
UpdateFromXmlError
serialize_assessments_to_xml_str
,
UpdateFromXmlError
,
parse_assessment_dictionaries
)
...
...
@@ -358,6 +358,21 @@ class TestParseAssessmentsFromXml(TestCase):
self
.
assertEqual
(
assessments
,
data
[
'assessments'
])
@ddt.ddt
class
TestParseAssessmentsFromDictionaries
(
TestCase
):
@ddt.file_data
(
'data/parse_assessment_dicts.json'
)
def
test_parse_assessments_dictionary
(
self
,
data
):
config
=
parse_assessment_dictionaries
(
data
[
'assessments_list'
])
for
i
in
range
(
0
,
len
(
config
)):
self
.
assertEqual
(
config
[
i
],
data
[
'results'
][
i
])
@ddt.file_data
(
'data/parse_assessment_dicts_error.json'
)
def
test_parse_assessments_dictionary_error
(
self
,
data
):
with
self
.
assertRaises
(
UpdateFromXmlError
):
parse_assessment_dictionaries
(
data
[
'assessments_list'
])
@ddt.ddt
class
TestUpdateFromXml
(
TestCase
):
...
...
@@ -399,3 +414,4 @@ class TestUpdateFromXml(TestCase):
def
test_parse_from_xml_error
(
self
,
data
):
with
self
.
assertRaises
(
UpdateFromXmlError
):
parse_from_xml_str
(
""
.
join
(
data
[
'xml'
]))
openassessment/xblock/validation.py
View file @
a73a0638
...
...
@@ -295,7 +295,7 @@ def validator(oa_block, strict_post_release=True):
# Dates
submission_dates
=
[(
submission_dict
[
'start'
],
submission_dict
[
'due'
])]
assessment_dates
=
[(
asmnt
[
'start'
],
asmnt
[
'due'
]
)
for
asmnt
in
assessments
]
assessment_dates
=
[(
asmnt
.
get
(
'start'
),
asmnt
.
get
(
'due'
)
)
for
asmnt
in
assessments
]
success
,
msg
=
validate_dates
(
oa_block
.
start
,
oa_block
.
due
,
submission_dates
+
assessment_dates
)
if
not
success
:
return
(
False
,
msg
)
...
...
openassessment/xblock/xml.py
View file @
a73a0638
...
...
@@ -374,6 +374,68 @@ def parse_examples_xml(examples):
return
examples_list
def
parse_assessment_dictionaries
(
input_assessments
):
assessments_list
=
[]
for
assessment
in
input_assessments
:
assessment_dict
=
dict
()
# Assessment name
if
assessment
.
get
(
'name'
):
assessment_dict
[
'name'
]
=
unicode
(
assessment
.
get
(
'name'
))
else
:
raise
UpdateFromXmlError
(
_
(
'All "assessment" elements must contain a "name" element.'
))
# Assessment start
if
assessment
.
get
(
'start'
):
try
:
parsed_start
=
parse_date
(
assessment
.
get
(
'start'
))
assessment_dict
[
'start'
]
=
parsed_start
except
UpdateFromXmlError
:
raise
UpdateFromXmlError
(
_
(
'The date format in the "start" attribute is invalid. Make sure the date is formatted as YYYY-MM-DDTHH:MM:SS.'
))
else
:
assessment_dict
[
'start'
]
=
None
# Assessment due
if
assessment
.
get
(
'due'
):
try
:
parsed_due
=
parse_date
(
assessment
.
get
(
'due'
))
assessment_dict
[
'due'
]
=
parsed_due
except
UpdateFromXmlError
:
raise
UpdateFromXmlError
(
_
(
'The date format in the "due" attribute is invalid. Make sure the date is formatted as YYYY-MM-DDTHH:MM:SS.'
))
else
:
assessment_dict
[
'due'
]
=
None
# Assessment must_grade
if
assessment
.
get
(
'must_grade'
):
try
:
assessment_dict
[
'must_grade'
]
=
int
(
assessment
.
get
(
'must_grade'
))
except
ValueError
:
raise
UpdateFromXmlError
(
_
(
'The "must_grade" value must be a positive integer.'
))
# Assessment must_be_graded_by
if
assessment
.
get
(
'must_be_graded_by'
):
try
:
assessment_dict
[
'must_be_graded_by'
]
=
int
(
assessment
.
get
(
'must_be_graded_by'
))
except
ValueError
:
raise
UpdateFromXmlError
(
_
(
'The "must_be_graded_by" value must be a positive integer.'
))
# Training examples (can be for AI or for Student Training)
if
assessment
.
get
(
'examples'
):
try
:
assessment_dict
[
'examples'
]
=
parse_examples_xml_str
(
assessment
.
get
(
'examples'
))
except
(
UpdateFromXmlError
,
UnicodeError
)
as
ex
:
raise
UpdateFromXmlError
(
_
(
"There was an error in parsing the {0} examples: {1}"
.
format
(
assessment
.
get
(
'name'
),
ex
)
))
# Update the list of assessments
assessments_list
.
append
(
assessment_dict
)
return
assessments_list
def
parse_assessments_xml
(
assessments_root
):
"""
Parse the <assessments> element in the OpenAssessment XBlock's content XML.
...
...
@@ -765,8 +827,12 @@ def parse_examples_xml_str(xml):
"""
xml
=
u"<data>"
+
xml
+
u"</data>"
return
parse_examples_xml
(
list
(
_unicode_to_xml
(
xml
)))
if
"<examples>"
not
in
xml
:
xml
=
u"<data>"
+
xml
+
u"</data>"
else
:
xml
=
unicode
(
xml
)
return
parse_examples_xml
(
list
(
_unicode_to_xml
(
xml
)
.
findall
(
'example'
)))
def
_unicode_to_xml
(
xml
):
...
...
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