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):
def i_see_only_the_settings_and_values(step):
world.verify_all_setting_entries(
[
['Category', "Week 1", True],
['Display Name', "Discussion Tag", True],
['Subcategory', "Topic-Level Student-Visible Label", True]
['Category', "Week 1", False],
['Display Name', "Discussion Tag", False],
['Subcategory', "Topic-Level Student-Visible Label", False]
])
......
......@@ -14,4 +14,4 @@ def i_created_blank_html_page(step):
@step('I see only the HTML display name setting$')
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):
world.create_component_instance(
step,
'.large-problem-icon',
'i4x://edx/templates/problem/Blank_Common_Problem',
'.xmodule_CapaModule'
'problem',
'.xmodule_CapaModule',
'blank_common.yaml'
)
......@@ -32,11 +33,12 @@ def i_edit_and_select_settings(step):
def i_see_five_settings_with_values(step):
world.verify_all_setting_entries(
[
[DISPLAY_NAME, "Blank Common Problem", True],
[DISPLAY_NAME, "New problem", True],
[MAXIMUM_ATTEMPTS, "", False],
[PROBLEM_WEIGHT, "", False],
[RANDOMIZATION, "Never", True],
[SHOW_ANSWER, "Finished", True]
# Not sure why these are True other than via inspection
[RANDOMIZATION, "Always", True],
[SHOW_ANSWER, "Closed", True]
])
......@@ -203,7 +205,7 @@ def verify_modified_display_name_with_special_chars():
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):
......
......@@ -7,7 +7,7 @@ from lettuce import world, step
@step('I see the correct settings and default values$')
def i_see_the_correct_settings_and_values(step):
world.verify_all_setting_entries([['Default Speed', 'OEoXaMPEzfM', False],
['Display Name', 'default', True],
['Display Name', 'Video Title', False],
['Download Track', '', False],
['Download Video', '', False],
['Show Captions', 'True', False],
......
......@@ -135,7 +135,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
self.check_components_on_page(ADVANCED_COMPONENT_TYPES, ['Video Alpha',
'Word cloud',
'Annotation',
'Open Ended Response',
'Open Ended Grading',
'Peer Grading Interface'])
def test_advanced_components_require_two_clicks(self):
......@@ -1271,6 +1271,28 @@ class ContentStoreTest(ModuleStoreTestCase):
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):
......
......@@ -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_end, "enrollment_end date somehow initialized " + str(details.enrollment_end))
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.effort, "effort somehow initialized" + str(details.effort))
......@@ -49,7 +48,6 @@ class CourseDetailsTestCase(CourseTestCase):
self.assertIsNone(jsondetails['enrollment_start'], "enrollment_start date somehow initialized ")
self.assertIsNone(jsondetails['enrollment_end'], "enrollment_end date 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['effort'], "effort somehow initialized")
......
......@@ -120,7 +120,7 @@ def add_histogram(get_html, module, user):
# doesn't like symlinks)
filepath = filename
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)
else:
edit_link = False
......
......@@ -6,12 +6,37 @@ from pkg_resources import resource_string
from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xblock.core import Scope, String
import textwrap
log = logging.getLogger(__name__)
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):
......
......@@ -77,6 +77,14 @@ class CapaFields(object):
"""
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",
default=0, scope=Scope.user_state)
max_attempts = Integer(
......@@ -94,7 +102,8 @@ class CapaFields(object):
display_name="Show Answer",
help=("Defines when to show the answer to the problem. "
"A default value can be set in Advanced Settings."),
scope=Scope.settings, default="closed",
scope=Scope.settings,
default="closed",
values=[
{"display_name": "Always", "value": "always"},
{"display_name": "Answered", "value": "answered"},
......@@ -106,21 +115,24 @@ class CapaFields(object):
)
force_save_button = Boolean(
help="Whether to force the save button to appear on the page",
scope=Scope.settings, default=False
scope=Scope.settings,
default=False
)
rerandomize = Randomization(
display_name="Randomization",
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. "
"A default value can be set in Advanced Settings.",
default="always", scope=Scope.settings, values=[
"This setting only applies to problems that can have randomly generated numeric values. "
"A default value can be set in Advanced Settings.",
default="always",
scope=Scope.settings,
values=[
{"display_name": "Always", "value": "always"},
{"display_name": "On Reset", "value": "onreset"},
{"display_name": "Never", "value": "never"},
{"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",
scope=Scope.user_state, default={})
input_state = Dict(help="Dictionary for maintaining the state of inputtypes", scope=Scope.user_state)
......@@ -134,13 +146,12 @@ class CapaFields(object):
values={"min": 0, "step": .1},
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(
help="Source code for LaTeX and Word problems. This feature is not well-supported.",
scope=Scope.settings
)
class CapaModule(CapaFields, XModule):
"""
An XModule implementing LonCapa format problems, implemented by way of
......
......@@ -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 collections import namedtuple
from .fields import Date
import textwrap
log = logging.getLogger("mitx.courseware")
......@@ -27,6 +28,38 @@ VERSION_TUPLES = {
}
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):
......@@ -85,13 +118,30 @@ class CombinedOpenEndedFields(object):
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(
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.",
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):
......
......@@ -4,17 +4,27 @@ from xmodule.x_module import XModule
from xmodule.raw_module import RawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor
from xblock.core import String, Scope
from uuid import uuid4
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(
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.",
scope=Scope.settings
)
discussion_target = String(
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.",
scope=Scope.settings
)
......@@ -36,9 +46,15 @@ class DiscussionModule(DiscussionFields, XModule):
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,
# but these would overload other module attributes, so we prefix them
# for actual use in the code
......
......@@ -13,12 +13,21 @@ from xmodule.html_checker import check_html
from xmodule.stringify import stringify_children
from xmodule.x_module import XModule
from xmodule.xml_module import XmlDescriptor, name_to_pathname
import textwrap
log = logging.getLogger("mitx.courseware")
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)
......@@ -32,7 +41,7 @@ class HtmlModule(HtmlFields, XModule):
css = {'scss': [resource_string(__name__, 'css/html/display.scss')]}
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
......@@ -169,26 +178,88 @@ class HtmlDescriptor(HtmlFields, XmlDescriptor, EditingDescriptor):
elt.set("filename", relname)
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
in order to be able to create new ones
"""
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
in order to be able to create new ones
"""
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
in order to be able to create new ones
"""
template_dir_name = "courseinfo"
module_class = CourseInfoModule
......@@ -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.",
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):
......
......@@ -13,7 +13,7 @@ class RawDescriptor(XmlDescriptor, XMLEditingDescriptor):
Module that provides a raw editing view of its data and children. It
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
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:
display_name: overview
data: |
<section class="about">
<h2>About This Course</h2>
<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>
</section>
<section class="prerequisites">
<h2>Prerequisites</h2>
<p>Add information about course prerequisites here.</p>
</section>
<section class="course-staff">
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #1</h3>
<p>Biography of instructor/staff member #1</p>
</article>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #2</h3>
<p>Biography of instructor/staff member #2</p>
</article>
</section>
<section class="faq">
<section class="responses">
<h2>Frequently Asked Questions</h2>
<article class="response">
<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>
</article>
<article class="response">
<h3>Question #2</h3>
<p>Your answer would be displayed here.</p>
</article>
</section>
</section>
children: []
<section class="about">
<h2>About This Course</h2>
<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>
</section>
<section class="prerequisites">
<h2>Prerequisites</h2>
<p>Add information about course prerequisites here.</p>
</section>
<section class="course-staff">
<h2>Course Staff</h2>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #1</h3>
<p>Biography of instructor/staff member #1</p>
</article>
<article class="teacher">
<div class="teacher-image">
<img src="/static/images/pl-faculty.png" align="left" style="margin:0 20 px 0">
</div>
<h3>Staff Member #2</h3>
<p>Biography of instructor/staff member #2</p>
</article>
</section>
<section class="faq">
<section class="responses">
<h2>Frequently Asked Questions</h2>
<article class="response">
<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>
</article>
<article class="response">
<h3>Question #2</h3>
<p>Your answer would be displayed here.</p>
</article>
</section>
</section>
---
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:
display_name: Announcement
display_name: Announcement
data: |
<ol>
<li>
......
---
metadata:
display_name: Blank HTML Page
data: |
children: []
\ No newline at end of file
{}
---
metadata:
display_name: E-text Written in LaTeX
source_code: |
\subsection{Example of E-text in LaTeX}
source_code: |
\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}
x = \frac{-b\pm\sqrt{b^2-4*a*c}}{2a}
\end{equation}
\begin{equation}
x = \frac{-b\pm\sqrt{b^2-4*a*c}}{2a}
\end{equation}
Seize the moment.
Seize the moment.
data: |
<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:
rerandomize: never
showanswer: finished
data: |
<problem >
Please make a voltage divider that splits the provided voltage evenly.
<schematicresponse>
<center>
<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;]]"
/>
</center>
<answer type="loncapa/python">
dc_value = "dc analysis not found"
for response in submission[0]:
if response[0] == 'dc':
for node in response[1:]:
dc_value = node['output']
if dc_value == .5:
correct = ['correct']
else:
correct = ['incorrect']
</answer>
</schematicresponse>
<schematicresponse>
<p>Make a high pass filter</p>
<center>
<schematic height="500" width="600" parts="g,r,s,c" analyses="ac"
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;]]"/>
</center>
<answer type="loncapa/python">
ac_values = None
for response in submission[0]:
if response[0] == 'ac':
for node in response[1:]:
ac_values = node['NodeA']
print "the ac analysis value:", ac_values
if ac_values == None:
correct = ['incorrect']
elif ac_values[0][1] &lt; ac_values[1][1]:
correct = ['correct']
else:
correct = ['incorrect']
</answer>
</schematicresponse>
<solution>
<div class="detailed-solution">
<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><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><img src="/static/images/high_pass_filter.png"/></p>
</div>
</solution>
</problem>
children: []
<problem >
Please make a voltage divider that splits the provided voltage evenly.
<schematicresponse>
<center>
<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;]]"
/>
</center>
<answer type="loncapa/python">
dc_value = "dc analysis not found"
for response in submission[0]:
if response[0] == 'dc':
for node in response[1:]:
dc_value = node['output']
if dc_value == .5:
correct = ['correct']
else:
correct = ['incorrect']
</answer>
</schematicresponse>
<schematicresponse>
<p>Make a high pass filter</p>
<center>
<schematic height="500" width="600" parts="g,r,s,c" analyses="ac"
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;]]"/>
</center>
<answer type="loncapa/python">
ac_values = None
for response in submission[0]:
if response[0] == 'ac':
for node in response[1:]:
ac_values = node['NodeA']
print "the ac analysis value:", ac_values
if ac_values == None:
correct = ['incorrect']
elif ac_values[0][1] &lt; ac_values[1][1]:
correct = ['correct']
else:
correct = ['incorrect']
</answer>
</schematicresponse>
<solution>
<div class="detailed-solution">
<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><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><img src="/static/images/high_pass_filter.png"/></p>
</div>
</solution>
</problem>
---
metadata:
display_name: Blank Common Problem
rerandomize: never
showanswer: finished
markdown: ""
data: |
<problem>
</problem>
children: []
{}
---
metadata:
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
{}
\ 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__)
class VideoFields(object):
"""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)
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")
......@@ -129,6 +140,10 @@ def _parse_video_xml(video, xml_data):
display_name = xml.get('display_name')
if 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')
if youtube:
......
......@@ -28,15 +28,27 @@ from xblock.core import Integer, Scope, String
import datetime
import time
import textwrap
log = logging.getLogger(__name__)
class VideoAlphaFields(object):
"""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)
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):
......
......@@ -14,7 +14,7 @@ from xmodule.raw_module import RawDescriptor
from xmodule.editing_module import MetadataOnlyEditingDescriptor
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__)
......@@ -31,6 +31,12 @@ def pretty_bool(value):
class WordCloudFields(object):
"""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(
display_name="Inputs",
help="Number of text boxes available for students to input words/sentences.",
......@@ -234,7 +240,7 @@ class WordCloudModule(WordCloudFields, XModule):
return self.content
class WordCloudDescriptor(MetadataOnlyEditingDescriptor, RawDescriptor, WordCloudFields):
class WordCloudDescriptor(WordCloudFields, MetadataOnlyEditingDescriptor, RawDescriptor):
"""Descriptor for WordCloud Xmodule."""
module_class = WordCloudModule
template_dir_name = 'word_cloud'
......@@ -7,8 +7,8 @@ from lxml import etree
from collections import namedtuple
from pkg_resources import resource_listdir, resource_string, resource_isdir
from xmodule.modulestore import Location
from xmodule.modulestore.exceptions import ItemNotFoundError
from xmodule.modulestore import inheritance, Location
from xmodule.modulestore.exceptions import ItemNotFoundError, InsufficientSpecificationError
from xblock.core import XBlock, Scope, String, Integer, Float, ModelType
......@@ -101,6 +101,8 @@ class XModuleFields(object):
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=None
)
......@@ -113,6 +115,14 @@ class XModuleFields(object):
scope=Scope.content,
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):
......@@ -148,8 +158,16 @@ class XModule(XModuleFields, HTMLSnippet, XBlock):
self._model_data = model_data
self.system = runtime
self.descriptor = descriptor
self.url_name = self.location.name
self.category = self.location.category
# LMS tests don't require descriptor but really it's required
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
@property
......@@ -290,36 +308,67 @@ Template = namedtuple("Template", "metadata data children")
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
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
Returns a list of dictionary field: value objects that describe possible templates that can be used
to seed 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):
if not template_file.endswith('.yaml'):
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))
dirname = cls.get_template_dir()
if dirname is not None:
for template_file in resource_listdir(__name__, dirname):
if not template_file.endswith('.yaml'):
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)
template['template_id'] = template_file
templates.append(template)
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):
"""
......@@ -346,9 +395,6 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
# be equal
equality_attributes = ('_model_data', 'location')
# Name of resource directory to load templates from
template_dir_name = "default"
# Class level variable
# True if this descriptor always requires recalculation of grades, for
......@@ -386,8 +432,12 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
"""
super(XModuleDescriptor, self).__init__(*args, **kwargs)
self.system = self.runtime
self.url_name = self.location.name
self.category = self.location.category
if isinstance(self.location, Location):
self.url_name = self.location.name
if not hasattr(self, 'category'):
self.category = self.location.category
else:
raise InsufficientSpecificationError()
self._child_instances = None
@property
......@@ -419,11 +469,14 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
if self._child_instances is None:
self._child_instances = []
for child_loc in self.children:
try:
child = self.system.load_item(child_loc)
except ItemNotFoundError:
log.exception('Unable to load item {loc}, skipping'.format(loc=child_loc))
continue
if isinstance(child_loc, XModuleDescriptor):
child = child_loc
else:
try:
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)
return self._child_instances
......@@ -591,6 +644,13 @@ class XModuleDescriptor(XModuleFields, HTMLSnippet, ResourceTemplates, XBlock):
"""
return [('{}', '{}')]
@property
def xblock_kvs(self):
"""
Use w/ caution. Really intended for use by the persistence layer.
"""
return self._model_data._kvs
# =============================== BUILTIN METHODS ==========================
def __eq__(self, other):
eq = (self.__class__ == other.__class__ and
......
......@@ -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 xmodule.fields import Date, Timedelta
from datetime import datetime
from pytz import UTC
class LmsNamespace(Namespace):
......@@ -25,7 +27,11 @@ class LmsNamespace(Namespace):
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)
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)
......
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