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
5f13249c
Commit
5f13249c
authored
Mar 04, 2015
by
Braden MacDonald
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
i18n
parent
dd7def1c
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
318 additions
and
139 deletions
+318
-139
mentoring/answer.py
+30
-17
mentoring/choice.py
+19
-9
mentoring/mcq.py
+38
-15
mentoring/mentoring.py
+30
-19
mentoring/message.py
+17
-7
mentoring/mrq.py
+22
-13
mentoring/public/js/mentoring.js
+6
-0
mentoring/public/js/mentoring_standard_view.js
+1
-1
mentoring/questionnaire.py
+26
-14
mentoring/table.py
+13
-9
mentoring/templates/html/mentoring.html
+6
-1
mentoring/templates/html/mentoring_add_buttons.html
+11
-11
mentoring/templates/html/mentoring_attempts.html
+10
-1
mentoring/templates/html/mentoring_grade.html
+32
-6
mentoring/templates/html/questionnaire_add_buttons.html
+2
-2
mentoring/templates/html/ratingblock_edit_preview.html
+3
-2
mentoring/tests/integration/test_assessment.py
+19
-4
mentoring/tip.py
+33
-8
No files found.
mentoring/answer.py
View file @
5f13249c
...
@@ -43,6 +43,11 @@ import uuid
...
@@ -43,6 +43,11 @@ import uuid
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
...
@@ -94,7 +99,12 @@ class AnswerMixin(object):
...
@@ -94,7 +99,12 @@ class AnswerMixin(object):
if
not
data
.
name
:
if
not
data
.
name
:
add_error
(
u"A Question ID is required."
)
add_error
(
u"A Question ID is required."
)
def
_
(
self
,
text
):
""" translate text """
return
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
(
text
)
@XBlock.needs
(
"i18n"
)
class
AnswerBlock
(
AnswerMixin
,
StepMixin
,
StudioEditableXBlockMixin
,
XBlock
):
class
AnswerBlock
(
AnswerMixin
,
StepMixin
,
StudioEditableXBlockMixin
,
XBlock
):
"""
"""
A field where the student enters an answer
A field where the student enters an answer
...
@@ -103,32 +113,32 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
...
@@ -103,32 +113,32 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
to make them searchable and referenceable across xblocks.
to make them searchable and referenceable across xblocks.
"""
"""
name
=
String
(
name
=
String
(
display_name
=
"Question ID (name)"
,
display_name
=
_
(
"Question ID (name)"
)
,
help
=
"The ID of this block. Should be unique unless you want the answer to be used in multiple places."
,
help
=
_
(
"The ID of this block. Should be unique unless you want the answer to be used in multiple places."
)
,
default
=
""
,
default
=
""
,
scope
=
Scope
.
content
scope
=
Scope
.
content
)
)
default_from
=
String
(
default_from
=
String
(
display_name
=
"Default From"
,
display_name
=
_
(
"Default From"
)
,
help
=
"If a question ID is specified, get the default value from this answer."
,
help
=
_
(
"If a question ID is specified, get the default value from this answer."
)
,
default
=
None
,
default
=
None
,
scope
=
Scope
.
content
scope
=
Scope
.
content
)
)
min_characters
=
Integer
(
min_characters
=
Integer
(
display_name
=
"Min. Allowed Characters"
,
display_name
=
_
(
"Min. Allowed Characters"
)
,
help
=
"Minimum number of characters allowed for the answer"
,
help
=
_
(
"Minimum number of characters allowed for the answer"
)
,
default
=
0
,
default
=
0
,
scope
=
Scope
.
content
scope
=
Scope
.
content
)
)
question
=
String
(
question
=
String
(
display_name
=
"Question"
,
display_name
=
_
(
"Question"
)
,
help
=
"Question to ask the student"
,
help
=
_
(
"Question to ask the student"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
default
=
""
)
)
weight
=
Float
(
weight
=
Float
(
display_name
=
"Weight"
,
display_name
=
_
(
"Weight"
)
,
help
=
"Defines the maximum total grade of the answer block."
,
help
=
_
(
"Defines the maximum total grade of the answer block."
)
,
default
=
1
,
default
=
1
,
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
enforce_type
=
True
enforce_type
=
True
...
@@ -138,7 +148,9 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
...
@@ -138,7 +148,9 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
@property
@property
def
studio_display_name
(
self
):
def
studio_display_name
(
self
):
return
u"Question {}"
.
format
(
self
.
step_number
)
if
not
self
.
lonely_step
else
u"Question"
if
not
self
.
lonely_step
:
return
self
.
_
(
u"Question {number}"
)
.
format
(
number
=
self
.
step_number
)
return
self
.
_
(
u"Question"
)
def
__getattribute__
(
self
,
name
):
def
__getattribute__
(
self
,
name
):
""" Provide a read-only display name without adding a display_name field to the class. """
""" Provide a read-only display name without adding a display_name field to the class. """
...
@@ -227,26 +239,27 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
...
@@ -227,26 +239,27 @@ class AnswerBlock(AnswerMixin, StepMixin, StudioEditableXBlockMixin, XBlock):
return
{
'metadata'
:
{},
'data'
:
{}}
return
{
'metadata'
:
{},
'data'
:
{}}
@XBlock.needs
(
"i18n"
)
class
AnswerRecapBlock
(
AnswerMixin
,
StudioEditableXBlockMixin
,
XBlock
):
class
AnswerRecapBlock
(
AnswerMixin
,
StudioEditableXBlockMixin
,
XBlock
):
"""
"""
A block that displays an answer previously entered by the student (read-only).
A block that displays an answer previously entered by the student (read-only).
"""
"""
name
=
String
(
name
=
String
(
display_name
=
"Question ID"
,
display_name
=
_
(
"Question ID"
)
,
help
=
"The ID of the question for which to display the student's answer."
,
help
=
_
(
"The ID of the question for which to display the student's answer."
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
)
)
display_name
=
String
(
display_name
=
String
(
display_name
=
"Title"
,
display_name
=
_
(
"Title"
)
,
help
=
"Title of this answer recap section"
,
help
=
_
(
"Title of this answer recap section"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
,
default
=
""
,
)
)
description
=
String
(
description
=
String
(
help
=
"Description of this answer (optional). Can include HTML."
,
display_name
=
_
(
"Description"
),
help
=
_
(
"Description of this answer (optional). Can include HTML."
),
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
,
default
=
""
,
display_name
=
"Description"
,
)
)
editable_fields
=
(
'name'
,
'display_name'
,
'description'
)
editable_fields
=
(
'name'
,
'display_name'
,
'description'
)
...
...
mentoring/choice.py
View file @
5f13249c
...
@@ -32,34 +32,44 @@ from xblock.fragment import Fragment
...
@@ -32,34 +32,44 @@ from xblock.fragment import Fragment
from
xblock.validation
import
ValidationMessage
from
xblock.validation
import
ValidationMessage
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
@XBlock.needs
(
"i18n"
)
class
ChoiceBlock
(
StudioEditableXBlockMixin
,
XBlock
):
class
ChoiceBlock
(
StudioEditableXBlockMixin
,
XBlock
):
"""
"""
Custom choice of an answer for a MCQ/MRQ
Custom choice of an answer for a MCQ/MRQ
"""
"""
value
=
String
(
value
=
String
(
display_name
=
"Value"
,
display_name
=
_
(
"Value"
)
,
help
=
"Value of the choice when selected. Should be unique."
,
help
=
_
(
"Value of the choice when selected. Should be unique."
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
,
default
=
""
,
)
)
content
=
String
(
content
=
String
(
display_name
=
"Choice Text"
,
display_name
=
_
(
"Choice Text"
)
,
help
=
"Human-readable version of the choice value"
,
help
=
_
(
"Human-readable version of the choice value"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
,
default
=
""
,
)
)
editable_fields
=
(
'content'
,
)
editable_fields
=
(
'content'
,
)
def
_
(
self
,
text
):
""" translate text """
return
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
(
text
)
@property
@property
def
studio_display_name
(
self
):
def
studio_display_name
(
self
):
try
:
try
:
status
=
self
.
get_parent
()
.
describe_choice_correctness
(
self
.
value
)
status
=
self
.
get_parent
()
.
describe_choice_correctness
(
self
.
value
)
except
Exception
:
except
Exception
:
status
=
u"Out of Context"
# Parent block should implement describe_choice_correctness()
status
=
self
.
_
(
u"Out of Context"
)
# Parent block should implement describe_choice_correctness()
return
u"Choice ({})"
.
format
(
status
)
return
self
.
_
(
u"Choice ({status})"
)
.
format
(
status
=
status
)
def
__getattribute__
(
self
,
name
):
def
__getattribute__
(
self
,
name
):
""" Provide a read-only display name without adding a display_name field to the class. """
""" Provide a read-only display name without adding a display_name field to the class. """
...
@@ -80,9 +90,9 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -80,9 +90,9 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
if
not
data
.
value
.
strip
():
if
not
data
.
value
.
strip
():
add_error
(
u"No value set. This choice will not work correctly."
)
add_error
(
self
.
_
(
u"No value set. This choice will not work correctly."
)
)
if
not
data
.
content
.
strip
():
if
not
data
.
content
.
strip
():
add_error
(
u"No choice text set yet."
)
add_error
(
self
.
_
(
u"No choice text set yet."
)
)
def
validate
(
self
):
def
validate
(
self
):
"""
"""
...
@@ -91,7 +101,7 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -91,7 +101,7 @@ class ChoiceBlock(StudioEditableXBlockMixin, XBlock):
validation
=
super
(
ChoiceBlock
,
self
)
.
validate
()
validation
=
super
(
ChoiceBlock
,
self
)
.
validate
()
if
self
.
get_parent
()
.
all_choice_values
.
count
(
self
.
value
)
>
1
:
if
self
.
get_parent
()
.
all_choice_values
.
count
(
self
.
value
)
>
1
:
validation
.
add
(
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
self
.
_
(
u"This choice has a non-unique ID and won't work properly. "
u"This choice has a non-unique ID and won't work properly. "
"This can happen if you duplicate a choice rather than use the Add Choice button."
"This can happen if you duplicate a choice rather than use the Add Choice button."
))
))
...
...
mentoring/mcq.py
View file @
5f13249c
...
@@ -39,17 +39,26 @@ log = logging.getLogger(__name__)
...
@@ -39,17 +39,26 @@ log = logging.getLogger(__name__)
loader
=
ResourceLoader
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
class
MCQBlock
(
QuestionnaireAbstractBlock
):
class
MCQBlock
(
QuestionnaireAbstractBlock
):
"""
"""
An XBlock used to ask multiple-choice questions
An XBlock used to ask multiple-choice questions
"""
"""
student_choice
=
String
(
help
=
"Last input submitted by the student"
,
default
=
""
,
scope
=
Scope
.
user_state
)
student_choice
=
String
(
# {Last input submitted by the student
default
=
""
,
scope
=
Scope
.
user_state
,
)
correct_choices
=
List
(
correct_choices
=
List
(
display_name
=
"Correct Choice[s]"
,
display_name
=
_
(
"Correct Choice[s]"
)
,
help
=
"Specify the value[s] that students may select for this question to be considered correct."
,
help
=
_
(
"Specify the value[s] that students may select for this question to be considered correct."
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
list_values_provider
=
QuestionnaireAbstractBlock
.
choice_values_provider
,
list_values_provider
=
QuestionnaireAbstractBlock
.
choice_values_provider
,
list_style
=
'set'
,
# Underered, unique items. Affects the UI editor.
list_style
=
'set'
,
# Underered, unique items. Affects the UI editor.
...
@@ -59,12 +68,12 @@ class MCQBlock(QuestionnaireAbstractBlock):
...
@@ -59,12 +68,12 @@ class MCQBlock(QuestionnaireAbstractBlock):
def
describe_choice_correctness
(
self
,
choice_value
):
def
describe_choice_correctness
(
self
,
choice_value
):
if
choice_value
in
self
.
correct_choices
:
if
choice_value
in
self
.
correct_choices
:
if
len
(
self
.
correct_choices
)
==
1
:
if
len
(
self
.
correct_choices
)
==
1
:
return
u"Correct"
return
self
.
_
(
u"Correct"
)
return
u"Acceptable"
return
self
.
_
(
u"Acceptable"
)
else
:
else
:
if
len
(
self
.
correct_choices
)
==
1
:
if
len
(
self
.
correct_choices
)
==
1
:
return
u"Wrong"
return
self
.
_
(
u"Wrong"
)
return
u"Not Acceptable"
return
self
.
_
(
u"Not Acceptable"
)
def
submit
(
self
,
submission
):
def
submit
(
self
,
submission
):
log
.
debug
(
u'Received MCQ submission: "
%
s"'
,
submission
)
log
.
debug
(
u'Received MCQ submission: "
%
s"'
,
submission
)
...
@@ -121,25 +130,39 @@ class MCQBlock(QuestionnaireAbstractBlock):
...
@@ -121,25 +130,39 @@ class MCQBlock(QuestionnaireAbstractBlock):
correct
=
set
(
data
.
correct_choices
)
correct
=
set
(
data
.
correct_choices
)
if
not
all_values
:
if
not
all_values
:
add_error
(
u"No choices set yet."
)
add_error
(
self
.
_
(
u"No choices set yet."
)
)
elif
not
correct
:
elif
not
correct
:
add_error
(
u"You must indicate the correct answer[s], or the student will always get this question wrong."
)
add_error
(
self
.
_
(
u"You must indicate the correct answer[s], or the student will always get this question wrong."
)
)
if
len
(
correct
)
<
len
(
data
.
correct_choices
):
if
len
(
correct
)
<
len
(
data
.
correct_choices
):
add_error
(
u"Duplicate correct choices set"
)
add_error
(
self
.
_
(
u"Duplicate correct choices set"
)
)
for
val
in
(
correct
-
all_values
):
for
val
in
(
correct
-
all_values
):
add_error
(
u"A choice value listed as correct does not exist: {}"
.
format
(
choice_name
(
val
)))
add_error
(
self
.
_
(
u"A choice value listed as correct does not exist: {choice}"
)
.
format
(
choice
=
choice_name
(
val
))
)
class
RatingBlock
(
MCQBlock
):
class
RatingBlock
(
MCQBlock
):
"""
"""
An XBlock used to rate something on a five-point scale, e.g. Likert Scale
An XBlock used to rate something on a five-point scale, e.g. Likert Scale
"""
"""
low
=
String
(
help
=
"Label for low ratings"
,
scope
=
Scope
.
content
,
default
=
"Less"
)
low
=
String
(
high
=
String
(
help
=
"Label for high ratings"
,
scope
=
Scope
.
content
,
default
=
"More"
)
display_name
=
_
(
"Low"
),
help
=
_
(
"Label for low ratings"
),
scope
=
Scope
.
content
,
default
=
_
(
"Less"
),
)
high
=
String
(
display_name
=
_
(
"High"
),
help
=
_
(
"Label for high ratings"
),
scope
=
Scope
.
content
,
default
=
_
(
"More"
),
)
FIXED_VALUES
=
[
"1"
,
"2"
,
"3"
,
"4"
,
"5"
]
FIXED_VALUES
=
[
"1"
,
"2"
,
"3"
,
"4"
,
"5"
]
correct_choices
=
List
(
correct_choices
=
List
(
display_name
=
"Accepted Choice[s]"
,
display_name
=
_
(
"Accepted Choice[s]"
)
,
help
=
"Specify the rating value[s] that students may select for this question to be considered correct."
,
help
=
_
(
"Specify the rating value[s] that students may select for this question to be considered correct."
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
FIXED_VALUES
,
default
=
FIXED_VALUES
,
list_values_provider
=
QuestionnaireAbstractBlock
.
choice_values_provider
,
list_values_provider
=
QuestionnaireAbstractBlock
.
choice_values_provider
,
...
...
mentoring/mentoring.py
View file @
5f13249c
...
@@ -49,6 +49,11 @@ _default_theme_config = {
...
@@ -49,6 +49,11 @@ _default_theme_config = {
'locations'
:
[
'public/themes/lms.css'
]
'locations'
:
[
'public/themes/lms.css'
]
}
}
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
Score
=
namedtuple
(
"Score"
,
[
"raw"
,
"percentage"
,
"correct"
,
"incorrect"
,
"partially_correct"
])
Score
=
namedtuple
(
"Score"
,
[
"raw"
,
"percentage"
,
"correct"
,
"incorrect"
,
"partially_correct"
])
...
@@ -68,38 +73,42 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
...
@@ -68,38 +73,42 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
# Content
# Content
MENTORING_MODES
=
(
'standard'
,
'assessment'
)
MENTORING_MODES
=
(
'standard'
,
'assessment'
)
mode
=
String
(
mode
=
String
(
display_name
=
"Mode"
,
display_name
=
_
(
"Mode"
)
,
help
=
"Mode of the mentoring. 'standard' or 'assessment'"
,
help
=
_
(
"Mode of the mentoring. 'standard' or 'assessment'"
)
,
default
=
'standard'
,
default
=
'standard'
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
values
=
MENTORING_MODES
values
=
MENTORING_MODES
)
)
followed_by
=
String
(
followed_by
=
String
(
help
=
"url_name of the step after the current mentoring block in workflow."
,
display_name
=
_
(
"Followed by"
),
help
=
_
(
"url_name of the step after the current mentoring block in workflow."
),
default
=
None
,
default
=
None
,
scope
=
Scope
.
content
scope
=
Scope
.
content
)
)
max_attempts
=
Integer
(
max_attempts
=
Integer
(
help
=
"Number of max attempts for this questions"
,
display_name
=
_
(
"Max. Attempts Allowed"
),
help
=
_
(
"Number of max attempts allowed for this questions"
),
default
=
0
,
default
=
0
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
enforce_type
=
True
enforce_type
=
True
)
)
enforce_dependency
=
Boolean
(
enforce_dependency
=
Boolean
(
help
=
"Should the next step be the current block to complete?"
,
display_name
=
_
(
"Enforce Dependency"
),
help
=
_
(
"Should the next step be the current block to complete?"
),
default
=
False
,
default
=
False
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
enforce_type
=
True
enforce_type
=
True
)
)
display_submit
=
Boolean
(
display_submit
=
Boolean
(
help
=
"Allow submission of the current block?"
,
display_name
=
_
(
"Show Submit Button"
),
help
=
_
(
"Allow submission of the current block?"
),
default
=
True
,
default
=
True
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
enforce_type
=
True
enforce_type
=
True
)
)
xml_content
=
String
(
xml_content
=
String
(
help
=
"Not used for version 2. This field is here only to preserve the data needed to upgrade from v1 to v2."
,
display_name
=
_
(
"XML content"
)
,
display_name
=
"XML content"
,
help
=
_
(
"Not used for version 2. This field is here only to preserve the data needed to upgrade from v1 to v2."
)
,
default
=
''
,
default
=
''
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
multiline_editor
=
True
multiline_editor
=
True
...
@@ -107,56 +116,58 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
...
@@ -107,56 +116,58 @@ class MentoringBlock(XBlock, StepParentMixin, StudioEditableXBlockMixin, StudioC
# Settings
# Settings
weight
=
Float
(
weight
=
Float
(
help
=
"Defines the maximum total grade of the block."
,
display_name
=
_
(
"Weight"
),
help
=
_
(
"Defines the maximum total grade of the block."
),
default
=
1
,
default
=
1
,
scope
=
Scope
.
settings
,
scope
=
Scope
.
settings
,
enforce_type
=
True
enforce_type
=
True
)
)
display_name
=
String
(
display_name
=
String
(
help
=
"Title to display"
,
display_name
=
_
(
"Title (Display name)"
),
default
=
"Mentoring Questions"
,
help
=
_
(
"Title to display"
),
default
=
_
(
"Mentoring Questions"
),
scope
=
Scope
.
settings
scope
=
Scope
.
settings
)
)
# User state
# User state
attempted
=
Boolean
(
attempted
=
Boolean
(
help
=
"Has the student attempted this mentoring step?"
,
# Has the student attempted this mentoring step?
default
=
False
,
default
=
False
,
scope
=
Scope
.
user_state
scope
=
Scope
.
user_state
)
)
completed
=
Boolean
(
completed
=
Boolean
(
help
=
"Has the student completed this mentoring step?"
,
# Has the student completed this mentoring step?
default
=
False
,
default
=
False
,
scope
=
Scope
.
user_state
scope
=
Scope
.
user_state
)
)
num_attempts
=
Integer
(
num_attempts
=
Integer
(
help
=
"Number of attempts a user has answered for this questions"
,
# Number of attempts a user has answered for this questions
default
=
0
,
default
=
0
,
scope
=
Scope
.
user_state
,
scope
=
Scope
.
user_state
,
enforce_type
=
True
enforce_type
=
True
)
)
step
=
Integer
(
step
=
Integer
(
help
=
"Keep track of the student assessment progress."
,
# Keep track of the student assessment progress.
default
=
0
,
default
=
0
,
scope
=
Scope
.
user_state
,
scope
=
Scope
.
user_state
,
enforce_type
=
True
enforce_type
=
True
)
)
student_results
=
List
(
student_results
=
List
(
help
=
"Store results of student choices."
,
# Store results of student choices.
default
=
[],
default
=
[],
scope
=
Scope
.
user_state
scope
=
Scope
.
user_state
)
)
# Global user state
# Global user state
next_step
=
String
(
next_step
=
String
(
help
=
"url_name of the next step the student must complete (global to all blocks)"
,
# url_name of the next step the student must complete (global to all blocks)
default
=
'mentoring_first'
,
default
=
'mentoring_first'
,
scope
=
Scope
.
preferences
scope
=
Scope
.
preferences
)
)
editable_fields
=
(
editable_fields
=
(
'mode'
,
'followed_by'
,
'max_attempts'
,
'enforce_dependency'
,
'
display_name'
,
'
mode'
,
'followed_by'
,
'max_attempts'
,
'enforce_dependency'
,
'display_submit'
,
'weight'
,
'display_name'
,
'display_submit'
,
'weight'
,
)
)
icon_class
=
'problem'
icon_class
=
'problem'
has_score
=
True
has_score
=
True
...
...
mentoring/message.py
View file @
5f13249c
...
@@ -29,24 +29,30 @@ from xblock.fields import Scope, String
...
@@ -29,24 +29,30 @@ from xblock.fields import Scope, String
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
@XBlock.needs
(
"i18n"
)
class
MentoringMessageBlock
(
XBlock
,
StudioEditableXBlockMixin
):
class
MentoringMessageBlock
(
XBlock
,
StudioEditableXBlockMixin
):
"""
"""
A message which can be conditionally displayed at the mentoring block level,
A message which can be conditionally displayed at the mentoring block level,
for example upon completion of the block
for example upon completion of the block
"""
"""
content
=
String
(
content
=
String
(
display_name
=
"Message"
,
display_name
=
_
(
"Message"
)
,
help
=
"Message to display upon completion"
,
help
=
_
(
"Message to display upon completion"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
,
default
=
""
,
multiline_editor
=
"html"
,
multiline_editor
=
"html"
,
resettable_editor
=
False
,
resettable_editor
=
False
,
)
)
type
=
String
(
type
=
String
(
help
=
"Type of message"
,
help
=
_
(
"Type of message"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
"completed"
,
default
=
"completed"
,
values
=
(
values
=
(
...
@@ -57,6 +63,10 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
...
@@ -57,6 +63,10 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
)
)
editable_fields
=
(
"content"
,
)
editable_fields
=
(
"content"
,
)
def
_
(
self
,
text
):
""" translate text """
return
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
(
text
)
def
fallback_view
(
self
,
view_name
,
context
):
def
fallback_view
(
self
,
view_name
,
context
):
html
=
u'<div class="message {msg_type}">{content}</div>'
.
format
(
msg_type
=
self
.
type
,
content
=
self
.
content
)
html
=
u'<div class="message {msg_type}">{content}</div>'
.
format
(
msg_type
=
self
.
type
,
content
=
self
.
content
)
return
Fragment
(
html
)
return
Fragment
(
html
)
...
@@ -65,13 +75,13 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
...
@@ -65,13 +75,13 @@ class MentoringMessageBlock(XBlock, StudioEditableXBlockMixin):
def
studio_display_name
(
self
):
def
studio_display_name
(
self
):
if
self
.
type
==
'max_attempts_reached'
:
if
self
.
type
==
'max_attempts_reached'
:
max_attempts
=
self
.
get_parent
()
.
max_attempts
max_attempts
=
self
.
get_parent
()
.
max_attempts
return
u"Message when student reaches max. # of attempts ({current_limit})"
.
format
(
return
self
.
_
(
u"Message when student reaches max. # of attempts ({limit})"
)
.
format
(
current_limit
=
u"unlimited"
if
max_attempts
==
0
else
max_attempts
limit
=
self
.
_
(
u"unlimited"
)
if
max_attempts
==
0
else
max_attempts
)
)
if
self
.
type
==
'completed'
:
if
self
.
type
==
'completed'
:
return
u"Message shown when complete"
return
self
.
_
(
u"Message shown when complete"
)
if
self
.
type
==
'incomplete'
:
if
self
.
type
==
'incomplete'
:
return
u"Message shown when incomplete"
return
self
.
_
(
u"Message shown when incomplete"
)
return
u"INVALID MESSAGE"
return
u"INVALID MESSAGE"
def
__getattribute__
(
self
,
name
):
def
__getattribute__
(
self
,
name
):
...
...
mentoring/mrq.py
View file @
5f13249c
...
@@ -36,24 +36,33 @@ from xblockutils.resources import ResourceLoader
...
@@ -36,24 +36,33 @@ from xblockutils.resources import ResourceLoader
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
class
MRQBlock
(
QuestionnaireAbstractBlock
):
class
MRQBlock
(
QuestionnaireAbstractBlock
):
"""
"""
An XBlock used to ask multiple-response questions
An XBlock used to ask multiple-response questions
"""
"""
student_choices
=
List
(
help
=
"Last submissions by the student"
,
default
=
[],
scope
=
Scope
.
user_state
)
student_choices
=
List
(
# Last submissions by the student
default
=
[],
scope
=
Scope
.
user_state
)
required_choices
=
List
(
required_choices
=
List
(
display_name
=
"Required Choices"
,
display_name
=
_
(
"Required Choices"
)
,
help
=
(
"Specify the value[s] that students must select for this MRQ to be considered correct.
"
),
help
=
_
(
"Specify the value[s] that students must select for this MRQ to be considered correct.
"
),
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
list_values_provider
=
QuestionnaireAbstractBlock
.
choice_values_provider
,
list_values_provider
=
QuestionnaireAbstractBlock
.
choice_values_provider
,
list_style
=
'set'
,
# Underered, unique items. Affects the UI editor.
list_style
=
'set'
,
# Underered, unique items. Affects the UI editor.
default
=
[],
default
=
[],
)
)
ignored_choices
=
List
(
ignored_choices
=
List
(
display_name
=
"Ignored Choices"
,
display_name
=
_
(
"Ignored Choices"
)
,
help
=
(
help
=
_
(
"Specify the value[s] that are neither correct nor incorrect. "
"Specify the value[s] that are neither correct nor incorrect. "
"Any values not listed as required or ignored will be considered wrong."
"Any values not listed as required or ignored will be considered wrong."
),
),
...
@@ -67,10 +76,10 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -67,10 +76,10 @@ class MRQBlock(QuestionnaireAbstractBlock):
def
describe_choice_correctness
(
self
,
choice_value
):
def
describe_choice_correctness
(
self
,
choice_value
):
if
choice_value
in
self
.
required_choices
:
if
choice_value
in
self
.
required_choices
:
return
u"Required"
return
self
.
_
(
u"Required"
)
elif
choice_value
in
self
.
ignored_choices
:
elif
choice_value
in
self
.
ignored_choices
:
return
u"Ignored"
return
self
.
_
(
u"Ignored"
)
return
u"Not Acceptable"
return
self
.
_
(
u"Not Acceptable"
)
def
submit
(
self
,
submissions
):
def
submit
(
self
,
submissions
):
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
...
@@ -144,12 +153,12 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -144,12 +153,12 @@ class MRQBlock(QuestionnaireAbstractBlock):
ignored
=
set
(
data
.
ignored_choices
)
ignored
=
set
(
data
.
ignored_choices
)
if
len
(
required
)
<
len
(
data
.
required_choices
):
if
len
(
required
)
<
len
(
data
.
required_choices
):
add_error
(
u"Duplicate required choices set"
)
add_error
(
self
.
_
(
u"Duplicate required choices set"
)
)
if
len
(
ignored
)
<
len
(
data
.
ignored_choices
):
if
len
(
ignored
)
<
len
(
data
.
ignored_choices
):
add_error
(
u"Duplicate ignored choices set"
)
add_error
(
self
.
_
(
u"Duplicate ignored choices set"
)
)
for
val
in
required
.
intersection
(
ignored
):
for
val
in
required
.
intersection
(
ignored
):
add_error
(
u"A choice is listed as both required and ignored: {}"
.
format
(
choice_name
(
val
)))
add_error
(
self
.
_
(
u"A choice is listed as both required and ignored: {}"
)
.
format
(
choice_name
(
val
)))
for
val
in
(
required
-
all_values
):
for
val
in
(
required
-
all_values
):
add_error
(
u"A choice value listed as required does not exist: {}"
.
format
(
choice_name
(
val
)))
add_error
(
self
.
_
(
u"A choice value listed as required does not exist: {}"
)
.
format
(
choice_name
(
val
)))
for
val
in
(
ignored
-
all_values
):
for
val
in
(
ignored
-
all_values
):
add_error
(
u"A choice value listed as ignored does not exist: {}"
.
format
(
choice_name
(
val
)))
add_error
(
self
.
_
(
u"A choice value listed as ignored does not exist: {}"
)
.
format
(
choice_name
(
val
)))
mentoring/public/js/mentoring.js
View file @
5f13249c
function
MentoringBlock
(
runtime
,
element
)
{
function
MentoringBlock
(
runtime
,
element
)
{
// Set up gettext in case it isn't available in the client runtime:
if
(
typeof
gettext
==
"undefined"
)
{
window
.
gettext
=
function
gettext_stub
(
string
)
{
return
string
;
};
window
.
ngettext
=
function
ngettext_stub
(
strA
,
strB
,
n
)
{
return
n
==
1
?
strA
:
strB
;
};
}
var
attemptsTemplate
=
_
.
template
(
$
(
'#xblock-attempts-template'
).
html
());
var
attemptsTemplate
=
_
.
template
(
$
(
'#xblock-attempts-template'
).
html
());
var
data
=
$
(
'.mentoring'
,
element
).
data
();
var
data
=
$
(
'.mentoring'
,
element
).
data
();
var
children
=
runtime
.
children
(
element
);
var
children
=
runtime
.
children
(
element
);
...
...
mentoring/public/js/mentoring_standard_view.js
View file @
5f13249c
...
@@ -25,7 +25,7 @@ function MentoringStandardView(runtime, element, mentoring) {
...
@@ -25,7 +25,7 @@ function MentoringStandardView(runtime, element, mentoring) {
// Messages should only be displayed upon hitting 'submit', not on page reload
// Messages should only be displayed upon hitting 'submit', not on page reload
mentoring
.
setContent
(
messagesDOM
,
results
.
message
);
mentoring
.
setContent
(
messagesDOM
,
results
.
message
);
if
(
messagesDOM
.
html
().
trim
())
{
if
(
messagesDOM
.
html
().
trim
())
{
messagesDOM
.
prepend
(
'<div class="title1">
Feedback
</div>'
);
messagesDOM
.
prepend
(
'<div class="title1">
'
+
gettext
(
'Feedback'
)
+
'
</div>'
);
messagesDOM
.
show
();
messagesDOM
.
show
();
}
}
...
...
mentoring/questionnaire.py
View file @
5f13249c
...
@@ -41,9 +41,15 @@ from .tip import TipBlock
...
@@ -41,9 +41,15 @@ from .tip import TipBlock
loader
=
ResourceLoader
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
@XBlock.needs
(
"i18n"
)
class
QuestionnaireAbstractBlock
(
StudioEditableXBlockMixin
,
StudioContainerXBlockMixin
,
StepMixin
,
XBlock
):
class
QuestionnaireAbstractBlock
(
StudioEditableXBlockMixin
,
StudioContainerXBlockMixin
,
StepMixin
,
XBlock
):
"""
"""
An abstract class used for MCQ/MRQ blocks
An abstract class used for MCQ/MRQ blocks
...
@@ -54,26 +60,26 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
...
@@ -54,26 +60,26 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
"""
"""
name
=
String
(
name
=
String
(
# This doesn't need to be a field but is kept for backwards compatibility with v1 student data
# This doesn't need to be a field but is kept for backwards compatibility with v1 student data
display_name
=
"Question ID (name)"
,
display_name
=
_
(
"Question ID (name)"
)
,
help
=
"The ID of this question (required). Should be unique within this mentoring component."
,
help
=
_
(
"The ID of this question (required). Should be unique within this mentoring component."
)
,
default
=
UNIQUE_ID
,
default
=
UNIQUE_ID
,
scope
=
Scope
.
settings
,
# Must be scope.settings, or the unique ID will change every time this block is edited
scope
=
Scope
.
settings
,
# Must be scope.settings, or the unique ID will change every time this block is edited
)
)
question
=
String
(
question
=
String
(
display_name
=
"Question"
,
display_name
=
_
(
"Question"
)
,
help
=
"Question to ask the student"
,
help
=
_
(
"Question to ask the student"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
default
=
""
)
)
message
=
String
(
message
=
String
(
display_name
=
"Message"
,
display_name
=
_
(
"Message"
)
,
help
=
"General feedback provided when submiting"
,
help
=
_
(
"General feedback provided when submiting"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
""
default
=
""
)
)
weight
=
Float
(
weight
=
Float
(
display_name
=
"Weight"
,
display_name
=
_
(
"Weight"
)
,
help
=
"Defines the maximum total grade of this question."
,
help
=
_
(
"Defines the maximum total grade of this question."
)
,
default
=
1
,
default
=
1
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
enforce_type
=
True
enforce_type
=
True
...
@@ -81,6 +87,10 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
...
@@ -81,6 +87,10 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
editable_fields
=
(
'question'
,
'message'
,
'weight'
)
editable_fields
=
(
'question'
,
'message'
,
'weight'
)
has_children
=
True
has_children
=
True
def
_
(
self
,
text
):
""" translate text """
return
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
(
text
)
@classmethod
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
"""
"""
...
@@ -108,7 +118,9 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
...
@@ -108,7 +118,9 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
@property
@property
def
studio_display_name
(
self
):
def
studio_display_name
(
self
):
return
u"Question {}"
.
format
(
self
.
step_number
)
if
not
self
.
lonely_step
else
u"Question"
if
not
self
.
lonely_step
:
return
self
.
_
(
u"Question {number}"
)
.
format
(
number
=
self
.
step_number
)
return
self
.
_
(
u"Question"
)
def
__getattribute__
(
self
,
name
):
def
__getattribute__
(
self
,
name
):
""" Provide a read-only display name without adding a display_name field to the class. """
""" Provide a read-only display name without adding a display_name field to the class. """
...
@@ -215,9 +227,9 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
...
@@ -215,9 +227,9 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
def
add_error
(
msg
):
def
add_error
(
msg
):
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
validation
.
add
(
ValidationMessage
(
ValidationMessage
.
ERROR
,
msg
))
if
not
data
.
name
:
if
not
data
.
name
:
add_error
(
u"A unique Question ID is required."
)
add_error
(
self
.
_
(
u"A unique Question ID is required."
)
)
elif
' '
in
data
.
name
:
elif
' '
in
data
.
name
:
add_error
(
u"Question ID should not contain spaces."
)
add_error
(
self
.
_
(
u"Question ID should not contain spaces."
)
)
def
validate
(
self
):
def
validate
(
self
):
"""
"""
...
@@ -232,12 +244,12 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
...
@@ -232,12 +244,12 @@ class QuestionnaireAbstractBlock(StudioEditableXBlockMixin, StudioContainerXBloc
all_choice_values
=
self
.
all_choice_values
all_choice_values
=
self
.
all_choice_values
all_choice_values_set
=
set
(
all_choice_values
)
all_choice_values_set
=
set
(
all_choice_values
)
if
len
(
all_choice_values
)
!=
len
(
all_choice_values_set
):
if
len
(
all_choice_values
)
!=
len
(
all_choice_values_set
):
add_error
(
u"Some choice values are not unique."
)
add_error
(
self
.
_
(
u"Some choice values are not unique."
)
)
# Validate the tips:
# Validate the tips:
values_with_tips
=
set
()
values_with_tips
=
set
()
for
tip
in
self
.
get_tips
():
for
tip
in
self
.
get_tips
():
values
=
set
(
tip
.
values
)
values
=
set
(
tip
.
values
)
for
val
in
(
values
&
values_with_tips
):
for
dummy
in
(
values
&
values_with_tips
):
add_error
(
u"Multiple tips for value '{}'"
.
format
(
val
))
add_error
(
self
.
_
(
u"Multiple tips configured for the same choice."
))
values_with_tips
.
update
(
values
)
values_with_tips
.
update
(
values
)
return
validation
return
validation
mentoring/table.py
View file @
5f13249c
...
@@ -27,7 +27,6 @@ import errno
...
@@ -27,7 +27,6 @@ import errno
from
xblock.core
import
XBlock
from
xblock.core
import
XBlock
from
xblock.exceptions
import
NoSuchViewError
from
xblock.fields
import
Scope
,
String
from
xblock.fields
import
Scope
,
String
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
...
@@ -38,6 +37,11 @@ from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContain
...
@@ -38,6 +37,11 @@ from xblockutils.studio_editable import StudioEditableXBlockMixin, StudioContain
loader
=
ResourceLoader
(
__name__
)
loader
=
ResourceLoader
(
__name__
)
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
...
@@ -49,14 +53,14 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin,
...
@@ -49,14 +53,14 @@ class MentoringTableBlock(StudioEditableXBlockMixin, StudioContainerXBlockMixin,
Supports different types of formatting through the `type` parameter.
Supports different types of formatting through the `type` parameter.
"""
"""
display_name
=
String
(
display_name
=
String
(
display_name
=
"Display name"
,
display_name
=
_
(
"Display name"
)
,
help
=
"Title of the table"
,
help
=
_
(
"Title of the table"
)
,
default
=
"Answers Table"
,
default
=
_
(
"Answers Table"
)
,
scope
=
Scope
.
settings
scope
=
Scope
.
settings
)
)
type
=
String
(
type
=
String
(
display_name
=
"Special Mode"
,
display_name
=
_
(
"Special Mode"
)
,
help
=
"Variant of the table that will display a specific background image."
,
help
=
_
(
"Variant of the table that will display a specific background image."
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
''
,
default
=
''
,
values
=
[
values
=
[
...
@@ -122,10 +126,10 @@ class MentoringTableColumn(StudioEditableXBlockMixin, StudioContainerXBlockMixin
...
@@ -122,10 +126,10 @@ class MentoringTableColumn(StudioEditableXBlockMixin, StudioContainerXBlockMixin
"""
"""
A column in a mentoring table. Has a header and can contain HTML and AnswerRecapBlocks.
A column in a mentoring table. Has a header and can contain HTML and AnswerRecapBlocks.
"""
"""
display_name
=
String
(
display_name
=
"Display Name"
,
default
=
"Column"
)
display_name
=
String
(
display_name
=
_
(
"Display Name"
)
,
default
=
"Column"
)
header
=
String
(
header
=
String
(
display_name
=
"Header"
,
display_name
=
_
(
"Header"
)
,
help
=
"Header of this column"
,
help
=
_
(
"Header of this column"
)
,
default
=
""
,
default
=
""
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
multiline_editor
=
"html"
,
multiline_editor
=
"html"
,
...
...
mentoring/templates/html/mentoring.html
View file @
5f13249c
{% load i18n %}
<div
class=
"mentoring themed-xblock"
data-mode=
"{{ self.mode }}"
data-step=
"{{ self.step }}"
>
<div
class=
"mentoring themed-xblock"
data-mode=
"{{ self.mode }}"
data-step=
"{{ self.step }}"
>
<div
class=
"missing-dependency warning"
data-missing=
"{{ self.has_missing_dependency }}"
>
<div
class=
"missing-dependency warning"
data-missing=
"{{ self.has_missing_dependency }}"
>
You need to complete
<a
href=
"{{ missing_dependency_url }}"
>
the previous step
</a>
before
{% with url=missing_dependency_url|safe %}
{% blocktrans with link_start="
<a
href=
'"|add:url|add:"'
>
" link_end="
</a>
" %}
You need to complete {{link_start}}the previous step{{link_end}} before
attempting this step.
attempting this step.
{% endblocktrans %}
{% endwith %}
</div>
</div>
{% if title %}
{% if title %}
...
...
mentoring/templates/html/mentoring_add_buttons.html
View file @
5f13249c
...
@@ -2,18 +2,18 @@
...
@@ -2,18 +2,18 @@
<div
class=
"add-xblock-component new-component-item adding"
>
<div
class=
"add-xblock-component new-component-item adding"
>
<div
class=
"new-component"
>
<div
class=
"new-component"
>
<h5>
Add New Component
</h5>
<h5>
{% trans "Add New Component" %}
</h5>
<ul
class=
"new-component-type"
>
<ul
class=
"new-component-type"
>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-answer"
data-boilerplate=
"studio_default"
>
Long Answer
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-answer"
data-boilerplate=
"studio_default"
>
{% trans "Long Answer" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-mcq"
>
Multiple Choice Question
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-mcq"
>
{% trans "Multiple Choice Question" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-rating"
>
Rating Question
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-rating"
>
{% trans "Rating Question" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-mrq"
>
Multiple Response Question
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-mrq"
>
{% trans "Multiple Response Question" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"html"
>
HTML
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"html"
>
{% trans "HTML" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-answer-recap"
>
Long Answer Recap
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-answer-recap"
>
{% trans "Long Answer Recap" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-table"
>
Answer Recap Table
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-table"
>
{% trans "Answer Recap Table" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-message"
data-boilerplate=
"completed"
>
Message (Complete)
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-message"
data-boilerplate=
"completed"
>
{% trans "Message (Complete)" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-message"
data-boilerplate=
"incomplete"
>
Message (Incomplete)
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-message"
data-boilerplate=
"incomplete"
>
{% trans "Message (Incomplete)" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-message"
data-boilerplate=
"max_attempts_reached"
>
Message (Max # Attempts)
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-message"
data-boilerplate=
"max_attempts_reached"
>
{% trans "Message (Max # Attempts)" %}
</a></li>
</ul>
</ul>
</div>
</div>
</div>
</div>
mentoring/templates/html/mentoring_attempts.html
View file @
5f13249c
<script
type=
"text/template"
id=
"xblock-attempts-template"
>
<script
type=
"text/template"
id=
"xblock-attempts-template"
>
<%
if
(
_
.
isNumber
(
max_attempts
)
&&
max_attempts
>
0
)
{{
%>
<%
if
(
_
.
isNumber
(
max_attempts
)
&&
max_attempts
>
0
)
{{
%>
<
span
>
You
have
used
<%=
_
.
min
([
num_attempts
,
max_attempts
])
%>
of
<%=
max_attempts
%>
submissions
.
<
/span
>
<
span
>
<%=
_
.
template
(
ngettext
(
"You have used {num_used} of 1 submission."
,
"You have used {num_used} of {max_attempts} submissions."
,
max_attempts
),
{
num_used
:
_
.
min
([
num_attempts
,
max_attempts
]),
max_attempts
:
max_attempts
},
{
interpolate
:
/
\{(
.+
?)\}
/g
}
)
%>
<
/span
>
<%
}}
%>
<%
}}
%>
</script>
</script>
mentoring/templates/html/mentoring_grade.html
View file @
5f13249c
<script
type=
"text/template"
id=
"xblock-grade-template"
>
<script
type=
"text/template"
id=
"xblock-grade-template"
>
<%
if
(
_
.
isNumber
(
max_attempts
)
&&
max_attempts
>
0
&&
num_attempts
>=
max_attempts
)
{{
%>
<%
if
(
_
.
isNumber
(
max_attempts
)
&&
max_attempts
>
0
&&
num_attempts
>=
max_attempts
)
{{
%>
<
p
>
Note
:
you
have
used
all
attempts
.
Continue
to
the
next
unit
.
<
/p
>
<
p
>
<%=
gettext
(
"Note: you have used all attempts. Continue to the next unit."
)
%>
<
/p
>
<%
}}
else
{{
%>
<%
}}
else
{{
%>
<
p
>
Note
:
if
you
retake
this
assessment
,
only
your
final
score
counts
.
<
/p
>
<
p
>
<%=
gettext
(
"Note: if you retake this assessment, only your final score counts."
)
%>
<
/p
>
<%
}}
%>
<%
}}
%>
<
h2
>
You
scored
<%=
score
%>%
on
this
assessment
.
<
/h2
>
<
h2
>
<%=
_
.
template
(
gettext
(
"You scored {percent}% on this assessment."
),
{
percent
:
score
},
{
interpolate
:
/
\{(
.+
?)\}
/g
})
%>
<
/h2
>
<
hr
/>
<
hr
/>
<
span
class
=
"assessment-checkmark icon-2x checkmark-correct icon-ok fa fa-check"
><
/span
>
<
span
class
=
"assessment-checkmark icon-2x checkmark-correct icon-ok fa fa-check"
><
/span
>
<
p
>
You
answered
<%=
correct_answer
%>
questions
correctly
.
<
/p
>
<
p
>
<%=
_
.
template
(
ngettext
(
"You answered 1 question correctly."
,
"You answered {number_correct} questions correctly."
,
correct_answer
),
{
number_correct
:
correct_answer
},
{
interpolate
:
/
\{(
.+
?)\}
/g
})
%>
<
/p
>
<
span
class
=
"assessment-checkmark icon-2x checkmark-partially-correct icon-ok fa fa-check"
><
/span
>
<
span
class
=
"assessment-checkmark icon-2x checkmark-partially-correct icon-ok fa fa-check"
><
/span
>
<
p
>
You
answered
<%=
partially_correct_answer
%>
questions
partially
correct
.
<
/p
>
<
p
>
<%=
_
.
template
(
ngettext
(
"You answered 1 question partially correctly."
,
"You answered {number_partially_correct} questions partially correctly."
,
partially_correct_answer
),
{
number_partially_correct
:
partially_correct_answer
},
{
interpolate
:
/
\{(
.+
?)\}
/g
})
%>
<
/p
>
<
span
class
=
"assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa fa-exclamation"
><
/span
>
<
span
class
=
"assessment-checkmark icon-2x checkmark-incorrect icon-exclamation fa fa-exclamation"
><
/span
>
<
p
>
You
answered
<%=
incorrect_answer
%>
questions
incorrectly
.
<
/p
>
<
p
>
<%=
_
.
template
(
ngettext
(
"You answered 1 question incorrectly."
,
"You answered {number_incorrect} questions incorrectly."
,
incorrect_answer
),
{
number_incorrect
:
incorrect_answer
},
{
interpolate
:
/
\{(
.+
?)\}
/g
})
%>
<
/p
>
</script>
</script>
mentoring/templates/html/questionnaire_add_buttons.html
View file @
5f13249c
...
@@ -3,8 +3,8 @@
...
@@ -3,8 +3,8 @@
<div
class=
"add-xblock-component new-component-item adding"
>
<div
class=
"add-xblock-component new-component-item adding"
>
<div
class=
"new-component"
>
<div
class=
"new-component"
>
<ul
class=
"new-component-type"
>
<ul
class=
"new-component-type"
>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-choice"
data-boilerplate=
"studio_default"
>
Add Custom Choice
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-choice"
data-boilerplate=
"studio_default"
>
{% trans "Add Custom Choice" %}
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-tip"
>
Add Tip
</a></li>
<li><a
href=
"#"
class=
"single-template add-xblock-component-button"
data-category=
"mentoring-tip"
>
{% trans "Add Tip" %}
</a></li>
</ul>
</ul>
</div>
</div>
</div>
</div>
mentoring/templates/html/ratingblock_edit_preview.html
View file @
5f13249c
{% load i18n %}
<p>
{{ question }}
</p>
<p>
{{ question }}
</p>
<h2>
Built-in choices:
</h2>
<h2>
{% trans "Built-in choices:" %}
</h2>
<ul>
<ul>
<li>
Choice (1):
<strong>
1 - {{ low }}
</strong>
({{accepted_statuses.1}})
</li>
<li>
Choice (1):
<strong>
1 - {{ low }}
</strong>
({{accepted_statuses.1}})
</li>
...
@@ -10,4 +11,4 @@
...
@@ -10,4 +11,4 @@
<li>
Choice (5):
<strong>
5 - {{ high }}
</strong>
({{accepted_statuses.5}})
</li>
<li>
Choice (5):
<strong>
5 - {{ high }}
</strong>
({{accepted_statuses.5}})
</li>
</ul>
</ul>
<h2>
Additional custom choices and tips:
</h2>
<h2>
{% trans "Additional custom choices and tips:" %}
</h2>
mentoring/tests/integration/test_assessment.py
View file @
5f13249c
...
@@ -213,10 +213,25 @@ class MentoringAssessmentTest(MentoringAssessmentBaseTest):
...
@@ -213,10 +213,25 @@ class MentoringAssessmentTest(MentoringAssessmentBaseTest):
self
.
assert_persistent_elements_present
(
mentoring
)
self
.
assert_persistent_elements_present
(
mentoring
)
if
expected
[
"num_attempts"
]
<
expected
[
"max_attempts"
]:
if
expected
[
"num_attempts"
]
<
expected
[
"max_attempts"
]:
self
.
assertIn
(
"Note: if you retake this assessment, only your final score counts."
,
mentoring
.
text
)
self
.
assertIn
(
"Note: if you retake this assessment, only your final score counts."
,
mentoring
.
text
)
self
.
assertIn
(
"You answered {correct} questions correctly."
.
format
(
**
expected
),
mentoring
.
text
)
if
expected
[
"correct"
]
==
1
:
self
.
assertIn
(
"You answered {partial} questions partially correct."
.
format
(
**
expected
),
mentoring
.
text
)
self
.
assertIn
(
"You answered 1 questions correctly."
.
format
(
**
expected
),
mentoring
.
text
)
self
.
assertIn
(
"You answered {incorrect} questions incorrectly."
.
format
(
**
expected
),
mentoring
.
text
)
else
:
self
.
assertIn
(
"You have used {num_attempts} of {max_attempts} submissions."
.
format
(
**
expected
),
mentoring
.
text
)
self
.
assertIn
(
"You answered {correct} questions correctly."
.
format
(
**
expected
),
mentoring
.
text
)
if
expected
[
"partial"
]
==
1
:
self
.
assertIn
(
"You answered 1 question partially correctly."
,
mentoring
.
text
)
else
:
self
.
assertIn
(
"You answered {partial} questions partially correctly."
.
format
(
**
expected
),
mentoring
.
text
)
if
expected
[
"incorrect"
]
==
1
:
self
.
assertIn
(
"You answered 1 question incorrectly."
,
mentoring
.
text
)
else
:
self
.
assertIn
(
"You answered {incorrect} questions incorrectly."
.
format
(
**
expected
),
mentoring
.
text
)
if
expected
[
"max_attempts"
]
==
1
:
self
.
assertIn
(
"You have used {num_attempts} of 1 submission."
.
format
(
**
expected
),
mentoring
.
text
)
else
:
self
.
assertIn
(
"You have used {num_attempts} of {max_attempts} submissions."
.
format
(
**
expected
),
mentoring
.
text
)
self
.
assert_hidden
(
controls
.
submit
)
self
.
assert_hidden
(
controls
.
submit
)
self
.
assert_hidden
(
controls
.
next_question
)
self
.
assert_hidden
(
controls
.
next_question
)
...
...
mentoring/tip.py
View file @
5f13249c
...
@@ -33,26 +33,51 @@ from xblock.validation import ValidationMessage
...
@@ -33,26 +33,51 @@ from xblock.validation import ValidationMessage
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.resources
import
ResourceLoader
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
from
xblockutils.studio_editable
import
StudioEditableXBlockMixin
# Make '_' a no-op so we can scrape strings
def
_
(
text
):
return
text
# Classes ###########################################################
# Classes ###########################################################
@XBlock.needs
(
"i18n"
)
class
TipBlock
(
StudioEditableXBlockMixin
,
XBlock
):
class
TipBlock
(
StudioEditableXBlockMixin
,
XBlock
):
"""
"""
Each choice can define a tip depending on selection
Each choice can define a tip depending on selection
"""
"""
content
=
String
(
help
=
"Text of the tip to provide if needed"
,
scope
=
Scope
.
content
,
default
=
""
)
content
=
String
(
display_name
=
_
(
"Content"
),
help
=
_
(
"Text of the tip to show if the student chooses this tip's associated choice[s]"
),
scope
=
Scope
.
content
,
default
=
""
)
values
=
List
(
values
=
List
(
display_name
=
"For Choices"
,
display_name
=
_
(
"For Choices"
)
,
help
=
"List of choices for which to display this tip"
,
help
=
_
(
"List of choices for which to display this tip"
)
,
scope
=
Scope
.
content
,
scope
=
Scope
.
content
,
default
=
[],
default
=
[],
list_values_provider
=
lambda
self
:
self
.
get_parent
()
.
human_readable_choices
,
list_values_provider
=
lambda
self
:
self
.
get_parent
()
.
human_readable_choices
,
list_style
=
'set'
,
# Underered, unique items. Affects the UI editor.
list_style
=
'set'
,
# Underered, unique items. Affects the UI editor.
)
)
width
=
String
(
help
=
"Width of the tip popup"
,
scope
=
Scope
.
content
,
default
=
''
)
width
=
String
(
height
=
String
(
help
=
"Height of the tip popup"
,
scope
=
Scope
.
content
,
default
=
''
)
display_name
=
_
(
"Width"
),
help
=
_
(
"Width of the tip popup (e.g. '400px')"
),
scope
=
Scope
.
content
,
default
=
''
)
height
=
String
(
display_name
=
_
(
"Height"
),
help
=
_
(
"Height of the tip popup (e.g. '200px')"
),
scope
=
Scope
.
content
,
default
=
''
)
editable_fields
=
(
'values'
,
'content'
,
'width'
,
'height'
)
editable_fields
=
(
'values'
,
'content'
,
'width'
,
'height'
)
def
_
(
self
,
text
):
""" translate text """
return
self
.
runtime
.
service
(
self
,
"i18n"
)
.
ugettext
(
text
)
@property
@property
def
studio_display_name
(
self
):
def
studio_display_name
(
self
):
values_list
=
[]
values_list
=
[]
...
@@ -62,7 +87,7 @@ class TipBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -62,7 +87,7 @@ class TipBlock(StudioEditableXBlockMixin, XBlock):
if
len
(
display_name
)
>
20
:
if
len
(
display_name
)
>
20
:
display_name
=
display_name
[:
20
]
+
u'…'
display_name
=
display_name
[:
20
]
+
u'…'
values_list
.
append
(
display_name
)
values_list
.
append
(
display_name
)
return
u"Tip for {}"
.
format
(
u", "
.
join
(
values_list
))
return
self
.
_
(
u"Tip for {list_of_choices}"
)
.
format
(
list_of_choices
=
u", "
.
join
(
values_list
))
def
__getattribute__
(
self
,
name
):
def
__getattribute__
(
self
,
name
):
""" Provide a read-only display name without adding a display_name field to the class. """
""" Provide a read-only display name without adding a display_name field to the class. """
...
@@ -99,8 +124,8 @@ class TipBlock(StudioEditableXBlockMixin, XBlock):
...
@@ -99,8 +124,8 @@ class TipBlock(StudioEditableXBlockMixin, XBlock):
except
Exception
:
except
Exception
:
pass
pass
else
:
else
:
for
val
in
set
(
data
.
values
)
-
valid_values
:
for
dummy
in
set
(
data
.
values
)
-
valid_values
:
add_error
(
u"A choice value listed for this tip does not exist: {}"
.
format
(
val
))
add_error
(
self
.
_
(
u"A choice selected for this tip does not exist."
))
@classmethod
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
...
...
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