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
d0da3ceb
Commit
d0da3ceb
authored
Feb 25, 2014
by
Xavier Antoviaque
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
light-children: Light children as a replacement for XBlock children - WIP
parent
da71d55d
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
255 additions
and
43 deletions
+255
-43
mentoring/__init__.py
+1
-0
mentoring/answer.py
+2
-3
mentoring/html.py
+56
-0
mentoring/light_children.py
+160
-0
mentoring/mentoring.py
+7
-8
mentoring/message.py
+3
-6
mentoring/quizz.py
+13
-19
mentoring/table.py
+8
-7
setup.py
+5
-0
No files found.
mentoring/__init__.py
View file @
d0da3ceb
from
.answer
import
AnswerBlock
from
.dataexport
import
MentoringDataExportBlock
from
.html
import
HTMLBlock
from
.quizz
import
QuizzBlock
,
QuizzChoiceBlock
,
QuizzTipBlock
from
.mentoring
import
MentoringBlock
from
.message
import
MentoringMessageBlock
...
...
mentoring/answer.py
View file @
d0da3ceb
...
...
@@ -27,10 +27,9 @@ import logging
from
lazy
import
lazy
from
xblock.core
import
XBlock
from
xblock.fields
import
Boolean
,
Scope
,
String
from
xblock.fragment
import
Fragment
from
.light_children
import
LightChild
,
Boolean
,
Scope
,
String
from
.models
import
Answer
from
.utils
import
load_resource
,
render_template
...
...
@@ -42,7 +41,7 @@ log = logging.getLogger(__name__)
# Classes ###########################################################
class
AnswerBlock
(
XBlock
):
class
AnswerBlock
(
LightChild
):
"""
A field where the student enters an answer
...
...
mentoring/html.py
0 → 100644
View file @
d0da3ceb
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# 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
xblock.fragment
import
Fragment
from
.light_children
import
LightChild
,
Scope
,
String
# Globals ###########################################################
log
=
logging
.
getLogger
(
__name__
)
# Classes ###########################################################
class
HTMLBlock
(
LightChild
):
"""
A simplistic replacement for the HTML XModule, as a light XBlock child
"""
content
=
String
(
help
=
"HTML content"
,
scope
=
Scope
.
content
,
default
=
""
)
@classmethod
def
init_block_from_node
(
cls
,
block
,
node
):
block
.
light_children
=
[]
# TODO-LIGHT-CHILDREN: get real value from `node` (lxml)
block
.
content
=
'<div>Placeholder HTML content</div>'
return
block
def
mentoring_view
(
self
,
context
=
None
):
return
Fragment
(
self
.
content
)
mentoring/light_children.py
0 → 100644
View file @
d0da3ceb
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 Harvard
#
# 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
xblock.fragment
import
Fragment
from
xblock.plugin
import
Plugin
from
.utils
import
XBlockWithChildrenFragmentsMixin
# Globals ###########################################################
log
=
logging
.
getLogger
(
__name__
)
# Classes ###########################################################
class
XBlockWithLightChildrenMixin
(
XBlockWithChildrenFragmentsMixin
):
"""
Allows to use lightweight children on a given XBlock, which will
have a similar behavior but will not be instanciated as full-fledged
XBlocks, which aren't correctly supported as children
TODO: Replace this once the support for XBlock children has matured
by a mixin implementing the following abstractions, used to keep
code reusable in the XBlocks:
* get_children_objects()
* Functionality of XBlockWithChildrenFragmentsMixin
Other changes caused by LightChild use:
* overrides of `parse_xml()` have been replaced by overrides of
`init_block_from_node()` on LightChildren
* fields on LightChild don't have any persistence
"""
def
__init__
(
self
,
*
args
,
**
kwargs
):
self
.
load_children_from_xml_content
()
def
load_children_from_xml_content
(
self
):
"""
Load light children from the `xml_content` attribute
"""
if
not
hasattr
(
self
,
'xml_content'
)
or
not
self
.
xml_content
:
return
# TODO-LIGHT-CHILDREN: replace by proper lxml call
node
=
None
# lxml.load(self.xml_content)
self
.
init_block_from_node
(
self
,
node
)
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
block
=
runtime
.
construct_xblock_from_class
(
cls
,
keys
)
cls
.
init_block_from_node
(
block
,
node
)
return
block
@classmethod
def
init_block_from_node
(
cls
,
block
,
node
):
block
.
light_children
=
[]
for
child_id
,
xml_child
in
enumerate
(
node
):
cls
.
add_node_as_child
(
block
,
xml_child
,
child_id
)
for
name
,
value
in
node
.
items
():
setattr
(
block
,
name
,
value
)
return
block
@classmethod
def
add_node_as_child
(
cls
,
block
,
xml_child
,
child_id
):
# Instantiate child
child_class
=
cls
.
get_class_by_element
(
xml_child
.
tag
)
child
=
child_class
()
child
.
name
=
u'{}_{}'
.
format
(
block
.
name
,
child_id
)
log
.
warn
(
child_class
)
# Add any children the child may itself have
child_class
.
init_block_from_node
(
child
,
xml_child
)
block
.
light_children
.
append
(
child
)
@classmethod
def
get_class_by_element
(
cls
,
xml_tag
):
return
LightChild
.
load_class
(
xml_tag
)
def
get_children_objects
(
self
):
"""
Replacement for ```[self.runtime.get_block(child_id) for child_id in self.children]```
"""
return
self
.
light_children
def
render_child
(
self
,
child
,
view_name
,
context
):
"""
Replacement for ```self.runtime.render_child()```
"""
return
getattr
(
child
,
view_name
)(
context
)
def
get_children_fragment
(
self
,
context
,
view_name
=
'student_view'
,
instance_of
=
None
,
not_instance_of
=
None
):
fragment
=
Fragment
()
named_child_frags
=
[]
for
child
in
self
.
get_children_objects
():
if
instance_of
is
not
None
and
not
isinstance
(
child
,
instance_of
):
continue
if
not_instance_of
is
not
None
and
isinstance
(
child
,
not_instance_of
):
continue
frag
=
self
.
render_child
(
child
,
view_name
,
context
)
fragment
.
add_frag_resources
(
frag
)
named_child_frags
.
append
((
child
.
name
,
frag
))
return
fragment
,
named_child_frags
class
LightChild
(
Plugin
,
XBlockWithLightChildrenMixin
):
"""
Base class for the light children
"""
entry_point
=
'xblock.light_children'
class
LightChildField
(
object
):
"""
Fake field with no persistence - allows to keep XBlocks fields definitions on LightChild
"""
def
__init__
(
self
,
*
args
,
**
kwargs
):
pass
class
String
(
LightChildField
):
pass
class
Boolean
(
LightChildField
):
pass
class
Scope
(
object
):
content
=
None
user_state
=
None
mentoring/mentoring.py
View file @
d0da3ceb
...
...
@@ -28,9 +28,9 @@ import logging
from
xblock.core
import
XBlock
from
xblock.fields
import
Boolean
,
Scope
,
String
from
.light_children
import
XBlockWithLightChildrenMixin
from
.message
import
MentoringMessageBlock
from
.utils
import
get_scenarios_from_path
,
load_resource
,
render_template
,
\
XBlockWithChildrenFragmentsMixin
from
.utils
import
get_scenarios_from_path
,
load_resource
,
render_template
# Globals ###########################################################
...
...
@@ -40,7 +40,7 @@ log = logging.getLogger(__name__)
# Classes ###########################################################
class
MentoringBlock
(
XBlock
,
XBlockWithChildrenFragmentsMixin
):
class
MentoringBlock
(
XBlock
WithLightChildrenMixin
,
XBlock
):
"""
An XBlock providing mentoring capabilities
...
...
@@ -61,6 +61,7 @@ class MentoringBlock(XBlock, XBlockWithChildrenFragmentsMixin):
default
=
'mentoring'
,
scope
=
Scope
.
content
)
enforce_dependency
=
Boolean
(
help
=
"Should the next step be the current block to complete?"
,
default
=
True
,
scope
=
Scope
.
content
)
xml_content
=
String
(
help
=
"XML content"
,
default
=
''
,
scope
=
Scope
.
content
)
has_children
=
True
def
student_view
(
self
,
context
):
...
...
@@ -107,8 +108,7 @@ class MentoringBlock(XBlock, XBlockWithChildrenFragmentsMixin):
submit_results
=
[]
completed
=
True
for
child_id
in
self
.
children
:
# pylint: disable=E1101
child
=
self
.
runtime
.
get_block
(
child_id
)
for
child
in
self
.
get_children_objects
():
if
child
.
name
and
child
.
name
in
submissions
:
submission
=
submissions
[
child
.
name
]
child_result
=
child
.
submit
(
submission
)
...
...
@@ -136,10 +136,9 @@ class MentoringBlock(XBlock, XBlockWithChildrenFragmentsMixin):
}
def
get_message_fragment
(
self
,
message_type
):
for
child_id
in
self
.
children
:
# pylint: disable=E1101
child
=
self
.
runtime
.
get_block
(
child_id
)
for
child
in
self
.
get_children_objects
():
if
isinstance
(
child
,
MentoringMessageBlock
)
and
child
.
type
==
message_type
:
return
self
.
r
untime
.
r
ender_child
(
child
,
'mentoring_view'
,
{})
return
self
.
render_child
(
child
,
'mentoring_view'
,
{})
def
get_message_html
(
self
,
message_type
):
fragment
=
self
.
get_message_fragment
(
message_type
)
...
...
mentoring/message.py
View file @
d0da3ceb
...
...
@@ -25,10 +25,8 @@
import
logging
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
from
.utils
import
render_template
,
XBlockWithChildrenFragmentsMixin
from
.light_children
import
LightChild
,
Scope
,
String
from
.utils
import
render_template
# Globals ###########################################################
...
...
@@ -38,14 +36,13 @@ log = logging.getLogger(__name__)
# Classes ###########################################################
class
MentoringMessageBlock
(
XBlock
,
XBlockWithChildrenFragmentsMixin
):
class
MentoringMessageBlock
(
LightChild
):
"""
A message which can be conditionally displayed at the mentoring block level,
for example upon completion of the block
"""
content
=
String
(
help
=
"Message to display upon completion"
,
scope
=
Scope
.
content
,
default
=
""
)
type
=
String
(
help
=
"Type of message"
,
scope
=
Scope
.
content
,
default
=
"completed"
)
has_children
=
True
def
mentoring_view
(
self
,
context
=
None
):
fragment
,
named_children
=
self
.
get_children_fragment
(
context
,
view_name
=
'mentoring_view'
)
...
...
mentoring/quizz.py
View file @
d0da3ceb
...
...
@@ -25,11 +25,10 @@
import
logging
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
from
xblock.fragment
import
Fragment
from
.utils
import
load_resource
,
render_template
,
XBlockWithChildrenFragmentsMixin
from
.light_children
import
LightChild
,
Scope
,
String
from
.utils
import
load_resource
,
render_template
# Globals ###########################################################
...
...
@@ -53,7 +52,7 @@ def commas_to_list(commas_str):
# Classes ###########################################################
class
QuizzBlock
(
XBlock
):
class
QuizzBlock
(
LightChild
):
"""
An XBlock used to ask multiple-choice questions
...
...
@@ -66,21 +65,18 @@ class QuizzBlock(XBlock):
student_choice
=
String
(
help
=
"Last input submitted by the student"
,
default
=
""
,
scope
=
Scope
.
user_state
)
low
=
String
(
help
=
"Label for low ratings"
,
scope
=
Scope
.
content
,
default
=
"Less"
)
high
=
String
(
help
=
"Label for high ratings"
,
scope
=
Scope
.
content
,
default
=
"More"
)
has_children
=
True
@classmethod
def
parse_xml
(
cls
,
node
,
runtime
,
keys
,
id_generator
):
block
=
runtime
.
construct_xblock_from_class
(
cls
,
keys
)
for
child
in
node
:
if
child
.
tag
==
"question"
:
block
.
question
=
child
.
text
def
init_block_from_node
(
cls
,
block
,
node
):
block
.
light_children
=
[]
for
child_id
,
xml_child
in
enumerate
(
node
):
if
xml_child
.
tag
==
"question"
:
block
.
question
=
xml_child
.
text
else
:
block
.
runtime
.
add_node_as_child
(
block
,
child
,
id_generator
)
cls
.
add_node_as_child
(
block
,
xml_child
,
child_id
)
for
name
,
value
in
node
.
items
():
if
name
in
block
.
fields
:
setattr
(
block
,
name
,
value
)
setattr
(
block
,
name
,
value
)
return
block
...
...
@@ -103,8 +99,7 @@ class QuizzBlock(XBlock):
@property
def
custom_choices
(
self
):
custom_choices
=
[]
for
child_id
in
self
.
children
:
# pylint: disable=E1101
child
=
self
.
runtime
.
get_block
(
child_id
)
for
child
in
self
.
get_children_objects
():
if
isinstance
(
child
,
QuizzChoiceBlock
):
custom_choices
.
append
(
child
)
return
custom_choices
...
...
@@ -156,14 +151,13 @@ class QuizzBlock(XBlock):
return
tips
class
QuizzTipBlock
(
XBlock
,
XBlockWithChildrenFragmentsMixin
):
class
QuizzTipBlock
(
LightChild
):
"""
Each quizz
"""
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
)
reject
=
String
(
help
=
"List of choices to reject"
,
scope
=
Scope
.
content
,
default
=
None
)
has_children
=
True
def
render
(
self
,
submission
):
"""
...
...
@@ -199,7 +193,7 @@ class QuizzTipBlock(XBlock, XBlockWithChildrenFragmentsMixin):
return
reject
or
[]
class
QuizzChoiceBlock
(
XBlock
):
class
QuizzChoiceBlock
(
LightChild
):
"""
Custom choice of an answer for a quizz
"""
...
...
mentoring/table.py
View file @
d0da3ceb
...
...
@@ -29,7 +29,8 @@ import logging
from
xblock.core
import
XBlock
from
xblock.fields
import
Scope
,
String
from
.utils
import
load_resource
,
render_template
,
XBlockWithChildrenFragmentsMixin
from
.light_children
import
LightChild
,
XBlockWithLightChildrenMixin
,
String
as
LightString
from
.utils
import
load_resource
,
render_template
# Globals ###########################################################
...
...
@@ -39,7 +40,8 @@ log = logging.getLogger(__name__)
# Classes ###########################################################
class
MentoringTableBlock
(
XBlock
,
XBlockWithChildrenFragmentsMixin
):
# TODO-LIGHT-CHILDREN: Transform this into always using as LightChildren
class
MentoringTableBlock
(
XBlockWithLightChildrenMixin
,
XBlock
):
"""
Table-type display of information from mentoring blocks
...
...
@@ -85,11 +87,11 @@ class MentoringTableBlock(XBlock, XBlockWithChildrenFragmentsMixin):
return
self
.
student_view
(
context
)
class
MentoringTableColumnBlock
(
XBlock
,
XBlockWithChildrenFragmentsMixin
):
class
MentoringTableColumnBlock
(
LightChild
):
"""
Individual column of a mentoring table
"""
header
=
String
(
help
=
"Header of the column"
,
scope
=
Scope
.
content
,
default
=
None
)
header
=
Light
String
(
help
=
"Header of the column"
,
scope
=
Scope
.
content
,
default
=
None
)
has_children
=
True
def
mentoring_table_view
(
self
,
context
):
...
...
@@ -119,12 +121,11 @@ class MentoringTableColumnBlock(XBlock, XBlockWithChildrenFragmentsMixin):
return
fragment
class
MentoringTableColumnHeaderBlock
(
XBlock
,
XBlockWithChildrenFragmentsMixin
):
class
MentoringTableColumnHeaderBlock
(
LightChild
):
"""
Header content for a given column
"""
content
=
String
(
help
=
"Body of the header"
,
scope
=
Scope
.
content
,
default
=
''
)
has_children
=
True
content
=
LightString
(
help
=
"Body of the header"
,
scope
=
Scope
.
content
,
default
=
''
)
def
mentoring_table_header_view
(
self
,
context
):
fragment
=
super
(
MentoringTableColumnHeaderBlock
,
self
)
.
children_view
(
context
)
...
...
setup.py
View file @
d0da3ceb
...
...
@@ -45,6 +45,9 @@ BLOCKS = [
'mentoring = mentoring:MentoringBlock'
,
'mentoring-dataexport = mentoring:MentoringDataExportBlock'
,
'mentoring-table = mentoring:MentoringTableBlock'
,
]
BLOCKS_CHILDREN
=
[
'column = mentoring:MentoringTableColumnBlock'
,
'header = mentoring:MentoringTableColumnHeaderBlock'
,
'answer = mentoring:AnswerBlock'
,
...
...
@@ -52,6 +55,7 @@ BLOCKS = [
'message = mentoring:MentoringMessageBlock'
,
'tip = mentoring:QuizzTipBlock'
,
'choice = mentoring:QuizzChoiceBlock'
,
'html = mentoring:HTMLBlock'
,
]
setup
(
...
...
@@ -64,6 +68,7 @@ setup(
],
entry_points
=
{
'xblock.v1'
:
BLOCKS
,
'xblock.light_children'
:
BLOCKS_CHILDREN
,
},
package_data
=
package_data
(
"mentoring"
,
"static"
),
)
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