Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
14431624
Commit
14431624
authored
Nov 16, 2012
by
Calen Pennington
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Remove old parts of xmodules used in sketch
parent
0a4dcf1e
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
14 additions
and
1044 deletions
+14
-1044
common/lib/xmodule/xmodule/course_module.py
+0
-300
common/lib/xmodule/xmodule/seq_module.py
+0
-29
common/lib/xmodule/xmodule/structure_module.py
+8
-5
common/lib/xmodule/xmodule/vertical_module.py
+0
-5
common/lib/xmodule/xmodule/xmodule.py
+6
-705
No files found.
common/lib/xmodule/xmodule/course_module.py
View file @
14431624
...
@@ -8,7 +8,6 @@ import hashlib
...
@@ -8,7 +8,6 @@ import hashlib
from
.util.decorators
import
lazyproperty
from
.util.decorators
import
lazyproperty
from
.graders
import
load_grading_policy
from
.graders
import
load_grading_policy
from
.modulestore
import
Location
from
.modulestore
import
Location
from
.seq_module
import
SequenceDescriptor
,
SequenceModule
from
.timeparse
import
parse_time
,
stringify_time
from
.timeparse
import
parse_time
,
stringify_time
from
.structure_module
import
StructureModule
from
.structure_module
import
StructureModule
from
.xmodule
import
Plugin
from
.xmodule
import
Plugin
...
@@ -164,302 +163,3 @@ class Reschedule(Policy):
...
@@ -164,302 +163,3 @@ class Reschedule(Policy):
class
CourseDescriptor
(
SequenceDescriptor
):
module_class
=
SequenceModule
class
Textbook
:
def
__init__
(
self
,
title
,
book_url
):
self
.
title
=
title
self
.
book_url
=
book_url
self
.
table_of_contents
=
self
.
_get_toc_from_s3
()
self
.
start_page
=
int
(
self
.
table_of_contents
[
0
]
.
attrib
[
'page'
])
# The last page should be the last element in the table of contents,
# but it may be nested. So recurse all the way down the last element
last_el
=
self
.
table_of_contents
[
-
1
]
while
last_el
.
getchildren
():
last_el
=
last_el
[
-
1
]
self
.
end_page
=
int
(
last_el
.
attrib
[
'page'
])
@property
def
table_of_contents
(
self
):
return
self
.
table_of_contents
def
_get_toc_from_s3
(
self
):
"""
Accesses the textbook's table of contents (default name "toc.xml") at the URL self.book_url
Returns XML tree representation of the table of contents
"""
toc_url
=
self
.
book_url
+
'toc.xml'
# Get the table of contents from S3
log
.
info
(
"Retrieving textbook table of contents from
%
s"
%
toc_url
)
try
:
r
=
requests
.
get
(
toc_url
)
except
Exception
as
err
:
msg
=
'Error
%
s: Unable to retrieve textbook table of contents at
%
s'
%
(
err
,
toc_url
)
log
.
error
(
msg
)
raise
Exception
(
msg
)
# TOC is XML. Parse it
try
:
table_of_contents
=
etree
.
fromstring
(
r
.
text
)
except
Exception
as
err
:
msg
=
'Error
%
s: Unable to parse XML for textbook table of contents at
%
s'
%
(
err
,
toc_url
)
log
.
error
(
msg
)
raise
Exception
(
msg
)
return
table_of_contents
def
__init__
(
self
,
system
,
definition
=
None
,
**
kwargs
):
super
(
CourseDescriptor
,
self
)
.
__init__
(
system
,
definition
,
**
kwargs
)
self
.
textbooks
=
[]
for
title
,
book_url
in
self
.
definition
[
'data'
][
'textbooks'
]:
try
:
self
.
textbooks
.
append
(
self
.
Textbook
(
title
,
book_url
))
except
:
# If we can't get to S3 (e.g. on a train with no internet), don't break
# the rest of the courseware.
log
.
exception
(
"Couldn't load textbook ({0}, {1})"
.
format
(
title
,
book_url
))
continue
self
.
wiki_slug
=
self
.
definition
[
'data'
][
'wiki_slug'
]
or
self
.
location
.
course
msg
=
None
if
self
.
start
is
None
:
msg
=
"Course loaded without a valid start date. id =
%
s"
%
self
.
id
# hack it -- start in 1970
self
.
metadata
[
'start'
]
=
stringify_time
(
time
.
gmtime
(
0
))
log
.
critical
(
msg
)
system
.
error_tracker
(
msg
)
self
.
enrollment_start
=
self
.
_try_parse_time
(
"enrollment_start"
)
self
.
enrollment_end
=
self
.
_try_parse_time
(
"enrollment_end"
)
self
.
end
=
self
.
_try_parse_time
(
"end"
)
# NOTE: relies on the modulestore to call set_grading_policy() right after
# init. (Modulestore is in charge of figuring out where to load the policy from)
# NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
# disable the syllabus content for courses that do not provide a syllabus
self
.
syllabus_present
=
self
.
system
.
resources_fs
.
exists
(
path
(
'syllabus'
))
def
set_grading_policy
(
self
,
policy_str
):
"""Parse the policy specified in policy_str, and save it"""
try
:
self
.
_grading_policy
=
load_grading_policy
(
policy_str
)
except
:
self
.
system
.
error_tracker
(
"Failed to load grading policy"
)
# Setting this to an empty dictionary will lead to errors when
# grading needs to happen, but should allow course staff to see
# the error log.
self
.
_grading_policy
=
{}
@classmethod
def
definition_from_xml
(
cls
,
xml_object
,
system
):
textbooks
=
[]
for
textbook
in
xml_object
.
findall
(
"textbook"
):
textbooks
.
append
((
textbook
.
get
(
'title'
),
textbook
.
get
(
'book_url'
)))
xml_object
.
remove
(
textbook
)
#Load the wiki tag if it exists
wiki_slug
=
None
wiki_tag
=
xml_object
.
find
(
"wiki"
)
if
wiki_tag
is
not
None
:
wiki_slug
=
wiki_tag
.
attrib
.
get
(
"slug"
,
default
=
None
)
xml_object
.
remove
(
wiki_tag
)
definition
=
super
(
CourseDescriptor
,
cls
)
.
definition_from_xml
(
xml_object
,
system
)
definition
.
setdefault
(
'data'
,
{})[
'textbooks'
]
=
textbooks
definition
[
'data'
][
'wiki_slug'
]
=
wiki_slug
return
definition
def
has_ended
(
self
):
"""
Returns True if the current time is after the specified course end date.
Returns False if there is no end date specified.
"""
if
self
.
end_date
is
None
:
return
False
return
time
.
gmtime
()
>
self
.
end
def
has_started
(
self
):
return
time
.
gmtime
()
>
self
.
start
@property
def
grader
(
self
):
return
self
.
_grading_policy
[
'GRADER'
]
@property
def
grade_cutoffs
(
self
):
return
self
.
_grading_policy
[
'GRADE_CUTOFFS'
]
@property
def
tabs
(
self
):
"""
Return the tabs config, as a python object, or None if not specified.
"""
return
self
.
metadata
.
get
(
'tabs'
)
@property
def
show_calculator
(
self
):
return
self
.
metadata
.
get
(
"show_calculator"
,
None
)
==
"Yes"
@lazyproperty
def
grading_context
(
self
):
"""
This returns a dictionary with keys necessary for quickly grading
a student. They are used by grades.grade()
The grading context has two keys:
graded_sections - This contains the sections that are graded, as
well as all possible children modules that can affect the
grading. This allows some sections to be skipped if the student
hasn't seen any part of it.
The format is a dictionary keyed by section-type. The values are
arrays of dictionaries containing
"section_descriptor" : The section descriptor
"xmoduledescriptors" : An array of xmoduledescriptors that
could possibly be in the section, for any student
all_descriptors - This contains a list of all xmodules that can
effect grading a student. This is used to efficiently fetch
all the xmodule state for a StudentModuleCache without walking
the descriptor tree again.
"""
all_descriptors
=
[]
graded_sections
=
{}
def
yield_descriptor_descendents
(
module_descriptor
):
for
child
in
module_descriptor
.
get_children
():
yield
child
for
module_descriptor
in
yield_descriptor_descendents
(
child
):
yield
module_descriptor
for
c
in
self
.
get_children
():
sections
=
[]
for
s
in
c
.
get_children
():
if
s
.
metadata
.
get
(
'graded'
,
False
):
xmoduledescriptors
=
list
(
yield_descriptor_descendents
(
s
))
xmoduledescriptors
.
append
(
s
)
# The xmoduledescriptors included here are only the ones that have scores.
section_description
=
{
'section_descriptor'
:
s
,
'xmoduledescriptors'
:
filter
(
lambda
child
:
child
.
has_score
,
xmoduledescriptors
)
}
section_format
=
s
.
metadata
.
get
(
'format'
,
""
)
graded_sections
[
section_format
]
=
graded_sections
.
get
(
section_format
,
[]
)
+
[
section_description
]
all_descriptors
.
extend
(
xmoduledescriptors
)
all_descriptors
.
append
(
s
)
return
{
'graded_sections'
:
graded_sections
,
'all_descriptors'
:
all_descriptors
,}
@staticmethod
def
make_id
(
org
,
course
,
url_name
):
return
'/'
.
join
([
org
,
course
,
url_name
])
@staticmethod
def
id_to_location
(
course_id
):
'''Convert the given course_id (org/course/name) to a location object.
Throws ValueError if course_id is of the wrong format.
'''
org
,
course
,
name
=
course_id
.
split
(
'/'
)
return
Location
(
'i4x'
,
org
,
course
,
'course'
,
name
)
@staticmethod
def
location_to_id
(
location
):
'''Convert a location of a course to a course_id. If location category
is not "course", raise a ValueError.
location: something that can be passed to Location
'''
loc
=
Location
(
location
)
if
loc
.
category
!=
"course"
:
raise
ValueError
(
"{0} is not a course location"
.
format
(
loc
))
return
"/"
.
join
([
loc
.
org
,
loc
.
course
,
loc
.
name
])
@property
def
id
(
self
):
"""Return the course_id for this course"""
return
self
.
location_to_id
(
self
.
location
)
@property
def
start_date_text
(
self
):
displayed_start
=
self
.
_try_parse_time
(
'advertised_start'
)
or
self
.
start
return
time
.
strftime
(
"
%
b
%
d,
%
Y"
,
displayed_start
)
# An extra property is used rather than the wiki_slug/number because
# there are courses that change the number for different runs. This allows
# courses to share the same css_class across runs even if they have
# different numbers.
#
# TODO get rid of this as soon as possible or potentially build in a robust
# way to add in course-specific styling. There needs to be a discussion
# about the right way to do this, but arjun will address this ASAP. Also
# note that the courseware template needs to change when this is removed.
@property
def
css_class
(
self
):
return
self
.
metadata
.
get
(
'css_class'
,
''
)
@property
def
info_sidebar_name
(
self
):
return
self
.
metadata
.
get
(
'info_sidebar_name'
,
'Course Handouts'
)
@property
def
discussion_link
(
self
):
"""TODO: This is a quick kludge to allow CS50 (and other courses) to
specify their own discussion forums as external links by specifying a
"discussion_link" in their policy JSON file. This should later get
folded in with Syllabus, Course Info, and additional Custom tabs in a
more sensible framework later."""
return
self
.
metadata
.
get
(
'discussion_link'
,
None
)
@property
def
forum_posts_allowed
(
self
):
try
:
blackout_periods
=
[(
parse_time
(
start
),
parse_time
(
end
))
for
start
,
end
in
self
.
metadata
.
get
(
'discussion_blackouts'
,
[])]
now
=
time
.
gmtime
()
for
start
,
end
in
blackout_periods
:
if
start
<=
now
<=
end
:
return
False
except
:
log
.
exception
(
"Error parsing discussion_blackouts for course {0}"
.
format
(
self
.
id
))
return
True
@property
def
hide_progress_tab
(
self
):
"""TODO: same as above, intended to let internal CS50 hide the progress tab
until we get grade integration set up."""
# Explicit comparison to True because we always want to return a bool.
return
self
.
metadata
.
get
(
'hide_progress_tab'
)
==
True
@property
def
title
(
self
):
return
self
.
display_name
@property
def
number
(
self
):
return
self
.
location
.
course
@property
def
org
(
self
):
return
self
.
location
.
org
common/lib/xmodule/xmodule/seq_module.py
View file @
14431624
...
@@ -3,8 +3,6 @@ import logging
...
@@ -3,8 +3,6 @@ import logging
from
lxml
import
etree
from
lxml
import
etree
from
.mako_module
import
MakoModuleDescriptor
from
.xml_module
import
XmlDescriptor
from
.xmodule
import
XModule
from
.xmodule
import
XModule
from
.progress
import
Progress
from
.progress
import
Progress
from
.exceptions
import
NotFoundError
from
.exceptions
import
NotFoundError
...
@@ -110,30 +108,3 @@ class SequenceModule(XModule):
...
@@ -110,30 +108,3 @@ class SequenceModule(XModule):
new_class
=
c
new_class
=
c
return
new_class
return
new_class
class
SequenceDescriptor
(
MakoModuleDescriptor
,
XmlDescriptor
):
mako_template
=
'widgets/sequence-edit.html'
module_class
=
SequenceModule
stores_state
=
True
# For remembering where in the sequence the student is
template_dir_name
=
'sequence'
@classmethod
def
definition_from_xml
(
cls
,
xml_object
,
system
):
children
=
[]
for
child
in
xml_object
:
try
:
children
.
append
(
system
.
process_xml
(
etree
.
tostring
(
child
))
.
location
.
url
())
except
:
log
.
exception
(
"Unable to load child when parsing Sequence. Continuing..."
)
continue
return
{
'children'
:
children
}
def
definition_to_xml
(
self
,
resource_fs
):
xml_object
=
etree
.
Element
(
'sequential'
)
for
child
in
self
.
get_children
():
xml_object
.
append
(
etree
.
fromstring
(
child
.
export_to_xml
(
resource_fs
)))
return
xml_object
common/lib/xmodule/xmodule/structure_module.py
View file @
14431624
...
@@ -8,11 +8,11 @@ class Usage(namedtuple('Usage', 'id source settings children')):
...
@@ -8,11 +8,11 @@ class Usage(namedtuple('Usage', 'id source settings children')):
@classmethod
@classmethod
def
create_usage
(
cls
,
source
):
def
create_usage
(
cls
,
source
):
x
module
=
xmodule
.
get_module
(
source
)
#
module = xmodule.get_module(source)
return
Usage
(
return
Usage
(
uuid
()
,
"UUID"
,
xmodule
.
id
,
"Foo"
,
xmodule
.
course_settings
,
{}
,
[],
[],
)
)
...
@@ -31,6 +31,9 @@ def load_usage(usage_tree):
...
@@ -31,6 +31,9 @@ def load_usage(usage_tree):
settings: default settings values set by the source xmodule
settings: default settings values set by the source xmodule
children: child usages
children: child usages
"""
"""
if
usage_tree
is
None
:
return
None
usage_tree
[
'children'
]
=
[
load_usage
(
child
)
for
child
in
usage_tree
[
'children'
]]
usage_tree
[
'children'
]
=
[
load_usage
(
child
)
for
child
in
usage_tree
[
'children'
]]
return
Usage
(
**
usage_tree
)
return
Usage
(
**
usage_tree
)
...
@@ -44,5 +47,5 @@ class StructureModule(XModule):
...
@@ -44,5 +47,5 @@ class StructureModule(XModule):
@property
@property
def
usage_tree
(
self
):
def
usage_tree
(
self
):
if
self
.
_usage_tree
is
None
:
if
self
.
_usage_tree
is
None
:
self
.
_usage_tree
=
load_usage
(
self
.
content
[
'usage_tree'
]
)
self
.
_usage_tree
=
load_usage
(
self
.
content
.
get
(
'usage_tree'
,
None
)
)
return
self
.
_usage_tree
return
self
.
_usage_tree
common/lib/xmodule/xmodule/vertical_module.py
View file @
14431624
from
.xmodule
import
XModule
,
register_view
from
.xmodule
import
XModule
,
register_view
from
.seq_module
import
SequenceDescriptor
from
.progress
import
Progress
from
.progress
import
Progress
from
.module_resources
import
render_template
from
.module_resources
import
render_template
...
@@ -31,7 +30,3 @@ class VerticalModule(XModule):
...
@@ -31,7 +30,3 @@ class VerticalModule(XModule):
if
c
in
child_classes
:
if
c
in
child_classes
:
new_class
=
c
new_class
=
c
return
new_class
return
new_class
class
VerticalDescriptor
(
SequenceDescriptor
):
module_class
=
VerticalModule
common/lib/xmodule/xmodule/xmodule.py
View file @
14431624
...
@@ -25,6 +25,9 @@ def dummy_track(event_type, event):
...
@@ -25,6 +25,9 @@ def dummy_track(event_type, event):
class
ModuleMissingError
(
Exception
):
class
ModuleMissingError
(
Exception
):
pass
pass
class
MissingXModuleView
(
Exception
):
pass
class
Plugin
(
object
):
class
Plugin
(
object
):
"""
"""
...
@@ -83,64 +86,13 @@ class Plugin(object):
...
@@ -83,64 +86,13 @@ class Plugin(object):
in
pkg_resources
.
iter_entry_points
(
cls
.
entry_point
)]
in
pkg_resources
.
iter_entry_points
(
cls
.
entry_point
)]
class
HTMLSnippet
(
object
):
"""
A base class defining an interface for an object that is able to present an
html snippet, along with associated javascript and css
"""
js
=
{}
js_module_name
=
None
css
=
{}
@classmethod
def
get_javascript
(
cls
):
"""
Return a dictionary containing some of the following keys:
coffee: A list of coffeescript fragments that should be compiled and
placed on the page
js: A list of javascript fragments that should be included on the
page
All of these will be loaded onto the page in the CMS
"""
return
cls
.
js
@classmethod
def
get_css
(
cls
):
"""
Return a dictionary containing some of the following keys:
css: A list of css fragments that should be applied to the html
contents of the snippet
sass: A list of sass fragments that should be applied to the html
contents of the snippet
scss: A list of scss fragments that should be applied to the html
contents of the snippet
"""
return
cls
.
css
def
get_html
(
self
):
"""
Return the html used to display this snippet
"""
raise
NotImplementedError
(
"get_html() must be provided by specific modules - not present in {0}"
.
format
(
self
.
__class__
))
def
register_view
(
view_name
):
def
register_view
(
view_name
):
def
wrapper
(
fn
):
def
wrapper
(
fn
):
setattr
(
fn
,
'view_name'
,
view_name
)
setattr
(
fn
,
'view_name'
,
view_name
)
return
fn
return
fn
return
wrapper
return
wrapper
class
XModule
(
Plugin
,
HTMLSnippet
):
class
XModule
(
Plugin
):
''' Implements a generic learning module.
''' Implements a generic learning module.
Subclasses must at a minimum provide a definition for get_html in order
Subclasses must at a minimum provide a definition for get_html in order
...
@@ -149,14 +101,6 @@ class XModule(Plugin, HTMLSnippet):
...
@@ -149,14 +101,6 @@ class XModule(Plugin, HTMLSnippet):
See the HTML module for a simple example.
See the HTML module for a simple example.
'''
'''
# The default implementation of get_icon_class returns the icon_class
# attribute of the class
#
# This attribute can be overridden by subclasses, and
# the function can also be overridden if the icon class depends on the data
# in the module
icon_class
=
'other'
entry_point
=
"xmodule.v2"
entry_point
=
"xmodule.v2"
def
__init__
(
self
,
runtime
,
content
,
course_settings
,
user_preferences
,
student_state
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
runtime
,
content
,
course_settings
,
user_preferences
,
student_state
,
*
args
,
**
kwargs
):
...
@@ -220,650 +164,7 @@ class XModule(Plugin, HTMLSnippet):
...
@@ -220,650 +164,7 @@ class XModule(Plugin, HTMLSnippet):
def
children
(
self
):
def
children
(
self
):
return
self
.
runtime
.
children
return
self
.
runtime
.
children
@property
def
display_name
(
self
):
'''
Return a display name for the module: use display_name if defined in
metadata, otherwise convert the url name.
'''
return
self
.
metadata
.
get
(
'display_name'
,
self
.
url_name
.
replace
(
'_'
,
' '
))
def
get_children
(
self
):
'''
Return module instances for all the children of this module.
'''
if
self
.
_loaded_children
is
None
:
child_locations
=
self
.
get_children_locations
()
children
=
[
self
.
system
.
get_module
(
loc
)
for
loc
in
child_locations
]
# get_module returns None if the current user doesn't have access
# to the location.
self
.
_loaded_children
=
[
c
for
c
in
children
if
c
is
not
None
]
return
self
.
_loaded_children
def
get_children_locations
(
self
):
'''
Returns the locations of each of child modules.
Overriding this changes the behavior of get_children and
anything that uses get_children, such as get_display_items.
This method will not instantiate the modules of the children
unless absolutely necessary, so it is cheaper to call than get_children
These children will be the same children returned by the
descriptor unless descriptor.has_dynamic_children() is true.
'''
return
self
.
definition
.
get
(
'children'
,
[])
def
get_display_items
(
self
):
'''
Returns a list of descendent module instances that will display
immediately inside this module.
'''
items
=
[]
for
child
in
self
.
get_children
():
items
.
extend
(
child
.
displayable_items
())
return
items
def
displayable_items
(
self
):
'''
Returns list of displayable modules contained by this module. If this
module is visible, should return [self].
'''
return
[
self
]
def
get_icon_class
(
self
):
'''
Return a css class identifying this module in the context of an icon
'''
return
self
.
icon_class
### Functions used in the LMS
def
get_instance_state
(
self
):
''' State of the object, as stored in the database
'''
return
'{}'
def
get_shared_state
(
self
):
'''
Get state that should be shared with other instances
using the same 'shared_state_key' attribute.
'''
return
'{}'
def
get_score
(
self
):
''' Score the student received on the problem.
'''
return
None
def
max_score
(
self
):
''' Maximum score. Two notes:
* This is generic; in abstract, a problem could be 3/5 points on one
randomization, and 5/7 on another
* In practice, this is a Very Bad Idea, and (a) will break some code
in place (although that code should get fixed), and (b) break some
analytics we plan to put in place.
'''
return
None
def
get_progress
(
self
):
''' Return a progress.Progress object that represents how far the
student has gone in this module. Must be implemented to get correct
progress tracking behavior in nesting modules like sequence and
vertical.
If this module has no notion of progress, return None.
'''
return
None
def
handle_ajax
(
self
,
dispatch
,
get
):
''' dispatch is last part of the URL.
get is a dictionary-like object '''
return
""
# cdodge: added to support dynamic substitutions of
# links for courseware assets (e.g. images). <link> is passed through from lxml.html parser
def
rewrite_content_links
(
self
,
link
):
# see if we start with our format, e.g. 'xasset:<filename>'
if
link
.
startswith
(
XASSET_SRCREF_PREFIX
):
# yes, then parse out the name
name
=
link
[
len
(
XASSET_SRCREF_PREFIX
):]
loc
=
Location
(
self
.
location
)
# resolve the reference to our internal 'filepath' which
link
=
StaticContent
.
compute_location_filename
(
loc
.
org
,
loc
.
course
,
name
)
return
link
def
policy_key
(
location
):
"""
Get the key for a location in a policy file. (Since the policy file is
specific to a course, it doesn't need the full location url).
"""
return
'{cat}/{name}'
.
format
(
cat
=
location
.
category
,
name
=
location
.
name
)
Template
=
namedtuple
(
"Template"
,
"metadata data children"
)
Template
=
namedtuple
(
"Template"
,
"metadata data children"
)
class
XModuleDescriptor
():
class
ResourceTemplates
(
object
):
pass
@classmethod
def
templates
(
cls
):
"""
Returns a list of Template objects that describe possible templates that can be used
to create a module of this type.
If no templates are provided, there will be no way to create a module of
this type
Expects a class attribute template_dir_name that defines the directory
inside the 'templates' resource directory to pull templates from
"""
templates
=
[]
dirname
=
os
.
path
.
join
(
'templates'
,
cls
.
template_dir_name
)
if
not
resource_isdir
(
__name__
,
dirname
):
log
.
warning
(
"No resource directory {dir} found when loading {cls_name} templates"
.
format
(
dir
=
dirname
,
cls_name
=
cls
.
__name__
,
))
return
[]
for
template_file
in
resource_listdir
(
__name__
,
dirname
):
template_content
=
resource_string
(
__name__
,
os
.
path
.
join
(
dirname
,
template_file
))
template
=
yaml
.
load
(
template_content
)
templates
.
append
(
Template
(
**
template
))
return
templates
class
XModuleDescriptor
(
Plugin
,
HTMLSnippet
,
ResourceTemplates
):
"""
An XModuleDescriptor is a specification for an element of a course. This
could be a problem, an organizational element (a group of content), or a
segment of video, for example.
XModuleDescriptors are independent and agnostic to the current student state
on a problem. They handle the editing interface used by instructors to
create a problem, and can generate XModules (which do know about student
state).
"""
entry_point
=
"xmodule.v1"
module_class
=
XModule
# Attributes for inpsection of the descriptor
stores_state
=
False
# Indicates whether the xmodule state should be
# stored in a database (independent of shared state)
has_score
=
False
# This indicates whether the xmodule is a problem-type.
# It should respond to max_score() and grade(). It can be graded or ungraded
# (like a practice problem).
# A list of metadata that this module can inherit from its parent module
inheritable_metadata
=
(
'graded'
,
'start'
,
'due'
,
'graceperiod'
,
'showanswer'
,
'rerandomize'
,
# TODO (ichuang): used for Fall 2012 xqa server access
'xqa_key'
,
# TODO: This is used by the XMLModuleStore to provide for locations for
# static files, and will need to be removed when that code is removed
'data_dir'
)
# cdodge: this is a list of metadata names which are 'system' metadata
# and should not be edited by an end-user
system_metadata_fields
=
[
'data_dir'
]
# A list of descriptor attributes that must be equal for the descriptors to
# be equal
equality_attributes
=
(
'definition'
,
'metadata'
,
'location'
,
'shared_state_key'
,
'_inherited_metadata'
)
# Name of resource directory to load templates from
template_dir_name
=
"default"
# ============================= STRUCTURAL MANIPULATION ===================
def
__init__
(
self
,
system
,
definition
=
None
,
**
kwargs
):
"""
Construct a new XModuleDescriptor. The only required arguments are the
system, used for interaction with external resources, and the
definition, which specifies all the data needed to edit and display the
problem (but none of the associated metadata that handles recordkeeping
around the problem).
This allows for maximal flexibility to add to the interface while
preserving backwards compatibility.
system: A DescriptorSystem for interacting with external resources
definition: A dict containing `data` and `children` representing the
problem definition
Current arguments passed in kwargs:
location: A xmodule.modulestore.Location object indicating the name
and ownership of this problem
shared_state_key: The key to use for sharing StudentModules with
other modules of this type
metadata: A dictionary containing the following optional keys:
goals: A list of strings of learning goals associated with this
module
display_name: The name to use for displaying this module to the
user
url_name: The name to use for this module in urls and other places
where a unique name is needed.
format: The format of this module ('Homework', 'Lab', etc)
graded (bool): Whether this module is should be graded or not
start (string): The date for which this module will be available
due (string): The due date for this module
graceperiod (string): The amount of grace period to allow when
enforcing the due date
showanswer (string): When to show answers for this module
rerandomize (string): When to generate a newly randomized
instance of the module data
"""
self
.
system
=
system
self
.
metadata
=
kwargs
.
get
(
'metadata'
,
{})
self
.
definition
=
definition
if
definition
is
not
None
else
{}
self
.
location
=
Location
(
kwargs
.
get
(
'location'
))
self
.
url_name
=
self
.
location
.
name
self
.
category
=
self
.
location
.
category
self
.
shared_state_key
=
kwargs
.
get
(
'shared_state_key'
)
self
.
_child_instances
=
None
self
.
_inherited_metadata
=
set
()
@property
def
display_name
(
self
):
'''
Return a display name for the module: use display_name if defined in
metadata, otherwise convert the url name.
'''
return
self
.
metadata
.
get
(
'display_name'
,
self
.
url_name
.
replace
(
'_'
,
' '
))
@property
def
start
(
self
):
"""
If self.metadata contains start, return it. Else return None.
"""
if
'start'
not
in
self
.
metadata
:
return
None
return
self
.
_try_parse_time
(
'start'
)
@property
def
own_metadata
(
self
):
"""
Return the metadata that is not inherited, but was defined on this module.
"""
return
dict
((
k
,
v
)
for
k
,
v
in
self
.
metadata
.
items
()
if
k
not
in
self
.
_inherited_metadata
)
@staticmethod
def
compute_inherited_metadata
(
node
):
"""Given a descriptor, traverse all of its descendants and do metadata
inheritance. Should be called on a CourseDescriptor after importing a
course.
NOTE: This means that there is no such thing as lazy loading at the
moment--this accesses all the children."""
for
c
in
node
.
get_children
():
c
.
inherit_metadata
(
node
.
metadata
)
XModuleDescriptor
.
compute_inherited_metadata
(
c
)
def
inherit_metadata
(
self
,
metadata
):
"""
Updates this module with metadata inherited from a containing module.
Only metadata specified in self.inheritable_metadata will
be inherited
"""
# Set all inheritable metadata from kwargs that are
# in self.inheritable_metadata and aren't already set in metadata
for
attr
in
self
.
inheritable_metadata
:
if
attr
not
in
self
.
metadata
and
attr
in
metadata
:
self
.
_inherited_metadata
.
add
(
attr
)
self
.
metadata
[
attr
]
=
metadata
[
attr
]
def
get_children
(
self
):
"""Returns a list of XModuleDescriptor instances for the children of
this module"""
if
self
.
_child_instances
is
None
:
self
.
_child_instances
=
[]
for
child_loc
in
self
.
definition
.
get
(
'children'
,
[]):
child
=
self
.
system
.
load_item
(
child_loc
)
# TODO (vshnayder): this should go away once we have
# proper inheritance support in mongo. The xml
# datastore does all inheritance on course load.
child
.
inherit_metadata
(
self
.
metadata
)
self
.
_child_instances
.
append
(
child
)
return
self
.
_child_instances
def
get_child_by_url_name
(
self
,
url_name
):
"""
Return a child XModuleDescriptor with the specified url_name, if it exists, and None otherwise.
"""
for
c
in
self
.
get_children
():
if
c
.
url_name
==
url_name
:
return
c
return
None
def
xmodule_constructor
(
self
,
system
):
"""
Returns a constructor for an XModule. This constructor takes two
arguments: instance_state and shared_state, and returns a fully
instantiated XModule
"""
return
partial
(
self
.
module_class
,
system
,
self
.
location
,
self
.
definition
,
self
,
metadata
=
self
.
metadata
)
def
has_dynamic_children
(
self
):
"""
Returns True if this descriptor has dynamic children for a given
student when the module is created.
Returns False if the children of this descriptor are the same
children that the module will return for any student.
"""
return
False
# ================================= JSON PARSING ===========================
@staticmethod
def
load_from_json
(
json_data
,
system
,
default_class
=
None
):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of json_data.
json_data must contain a 'location' element, and must be suitable to be
passed into the subclasses `from_json` method.
"""
class_
=
XModuleDescriptor
.
load_class
(
json_data
[
'location'
][
'category'
],
default_class
)
return
class_
.
from_json
(
json_data
,
system
)
@classmethod
def
from_json
(
cls
,
json_data
,
system
):
"""
Creates an instance of this descriptor from the supplied json_data.
This may be overridden by subclasses
json_data: A json object specifying the definition and any optional
keyword arguments for the XModuleDescriptor
system: A DescriptorSystem for interacting with external resources
"""
return
cls
(
system
=
system
,
**
json_data
)
# ================================= XML PARSING ============================
@staticmethod
def
load_from_xml
(
xml_data
,
system
,
org
=
None
,
course
=
None
,
default_class
=
None
):
"""
This method instantiates the correct subclass of XModuleDescriptor based
on the contents of xml_data.
xml_data must be a string containing valid xml
system is an XMLParsingSystem
org and course are optional strings that will be used in the generated
module's url identifiers
"""
class_
=
XModuleDescriptor
.
load_class
(
etree
.
fromstring
(
xml_data
)
.
tag
,
default_class
)
# leave next line, commented out - useful for low-level debugging
# log.debug('[XModuleDescriptor.load_from_xml] tag=%s, class_=%s' % (
# etree.fromstring(xml_data).tag,class_))
return
class_
.
from_xml
(
xml_data
,
system
,
org
,
course
)
@classmethod
def
from_xml
(
cls
,
xml_data
,
system
,
org
=
None
,
course
=
None
):
"""
Creates an instance of this descriptor from the supplied xml_data.
This may be overridden by subclasses
xml_data: A string of xml that will be translated into data and children
for this module
system is an XMLParsingSystem
org and course are optional strings that will be used in the generated
module's url identifiers
"""
raise
NotImplementedError
(
'Modules must implement from_xml to be parsable from xml'
)
def
export_to_xml
(
self
,
resource_fs
):
"""
Returns an xml string representing this module, and all modules
underneath it. May also write required resources out to resource_fs
Assumes that modules have single parentage (that no module appears twice
in the same course), and that it is thus safe to nest modules as xml
children as appropriate.
The returned XML should be able to be parsed back into an identical
XModuleDescriptor using the from_xml method with the same system, org,
and course
"""
raise
NotImplementedError
(
'Modules must implement export_to_xml to enable xml export'
)
# =============================== Testing ==================================
def
get_sample_state
(
self
):
"""
Return a list of tuples of instance_state, shared_state. Each tuple
defines a sample case for this module
"""
return
[(
'{}'
,
'{}'
)]
# =============================== BUILTIN METHODS ==========================
def
__eq__
(
self
,
other
):
eq
=
(
self
.
__class__
==
other
.
__class__
and
all
(
getattr
(
self
,
attr
,
None
)
==
getattr
(
other
,
attr
,
None
)
for
attr
in
self
.
equality_attributes
))
if
not
eq
:
for
attr
in
self
.
equality_attributes
:
pprint
((
getattr
(
self
,
attr
,
None
),
getattr
(
other
,
attr
,
None
),
getattr
(
self
,
attr
,
None
)
==
getattr
(
other
,
attr
,
None
)))
return
eq
def
__repr__
(
self
):
return
(
"{class_}({system!r}, {definition!r}, location={location!r},"
" metadata={metadata!r})"
.
format
(
class_
=
self
.
__class__
.
__name__
,
system
=
self
.
system
,
definition
=
self
.
definition
,
location
=
self
.
location
,
metadata
=
self
.
metadata
))
# ================================ Internal helpers =======================
def
_try_parse_time
(
self
,
key
):
"""
Parse an optional metadata key containing a time: if present, complain
if it doesn't parse.
Return None if not present or invalid.
"""
if
key
in
self
.
metadata
:
try
:
return
parse_time
(
self
.
metadata
[
key
])
except
ValueError
as
e
:
msg
=
"Descriptor {0} loaded with a bad metadata key '{1}': '{2}'"
.
format
(
self
.
location
.
url
(),
self
.
metadata
[
key
],
e
)
log
.
warning
(
msg
)
return
None
class
DescriptorSystem
(
object
):
def
__init__
(
self
,
load_item
,
resources_fs
,
error_tracker
,
**
kwargs
):
"""
load_item: Takes a Location and returns an XModuleDescriptor
resources_fs: A Filesystem object that contains all of the
resources needed for the course
error_tracker: A hook for tracking errors in loading the descriptor.
Used for example to get a list of all non-fatal problems on course
load, and display them to the user.
A function of (error_msg). errortracker.py provides a
handy make_error_tracker() function.
Patterns for using the error handler:
try:
x = access_some_resource()
check_some_format(x)
except SomeProblem as err:
msg = 'Grommet {0} is broken: {1}'.format(x, str(err))
log.warning(msg) # don't rely on tracker to log
# NOTE: we generally don't want content errors logged as errors
self.system.error_tracker(msg)
# work around
return 'Oops, couldn't load grommet'
OR, if not in an exception context:
if not check_something(thingy):
msg = "thingy {0} is broken".format(thingy)
log.critical(msg)
self.system.error_tracker(msg)
NOTE: To avoid duplication, do not call the tracker on errors
that you're about to re-raise---let the caller track them.
"""
self
.
load_item
=
load_item
self
.
resources_fs
=
resources_fs
self
.
error_tracker
=
error_tracker
class
XMLParsingSystem
(
DescriptorSystem
):
def
__init__
(
self
,
load_item
,
resources_fs
,
error_tracker
,
process_xml
,
policy
,
**
kwargs
):
"""
load_item, resources_fs, error_tracker: see DescriptorSystem
policy: a policy dictionary for overriding xml metadata
process_xml: Takes an xml string, and returns a XModuleDescriptor
created from that xml
"""
DescriptorSystem
.
__init__
(
self
,
load_item
,
resources_fs
,
error_tracker
,
**
kwargs
)
self
.
process_xml
=
process_xml
self
.
policy
=
policy
class
ModuleSystem
(
object
):
'''
This is an abstraction such that x_modules can function independent
of the courseware (e.g. import into other types of courseware, LMS,
or if we want to have a sandbox server for user-contributed content)
ModuleSystem objects are passed to x_modules to provide access to system
functionality.
Note that these functions can be closures over e.g. a django request
and user, or other environment-specific info.
'''
def
__init__
(
self
,
ajax_url
,
track_function
,
get_module
,
render_template
,
replace_urls
,
user
=
None
,
filestore
=
None
,
debug
=
False
,
xqueue
=
None
,
node_path
=
""
,
anonymous_student_id
=
''
):
'''
Create a closure around the system environment.
ajax_url - the url where ajax calls to the encapsulating module go.
track_function - function of (event_type, event), intended for logging
or otherwise tracking the event.
TODO: Not used, and has inconsistent args in different
files. Update or remove.
get_module - function that takes (location) and returns a corresponding
module instance object. If the current user does not have
access to that location, returns None.
render_template - a function that takes (template_file, context), and
returns rendered html.
user - The user to base the random number generator seed off of for this
request
filestore - A filestore ojbect. Defaults to an instance of OSFS based
at settings.DATA_DIR.
xqueue - Dict containing XqueueInterface object, as well as parameters
for the specific StudentModule:
xqueue = {'interface': XQueueInterface object,
'callback_url': Callback into the LMS,
'queue_name': Target queuename in Xqueue}
replace_urls - TEMPORARY - A function like static_replace.replace_urls
that capa_module can use to fix up the static urls in
ajax results.
anonymous_student_id - Used for tracking modules with student id
'''
self
.
ajax_url
=
ajax_url
self
.
xqueue
=
xqueue
self
.
track_function
=
track_function
self
.
filestore
=
filestore
self
.
get_module
=
get_module
self
.
render_template
=
render_template
self
.
DEBUG
=
self
.
debug
=
debug
self
.
seed
=
user
.
id
if
user
is
not
None
else
0
self
.
replace_urls
=
replace_urls
self
.
node_path
=
node_path
self
.
anonymous_student_id
=
anonymous_student_id
self
.
user_is_staff
=
user
is
not
None
and
user
.
is_staff
def
get
(
self
,
attr
):
''' provide uniform access to attributes (like etree).'''
return
self
.
__dict__
.
get
(
attr
)
def
set
(
self
,
attr
,
val
):
'''provide uniform access to attributes (like etree)'''
self
.
__dict__
[
attr
]
=
val
def
__repr__
(
self
):
return
repr
(
self
.
__dict__
)
def
__str__
(
self
):
return
str
(
self
.
__dict__
)
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