Commit 8c904f31 by Don Mitchell

Move defaults from yaml templates to field definitions.

This standardizes the XModule field default values to be the same as the
values that are presented by studio when a component is added to a
course.
parent ef8618f7
...@@ -17,9 +17,9 @@ def i_created_discussion_tag(step): ...@@ -17,9 +17,9 @@ def i_created_discussion_tag(step):
def i_see_only_the_settings_and_values(step): def i_see_only_the_settings_and_values(step):
world.verify_all_setting_entries( world.verify_all_setting_entries(
[ [
['Category', "Week 1", True], ['Category', "Week 1", False],
['Display Name', "Discussion Tag", True], ['Display Name', "Discussion Tag", False],
['Subcategory', "Topic-Level Student-Visible Label", True] ['Subcategory', "Topic-Level Student-Visible Label", False]
]) ])
......
...@@ -14,4 +14,4 @@ def i_created_blank_html_page(step): ...@@ -14,4 +14,4 @@ def i_created_blank_html_page(step):
@step('I see only the HTML display name setting$') @step('I see only the HTML display name setting$')
def i_see_only_the_html_display_name(step): def i_see_only_the_html_display_name(step):
world.verify_all_setting_entries([['Display Name', "Blank HTML Page", True]]) world.verify_all_setting_entries([['Display Name', "Blank HTML Page", False]])
...@@ -18,8 +18,9 @@ def i_created_blank_common_problem(step): ...@@ -18,8 +18,9 @@ def i_created_blank_common_problem(step):
world.create_component_instance( world.create_component_instance(
step, step,
'.large-problem-icon', '.large-problem-icon',
'i4x://edx/templates/problem/Blank_Common_Problem', 'problem',
'.xmodule_CapaModule' '.xmodule_CapaModule',
'blank_common.yaml'
) )
...@@ -32,11 +33,12 @@ def i_edit_and_select_settings(step): ...@@ -32,11 +33,12 @@ def i_edit_and_select_settings(step):
def i_see_five_settings_with_values(step): def i_see_five_settings_with_values(step):
world.verify_all_setting_entries( world.verify_all_setting_entries(
[ [
[DISPLAY_NAME, "Blank Common Problem", True], [DISPLAY_NAME, "New problem", True],
[MAXIMUM_ATTEMPTS, "", False], [MAXIMUM_ATTEMPTS, "", False],
[PROBLEM_WEIGHT, "", False], [PROBLEM_WEIGHT, "", False],
[RANDOMIZATION, "Never", True], # Not sure why these are True other than via inspection
[SHOW_ANSWER, "Finished", True] [RANDOMIZATION, "Always", True],
[SHOW_ANSWER, "Closed", True]
]) ])
...@@ -203,7 +205,7 @@ def verify_modified_display_name_with_special_chars(): ...@@ -203,7 +205,7 @@ def verify_modified_display_name_with_special_chars():
def verify_unset_display_name(): def verify_unset_display_name():
world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, '', False) world.verify_setting_entry(world.get_setting_entry(DISPLAY_NAME), DISPLAY_NAME, 'Blank Advanced Problem', False)
def set_weight(weight): def set_weight(weight):
......
...@@ -7,7 +7,7 @@ from lettuce import world, step ...@@ -7,7 +7,7 @@ from lettuce import world, step
@step('I see the correct settings and default values$') @step('I see the correct settings and default values$')
def i_see_the_correct_settings_and_values(step): def i_see_the_correct_settings_and_values(step):
world.verify_all_setting_entries([['Default Speed', 'OEoXaMPEzfM', False], world.verify_all_setting_entries([['Default Speed', 'OEoXaMPEzfM', False],
['Display Name', 'default', True], ['Display Name', 'Video Title', False],
['Download Track', '', False], ['Download Track', '', False],
['Download Video', '', False], ['Download Video', '', False],
['Show Captions', 'True', False], ['Show Captions', 'True', False],
......
...@@ -135,7 +135,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -135,7 +135,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha', self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha',
'Word cloud', 'Word cloud',
'Annotation', 'Annotation',
'Open Ended Response', 'Open Ended Grading',
'Peer Grading Interface']) 'Peer Grading Interface'])
def test_advanced_components_require_two_clicks(self): def test_advanced_components_require_two_clicks(self):
...@@ -1271,6 +1271,28 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1271,6 +1271,28 @@ class ContentStoreTest(ModuleStoreTestCase):
self.assertEqual(timedelta(1), new_module.lms.graceperiod) self.assertEqual(timedelta(1), new_module.lms.graceperiod)
def test_default_metadata_inheritance(self):
course = CourseFactory.create()
vertical = ItemFactory.create(parent_location=course.location)
course.children.append(vertical)
# in memory
self.assertIsNotNone(course.start)
self.assertEqual(course.start, vertical.lms.start)
self.assertEqual(course.textbooks, [])
self.assertIn('GRADER', course.grading_policy)
self.assertIn('GRADE_CUTOFFS', course.grading_policy)
self.assertGreaterEqual(len(course.checklists), 4)
# by fetching
module_store = modulestore('direct')
fetched_course = module_store.get_item(course.location)
fetched_item = module_store.get_item(vertical.location)
self.assertIsNotNone(fetched_course.start)
self.assertEqual(course.start, fetched_course.start)
self.assertEqual(fetched_course.start, fetched_item.lms.start)
self.assertEqual(course.textbooks, fetched_course.textbooks)
# is this test too strict? i.e., it requires the dicts to be ==
self.assertEqual(course.checklists, fetched_course.checklists)
class TemplateTestCase(ModuleStoreTestCase): class TemplateTestCase(ModuleStoreTestCase):
......
...@@ -36,7 +36,6 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -36,7 +36,6 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertIsNone(details.enrollment_start, "enrollment_start date somehow initialized " + str(details.enrollment_start)) self.assertIsNone(details.enrollment_start, "enrollment_start date somehow initialized " + str(details.enrollment_start))
self.assertIsNone(details.enrollment_end, "enrollment_end date somehow initialized " + str(details.enrollment_end)) self.assertIsNone(details.enrollment_end, "enrollment_end date somehow initialized " + str(details.enrollment_end))
self.assertIsNone(details.syllabus, "syllabus somehow initialized" + str(details.syllabus)) self.assertIsNone(details.syllabus, "syllabus somehow initialized" + str(details.syllabus))
self.assertEqual(details.overview, "", "overview somehow initialized" + details.overview)
self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video)) self.assertIsNone(details.intro_video, "intro_video somehow initialized" + str(details.intro_video))
self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort)) self.assertIsNone(details.effort, "effort somehow initialized" + str(details.effort))
...@@ -49,7 +48,6 @@ class CourseDetailsTestCase(CourseTestCase): ...@@ -49,7 +48,6 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertIsNone(jsondetails['enrollment_start'], "enrollment_start date somehow initialized ") self.assertIsNone(jsondetails['enrollment_start'], "enrollment_start date somehow initialized ")
self.assertIsNone(jsondetails['enrollment_end'], "enrollment_end date somehow initialized ") self.assertIsNone(jsondetails['enrollment_end'], "enrollment_end date somehow initialized ")
self.assertIsNone(jsondetails['syllabus'], "syllabus somehow initialized") self.assertIsNone(jsondetails['syllabus'], "syllabus somehow initialized")
self.assertEqual(jsondetails['overview'], "", "overview somehow initialized")
self.assertIsNone(jsondetails['intro_video'], "intro_video somehow initialized") self.assertIsNone(jsondetails['intro_video'], "intro_video somehow initialized")
self.assertIsNone(jsondetails['effort'], "effort somehow initialized") self.assertIsNone(jsondetails['effort'], "effort somehow initialized")
......
...@@ -120,7 +120,7 @@ def add_histogram(get_html, module, user): ...@@ -120,7 +120,7 @@ def add_histogram(get_html, module, user):
# doesn't like symlinks) # doesn't like symlinks)
filepath = filename filepath = filename
data_dir = osfs.root_path.rsplit('/')[-1] data_dir = osfs.root_path.rsplit('/')[-1]
giturl = getattr(module.lms, 'giturl', '') or 'https://github.com/MITx' giturl = module.lms.giturl or 'https://github.com/MITx'
edit_link = "%s/%s/tree/master/%s" % (giturl, data_dir, filepath) edit_link = "%s/%s/tree/master/%s" % (giturl, data_dir, filepath)
else: else:
edit_link = False edit_link = False
......
...@@ -6,12 +6,37 @@ from pkg_resources import resource_string ...@@ -6,12 +6,37 @@ from pkg_resources import resource_string
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xblock.core import Scope, String from xblock.core import Scope, String
import textwrap
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class AnnotatableFields(object): class AnnotatableFields(object):
data = String(help="XML data for the annotation", scope=Scope.content) data = String(help="XML data for the annotation", scope=Scope.content,
default=textwrap.dedent(
"""\
<annotatable>
<instructions>
<p>Enter your (optional) instructions for the exercise in HTML format.</p>
<p>Annotations are specified by an <code>&lt;annotation&gt;</code> tag which may may have the following attributes:</p>
<ul class="instructions-template">
<li><code>title</code> (optional). Title of the annotation. Defaults to <i>Commentary</i> if omitted.</li>
<li><code>body</code> (<b>required</b>). Text of the annotation.</li>
<li><code>problem</code> (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have <code>problem="0"</code>.</li>
<li><code>highlight</code> (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.</li>
</ul>
</instructions>
<p>Add your HTML with annotation spans here.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <annotation title="My title" body="My comment" highlight="yellow" problem="0">Ut sodales laoreet est, egestas gravida felis egestas nec.</annotation> Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.</p>
<p>Nulla facilisi. <annotation body="Basic annotation example." problem="1">Pellentesque id vestibulum libero.</annotation> Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.</p>
</annotatable>
"""))
display_name = String(
display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
default='Annotation',
)
class AnnotatableModule(AnnotatableFields, XModule): class AnnotatableModule(AnnotatableFields, XModule):
......
...@@ -77,6 +77,14 @@ class CapaFields(object): ...@@ -77,6 +77,14 @@ class CapaFields(object):
""" """
Define the possible fields for a Capa problem Define the possible fields for a Capa problem
""" """
display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
# it'd be nice to have a useful default but it screws up other things; so,
# use display_name_with_default for those
default="Blank Advanced Problem"
)
attempts = Integer(help="Number of attempts taken by the student on this problem", attempts = Integer(help="Number of attempts taken by the student on this problem",
default=0, scope=Scope.user_state) default=0, scope=Scope.user_state)
max_attempts = Integer( max_attempts = Integer(
...@@ -94,7 +102,8 @@ class CapaFields(object): ...@@ -94,7 +102,8 @@ class CapaFields(object):
display_name="Show Answer", display_name="Show Answer",
help=("Defines when to show the answer to the problem. " help=("Defines when to show the answer to the problem. "
"A default value can be set in Advanced Settings."), "A default value can be set in Advanced Settings."),
scope=Scope.settings, default="closed", scope=Scope.settings,
default="closed",
values=[ values=[
{"display_name": "Always", "value": "always"}, {"display_name": "Always", "value": "always"},
{"display_name": "Answered", "value": "answered"}, {"display_name": "Answered", "value": "answered"},
...@@ -106,21 +115,24 @@ class CapaFields(object): ...@@ -106,21 +115,24 @@ class CapaFields(object):
) )
force_save_button = Boolean( force_save_button = Boolean(
help="Whether to force the save button to appear on the page", help="Whether to force the save button to appear on the page",
scope=Scope.settings, default=False scope=Scope.settings,
default=False
) )
rerandomize = Randomization( rerandomize = Randomization(
display_name="Randomization", display_name="Randomization",
help="Defines how often inputs are randomized when a student loads the problem. " help="Defines how often inputs are randomized when a student loads the problem. "
"This setting only applies to problems that can have randomly generated numeric values. " "This setting only applies to problems that can have randomly generated numeric values. "
"A default value can be set in Advanced Settings.", "A default value can be set in Advanced Settings.",
default="always", scope=Scope.settings, values=[ default="always",
scope=Scope.settings,
values=[
{"display_name": "Always", "value": "always"}, {"display_name": "Always", "value": "always"},
{"display_name": "On Reset", "value": "onreset"}, {"display_name": "On Reset", "value": "onreset"},
{"display_name": "Never", "value": "never"}, {"display_name": "Never", "value": "never"},
{"display_name": "Per Student", "value": "per_student"} {"display_name": "Per Student", "value": "per_student"}
] ]
) )
data = String(help="XML data for the problem", scope=Scope.content) data = String(help="XML data for the problem", scope=Scope.content, default="<problem></problem>")
correct_map = Dict(help="Dictionary with the correctness of current student answers", correct_map = Dict(help="Dictionary with the correctness of current student answers",
scope=Scope.user_state, default={}) scope=Scope.user_state, default={})
input_state = Dict(help="Dictionary for maintaining the state of inputtypes", scope=Scope.user_state) input_state = Dict(help="Dictionary for maintaining the state of inputtypes", scope=Scope.user_state)
...@@ -134,13 +146,12 @@ class CapaFields(object): ...@@ -134,13 +146,12 @@ class CapaFields(object):
values={"min": 0, "step": .1}, values={"min": 0, "step": .1},
scope=Scope.settings scope=Scope.settings
) )
markdown = String(help="Markdown source of this module", scope=Scope.settings) markdown = String(help="Markdown source of this module", default="", scope=Scope.settings)
source_code = String( source_code = String(
help="Source code for LaTeX and Word problems. This feature is not well-supported.", help="Source code for LaTeX and Word problems. This feature is not well-supported.",
scope=Scope.settings scope=Scope.settings
) )
class CapaModule(CapaFields, XModule): class CapaModule(CapaFields, XModule):
""" """
An XModule implementing LonCapa format problems, implemented by way of An XModule implementing LonCapa format problems, implemented by way of
......
...@@ -9,6 +9,7 @@ from xblock.core import Integer, Scope, String, List, Float, Boolean ...@@ -9,6 +9,7 @@ from xblock.core import Integer, Scope, String, List, Float, Boolean
from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor from xmodule.open_ended_grading_classes.combined_open_ended_modulev1 import CombinedOpenEndedV1Module, CombinedOpenEndedV1Descriptor
from collections import namedtuple from collections import namedtuple
from .fields import Date from .fields import Date
import textwrap
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
...@@ -27,6 +28,38 @@ VERSION_TUPLES = { ...@@ -27,6 +28,38 @@ VERSION_TUPLES = {
} }
DEFAULT_VERSION = 1 DEFAULT_VERSION = 1
DEFAULT_DATA = textwrap.dedent("""\
<combinedopenended>
<rubric>
<rubric>
<category>
<description>Category 1</description>
<option>
The response does not incorporate what is needed for a one response.
</option>
<option>
The response is correct for category 1.
</option>
</category>
</rubric>
</rubric>
<prompt>
<p>Why is the sky blue?</p>
</prompt>
<task>
<selfassessment/>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="2">
<openendedparam>
<initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "peer_grading.conf", "problem_id" : "700x/Demo"}</grader_payload>
</openendedparam>
</openended>
</task>
</combinedopenended>
""")
class VersionInteger(Integer): class VersionInteger(Integer):
...@@ -85,13 +118,30 @@ class CombinedOpenEndedFields(object): ...@@ -85,13 +118,30 @@ class CombinedOpenEndedFields(object):
scope=Scope.settings scope=Scope.settings
) )
version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings) version = VersionInteger(help="Current version number", default=DEFAULT_VERSION, scope=Scope.settings)
data = String(help="XML data for the problem", scope=Scope.content) data = String(help="XML data for the problem", scope=Scope.content,
default=DEFAULT_DATA)
weight = Float( weight = Float(
display_name="Problem Weight", display_name="Problem Weight",
help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.", help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values={"min" : 0 , "step": ".1"} scope=Scope.settings, values={"min" : 0 , "step": ".1"}
) )
markdown = String(help="Markdown source of this module", scope=Scope.settings) markdown = String(
help="Markdown source of this module",
default=textwrap.dedent("""\
[rubric]
+ Category 1
- The response does not incorporate what is needed for a one response.
- The response is correct for category 1.
[rubric]
[prompt]
<p>Why is the sky blue?</p>
[prompt]
[tasks]
(Self), ({1-2}AI)
[tasks]
"""),
scope=Scope.settings
)
class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule): class CombinedOpenEndedModule(CombinedOpenEndedFields, XModule):
......
...@@ -4,17 +4,27 @@ from xmodule.x_module import XModule ...@@ -4,17 +4,27 @@ from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor from xmodule.raw_module import RawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xblock.core import String, Scope from xblock.core import String, Scope
from uuid import uuid4
class DiscussionFields(object): class DiscussionFields(object):
discussion_id = String(scope=Scope.settings) discussion_id = String(scope=Scope.settings, default="$$GUID$$")
display_name = String(
display_name="Display Name",
help="Display name for this module",
default="Discussion Tag",
scope=Scope.settings)
data = String(help="XML data for the problem", scope=Scope.content,
default="<discussion></discussion>")
discussion_category = String( discussion_category = String(
display_name="Category", display_name="Category",
default="Week 1",
help="A category name for the discussion. This name appears in the left pane of the discussion forum for the course.", help="A category name for the discussion. This name appears in the left pane of the discussion forum for the course.",
scope=Scope.settings scope=Scope.settings
) )
discussion_target = String( discussion_target = String(
display_name="Subcategory", display_name="Subcategory",
default="Topic-Level Student-Visible Label",
help="A subcategory name for the discussion. This name appears in the left pane of the discussion forum for the course.", help="A subcategory name for the discussion. This name appears in the left pane of the discussion forum for the course.",
scope=Scope.settings scope=Scope.settings
) )
...@@ -36,9 +46,15 @@ class DiscussionModule(DiscussionFields, XModule): ...@@ -36,9 +46,15 @@ class DiscussionModule(DiscussionFields, XModule):
class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawDescriptor): class DiscussionDescriptor(DiscussionFields, MetadataOnlyEditingDescriptor, RawDescriptor):
module_class = DiscussionModule
template_dir_name = "discussion"
def __init__(self, *args, **kwargs):
super(DiscussionDescriptor, self).__init__(*args, **kwargs)
# is this too late? i.e., will it get persisted and stay static w/ the first value
# any code references. I believe so.
if self.discussion_id == '$$GUID$$':
self.discussion_id = uuid4().hex
module_class = DiscussionModule
# The discussion XML format uses `id` and `for` attributes, # The discussion XML format uses `id` and `for` attributes,
# but these would overload other module attributes, so we prefix them # but these would overload other module attributes, so we prefix them
# for actual use in the code # for actual use in the code
......
...@@ -13,12 +13,21 @@ from xmodule.html_checker import check_html ...@@ -13,12 +13,21 @@ from xmodule.html_checker import check_html
from xmodule.stringify import stringify_children from xmodule.stringify import stringify_children
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xmodule.xml_module import XmlDescriptor, name_to_pathname from xmodule.xml_module import XmlDescriptor, name_to_pathname
import textwrap
log = logging.getLogger("mitx.courseware") log = logging.getLogger("mitx.courseware")
class HtmlFields(object): class HtmlFields(object):
data = String(help="Html contents to display for this module", scope=Scope.content) display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
# it'd be nice to have a useful default but it screws up other things; so,
# use display_name_with_default for those
default="Blank HTML Page"
)
data = String(help="Html contents to display for this module", default="", scope=Scope.content)
source_code = String(help="Source code for LaTeX documents. This feature is not well-supported.", scope=Scope.settings) source_code = String(help="Source code for LaTeX documents. This feature is not well-supported.", scope=Scope.settings)
...@@ -32,7 +41,7 @@ class HtmlModule(HtmlFields, XModule): ...@@ -32,7 +41,7 @@ class HtmlModule(HtmlFields, XModule):
css = {'scss': [resource_string(__name__, 'css/html/display.scss')]} css = {'scss': [resource_string(__name__, 'css/html/display.scss')]}
def get_html(self): def get_html(self):
if self.system.anonymous_student_id: if self.system.anonymous_student_id:
return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id) return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id)
return self.data return self.data
...@@ -169,26 +178,88 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor): ...@@ -169,26 +178,88 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
elt.set("filename", relname) elt.set("filename", relname)
return elt return elt
class AboutFields(object):
display_name = String(
help="Display name for this module",
scope=Scope.settings,
default="overview",
)
data = String(
help="Html contents to display for this module",
default="",
scope=Scope.content
)
class AboutModule(AboutFields, HtmlModule):
"""
Overriding defaults but otherwise treated as HtmlModule.
"""
pass
class AboutDescriptor(HtmlDescriptor): class AboutDescriptor(AboutFields, HtmlDescriptor):
""" """
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
in order to be able to create new ones in order to be able to create new ones
""" """
template_dir_name = "about" template_dir_name = "about"
module_class = AboutModule
class StaticTabFields(object):
"""
The overrides for Static Tabs
"""
display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
default="Empty",
)
data = String(
default=textwrap.dedent("""\
<p>This is where you can add additional pages to your courseware. Click the 'edit' button to begin editing.</p>
"""),
scope=Scope.content,
help="HTML for the additional pages"
)
class StaticTabModule(StaticTabFields, HtmlModule):
"""
Supports the field overrides
"""
pass
class StaticTabDescriptor(HtmlDescriptor): class StaticTabDescriptor(StaticTabFields, HtmlDescriptor):
""" """
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
in order to be able to create new ones in order to be able to create new ones
""" """
template_dir_name = "statictab" template_dir_name = "statictab"
module_class = StaticTabModule
class CourseInfoFields(object):
"""
Field overrides
"""
data = String(
help="Html contents to display for this module",
default="<ol></ol>",
scope=Scope.content
)
class CourseInfoModule(CourseInfoFields, HtmlModule):
"""
Just to support xblock field overrides
"""
pass
class CourseInfoDescriptor(HtmlDescriptor): class CourseInfoDescriptor(CourseInfoFields, HtmlDescriptor):
""" """
These pieces of course content are treated as HtmlModules but we need to overload where the templates are located These pieces of course content are treated as HtmlModules but we need to overload where the templates are located
in order to be able to create new ones in order to be able to create new ones
""" """
template_dir_name = "courseinfo" template_dir_name = "courseinfo"
module_class = CourseInfoModule
...@@ -59,6 +59,15 @@ class PeerGradingFields(object): ...@@ -59,6 +59,15 @@ class PeerGradingFields(object):
help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.", help="Defines the number of points each problem is worth. If the value is not set, each problem is worth one point.",
scope=Scope.settings, values={"min": 0, "step": ".1"} scope=Scope.settings, values={"min": 0, "step": ".1"}
) )
display_name = String(
display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
default="Peer Grading Interface"
)
data = String(help="Html contents to display for this module",
default='<peergrading></peergrading>',
scope=Scope.content)
class PeerGradingModule(PeerGradingFields, XModule): class PeerGradingModule(PeerGradingFields, XModule):
......
...@@ -13,7 +13,7 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor): ...@@ -13,7 +13,7 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor):
Module that provides a raw editing view of its data and children. It Module that provides a raw editing view of its data and children. It
requires that the definition xml is valid. requires that the definition xml is valid.
""" """
data = String(help="XML data for the module", scope=Scope.content) data = String(help="XML data for the module", default="", scope=Scope.content)
@classmethod @classmethod
def definition_from_xml(cls, xml_object, system): def definition_from_xml(cls, xml_object, system):
......
--- {}
metadata:
display_name: Empty
data: "<p>This is where you can add additional information about your course.</p>"
children: []
\ No newline at end of file
...@@ -3,51 +3,50 @@ metadata: ...@@ -3,51 +3,50 @@ metadata:
display_name: overview display_name: overview
data: | data: |
<section class="about"> <section class="about">
<h2>About This Course</h2> <h2>About This Course</h2>
<p>Include your long course description here. The long course description should contain 150-400 words.</p> <p>Include your long course description here. The long course description should contain 150-400 words.</p>
<p>This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.</p> <p>This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.</p>
</section> </section>
<section class="prerequisites"> <section class="prerequisites">
<h2>Prerequisites</h2> <h2>Prerequisites</h2>
<p>Add information about course prerequisites here.</p> <p>Add information about course prerequisites here.</p>
</section> </section>
<section class="course-staff"> <section class="course-staff">
<h2>Course Staff</h2> <h2>Course Staff</h2>
<article class="teacher"> <article class="teacher">
<div class="teacher-image"> <div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0"> <img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div> </div>
<h3>Staff Member #1</h3> <h3>Staff Member #1</h3>
<p>Biography of instructor/staff member #1</p> <p>Biography of instructor/staff member #1</p>
</article> </article>
<article class="teacher"> <article class="teacher">
<div class="teacher-image"> <div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0"> <img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div> </div>
<h3>Staff Member #2</h3> <h3>Staff Member #2</h3>
<p>Biography of instructor/staff member #2</p> <p>Biography of instructor/staff member #2</p>
</article> </article>
</section> </section>
<section class="faq"> <section class="faq">
<section class="responses"> <section class="responses">
<h2>Frequently Asked Questions</h2> <h2>Frequently Asked Questions</h2>
<article class="response"> <article class="response">
<h3>Do I need to buy a textbook?</h3> <h3>Do I need to buy a textbook?</h3>
<p>No, a free online version of Chemistry: Principles, Patterns, and Applications, First Edition by Bruce Averill and Patricia Eldredge will be available, though you can purchase a printed version (published by FlatWorld Knowledge) if you’d like.</p> <p>No, a free online version of Chemistry: Principles, Patterns, and Applications, First Edition by Bruce Averill and Patricia Eldredge will be available, though you can purchase a printed version (published by FlatWorld Knowledge) if you’d like.</p>
</article> </article>
<article class="response"> <article class="response">
<h3>Question #2</h3> <h3>Question #2</h3>
<p>Your answer would be displayed here.</p> <p>Your answer would be displayed here.</p>
</article> </article>
</section> </section>
</section> </section>
children: []
--- {}
metadata:
display_name: 'Annotation'
data: |
<annotatable>
<instructions>
<p>Enter your (optional) instructions for the exercise in HTML format.</p>
<p>Annotations are specified by an <code>&lt;annotation&gt;</code> tag which may may have the following attributes:</p>
<ul class="instructions-template">
<li><code>title</code> (optional). Title of the annotation. Defaults to <i>Commentary</i> if omitted.</li>
<li><code>body</code> (<b>required</b>). Text of the annotation.</li>
<li><code>problem</code> (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have <code>problem="0"</code>.</li>
<li><code>highlight</code> (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.</li>
</ul>
</instructions>
<p>Add your HTML with annotation spans here.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <annotation title="My title" body="My comment" highlight="yellow" problem="0">Ut sodales laoreet est, egestas gravida felis egestas nec.</annotation> Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.</p>
<p>Nulla facilisi. <annotation body="Basic annotation example." problem="1">Pellentesque id vestibulum libero.</annotation> Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.</p>
</annotatable>
children: []
--- {}
metadata:
display_name: Open Ended Response
markdown: ""
data: |
<combinedopenended>
<rubric>
<rubric>
<category>
<description>Category 1</description>
<option>
The response does not incorporate what is needed for a one response.
</option>
<option>
The response is correct for category 1.
</option>
</category>
</rubric>
</rubric>
<prompt>
<p>Why is the sky blue?</p>
</prompt>
<task>
<selfassessment/>
</task>
<task>
<openended min_score_to_attempt="1" max_score_to_attempt="2">
<openendedparam>
<initial_display>Enter essay here.</initial_display>
<answer_display>This is the answer.</answer_display>
<grader_payload>{"grader_settings" : "peer_grading.conf", "problem_id" : "700x/Demo"}</grader_payload>
</openendedparam>
</openended>
</task>
</combinedopenended>
children: []
--- {}
metadata:
display_name: Empty
start: 2020-10-10T10:00
checklists: [
{"short_description" : "Getting Started With Studio",
"items" : [{"short_description": "Add Course Team Members",
"long_description": "Grant your collaborators permission to edit your course so you can work together.",
"is_checked": false,
"action_url": "ManageUsers",
"action_text": "Edit Course Team",
"action_external": false},
{"short_description": "Set Important Dates for Your Course",
"long_description": "Establish your course's student enrollment and launch dates on the Schedule and Details page.",
"is_checked": false,
"action_url": "SettingsDetails",
"action_text": "Edit Course Details &amp; Schedule",
"action_external": false},
{"short_description": "Draft Your Course's Grading Policy",
"long_description": "Set up your assignment types and grading policy even if you haven't created all your assignments.",
"is_checked": false,
"action_url": "SettingsGrading",
"action_text": "Edit Grading Settings",
"action_external": false},
{"short_description": "Explore the Other Studio Checklists",
"long_description": "Discover other available course authoring tools, and find help when you need it.",
"is_checked": false,
"action_url": "",
"action_text": "",
"action_external": false}]
},
{"short_description" : "Draft a Rough Course Outline",
"items" : [{"short_description": "Create Your First Section and Subsection",
"long_description": "Use your course outline to build your first Section and Subsection.",
"is_checked": false,
"action_url": "CourseOutline",
"action_text": "Edit Course Outline",
"action_external": false},
{"short_description": "Set Section Release Dates",
"long_description": "Specify the release dates for each Section in your course. Sections become visible to students on their release dates.",
"is_checked": false,
"action_url": "CourseOutline",
"action_text": "Edit Course Outline",
"action_external": false},
{"short_description": "Designate a Subsection as Graded",
"long_description": "Set a Subsection to be graded as a specific assignment type. Assignments within graded Subsections count toward a student's final grade.",
"is_checked": false,
"action_url": "CourseOutline",
"action_text": "Edit Course Outline",
"action_external": false},
{"short_description": "Reordering Course Content",
"long_description": "Use drag and drop to reorder the content in your course.",
"is_checked": false,
"action_url": "CourseOutline",
"action_text": "Edit Course Outline",
"action_external": false},
{"short_description": "Renaming Sections",
"long_description": "Rename Sections by clicking the Section name from the Course Outline.",
"is_checked": false,
"action_url": "CourseOutline",
"action_text": "Edit Course Outline",
"action_external": false},
{"short_description": "Deleting Course Content",
"long_description": "Delete Sections, Subsections, or Units you don't need anymore. Be careful, as there is no Undo function.",
"is_checked": false,
"action_url": "CourseOutline",
"action_text": "Edit Course Outline",
"action_external": false},
{"short_description": "Add an Instructor-Only Section to Your Outline",
"long_description": "Some course authors find using a section for unsorted, in-progress work useful. To do this, create a section and set the release date to the distant future.",
"is_checked": false,
"action_url": "CourseOutline",
"action_text": "Edit Course Outline",
"action_external": false}]
},
{"short_description" : "Explore edX's Support Tools",
"items" : [{"short_description": "Explore the Studio Help Forum",
"long_description": "Access the Studio Help forum from the menu that appears when you click your user name in the top right corner of Studio.",
"is_checked": false,
"action_url": "http://help.edge.edx.org/",
"action_text": "Visit Studio Help",
"action_external": true},
{"short_description": "Enroll in edX 101",
"long_description": "Register for edX 101, edX's primer for course creation.",
"is_checked": false,
"action_url": "https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about",
"action_text": "Register for edX 101",
"action_external": true},
{"short_description": "Download the Studio Documentation",
"long_description": "Download the searchable Studio reference documentation in PDF form.",
"is_checked": false,
"action_url": "http://files.edx.org/Getting_Started_with_Studio.pdf",
"action_text": "Download Documentation",
"action_external": true}]
},
{"short_description" : "Draft Your Course About Page",
"items" : [{"short_description": "Draft a Course Description",
"long_description": "Courses on edX have an About page that includes a course video, description, and more. Draft the text students will read before deciding to enroll in your course.",
"is_checked": false,
"action_url": "SettingsDetails",
"action_text": "Edit Course Schedule &amp; Details",
"action_external": false},
{"short_description": "Add Staff Bios",
"long_description": "Showing prospective students who their instructor will be is helpful. Include staff bios on the course About page.",
"is_checked": false,
"action_url": "SettingsDetails",
"action_text": "Edit Course Schedule &amp; Details",
"action_external": false},
{"short_description": "Add Course FAQs",
"long_description": "Include a short list of frequently asked questions about your course.",
"is_checked": false,
"action_url": "SettingsDetails",
"action_text": "Edit Course Schedule &amp; Details",
"action_external": false},
{"short_description": "Add Course Prerequisites",
"long_description": "Let students know what knowledge and/or skills they should have before they enroll in your course.",
"is_checked": false,
"action_url": "SettingsDetails",
"action_text": "Edit Course Schedule &amp; Details",
"action_external": false}]
}
]
data: { 'textbooks' : [ ], 'wiki_slug' : null }
children: []
--- {}
metadata:
display_name: Empty
data: "<ol></ol>"
children: []
\ No newline at end of file
--- {}
metadata:
display_name: Empty
data: ""
children: []
--- {}
metadata:
display_name: Discussion Tag
for: Topic-Level Student-Visible Label
id: $$GUID$$
discussion_category: Week 1
data: |
<discussion />
children: []
--- ---
metadata: metadata:
display_name: Announcement display_name: Announcement
data: | data: |
<ol> <ol>
<li> <li>
......
--- {}
metadata:
display_name: Blank HTML Page
data: |
children: []
\ No newline at end of file
--- ---
metadata: metadata:
display_name: E-text Written in LaTeX display_name: E-text Written in LaTeX
source_code: | source_code: |
\subsection{Example of E-text in LaTeX} \subsection{Example of E-text in LaTeX}
It is very convenient to write complex equations in LaTeX. It is very convenient to write complex equations in LaTeX.
\begin{equation} \begin{equation}
x = \frac{-b\pm\sqrt{b^2-4*a*c}}{2a} x = \frac{-b\pm\sqrt{b^2-4*a*c}}{2a}
\end{equation} \end{equation}
Seize the moment. Seize the moment.
data: | data: |
<html> <html>
......
--- {}
metadata:
display_name: Peer Grading Interface
max_grade: 1
data: |
<peergrading>
</peergrading>
children: []
---
metadata:
display_name: Blank Common Problem
markdown: ""
data: "<problem></problem>"
...@@ -5,59 +5,58 @@ metadata: ...@@ -5,59 +5,58 @@ metadata:
rerandomize: never rerandomize: never
showanswer: finished showanswer: finished
data: | data: |
<problem > <problem >
Please make a voltage divider that splits the provided voltage evenly. Please make a voltage divider that splits the provided voltage evenly.
<schematicresponse> <schematicresponse>
<center> <center>
<schematic height="500" width="600" parts="g,r" analyses="dc" <schematic height="500" width="600" parts="g,r" analyses="dc"
initial_value="[[&quot;v&quot;,[168,144,0],{&quot;value&quot;:&quot;dc(1)&quot;,&quot;_json_&quot;:0},[&quot;1&quot;,&quot;0&quot;]],[&quot;r&quot;,[296,120,0],{&quot;r&quot;:&quot;1&quot;,&quot;_json_&quot;:1},[&quot;1&quot;,&quot;output&quot;]],[&quot;L&quot;,[296,168,3],{&quot;label&quot;:&quot;output&quot;,&quot;_json_&quot;:2},[&quot;output&quot;]],[&quot;w&quot;,[296,216,168,216]],[&quot;w&quot;,[168,216,168,192]],[&quot;w&quot;,[168,144,168,120]],[&quot;w&quot;,[168,120,296,120]],[&quot;g&quot;,[168,216,0],{&quot;_json_&quot;:7},[&quot;0&quot;]],[&quot;view&quot;,-67.49999999999994,-78.49999999999994,1.6000000000000003,&quot;50&quot;,&quot;10&quot;,&quot;1G&quot;,null,&quot;100&quot;,&quot;1&quot;,&quot;1000&quot;]]" initial_value="[[&quot;v&quot;,[168,144,0],{&quot;value&quot;:&quot;dc(1)&quot;,&quot;_json_&quot;:0},[&quot;1&quot;,&quot;0&quot;]],[&quot;r&quot;,[296,120,0],{&quot;r&quot;:&quot;1&quot;,&quot;_json_&quot;:1},[&quot;1&quot;,&quot;output&quot;]],[&quot;L&quot;,[296,168,3],{&quot;label&quot;:&quot;output&quot;,&quot;_json_&quot;:2},[&quot;output&quot;]],[&quot;w&quot;,[296,216,168,216]],[&quot;w&quot;,[168,216,168,192]],[&quot;w&quot;,[168,144,168,120]],[&quot;w&quot;,[168,120,296,120]],[&quot;g&quot;,[168,216,0],{&quot;_json_&quot;:7},[&quot;0&quot;]],[&quot;view&quot;,-67.49999999999994,-78.49999999999994,1.6000000000000003,&quot;50&quot;,&quot;10&quot;,&quot;1G&quot;,null,&quot;100&quot;,&quot;1&quot;,&quot;1000&quot;]]"
/> />
</center> </center>
<answer type="loncapa/python"> <answer type="loncapa/python">
dc_value = "dc analysis not found" dc_value = "dc analysis not found"
for response in submission[0]: for response in submission[0]:
if response[0] == 'dc': if response[0] == 'dc':
for node in response[1:]: for node in response[1:]:
dc_value = node['output'] dc_value = node['output']
if dc_value == .5: if dc_value == .5:
correct = ['correct'] correct = ['correct']
else: else:
correct = ['incorrect'] correct = ['incorrect']
</answer> </answer>
</schematicresponse> </schematicresponse>
<schematicresponse> <schematicresponse>
<p>Make a high pass filter</p> <p>Make a high pass filter</p>
<center> <center>
<schematic height="500" width="600" parts="g,r,s,c" analyses="ac" <schematic height="500" width="600" parts="g,r,s,c" analyses="ac"
submit_analyses="{&quot;ac&quot;:[[&quot;NodeA&quot;,1,9]]}" submit_analyses="{&quot;ac&quot;:[[&quot;NodeA&quot;,1,9]]}"
initial_value="[[&quot;v&quot;,[160,152,0],{&quot;name&quot;:&quot;v1&quot;,&quot;value&quot;:&quot;sin(0,1,1,0,0)&quot;,&quot;_json_&quot;:0},[&quot;1&quot;,&quot;0&quot;]],[&quot;w&quot;,[160,200,240,200]],[&quot;g&quot;,[160,200,0],{&quot;_json_&quot;:2},[&quot;0&quot;]],[&quot;L&quot;,[240,152,3],{&quot;label&quot;:&quot;NodeA&quot;,&quot;_json_&quot;:3},[&quot;NodeA&quot;]],[&quot;s&quot;,[240,152,0],{&quot;color&quot;:&quot;cyan&quot;,&quot;offset&quot;:&quot;0&quot;,&quot;_json_&quot;:4},[&quot;NodeA&quot;]],[&quot;view&quot;,64.55878906250004,54.114697265625054,2.5000000000000004,&quot;50&quot;,&quot;10&quot;,&quot;1G&quot;,null,&quot;100&quot;,&quot;1&quot;,&quot;1000&quot;]]"/> initial_value="[[&quot;v&quot;,[160,152,0],{&quot;name&quot;:&quot;v1&quot;,&quot;value&quot;:&quot;sin(0,1,1,0,0)&quot;,&quot;_json_&quot;:0},[&quot;1&quot;,&quot;0&quot;]],[&quot;w&quot;,[160,200,240,200]],[&quot;g&quot;,[160,200,0],{&quot;_json_&quot;:2},[&quot;0&quot;]],[&quot;L&quot;,[240,152,3],{&quot;label&quot;:&quot;NodeA&quot;,&quot;_json_&quot;:3},[&quot;NodeA&quot;]],[&quot;s&quot;,[240,152,0],{&quot;color&quot;:&quot;cyan&quot;,&quot;offset&quot;:&quot;0&quot;,&quot;_json_&quot;:4},[&quot;NodeA&quot;]],[&quot;view&quot;,64.55878906250004,54.114697265625054,2.5000000000000004,&quot;50&quot;,&quot;10&quot;,&quot;1G&quot;,null,&quot;100&quot;,&quot;1&quot;,&quot;1000&quot;]]"/>
</center> </center>
<answer type="loncapa/python"> <answer type="loncapa/python">
ac_values = None ac_values = None
for response in submission[0]: for response in submission[0]:
if response[0] == 'ac': if response[0] == 'ac':
for node in response[1:]: for node in response[1:]:
ac_values = node['NodeA'] ac_values = node['NodeA']
print "the ac analysis value:", ac_values print "the ac analysis value:", ac_values
if ac_values == None: if ac_values == None:
correct = ['incorrect'] correct = ['incorrect']
elif ac_values[0][1] &lt; ac_values[1][1]: elif ac_values[0][1] &lt; ac_values[1][1]:
correct = ['correct'] correct = ['correct']
else: else:
correct = ['incorrect'] correct = ['incorrect']
</answer> </answer>
</schematicresponse> </schematicresponse>
<solution> <solution>
<div class="detailed-solution"> <div class="detailed-solution">
<p>Explanation</p> <p>Explanation</p>
<p>A voltage divider that evenly divides the input voltage can be formed with two identically valued resistors, with the sampled voltage taken in between the two.</p> <p>A voltage divider that evenly divides the input voltage can be formed with two identically valued resistors, with the sampled voltage taken in between the two.</p>
<p><img src="/static/images/voltage_divider.png"/></p> <p><img src="/static/images/voltage_divider.png"/></p>
<p>A simple high-pass filter without any further constaints can be formed by simply putting a resister in series with a capacitor. The actual values of the components do not really matter in order to meet the constraints of the problem.</p> <p>A simple high-pass filter without any further constaints can be formed by simply putting a resister in series with a capacitor. The actual values of the components do not really matter in order to meet the constraints of the problem.</p>
<p><img src="/static/images/high_pass_filter.png"/></p> <p><img src="/static/images/high_pass_filter.png"/></p>
</div> </div>
</solution> </solution>
</problem> </problem>
children: []
--- {}
metadata:
display_name: Blank Common Problem
rerandomize: never
showanswer: finished
markdown: ""
data: |
<problem>
</problem>
children: []
--- {}
metadata: \ No newline at end of file
display_name: Empty
data: "<p>This is where you can add additional pages to your courseware. Click the 'edit' button to begin editing.</p>"
children: []
\ No newline at end of file
--- {}
metadata:
display_name: default
data: ""
children: []
--- {}
metadata:
display_name: Video Alpha
version: 1
data: |
<videoalpha show_captions="true" sub="name_of_file" youtube="0.75:JMD_ifUUfsU,1.0:OEoXaMPEzfM,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" >
<source src="https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp4"/>
<source src="https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.webm"/>
<source src="https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.ogv"/>
</videoalpha>
children: []
--- {}
metadata:
display_name: Word cloud
data: {}
children: []
...@@ -21,6 +21,17 @@ log = logging.getLogger(__name__) ...@@ -21,6 +21,17 @@ log = logging.getLogger(__name__)
class VideoFields(object): class VideoFields(object):
"""Fields for `VideoModule` and `VideoDescriptor`.""" """Fields for `VideoModule` and `VideoDescriptor`."""
display_name = String(
display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings,
# it'd be nice to have a useful default but it screws up other things; so,
# use display_name_with_default for those
default="Video Title"
)
data = String(help="XML data for the problem",
default='',
scope=Scope.content)
position = Integer(help="Current position in the video", scope=Scope.user_state, default=0) position = Integer(help="Current position in the video", scope=Scope.user_state, default=0)
show_captions = Boolean(help="This controls whether or not captions are shown by default.", display_name="Show Captions", scope=Scope.settings, default=True) show_captions = Boolean(help="This controls whether or not captions are shown by default.", display_name="Show Captions", scope=Scope.settings, default=True)
youtube_id_1_0 = String(help="This is the Youtube ID reference for the normal speed video.", display_name="Default Speed", scope=Scope.settings, default="OEoXaMPEzfM") youtube_id_1_0 = String(help="This is the Youtube ID reference for the normal speed video.", display_name="Default Speed", scope=Scope.settings, default="OEoXaMPEzfM")
...@@ -129,6 +140,10 @@ def _parse_video_xml(video, xml_data): ...@@ -129,6 +140,10 @@ def _parse_video_xml(video, xml_data):
display_name = xml.get('display_name') display_name = xml.get('display_name')
if display_name: if display_name:
video.display_name = display_name video.display_name = display_name
elif video.url_name is not None:
# copies the logic of display_name_with_default in order that studio created videos will have an
# initial non guid name
video.display_name = video.url_name.replace('_', ' ')
youtube = xml.get('youtube') youtube = xml.get('youtube')
if youtube: if youtube:
......
...@@ -28,15 +28,27 @@ from xblock.core import Integer, Scope, String ...@@ -28,15 +28,27 @@ from xblock.core import Integer, Scope, String
import datetime import datetime
import time import time
import textwrap
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class VideoAlphaFields(object): class VideoAlphaFields(object):
"""Fields for `VideoAlphaModule` and `VideoAlphaDescriptor`.""" """Fields for `VideoAlphaModule` and `VideoAlphaDescriptor`."""
data = String(help="XML data for the problem", scope=Scope.content) data = String(help="XML data for the problem",
default=textwrap.dedent('''\
<videoalpha show_captions="true" sub="name_of_file" youtube="0.75:JMD_ifUUfsU,1.0:OEoXaMPEzfM,1.25:AKqURZnYqpk,1.50:DYpADpL7jAY" >
<source src="https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.mp4"/>
<source src="https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.webm"/>
<source src="https://s3.amazonaws.com/edx-course-videos/edx-intro/edX-FA12-cware-1_100.ogv"/>
</videoalpha>'''),
scope=Scope.content)
position = Integer(help="Current position in the video", scope=Scope.user_state, default=0) position = Integer(help="Current position in the video", scope=Scope.user_state, default=0)
display_name = String(help="Display name for this module", scope=Scope.settings) display_name = String(
display_name="Display Name", help="Display name for this module",
default="Video Alpha",
scope=Scope.settings
)
class VideoAlphaModule(VideoAlphaFields, XModule): class VideoAlphaModule(VideoAlphaFields, XModule):
......
...@@ -14,7 +14,7 @@ from xmodule.raw_module import RawDescriptor ...@@ -14,7 +14,7 @@ from xmodule.raw_module import RawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xmodule.x_module import XModule from xmodule.x_module import XModule
from xblock.core import Scope, Dict, Boolean, List, Integer from xblock.core import Scope, Dict, Boolean, List, Integer, String
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -31,6 +31,12 @@ def pretty_bool(value): ...@@ -31,6 +31,12 @@ def pretty_bool(value):
class WordCloudFields(object): class WordCloudFields(object):
"""XFields for word cloud.""" """XFields for word cloud."""
display_name = String(
display_name="Display Name",
help="Display name for this module",
scope=Scope.settings,
default="Word cloud"
)
num_inputs = Integer( num_inputs = Integer(
display_name="Inputs", display_name="Inputs",
help="Number of text boxes available for students to input words/sentences.", help="Number of text boxes available for students to input words/sentences.",
...@@ -234,7 +240,7 @@ class WordCloudModule(WordCloudFields, XModule): ...@@ -234,7 +240,7 @@ class WordCloudModule(WordCloudFields, XModule):
return self.content return self.content
class WordCloudDescriptor(MetadataOnlyEditingDescriptor, RawDescriptor, WordCloudFields): class WordCloudDescriptor(WordCloudFields, MetadataOnlyEditingDescriptor, RawDescriptor):
"""Descriptor for WordCloud Xmodule.""" """Descriptor for WordCloud Xmodule."""
module_class = WordCloudModule module_class = WordCloudModule
template_dir_name = 'word_cloud' template_dir_name = 'word_cloud'
...@@ -7,8 +7,8 @@ from lxml import etree ...@@ -7,8 +7,8 @@ from lxml import etree
from collections import namedtuple from collections import namedtuple
from pkg_resources import resource_listdir, resource_string, resource_isdir from pkg_resources import resource_listdir, resource_string, resource_isdir
from xmodule.modulestore import Location from xmodule.modulestore import inheritance, Location
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError, InsufficientSpecificationError
from xblock.core import XBlock, Scope, String, Integer, Float, ModelType from xblock.core import XBlock, Scope, String, Integer, Float, ModelType
...@@ -101,6 +101,8 @@ class XModuleFields(object): ...@@ -101,6 +101,8 @@ class XModuleFields(object):
display_name="Display Name", display_name="Display Name",
help="This name appears in the horizontal navigation at the top of the page.", help="This name appears in the horizontal navigation at the top of the page.",
scope=Scope.settings, scope=Scope.settings,
# it'd be nice to have a useful default but it screws up other things; so,
# use display_name_with_default for those
default=None default=None
) )
...@@ -113,6 +115,14 @@ class XModuleFields(object): ...@@ -113,6 +115,14 @@ class XModuleFields(object):
scope=Scope.content, scope=Scope.content,
default=Location(None), default=Location(None),
) )
# Please note that in order to be compatible with XBlocks more generally,
# the LMS and CMS shouldn't be using this field. It's only for internal
# consumption by the XModules themselves
category = String(
display_name="xmodule category",
help="This is the category id for the XModule. It's for internal use only",
scope=Scope.content,
)
class XModule(XModuleFields, HTMLSnippet, XBlock): class XModule(XModuleFields, HTMLSnippet, XBlock):
...@@ -148,8 +158,16 @@ class XModule(XModuleFields, HTMLSnippet, XBlock): ...@@ -148,8 +158,16 @@ class XModule(XModuleFields, HTMLSnippet, XBlock):
self._model_data = model_data self._model_data = model_data
self.system = runtime self.system = runtime
self.descriptor = descriptor self.descriptor = descriptor
self.url_name = self.location.name # LMS tests don't require descriptor but really it's required
self.category = self.location.category if descriptor:
self.url_name = descriptor.url_name
# don't need to set category as it will automatically get from descriptor
elif isinstance(self.location, Location):
self.url_name = self.location.name
if not hasattr(self, 'category'):
self.category = self.location.category
else:
raise InsufficientSpecificationError()
self._loaded_children = None self._loaded_children = None
@property @property
...@@ -290,36 +308,67 @@ Template = namedtuple("Template", "metadata data children") ...@@ -290,36 +308,67 @@ Template = namedtuple("Template", "metadata data children")
class ResourceTemplates(object): class ResourceTemplates(object):
"""
Gets the templates associated w/ a containing cls. The cls must have a 'template_dir_name' attribute.
It finds the templates as directly in this directory under 'templates'.
"""
@classmethod @classmethod
def templates(cls): def templates(cls):
""" """
Returns a list of Template objects that describe possible templates that can be used Returns a list of dictionary field: value objects that describe possible templates that can be used
to create a module of this type. to seed 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 Expects a class attribute template_dir_name that defines the directory
inside the 'templates' resource directory to pull templates from inside the 'templates' resource directory to pull templates from
""" """
templates = [] templates = []
dirname = os.path.join('templates', cls.template_dir_name) dirname = cls.get_template_dir()
if not resource_isdir(__name__, dirname): if dirname is not None:
log.warning("No resource directory {dir} found when loading {cls_name} templates".format( for template_file in resource_listdir(__name__, dirname):
dir=dirname, if not template_file.endswith('.yaml'):
cls_name=cls.__name__, log.warning("Skipping unknown template file %s", template_file)
)) continue
return [] template_content = resource_string(__name__, os.path.join(dirname, template_file))
template = yaml.safe_load(template_content)
for template_file in resource_listdir(__name__, dirname): template['template_id'] = template_file
if not template_file.endswith('.yaml'): templates.append(template)
log.warning("Skipping unknown template file %s" % template_file)
continue
template_content = resource_string(__name__, os.path.join(dirname, template_file))
template = yaml.safe_load(template_content)
templates.append(Template(**template))
return templates return templates
@classmethod
def get_template_dir(cls):
if getattr(cls, 'template_dir_name', None):
dirname = os.path.join('templates', getattr(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 None
else:
return dirname
else:
return None
@classmethod
def get_template(cls, template_id):
"""
Get a single template by the given id (which is the file name identifying it w/in the class's
template_dir_name)
"""
dirname = cls.get_template_dir()
if dirname is not None:
try:
template_content = resource_string(__name__, os.path.join(dirname, template_id))
except IOError:
return None
template = yaml.safe_load(template_content)
template['template_id'] = template_id
return template
else:
return None
class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
""" """
...@@ -346,9 +395,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): ...@@ -346,9 +395,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# be equal # be equal
equality_attributes = ('_model_data', 'location') equality_attributes = ('_model_data', 'location')
# Name of resource directory to load templates from
template_dir_name = "default"
# Class level variable # Class level variable
# True if this descriptor always requires recalculation of grades, for # True if this descriptor always requires recalculation of grades, for
...@@ -386,8 +432,12 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): ...@@ -386,8 +432,12 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
""" """
super(XModuleDescriptor, self).__init__(*args, **kwargs) super(XModuleDescriptor, self).__init__(*args, **kwargs)
self.system = self.runtime self.system = self.runtime
self.url_name = self.location.name if isinstance(self.location, Location):
self.category = self.location.category self.url_name = self.location.name
if not hasattr(self, 'category'):
self.category = self.location.category
else:
raise InsufficientSpecificationError()
self._child_instances = None self._child_instances = None
@property @property
...@@ -419,11 +469,14 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): ...@@ -419,11 +469,14 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
if self._child_instances is None: if self._child_instances is None:
self._child_instances = [] self._child_instances = []
for child_loc in self.children: for child_loc in self.children:
try: if isinstance(child_loc, XModuleDescriptor):
child = self.system.load_item(child_loc) child = child_loc
except ItemNotFoundError: else:
log.exception('Unable to load item {loc}, skipping'.format(loc=child_loc)) try:
continue child = self.system.load_item(child_loc)
except ItemNotFoundError:
log.exception('Unable to load item {loc}, skipping'.format(loc=child_loc))
continue
self._child_instances.append(child) self._child_instances.append(child)
return self._child_instances return self._child_instances
...@@ -591,6 +644,13 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock): ...@@ -591,6 +644,13 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
""" """
return [('{}', '{}')] return [('{}', '{}')]
@property
def xblock_kvs(self):
"""
Use w/ caution. Really intended for use by the persistence layer.
"""
return self._model_data._kvs
# =============================== BUILTIN METHODS ========================== # =============================== BUILTIN METHODS ==========================
def __eq__(self, other): def __eq__(self, other):
eq = (self.__class__ == other.__class__ and eq = (self.__class__ == other.__class__ and
......
...@@ -3,6 +3,8 @@ Namespace that defines fields common to all blocks used in the LMS ...@@ -3,6 +3,8 @@ Namespace that defines fields common to all blocks used in the LMS
""" """
from xblock.core import Namespace, Boolean, Scope, String, Float from xblock.core import Namespace, Boolean, Scope, String, Float
from xmodule.fields import Date, Timedelta from xmodule.fields import Date, Timedelta
from datetime import datetime
from pytz import UTC
class LmsNamespace(Namespace): class LmsNamespace(Namespace):
...@@ -25,7 +27,11 @@ class LmsNamespace(Namespace): ...@@ -25,7 +27,11 @@ class LmsNamespace(Namespace):
scope=Scope.settings, scope=Scope.settings,
) )
start = Date(help="Start time when this module is visible", scope=Scope.settings) start = Date(
help="Start time when this module is visible",
default=datetime.fromtimestamp(0, UTC),
scope=Scope.settings
)
due = Date(help="Date that this problem is due by", scope=Scope.settings) due = Date(help="Date that this problem is due by", scope=Scope.settings)
source_file = String(help="source file name (eg for latex)", scope=Scope.settings) source_file = String(help="source file name (eg for latex)", scope=Scope.settings)
giturl = String(help="url root for course data git repository", scope=Scope.settings) giturl = String(help="url root for course data git repository", scope=Scope.settings)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment