Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
problem-builder
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
OpenEdx
problem-builder
Commits
9b9b2aa1
Commit
9b9b2aa1
authored
Mar 09, 2014
by
Xavier Antoviaque
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add base support for MRQ, with tips displayed next to the answers
parent
f03bf074
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
221 additions
and
58 deletions
+221
-58
mentoring/__init__.py
+1
-0
mentoring/light_children.py
+6
-0
mentoring/mcq.py
+13
-5
mentoring/mrq.py
+83
-0
mentoring/public/css/questionnaire.css
+20
-1
mentoring/public/img/incorrect-icon.png
+0
-0
mentoring/public/js/mentoring_edit.js
+1
-1
mentoring/public/js/questionnaire.js
+38
-11
mentoring/questionnaire.py
+11
-3
mentoring/templates/html/mcqblock_choices.html
+6
-3
mentoring/templates/html/mrqblock_choices.html
+14
-0
mentoring/templates/html/tip_choice_group.html
+12
-0
mentoring/templates/html/tip_question_group.html
+1
-1
mentoring/tip.py
+14
-32
mentoring/utils.py
+0
-1
setup.py
+1
-0
No files found.
mentoring/__init__.py
View file @
9b9b2aa1
...
@@ -3,6 +3,7 @@ from .choice import ChoiceBlock
...
@@ -3,6 +3,7 @@ from .choice import ChoiceBlock
from
.dataexport
import
MentoringDataExportBlock
from
.dataexport
import
MentoringDataExportBlock
from
.html
import
HTMLBlock
from
.html
import
HTMLBlock
from
.mcq
import
MCQBlock
from
.mcq
import
MCQBlock
from
.mrq
import
MRQBlock
from
.mentoring
import
MentoringBlock
from
.mentoring
import
MentoringBlock
from
.message
import
MentoringMessageBlock
from
.message
import
MentoringMessageBlock
from
.table
import
MentoringTableBlock
,
MentoringTableColumnBlock
,
MentoringTableColumnHeaderBlock
from
.table
import
MentoringTableBlock
,
MentoringTableColumnBlock
,
MentoringTableColumnHeaderBlock
...
...
mentoring/light_children.py
View file @
9b9b2aa1
...
@@ -249,6 +249,12 @@ class String(LightChildField):
...
@@ -249,6 +249,12 @@ class String(LightChildField):
class
Boolean
(
LightChildField
):
class
Boolean
(
LightChildField
):
pass
pass
class
List
(
LightChildField
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
value
=
kwargs
.
get
(
'default'
,
[])
class
Scope
(
object
):
class
Scope
(
object
):
content
=
None
content
=
None
user_state
=
None
user_state
=
None
mentoring/mcq.py
View file @
9b9b2aa1
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
#
# Copyright (C) 2014 Harvard
# Copyright (C) 2014 Harvard
...
@@ -56,11 +55,11 @@ class MCQBlock(QuestionnaireAbstractBlock):
...
@@ -56,11 +55,11 @@ class MCQBlock(QuestionnaireAbstractBlock):
completed
=
True
completed
=
True
tips_fragments
=
[]
tips_fragments
=
[]
for
tip
in
self
.
get_tips
():
for
tip
in
self
.
get_tips
():
completed
=
completed
and
tip
.
is_completed
([
submission
]
)
completed
=
completed
and
self
.
is_tip_completed
(
tip
,
submission
)
if
tip
.
is_tip_displayed
([
submission
])
:
if
submission
in
tip
.
display_with_defaults
:
tips_fragments
.
append
(
tip
.
render
(
submission
))
tips_fragments
.
append
(
tip
.
render
())
formatted_tips
=
render_template
(
'templates/html/tip_group.html'
,
{
formatted_tips
=
render_template
(
'templates/html/tip_
question_
group.html'
,
{
'self'
:
self
,
'self'
:
self
,
'tips_fragments'
:
tips_fragments
,
'tips_fragments'
:
tips_fragments
,
'submission'
:
submission
,
'submission'
:
submission
,
...
@@ -75,3 +74,12 @@ class MCQBlock(QuestionnaireAbstractBlock):
...
@@ -75,3 +74,12 @@ class MCQBlock(QuestionnaireAbstractBlock):
}
}
log
.
debug
(
u'MCQ submission result:
%
s'
,
result
)
log
.
debug
(
u'MCQ submission result:
%
s'
,
result
)
return
result
return
result
def
is_tip_completed
(
self
,
tip
,
submission
):
if
not
submission
:
return
False
if
submission
in
tip
.
reject_with_defaults
:
return
False
return
True
mentoring/mrq.py
0 → 100644
View file @
9b9b2aa1
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 edX
#
# Authors:
# Xavier Antoviaque <xavier@antoviaque.org>
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
# General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
# Imports ###########################################################
import
logging
from
.light_children
import
List
,
Scope
from
.questionnaire
import
QuestionnaireAbstractBlock
from
.utils
import
render_template
# Globals ###########################################################
log
=
logging
.
getLogger
(
__name__
)
# Classes ###########################################################
class
MRQBlock
(
QuestionnaireAbstractBlock
):
"""
An XBlock used to ask multiple-response questions
"""
student_choices
=
List
(
help
=
"Last submissions by the student"
,
default
=
[],
scope
=
Scope
.
user_state
)
def
submit
(
self
,
submissions
):
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
completed
=
True
results
=
[]
for
choice
in
self
.
custom_choices
:
choice_completed
=
True
choice_tips_fragments
=
[]
choice_selected
=
choice
.
value
in
submissions
for
tip
in
self
.
get_tips
():
if
choice
.
value
in
tip
.
display_with_defaults
:
choice_tips_fragments
.
append
(
tip
.
render
())
if
((
not
choice_selected
and
choice
.
value
in
tip
.
require_with_defaults
)
or
(
choice_selected
and
choice
.
value
in
tip
.
reject_with_defaults
)):
choice_completed
=
False
completed
=
completed
and
choice_completed
results
.
append
({
'value'
:
choice
.
value
,
'selected'
:
choice_selected
,
'completed'
:
choice_completed
,
'tips'
:
render_template
(
'templates/html/tip_choice_group.html'
,
{
'self'
:
self
,
'tips_fragments'
:
choice_tips_fragments
,
'completed'
:
choice_completed
,
}),
})
self
.
student_choices
=
submissions
result
=
{
'submissions'
:
submissions
,
'completed'
:
completed
,
'choices'
:
results
,
}
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
return
result
mentoring/public/css/questionnaire.css
View file @
9b9b2aa1
...
@@ -8,7 +8,26 @@
...
@@ -8,7 +8,26 @@
margin-bottom
:
5px
;
margin-bottom
:
5px
;
}
}
.mentoring
.choices
.choice
{
.mentoring
.choices
.choices
.choice
{
margin
:
10px
0
;
}
.mentoring
.choices
.choices
.choice-result
{
padding-right
:
40px
;
background-position
:
center
;
background-repeat
:
no-repeat
;
}
.mentoring
.choices
.choices
.choice-result.correct
{
background-image
:
url({{ correct_icon_url }})
;
cursor
:
pointer
;
}
.mentoring
.choices
.choices
.choice-result.incorrect
{
background-image
:
url({{ incorrect_icon_url }})
;
cursor
:
pointer
;
}
.mentoring
.rating
.choices
.choice
{
margin-right
:
10px
;
margin-right
:
10px
;
}
}
...
...
mentoring/public/img/incorrect-icon.png
0 → 100644
View file @
9b9b2aa1
257 Bytes
mentoring/public/js/mentoring_edit.js
View file @
9b9b2aa1
...
@@ -2,7 +2,7 @@ function MentoringEditBlock(runtime, element) {
...
@@ -2,7 +2,7 @@ function MentoringEditBlock(runtime, element) {
var
xmlEditorTextarea
=
$
(
'.block-xml-editor'
,
element
),
var
xmlEditorTextarea
=
$
(
'.block-xml-editor'
,
element
),
xmlEditor
=
CodeMirror
.
fromTextArea
(
xmlEditorTextarea
[
0
],
{
mode
:
'xml'
});
xmlEditor
=
CodeMirror
.
fromTextArea
(
xmlEditorTextarea
[
0
],
{
mode
:
'xml'
});
$
(
'.save-button'
).
bind
(
'click'
,
function
()
{
$
(
'.save-button'
,
element
).
bind
(
'click'
,
function
()
{
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'studio_submit'
),
var
handlerUrl
=
runtime
.
handlerUrl
(
element
,
'studio_submit'
),
data
=
{
data
=
{
'xml_content'
:
xmlEditor
.
getValue
(),
'xml_content'
:
xmlEditor
.
getValue
(),
...
...
mentoring/public/js/questionnaire.js
View file @
9b9b2aa1
function
QuestionnaireBlock
(
runtime
,
element
)
{
// TODO: Split in two files
function
MCQBlock
(
runtime
,
element
)
{
return
{
return
{
submit
:
function
()
{
var
checkedRadio
=
$
(
'input[type=radio]:checked'
,
element
);
if
(
checkedRadio
.
length
)
{
return
checkedRadio
.
val
();
}
else
{
return
null
;
}
},
handleSubmit
:
function
(
result
)
{
handleSubmit
:
function
(
result
)
{
var
tipsDom
=
$
(
element
).
parent
().
find
(
'.messages'
),
var
tipsDom
=
$
(
element
).
parent
().
find
(
'.messages'
),
tipHtml
=
(
result
||
{}).
tips
||
''
;
tipHtml
=
(
result
||
{}).
tips
||
''
;
...
@@ -8,21 +20,36 @@ function QuestionnaireBlock(runtime, element) {
...
@@ -8,21 +20,36 @@ function QuestionnaireBlock(runtime, element) {
tipsDom
.
append
(
tipHtml
);
tipsDom
.
append
(
tipHtml
);
}
}
}
}
}
}
;
}
}
function
MCQBlock
(
runtime
,
element
)
{
function
MRQBlock
(
runtime
,
element
)
{
var
init
=
QuestionnaireBlock
(
runtime
,
element
);
return
{
submit
:
function
()
{
var
checkedCheckboxes
=
$
(
'input[type=checkbox]:checked'
,
element
),
checkedValues
=
[];
init
.
submit
=
function
()
{
$
.
each
(
checkedCheckboxes
,
function
(
index
,
checkedCheckbox
)
{
var
checkedRadio
=
$
(
'input[type=radio]:checked'
,
element
);
checkedValues
.
push
(
$
(
checkedCheckbox
).
val
());
});
return
checkedValues
;
},
if
(
checkedRadio
.
length
)
{
handleSubmit
:
function
(
result
)
{
return
checkedRadio
.
val
();
$
.
each
(
result
.
choices
,
function
(
index
,
choice
)
{
var
choice_input_dom
=
$
(
'.choice input[value='
+
choice
.
value
+
']'
,
element
),
choice_dom
=
choice_input_dom
.
closest
(
'.choice'
),
choice_result_dom
=
$
(
'.choice-result'
,
choice_dom
),
choice_tips_dom
=
$
(
'.choice-tips'
,
choice_dom
);
if
(
choice
.
completed
)
{
choice_result_dom
.
removeClass
(
'incorrect'
).
addClass
(
'correct'
);
}
else
{
}
else
{
return
null
;
choice_result_dom
.
removeClass
(
'correct'
).
addClass
(
'incorrect'
)
;
}
}
};
return
init
;
choice_tips_dom
.
html
(
choice
.
tips
);
});
}
};
}
}
mentoring/questionnaire.py
View file @
9b9b2aa1
...
@@ -48,8 +48,11 @@ class QuestionnaireAbstractBlock(LightChild):
...
@@ -48,8 +48,11 @@ class QuestionnaireAbstractBlock(LightChild):
values entered by the student, and supports multiple types of multiple-choice
values entered by the student, and supports multiple types of multiple-choice
set, with preset choices and author-defined values.
set, with preset choices and author-defined values.
"""
"""
type
=
String
(
help
=
"Type of questionnaire"
,
scope
=
Scope
.
content
,
default
=
"choices"
)
question
=
String
(
help
=
"Question to ask the student"
,
scope
=
Scope
.
content
,
default
=
""
)
question
=
String
(
help
=
"Question to ask the student"
,
scope
=
Scope
.
content
,
default
=
""
)
valid_types
=
(
'choices'
)
@classmethod
@classmethod
def
init_block_from_node
(
cls
,
block
,
node
,
attr
):
def
init_block_from_node
(
cls
,
block
,
node
,
attr
):
block
.
light_children
=
[]
block
.
light_children
=
[]
...
@@ -77,11 +80,16 @@ class QuestionnaireAbstractBlock(LightChild):
...
@@ -77,11 +80,16 @@ class QuestionnaireAbstractBlock(LightChild):
})
})
fragment
=
Fragment
(
html
)
fragment
=
Fragment
(
html
)
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
fragment
.
add_css
(
render_template
(
'public/css/questionnaire.css'
,
{
'public/css/questionnaire.css'
))
'self'
:
self
,
'correct_icon_url'
:
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
'public/img/correct-icon.png'
),
'incorrect_icon_url'
:
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
'public/img/incorrect-icon.png'
),
}))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
'public/js/questionnaire.js'
))
'public/js/questionnaire.js'
))
fragment
.
initialize_js
(
'QuestionnaireBlock'
)
fragment
.
initialize_js
(
name
)
return
fragment
return
fragment
@property
@property
...
...
mentoring/templates/html/mcqblock_choices.html
View file @
9b9b2aa1
...
@@ -2,9 +2,12 @@
...
@@ -2,9 +2,12 @@
<legend
class=
"question"
>
{{ self.question }}
</legend>
<legend
class=
"question"
>
{{ self.question }}
</legend>
<div
class=
"choices"
>
<div
class=
"choices"
>
{% for choice in custom_choices %}
{% for choice in custom_choices %}
<span
class=
"choice"
>
<div
class=
"choice"
>
<label><input
class=
"choice-selector"
type=
"radio"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
self
.
student_choice =
=
choice
.
value
%}
checked
{%
endif
%}
>
{{ choice.content }}
</label>
<span
class=
"choice-result"
></span>
</span>
<label
class=
"choice-label"
>
<input
class=
"choice-selector"
type=
"radio"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
self
.
student_choice =
=
choice
.
value
%}
checked
{%
endif
%}
>
{{ choice.content }}
</label>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
</fieldset>
</fieldset>
mentoring/templates/html/mrqblock_choices.html
0 → 100644
View file @
9b9b2aa1
<fieldset
class=
"choices"
>
<legend
class=
"question"
>
{{ self.question }}
</legend>
<div
class=
"choices"
>
{% for choice in custom_choices %}
<div
class=
"choice"
>
<span
class=
"choice-result"
></span>
<label
class=
"choice-label"
>
<input
class=
"choice-selector"
type=
"checkbox"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
choice
.
value
in
self
.
student_choices
%}
checked
{%
endif
%}
>
{{ choice.content }}
</label>
<div
class=
"choice-tips"
></div>
</div>
{% endfor %}
</div>
</fieldset>
mentoring/templates/html/tip_choice_group.html
0 → 100644
View file @
9b9b2aa1
<div
class=
"tip-choice-group"
>
<strong>
{% if completed %}
Correct! You have made the right choice.
{% else %}
This is not the right choice.
{% endif %}
</strong>
{% for tip_fragment in tips_fragments %}
{{ tip_fragment.body_html|safe }}
{% endfor %}
</div>
mentoring/templates/html/tip_group.html
→
mentoring/templates/html/tip_
question_
group.html
View file @
9b9b2aa1
<div
class=
"
mcq-ti
p"
>
<div
class=
"
tip-question-grou
p"
>
<strong>
<strong>
To the question
<span
class=
"italic"
>
"{{ self.question }}"
</span>
,
To the question
<span
class=
"italic"
>
"{{ self.question }}"
</span>
,
{% if submission %}
{% if submission %}
...
...
mentoring/tip.py
View file @
9b9b2aa1
...
@@ -36,16 +36,14 @@ log = logging.getLogger(__name__)
...
@@ -36,16 +36,14 @@ log = logging.getLogger(__name__)
# Functions #########################################################
# Functions #########################################################
def
commas_to_
lis
t
(
commas_str
):
def
commas_to_
se
t
(
commas_str
):
"""
"""
Converts a comma-separated string to a
lis
t
Converts a comma-separated string to a
se
t
"""
"""
if
commas_str
is
None
:
if
not
commas_str
:
return
None
# Means default value (which can be non-empty)
return
set
()
elif
commas_str
==
''
:
return
[]
# Means empty list
else
:
else
:
return
commas_str
.
split
(
','
)
return
set
(
commas_str
.
split
(
','
)
)
...
@@ -58,8 +56,9 @@ class TipBlock(LightChild):
...
@@ -58,8 +56,9 @@ class TipBlock(LightChild):
content
=
String
(
help
=
"Text of the tip to provide if needed"
,
scope
=
Scope
.
content
,
default
=
""
)
content
=
String
(
help
=
"Text of the tip to provide if needed"
,
scope
=
Scope
.
content
,
default
=
""
)
display
=
String
(
help
=
"List of choices to display the tip for"
,
scope
=
Scope
.
content
,
default
=
None
)
display
=
String
(
help
=
"List of choices to display the tip for"
,
scope
=
Scope
.
content
,
default
=
None
)
reject
=
String
(
help
=
"List of choices to reject"
,
scope
=
Scope
.
content
,
default
=
None
)
reject
=
String
(
help
=
"List of choices to reject"
,
scope
=
Scope
.
content
,
default
=
None
)
require
=
String
(
help
=
"List of choices to require"
,
scope
=
Scope
.
content
,
default
=
None
)
def
render
(
self
,
submission
):
def
render
(
self
):
"""
"""
Returns a fragment containing the formatted tip
Returns a fragment containing the formatted tip
"""
"""
...
@@ -70,32 +69,15 @@ class TipBlock(LightChild):
...
@@ -70,32 +69,15 @@ class TipBlock(LightChild):
}))
}))
return
self
.
xblock_container
.
fragment_text_rewriting
(
fragment
)
return
self
.
xblock_container
.
fragment_text_rewriting
(
fragment
)
def
is_completed
(
self
,
submissions
):
if
not
submissions
:
return
False
for
submission
in
submissions
:
if
submission
in
self
.
reject_with_defaults
:
return
False
return
True
def
is_tip_displayed
(
self
,
submissions
):
for
submission
in
submissions
:
if
submission
in
self
.
display_with_defaults
:
return
True
return
False
@property
@property
def
display_with_defaults
(
self
):
def
display_with_defaults
(
self
):
display
=
commas_to_list
(
self
.
display
)
display
=
commas_to_set
(
self
.
display
)
if
display
is
None
:
return
display
|
self
.
reject_with_defaults
|
self
.
require_with_defaults
display
=
self
.
reject_with_defaults
else
:
display
+=
[
choice
for
choice
in
self
.
reject_with_defaults
if
choice
not
in
display
]
return
display
@property
@property
def
reject_with_defaults
(
self
):
def
reject_with_defaults
(
self
):
reject
=
commas_to_list
(
self
.
reject
)
return
commas_to_set
(
self
.
reject
)
return
reject
or
[]
@property
def
require_with_defaults
(
self
):
return
commas_to_set
(
self
.
require
)
mentoring/utils.py
View file @
9b9b2aa1
...
@@ -73,7 +73,6 @@ def get_scenarios_from_path(scenarios_path, include_identifier=False):
...
@@ -73,7 +73,6 @@ def get_scenarios_from_path(scenarios_path, include_identifier=False):
"""
"""
base_fullpath
=
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
))
base_fullpath
=
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
))
scenarios_fullpath
=
os
.
path
.
join
(
base_fullpath
,
scenarios_path
)
scenarios_fullpath
=
os
.
path
.
join
(
base_fullpath
,
scenarios_path
)
print
scenarios_fullpath
scenarios
=
[]
scenarios
=
[]
if
os
.
path
.
isdir
(
scenarios_fullpath
):
if
os
.
path
.
isdir
(
scenarios_fullpath
):
...
...
setup.py
View file @
9b9b2aa1
...
@@ -54,6 +54,7 @@ BLOCKS_CHILDREN = [
...
@@ -54,6 +54,7 @@ BLOCKS_CHILDREN = [
'answer = mentoring:AnswerBlock'
,
'answer = mentoring:AnswerBlock'
,
'quizz = mentoring:MCQBlock'
,
'quizz = mentoring:MCQBlock'
,
'mcq = mentoring:MCQBlock'
,
'mcq = mentoring:MCQBlock'
,
'mrq = mentoring:MRQBlock'
,
'message = mentoring:MentoringMessageBlock'
,
'message = mentoring:MentoringMessageBlock'
,
'tip = mentoring:TipBlock'
,
'tip = mentoring:TipBlock'
,
'choice = mentoring:ChoiceBlock'
,
'choice = mentoring:ChoiceBlock'
,
...
...
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