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
b3d47cb7
Commit
b3d47cb7
authored
Mar 27, 2014
by
Xavier Antoviaque
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5 from aboudreault/mrq-max-attempts
Mrq max attempts
parents
7d794f08
c810ef22
Hide whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
375 additions
and
38 deletions
+375
-38
README.md
+22
-0
mentoring/answer.py
+2
-2
mentoring/light_children.py
+116
-10
mentoring/mentoring.py
+1
-0
mentoring/migrations/0001_initial.py
+1
-2
mentoring/migrations/0004_auto__add_lightchild__add_unique_lightchild_student_id_course_id_name.py
+58
-0
mentoring/models.py
+18
-0
mentoring/mrq.py
+30
-6
mentoring/public/css/mentoring.css
+1
-1
mentoring/public/css/questionnaire.css
+24
-5
mentoring/public/js/mentoring.js
+7
-1
mentoring/public/js/questionnaire.js
+69
-5
mentoring/templates/html/mentoring.html
+1
-1
mentoring/templates/html/mrqblock_attempts.html
+12
-0
mentoring/templates/html/mrqblock_choices.html
+11
-3
mentoring/utils.py
+2
-2
No files found.
README.md
View file @
b3d47cb7
...
@@ -75,6 +75,28 @@ Second XBlock instance:
...
@@ -75,6 +75,28 @@ Second XBlock instance:
</mentoring>
</mentoring>
```
```
### Self-assessment MRQs
```
xml
<mentoring
url_name=
"mcq_1"
enforce_dependency=
"false"
>
<mrq
name=
"mrq_1_1"
type=
"choices"
max_attempts=
"3"
>
<question>
What do you like in this MRQ?
</question>
<choice
value=
"elegance"
>
Its elegance
</choice>
<choice
value=
"beauty"
>
Its beauty
</choice>
<choice
value=
"gracefulness"
>
Its gracefulness
</choice>
<choice
value=
"bugs"
>
Its bugs
</choice>
<tip
require=
"gracefulness"
>
This MRQ is indeed very graceful
</tip>
<tip
require=
"elegance,beauty"
>
This is something everyone has to like about this MRQ
</tip>
<tip
reject=
"bugs"
>
Nah, there isn't any!
</tip>
<message
type=
"on-submit"
>
Thank you for answering!
</message>
</mrq>
<message
type=
"completed"
>
All is good now...
<html><p>
Congratulations!
</p></html>
</message>
</mentoring>
### Tables
### Tables
```
xml
```
xml
...
...
mentoring/answer.py
View file @
b3d47cb7
...
@@ -55,7 +55,7 @@ class AnswerBlock(LightChild):
...
@@ -55,7 +55,7 @@ class AnswerBlock(LightChild):
@lazy
@lazy
def
student_input
(
self
):
def
student_input
(
self
):
"""
"""
Use lazy property instead of XBlock field, as __init__() doesn't support
Use lazy property instead of XBlock field, as __init__() doesn't support
overwriting field values
overwriting field values
"""
"""
# Only attempt to locate a model object for this block when the answer has a name
# Only attempt to locate a model object for this block when the answer has a name
...
@@ -79,7 +79,7 @@ class AnswerBlock(LightChild):
...
@@ -79,7 +79,7 @@ class AnswerBlock(LightChild):
html
=
render_template
(
'templates/html/answer_read_only.html'
,
{
html
=
render_template
(
'templates/html/answer_read_only.html'
,
{
'self'
:
self
,
'self'
:
self
,
})
})
fragment
=
Fragment
(
html
)
fragment
=
Fragment
(
html
)
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
'public/css/answer.css'
))
fragment
.
add_css_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
'public/css/answer.css'
))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
.
xblock_container
,
...
...
mentoring/light_children.py
View file @
b3d47cb7
...
@@ -24,6 +24,10 @@
...
@@ -24,6 +24,10 @@
# Imports ###########################################################
# Imports ###########################################################
import
logging
import
logging
import
json
from
lazy
import
lazy
from
weakref
import
WeakKeyDictionary
from
cStringIO
import
StringIO
from
cStringIO
import
StringIO
from
lxml
import
etree
from
lxml
import
etree
...
@@ -34,6 +38,8 @@ from xblock.core import XBlock
...
@@ -34,6 +38,8 @@ from xblock.core import XBlock
from
xblock.fragment
import
Fragment
from
xblock.fragment
import
Fragment
from
xblock.plugin
import
Plugin
from
xblock.plugin
import
Plugin
from
.models
import
LightChild
as
LightChildModel
try
:
try
:
from
xmodule_modifiers
import
replace_jump_to_id_urls
from
xmodule_modifiers
import
replace_jump_to_id_urls
except
:
except
:
...
@@ -133,6 +139,7 @@ class LightChildrenMixin(XBlockWithChildrenFragmentsMixin):
...
@@ -133,6 +139,7 @@ class LightChildrenMixin(XBlockWithChildrenFragmentsMixin):
"""
"""
Replacement for ```self.runtime.render_child()```
Replacement for ```self.runtime.render_child()```
"""
"""
frag
=
getattr
(
child
,
view_name
)(
context
)
frag
=
getattr
(
child
,
view_name
)(
context
)
frag
.
content
=
u'<div class="xblock-light-child" name="{}" data-type="{}">{}</div>'
.
format
(
frag
.
content
=
u'<div class="xblock-light-child" name="{}" data-type="{}">{}</div>'
.
format
(
child
.
name
,
child
.
__class__
.
__name__
,
frag
.
content
)
child
.
name
,
child
.
__class__
.
__name__
,
frag
.
content
)
...
@@ -167,6 +174,7 @@ class XBlockWithLightChildren(LightChildrenMixin, XBlock):
...
@@ -167,6 +174,7 @@ class XBlockWithLightChildren(LightChildrenMixin, XBlock):
"""
"""
Current HTML view of the XBlock, for refresh by client
Current HTML view of the XBlock, for refresh by client
"""
"""
frag
=
self
.
student_view
({})
frag
=
self
.
student_view
({})
frag
=
self
.
fragment_text_rewriting
(
frag
)
frag
=
self
.
fragment_text_rewriting
(
frag
)
...
@@ -194,6 +202,7 @@ class XBlockWithLightChildren(LightChildrenMixin, XBlock):
...
@@ -194,6 +202,7 @@ class XBlockWithLightChildren(LightChildrenMixin, XBlock):
fragment
=
replace_jump_to_id_urls
(
course_id
,
jump_to_url
,
self
,
'student_view'
,
fragment
,
{})
fragment
=
replace_jump_to_id_urls
(
course_id
,
jump_to_url
,
self
,
'student_view'
,
fragment
,
{})
return
fragment
return
fragment
class
LightChild
(
Plugin
,
LightChildrenMixin
):
class
LightChild
(
Plugin
,
LightChildrenMixin
):
"""
"""
Base class for the light children
Base class for the light children
...
@@ -203,6 +212,7 @@ class LightChild(Plugin, LightChildrenMixin):
...
@@ -203,6 +212,7 @@ class LightChild(Plugin, LightChildrenMixin):
def
__init__
(
self
,
parent
):
def
__init__
(
self
,
parent
):
self
.
parent
=
parent
self
.
parent
=
parent
self
.
xblock_container
=
parent
.
xblock_container
self
.
xblock_container
=
parent
.
xblock_container
self
.
_student_data_loaded
=
False
@property
@property
def
runtime
(
self
):
def
runtime
(
self
):
...
@@ -220,30 +230,125 @@ class LightChild(Plugin, LightChildrenMixin):
...
@@ -220,30 +230,125 @@ class LightChild(Plugin, LightChildrenMixin):
xmodule_runtime
=
xmodule_runtime
()
xmodule_runtime
=
xmodule_runtime
()
return
xmodule_runtime
return
xmodule_runtime
@lazy
def
student_data
(
self
):
"""
Use lazy property instead of XBlock field, as __init__() doesn't support
overwriting field values
"""
if
not
self
.
name
:
return
''
student_data
=
self
.
get_lightchild_model_object
()
.
student_data
return
student_data
def
load_student_data
(
self
):
"""
Load the student data from the database.
"""
if
self
.
_student_data_loaded
:
return
fields
=
self
.
get_fields_to_save
()
if
not
fields
or
not
self
.
student_data
:
return
student_data
=
json
.
loads
(
self
.
student_data
)
for
field
in
fields
:
if
field
in
student_data
:
setattr
(
self
,
field
,
student_data
[
field
])
self
.
_student_data_loaded
=
True
@classmethod
def
get_fields_to_save
(
cls
):
"""
Returns a list of all LightChildField of the class. Used for saving student data.
"""
return
[]
def
save
(
self
):
def
save
(
self
):
pass
"""
Replicate data changes on the related Django model used for sharing of data accross XBlocks
"""
# Save all children
for
child
in
self
.
get_children_objects
():
child
.
save
()
self
.
student_data
=
{}
# Get All LightChild fields to save
for
field
in
self
.
get_fields_to_save
():
self
.
student_data
[
field
]
=
getattr
(
self
,
field
)
if
self
.
name
:
lightchild_data
=
self
.
get_lightchild_model_object
()
if
lightchild_data
.
student_data
!=
self
.
student_data
:
lightchild_data
.
student_data
=
json
.
dumps
(
self
.
student_data
)
lightchild_data
.
save
()
def
get_lightchild_model_object
(
self
,
name
=
None
):
"""
Fetches the LightChild model object for the lightchild named `name`
"""
if
not
name
:
name
=
self
.
name
if
not
name
:
raise
ValueError
,
'LightChild.name field need to be set to a non-null/empty value'
student_id
=
self
.
xmodule_runtime
.
anonymous_student_id
course_id
=
self
.
xmodule_runtime
.
course_id
lightchild_data
,
created
=
LightChildModel
.
objects
.
get_or_create
(
student_id
=
student_id
,
course_id
=
course_id
,
name
=
name
,
)
return
lightchild_data
class
LightChildField
(
object
):
class
LightChildField
(
object
):
"""
"""
Fake field with no persistence - allows to keep XBlocks fields definitions on LightChild
Fake field with no persistence - allows to keep XBlocks fields definitions on LightChild
"""
"""
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
value
=
kwargs
.
get
(
'default'
,
''
)
self
.
default
=
kwargs
.
get
(
'default'
,
''
)
self
.
data
=
WeakKeyDictionary
()
def
__get__
(
self
,
instance
,
name
):
def
__nonzero__
(
self
):
# A LightChildField can depend on student_data
return
bool
(
self
.
value
)
instance
.
load_student_data
(
)
return
self
.
data
.
get
(
instance
,
self
.
default
)
def
__set__
(
self
,
instance
,
value
):
self
.
data
[
instance
]
=
value
class
String
(
LightChildField
):
class
String
(
LightChildField
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
value
=
kwargs
.
get
(
'default'
,
''
)
or
''
super
(
String
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
default
=
kwargs
.
get
(
'default'
,
''
)
or
''
# def split(self, *args, **kwargs):
# return self.value.split(*args, **kwargs)
def
__str__
(
self
):
return
self
.
value
def
split
(
self
,
*
args
,
**
kwargs
):
class
Integer
(
LightChildField
):
return
self
.
value
.
split
(
*
args
,
**
kwargs
)
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
Integer
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
default
=
kwargs
.
get
(
'default'
,
0
)
def
__set__
(
self
,
instance
,
value
):
try
:
self
.
data
[
instance
]
=
int
(
value
)
except
(
TypeError
,
ValueError
):
# not an integer
self
.
data
[
instance
]
=
0
class
Boolean
(
LightChildField
):
class
Boolean
(
LightChildField
):
...
@@ -252,7 +357,8 @@ class Boolean(LightChildField):
...
@@ -252,7 +357,8 @@ class Boolean(LightChildField):
class
List
(
LightChildField
):
class
List
(
LightChildField
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
value
=
kwargs
.
get
(
'default'
,
[])
super
(
List
,
self
)
.
__init__
(
*
args
,
**
kwargs
)
self
.
default
=
kwargs
.
get
(
'default'
,
[])
class
Scope
(
object
):
class
Scope
(
object
):
...
...
mentoring/mentoring.py
View file @
b3d47cb7
...
@@ -84,6 +84,7 @@ class MentoringBlock(XBlockWithLightChildren):
...
@@ -84,6 +84,7 @@ class MentoringBlock(XBlockWithLightChildren):
self
.
runtime
.
local_resource_url
(
self
,
'public/js/vendor/underscore-min.js'
))
self
.
runtime
.
local_resource_url
(
self
,
'public/js/vendor/underscore-min.js'
))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/mentoring.js'
))
fragment
.
add_javascript_url
(
self
.
runtime
.
local_resource_url
(
self
,
'public/js/mentoring.js'
))
fragment
.
add_resource
(
load_resource
(
'templates/html/mentoring_progress.html'
),
"text/html"
)
fragment
.
add_resource
(
load_resource
(
'templates/html/mentoring_progress.html'
),
"text/html"
)
fragment
.
add_resource
(
load_resource
(
'templates/html/mrqblock_attempts.html'
),
"text/html"
)
fragment
.
initialize_js
(
'MentoringBlock'
)
fragment
.
initialize_js
(
'MentoringBlock'
)
...
...
mentoring/migrations/0001_initial.py
View file @
b3d47cb7
...
@@ -43,4 +43,4 @@ class Migration(SchemaMigration):
...
@@ -43,4 +43,4 @@ class Migration(SchemaMigration):
}
}
}
}
complete_apps
=
[
'mentoring'
]
complete_apps
=
[
'mentoring'
]
\ No newline at end of file
mentoring/migrations/0004_auto__add_lightchild__add_unique_lightchild_student_id_course_id_name.py
0 → 100644
View file @
b3d47cb7
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding model 'LightChild'
db
.
create_table
(
'mentoring_lightchild'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
50
,
db_index
=
True
)),
(
'student_id'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'course_id'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
50
,
db_index
=
True
)),
(
'student_data'
,
self
.
gf
(
'django.db.models.fields.TextField'
)(
default
=
''
,
blank
=
True
)),
(
'created_on'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now_add
=
True
,
blank
=
True
)),
(
'modified_on'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
auto_now
=
True
,
blank
=
True
)),
))
db
.
send_create_signal
(
'mentoring'
,
[
'LightChild'
])
# Adding unique constraint on 'LightChild', fields ['student_id', 'course_id', 'name']
db
.
create_unique
(
'mentoring_lightchild'
,
[
'student_id'
,
'course_id'
,
'name'
])
def
backwards
(
self
,
orm
):
# Removing unique constraint on 'LightChild', fields ['student_id', 'course_id', 'name']
db
.
delete_unique
(
'mentoring_lightchild'
,
[
'student_id'
,
'course_id'
,
'name'
])
# Deleting model 'LightChild'
db
.
delete_table
(
'mentoring_lightchild'
)
models
=
{
'mentoring.answer'
:
{
'Meta'
:
{
'unique_together'
:
"(('student_id', 'course_id', 'name'),)"
,
'object_name'
:
'Answer'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'created_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'student_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'student_input'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'blank'
:
'True'
})
},
'mentoring.lightchild'
:
{
'Meta'
:
{
'unique_together'
:
"(('student_id', 'course_id', 'name'),)"
,
'object_name'
:
'LightChild'
},
'course_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'created_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'student_data'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'modified_on'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
,
'db_index'
:
'True'
}),
'student_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'db_index'
:
'True'
})
}
}
complete_apps
=
[
'mentoring'
]
mentoring/models.py
View file @
b3d47cb7
...
@@ -49,3 +49,21 @@ class Answer(models.Model):
...
@@ -49,3 +49,21 @@ class Answer(models.Model):
# Force validation of max_length
# Force validation of max_length
self
.
full_clean
()
self
.
full_clean
()
super
(
Answer
,
self
)
.
save
(
*
args
,
**
kwargs
)
super
(
Answer
,
self
)
.
save
(
*
args
,
**
kwargs
)
class
LightChild
(
models
.
Model
):
"""
Django model used to store LightChild student data that need to be shared and queried accross
XBlock instances (workaround). Since this is temporary, `data` are stored in json.
"""
class
Meta
:
app_label
=
'mentoring'
unique_together
=
((
'student_id'
,
'course_id'
,
'name'
),)
name
=
models
.
CharField
(
max_length
=
50
,
db_index
=
True
)
student_id
=
models
.
CharField
(
max_length
=
32
,
db_index
=
True
)
course_id
=
models
.
CharField
(
max_length
=
50
,
db_index
=
True
)
student_data
=
models
.
TextField
(
blank
=
True
,
default
=
''
)
created_on
=
models
.
DateTimeField
(
'created on'
,
auto_now_add
=
True
)
modified_on
=
models
.
DateTimeField
(
'modified on'
,
auto_now
=
True
)
mentoring/mrq.py
View file @
b3d47cb7
...
@@ -26,7 +26,7 @@
...
@@ -26,7 +26,7 @@
import
logging
import
logging
from
.light_children
import
List
,
Scope
from
.light_children
import
Integer
,
List
,
Scope
from
.questionnaire
import
QuestionnaireAbstractBlock
from
.questionnaire
import
QuestionnaireAbstractBlock
from
.utils
import
render_template
from
.utils
import
render_template
...
@@ -43,11 +43,21 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -43,11 +43,21 @@ 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
(
help
=
"Last submissions by the student"
,
default
=
[],
scope
=
Scope
.
user_state
)
max_attempts
=
Integer
(
help
=
"Number of max attempts for this questions"
,
scope
=
Scope
.
content
)
num_attempts
=
Integer
(
help
=
"Number of attempts a user has answered for this questions"
,
scope
=
Scope
.
user_state
)
# TODO REMOVE THIS, ONLY NEEDED FOR LIGHTCHILDREN
@classmethod
def
get_fields_to_save
(
cls
):
return
[
'num_attempts'
]
def
submit
(
self
,
submissions
):
def
submit
(
self
,
submissions
):
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
log
.
debug
(
u'Received MRQ submissions: "
%
s"'
,
submissions
)
completed
=
True
completed
=
True
results
=
[]
results
=
[]
for
choice
in
self
.
custom_choices
:
for
choice
in
self
.
custom_choices
:
choice_completed
=
True
choice_completed
=
True
...
@@ -73,12 +83,26 @@ class MRQBlock(QuestionnaireAbstractBlock):
...
@@ -73,12 +83,26 @@ class MRQBlock(QuestionnaireAbstractBlock):
}),
}),
})
})
self
.
student_choices
=
submissions
self
.
message
=
u'Your answer is correct!'
if
completed
else
u'Your answer is incorrect.'
# Do not increase the counter is the answer is correct
if
not
completed
:
setattr
(
self
,
'num_attempts'
,
self
.
num_attempts
+
1
)
if
self
.
max_attempts
>
0
and
self
.
num_attempts
>=
self
.
max_attempts
:
completed
=
True
self
.
message
+=
u' You have reached the maximum number of attempts for this question. '
\
u'Your next answers won''t be saved. You can check the answer(s) using the "Show Answer(s)" button.'
else
:
self
.
student_choices
=
submissions
result
=
{
result
=
{
'submissions'
:
submissions
,
'submissions'
:
submissions
,
'completed'
:
completed
,
'completed'
:
completed
,
'choices'
:
results
,
'choices'
:
results
,
'message'
:
self
.
message
,
'message'
:
self
.
message
,
'max_attempts'
:
self
.
max_attempts
,
'num_attempts'
:
self
.
num_attempts
}
}
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
log
.
debug
(
u'MRQ submissions result:
%
s'
,
result
)
return
result
return
result
mentoring/public/css/mentoring.css
View file @
b3d47cb7
...
@@ -51,7 +51,7 @@
...
@@ -51,7 +51,7 @@
.mentoring
.progress
.indicator
{
.mentoring
.progress
.indicator
{
display
:
inline-block
;
display
:
inline-block
;
margin-top
:
5px
;
vertical-align
:
middle
;
}
}
.mentoring
.progress
.indicator
.checkmark-correct
{
.mentoring
.progress
.indicator
.checkmark-correct
{
...
...
mentoring/public/css/questionnaire.css
View file @
b3d47cb7
...
@@ -11,21 +11,28 @@
...
@@ -11,21 +11,28 @@
margin
:
10px
0
;
margin
:
10px
0
;
}
}
.mentoring
.choices
.choice-checkbox
{
display
:
inline-block
;
margin-top
:
5px
;
margin-bottom
:
5px
;
}
.mentoring
.choices
.choice-result
{
.mentoring
.choices
.choice-result
{
padding-right
:
10px
;
display
:
inline-block
;
width
:
40px
;
vertical-align
:
middle
;
vertical-align
:
middle
;
cursor
:
pointer
;
}
}
.mentoring
.choices
.choice-result.correct
{
.mentoring
.choices
.choice-result.correct
,
.choice-answer.correct
{
cursor
:
pointer
;
color
:
#006600
;
color
:
#006600
;
position
:
relative
;
position
:
relative
;
top
:
-3px
;
top
:
-3px
;
}
}
.mentoring
.choices
.choice-result.incorrect
{
.mentoring
.choices
.choice-result.incorrect
{
margin-right
:
10px
;
text-align
:
center
;
padding-left
:
10px
;
padding-right
:
10px
;
color
:
#ff0000
;
color
:
#ff0000
;
}
}
...
@@ -78,3 +85,15 @@
...
@@ -78,3 +85,15 @@
.mentoring
.choices-list
.choice-selector
{
.mentoring
.choices-list
.choice-selector
{
margin-right
:
5px
;
margin-right
:
5px
;
}
}
.mentoring
.mrq-attempts
{
display
:
inline-block
;
vertical-align
:
baseline
;
color
:
#777
;
font-style
:
italic
;
webkit-font-smoothing
:
antialiased
;
}
.mentoring
.mrq-attempts
div
{
display
:
inline-block
;
}
mentoring/public/js/mentoring.js
View file @
b3d47cb7
...
@@ -74,7 +74,7 @@ function MentoringBlock(runtime, element) {
...
@@ -74,7 +74,7 @@ function MentoringBlock(runtime, element) {
}
}
function
initXBlock
()
{
function
initXBlock
()
{
var
submit_dom
=
$
(
element
).
find
(
'.submit'
);
var
submit_dom
=
$
(
element
).
find
(
'.submit
.input-main
'
);
submit_dom
.
bind
(
'click'
,
function
()
{
submit_dom
.
bind
(
'click'
,
function
()
{
var
data
=
{};
var
data
=
{};
...
@@ -89,6 +89,12 @@ function MentoringBlock(runtime, element) {
...
@@ -89,6 +89,12 @@ function MentoringBlock(runtime, element) {
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
data
)).
success
(
handleSubmitResults
);
$
.
post
(
handlerUrl
,
JSON
.
stringify
(
data
)).
success
(
handleSubmitResults
);
});
});
// init children (especially mrq blocks)
var
children
=
getChildren
(
element
);
_
.
each
(
children
,
function
(
child
)
{
callIfExists
(
child
,
'init'
);
});
if
(
submit_dom
.
length
)
{
if
(
submit_dom
.
length
)
{
renderProgress
();
renderProgress
();
}
}
...
...
mentoring/public/js/questionnaire.js
View file @
b3d47cb7
// TODO: Split in two files
// TODO: Split in two files
var
mrqAttemptsTemplate
=
_
.
template
(
$
(
'#xblock-mrq-attempts'
).
html
());
function
MCQBlock
(
runtime
,
element
)
{
function
MCQBlock
(
runtime
,
element
)
{
return
{
return
{
...
@@ -25,7 +26,29 @@ function MCQBlock(runtime, element) {
...
@@ -25,7 +26,29 @@ function MCQBlock(runtime, element) {
function
MRQBlock
(
runtime
,
element
)
{
function
MRQBlock
(
runtime
,
element
)
{
return
{
return
{
renderAttempts
:
function
()
{
var
data
=
$
(
'.mrq-attempts'
,
element
).
data
();
$
(
'.mrq-attempts'
,
element
).
html
(
mrqAttemptsTemplate
(
data
));
// bind show answer button
var
showAnswerButton
=
$
(
'button'
,
element
);
if
(
showAnswerButton
.
length
!=
0
)
{
if
(
_
.
isUndefined
(
this
.
answers
))
showAnswerButton
.
hide
();
else
showAnswerButton
.
on
(
'click'
,
_
.
bind
(
this
.
toggleAnswers
,
this
));
}
},
init
:
function
()
{
this
.
renderAttempts
();
},
submit
:
function
()
{
submit
:
function
()
{
// hide answers
var
choiceInputDOM
=
$
(
'.choice input'
,
element
),
choiceResultDOM
=
$
(
'.choice-answer'
,
choiceInputDOM
.
closest
(
'.choice'
));
choiceResultDOM
.
removeClass
(
'incorrect icon-exclamation correct icon-ok'
);
var
checkedCheckboxes
=
$
(
'input[type=checkbox]:checked'
,
element
),
var
checkedCheckboxes
=
$
(
'input[type=checkbox]:checked'
,
element
),
checkedValues
=
[];
checkedValues
=
[];
...
@@ -58,17 +81,29 @@ function MRQBlock(runtime, element) {
...
@@ -58,17 +81,29 @@ function MRQBlock(runtime, element) {
showPopup
(
messageDOM
);
showPopup
(
messageDOM
);
}
}
var
answers
=
[];
// used in displayAnswers
$
.
each
(
result
.
choices
,
function
(
index
,
choice
)
{
$
.
each
(
result
.
choices
,
function
(
index
,
choice
)
{
var
choiceInputDOM
=
$
(
'.choice input[value='
+
choice
.
value
+
']'
,
element
),
var
choiceInputDOM
=
$
(
'.choice input[value='
+
choice
.
value
+
']'
,
element
),
choiceDOM
=
choiceInputDOM
.
closest
(
'.choice'
),
choiceDOM
=
choiceInputDOM
.
closest
(
'.choice'
),
choiceResultDOM
=
$
(
'.choice-result'
,
choiceDOM
),
choiceResultDOM
=
$
(
'.choice-result'
,
choiceDOM
),
choiceAnswerDOM
=
$
(
'.choice-answer'
,
choiceDOM
),
choiceTipsDOM
=
$
(
'.choice-tips'
,
choiceDOM
),
choiceTipsDOM
=
$
(
'.choice-tips'
,
choiceDOM
),
choiceTipsCloseDOM
;
choiceTipsCloseDOM
;
if
(
choice
.
completed
)
{
/* update our answers dict */
choiceResultDOM
.
removeClass
(
'incorrect icon-exclamation'
).
addClass
(
'correct icon-ok'
);
answers
.
push
({
}
else
{
input
:
choiceInputDOM
,
choiceResultDOM
.
removeClass
(
'correct icon-ok'
).
addClass
(
'incorrect icon-exclamation'
);
answer
:
choice
.
completed
?
choiceInputDOM
.
attr
(
'checked'
)
:
!
choiceInputDOM
.
attr
(
'checked'
)
});
choiceResultDOM
.
removeClass
(
'incorrect icon-exclamation correct icon-ok'
);
/* show hint if checked or max_attempts is disabled */
if
(
result
.
completed
||
choiceInputDOM
.
prop
(
'checked'
)
||
result
.
max_attempts
<=
0
)
{
if
(
choice
.
completed
)
{
choiceResultDOM
.
addClass
(
'correct icon-ok'
);
}
else
if
(
!
choice
.
completed
)
{
choiceResultDOM
.
addClass
(
'incorrect icon-exclamation'
);
}
}
}
choiceTipsDOM
.
html
(
choice
.
tips
);
choiceTipsDOM
.
html
(
choice
.
tips
);
...
@@ -77,7 +112,36 @@ function MRQBlock(runtime, element) {
...
@@ -77,7 +112,36 @@ function MRQBlock(runtime, element) {
choiceResultDOM
.
off
(
'click'
).
on
(
'click'
,
function
()
{
choiceResultDOM
.
off
(
'click'
).
on
(
'click'
,
function
()
{
showPopup
(
choiceTipsDOM
);
showPopup
(
choiceTipsDOM
);
});
});
choiceAnswerDOM
.
off
(
'click'
).
on
(
'click'
,
function
()
{
showPopup
(
choiceTipsDOM
);
});
});
});
}
this
.
answers
=
answers
;
$
(
'.mrq-attempts'
,
element
).
data
(
'num_attempts'
,
result
.
num_attempts
);
this
.
renderAttempts
();
},
toggleAnswers
:
function
()
{
var
showAnswerButton
=
$
(
'button span'
,
element
);
var
answers_displayed
=
this
.
answers_displayed
=
!
this
.
answers_displayed
;
_
.
each
(
this
.
answers
,
function
(
answer
)
{
var
choiceResultDOM
=
$
(
'.choice-answer'
,
answer
.
input
.
closest
(
'.choice'
));
choiceResultDOM
.
removeClass
(
'correct icon-ok'
);
if
(
answers_displayed
)
{
if
(
answer
.
answer
)
choiceResultDOM
.
addClass
(
'correct icon-ok'
);
showAnswerButton
.
text
(
'Hide Answer(s)'
);
}
else
{
showAnswerButton
.
text
(
'Show Answer(s)'
);
}
});
}
};
};
}
}
mentoring/templates/html/mentoring.html
View file @
b3d47cb7
...
@@ -8,7 +8,7 @@
...
@@ -8,7 +8,7 @@
{% endfor %}
{% endfor %}
{% if self.display_submit %}
{% if self.display_submit %}
<div
class=
"submit"
>
<div
class=
"submit"
>
<input
type=
"button"
value=
"Submit"
></input>
<input
type=
"button"
class=
"input-main"
value=
"Submit"
></input>
<span
class=
"progress"
data-completed=
"{{ self.completed }}"
data-attempted=
"{{ self.attempted }}"
>
<span
class=
"progress"
data-completed=
"{{ self.completed }}"
data-attempted=
"{{ self.attempted }}"
>
<span
class=
'indicator'
></span>
<span
class=
'indicator'
></span>
</span>
</span>
...
...
mentoring/templates/html/mrqblock_attempts.html
0 → 100644
View file @
b3d47cb7
<script
type=
"text/template"
id=
"xblock-mrq-attempts"
>
<%
if
(
_
.
isNumber
(
max_attempts
)
&&
max_attempts
>
0
)
{{
%>
<%
if
(
num_attempts
>=
max_attempts
)
{{
%>
<
button
class
=
"show"
>
<
span
class
=
"show-label"
>
Show
Answer
(
s
)
<
/span
>
<
/button
>
<%
}}
%>
<
div
>
You
have
used
<%=
_
.
min
([
num_attempts
,
max_attempts
])
%>
of
<%=
max_attempts
%>
attempts
for
this
question
.
<
/div
>
<%
}}
%>
</script>
mentoring/templates/html/mrqblock_choices.html
View file @
b3d47cb7
...
@@ -5,12 +5,20 @@
...
@@ -5,12 +5,20 @@
{% for choice in custom_choices %}
{% for choice in custom_choices %}
<div
class=
"choice"
>
<div
class=
"choice"
>
<span
class=
"choice-result icon-2x"
></span>
<span
class=
"choice-result icon-2x"
></span>
<label
class=
"choice-label"
>
<div
class=
"choice-checkbox"
>
<input
class=
"choice-selector"
type=
"checkbox"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
choice
.
value
in
self
.
student_choices
%}
checked
{%
endif
%}
>
{{ choice.content }}
<label
class=
"choice-label"
>
</label>
<input
class=
"choice-selector"
type=
"checkbox"
name=
"{{ self.name }}"
value=
"{{ choice.value }}"
{%
if
choice
.
value
in
self
.
student_choices
%}
checked
{%
endif
%}
>
{{ choice.content }}
</input>
</label>
</div>
<span
class=
"choice-answer icon-2x"
></span>
<div
class=
"choice-tips"
></div>
<div
class=
"choice-tips"
></div>
</div>
</div>
{% endfor %}
{% endfor %}
<div
class=
"choice-message"
></div>
<div
class=
"choice-message"
></div>
</div>
</div>
</fieldset>
</fieldset>
<div
class=
"mrq-attempts"
data-max_attempts=
"{{ self.max_attempts }}"
data-num_attempts=
"{{ self.num_attempts }}"
></div>
mentoring/utils.py
View file @
b3d47cb7
...
@@ -105,9 +105,9 @@ class XBlockWithChildrenFragmentsMixin(object):
...
@@ -105,9 +105,9 @@ class XBlockWithChildrenFragmentsMixin(object):
and a list of fragments, one per children
and a list of fragments, one per children
- `view_name` allows to select a specific view method on the children
- `view_name` allows to select a specific view method on the children
- `instance_of` allows to only return fragments for children which are instances of
- `instance_of` allows to only return fragments for children which are instances of
the provided class
the provided class
- `not_instance_of` allows to only return fragments for children which are *NOT*
- `not_instance_of` allows to only return fragments for children which are *NOT*
instances of the provided class
instances of the provided class
"""
"""
fragment
=
Fragment
()
fragment
=
Fragment
()
...
...
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