Merge remote-tracking branch 'origin/master' into feature/vik/oe-ui
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -45,3 +45,4 @@ node_modules
|
||||
autodeploy.properties
|
||||
.ws_migrations_complete
|
||||
.vagrant/
|
||||
logs
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[edx-studio.django-partial]
|
||||
[edx-platform.django-partial]
|
||||
file_filter = conf/locale/<lang>/LC_MESSAGES/django-partial.po
|
||||
source_file = conf/locale/en/LC_MESSAGES/django-partial.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
[edx-studio.djangojs]
|
||||
[edx-platform.djangojs]
|
||||
file_filter = conf/locale/<lang>/LC_MESSAGES/djangojs.po
|
||||
source_file = conf/locale/en/LC_MESSAGES/djangojs.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
[edx-studio.mako]
|
||||
[edx-platform.mako]
|
||||
file_filter = conf/locale/<lang>/LC_MESSAGES/mako.po
|
||||
source_file = conf/locale/en/LC_MESSAGES/mako.po
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
[edx-studio.messages]
|
||||
[edx-platform.messages]
|
||||
file_filter = conf/locale/<lang>/LC_MESSAGES/messages.po
|
||||
source_file = conf/locale/en/LC_MESSAGES/messages.po
|
||||
source_lang = en
|
||||
|
||||
@@ -43,6 +43,13 @@ history of background tasks for a given problem and student.
|
||||
Blades: Small UX fix on capa multiple-choice problems. Make labels only
|
||||
as wide as the text to reduce accidental choice selections.
|
||||
|
||||
Studio:
|
||||
- use xblock field defaults to initialize all new instances' fields and
|
||||
only use templates as override samples.
|
||||
- create new instances via in memory create_xmodule and related methods rather
|
||||
than cloning a db record.
|
||||
- have an explicit method for making a draft copy as distinct from making a new module.
|
||||
|
||||
Studio: Remove XML from the video component editor. All settings are
|
||||
moved to be edited as metadata.
|
||||
|
||||
|
||||
@@ -239,7 +239,6 @@ CMS templates. Fortunately, `rake` will do all of this for you! Just run:
|
||||
|
||||
$ rake django-admin[syncdb]
|
||||
$ rake django-admin[migrate]
|
||||
$ rake cms:update_templates
|
||||
|
||||
If you are running these commands using the [`zsh`](http://www.zsh.org/) shell,
|
||||
zsh will assume that you are doing
|
||||
|
||||
@@ -20,8 +20,8 @@ def get_course_updates(location):
|
||||
try:
|
||||
course_updates = modulestore('direct').get_item(location)
|
||||
except ItemNotFoundError:
|
||||
template = Location(['i4x', 'edx', "templates", 'course_info', "Empty"])
|
||||
course_updates = modulestore('direct').clone_item(template, Location(location))
|
||||
modulestore('direct').create_and_save_xmodule(location)
|
||||
course_updates = modulestore('direct').get_item(location)
|
||||
|
||||
# current db rep: {"_id" : locationjson, "definition" : { "data" : "<ol>[<li><h2>date</h2>content</li>]</ol>"} "metadata" : ignored}
|
||||
location_base = course_updates.location.url()
|
||||
|
||||
@@ -208,7 +208,7 @@ def set_date_and_time(date_css, desired_date, time_css, desired_time):
|
||||
def i_created_a_video_component(step):
|
||||
world.create_component_instance(
|
||||
step, '.large-video-icon',
|
||||
'i4x://edx/templates/video/default',
|
||||
'video',
|
||||
'.xmodule_VideoModule'
|
||||
)
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ from terrain.steps import reload_the_page
|
||||
|
||||
|
||||
@world.absorb
|
||||
def create_component_instance(step, component_button_css, instance_id, expected_css):
|
||||
def create_component_instance(step, component_button_css, category, expected_css, boilerplate=None):
|
||||
click_new_component_button(step, component_button_css)
|
||||
click_component_from_menu(instance_id, expected_css)
|
||||
click_component_from_menu(category, boilerplate, expected_css)
|
||||
|
||||
|
||||
@world.absorb
|
||||
@@ -19,7 +19,7 @@ def click_new_component_button(step, component_button_css):
|
||||
|
||||
|
||||
@world.absorb
|
||||
def click_component_from_menu(instance_id, expected_css):
|
||||
def click_component_from_menu(category, boilerplate, expected_css):
|
||||
"""
|
||||
Creates a component from `instance_id`. For components with more
|
||||
than one template, clicks on `elem_css` to create the new
|
||||
@@ -27,11 +27,13 @@ def click_component_from_menu(instance_id, expected_css):
|
||||
as the user clicks the appropriate button, so we assert that the
|
||||
expected component is present.
|
||||
"""
|
||||
elem_css = "a[data-location='%s']" % instance_id
|
||||
if boilerplate:
|
||||
elem_css = "a[data-category='{}'][data-boilerplate='{}']".format(category, boilerplate)
|
||||
else:
|
||||
elem_css = "a[data-category='{}']:not([data-boilerplate])".format(category)
|
||||
elements = world.css_find(elem_css)
|
||||
assert(len(elements) == 1)
|
||||
if elements[0]['id'] == instance_id: # If this is a component with multiple templates
|
||||
world.css_click(elem_css)
|
||||
assert_equal(len(elements), 1)
|
||||
world.css_click(elem_css)
|
||||
assert_equal(1, len(world.css_find(expected_css)))
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from lettuce import world, step
|
||||
def i_created_discussion_tag(step):
|
||||
world.create_component_instance(
|
||||
step, '.large-discussion-icon',
|
||||
'i4x://edx/templates/discussion/Discussion_Tag',
|
||||
'discussion',
|
||||
'.xmodule_DiscussionModule'
|
||||
)
|
||||
|
||||
@@ -17,14 +17,14 @@ 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]
|
||||
])
|
||||
|
||||
|
||||
@step('creating a discussion takes a single click')
|
||||
def discussion_takes_a_single_click(step):
|
||||
assert(not world.is_css_present('.xmodule_DiscussionModule'))
|
||||
world.css_click("a[data-location='i4x://edx/templates/discussion/Discussion_Tag']")
|
||||
world.css_click("a[data-category='discussion']")
|
||||
assert(world.is_css_present('.xmodule_DiscussionModule'))
|
||||
|
||||
@@ -7,11 +7,11 @@ from lettuce import world, step
|
||||
@step('I have created a Blank HTML Page$')
|
||||
def i_created_blank_html_page(step):
|
||||
world.create_component_instance(
|
||||
step, '.large-html-icon', 'i4x://edx/templates/html/Blank_HTML_Page',
|
||||
step, '.large-html-icon', 'html',
|
||||
'.xmodule_HtmlModule'
|
||||
)
|
||||
|
||||
|
||||
@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'
|
||||
)
|
||||
|
||||
|
||||
@@ -35,8 +36,8 @@ def i_see_five_settings_with_values(step):
|
||||
[DISPLAY_NAME, "Blank Common Problem", True],
|
||||
[MAXIMUM_ATTEMPTS, "", False],
|
||||
[PROBLEM_WEIGHT, "", False],
|
||||
[RANDOMIZATION, "Never", True],
|
||||
[SHOW_ANSWER, "Finished", True]
|
||||
[RANDOMIZATION, "Never", False],
|
||||
[SHOW_ANSWER, "Finished", False]
|
||||
])
|
||||
|
||||
|
||||
@@ -94,7 +95,7 @@ def my_change_to_randomization_is_persisted(step):
|
||||
def i_can_revert_to_default_for_randomization(step):
|
||||
world.revert_setting_entry(RANDOMIZATION)
|
||||
world.save_component_and_reopen(step)
|
||||
world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Always", False)
|
||||
world.verify_setting_entry(world.get_setting_entry(RANDOMIZATION), RANDOMIZATION, "Never", False)
|
||||
|
||||
|
||||
@step('I can set the weight to "(.*)"?')
|
||||
@@ -156,7 +157,7 @@ def create_latex_problem(step):
|
||||
world.click_new_component_button(step, '.large-problem-icon')
|
||||
# Go to advanced tab.
|
||||
world.css_click('#ui-id-2')
|
||||
world.click_component_from_menu("i4x://edx/templates/problem/Problem_Written_in_LaTeX", '.xmodule_CapaModule')
|
||||
world.click_component_from_menu("problem", "latex_problem.yaml", '.xmodule_CapaModule')
|
||||
|
||||
|
||||
@step('I edit and compile the High Level Source')
|
||||
@@ -203,7 +204,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):
|
||||
|
||||
@@ -22,7 +22,7 @@ def have_a_course_with_1_section(step):
|
||||
section = world.ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = world.ItemFactory.create(
|
||||
parent_location=section.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
category='sequential',
|
||||
display_name='Subsection One',)
|
||||
|
||||
|
||||
@@ -33,18 +33,18 @@ def have_a_course_with_two_sections(step):
|
||||
section = world.ItemFactory.create(parent_location=course.location)
|
||||
subsection1 = world.ItemFactory.create(
|
||||
parent_location=section.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
category='sequential',
|
||||
display_name='Subsection One',)
|
||||
section2 = world.ItemFactory.create(
|
||||
parent_location=course.location,
|
||||
display_name='Section Two',)
|
||||
subsection2 = world.ItemFactory.create(
|
||||
parent_location=section2.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
category='sequential',
|
||||
display_name='Subsection Alpha',)
|
||||
subsection3 = world.ItemFactory.create(
|
||||
parent_location=section2.location,
|
||||
template='i4x://edx/templates/sequential/Empty',
|
||||
category='sequential',
|
||||
display_name='Subsection Beta',)
|
||||
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -14,7 +14,7 @@ def does_not_autoplay(_step):
|
||||
@step('creating a video takes a single click')
|
||||
def video_takes_a_single_click(_step):
|
||||
assert(not world.is_css_present('.xmodule_VideoModule'))
|
||||
world.css_click("a[data-location='i4x://edx/templates/video/default']")
|
||||
world.css_click("a[data-category='video']")
|
||||
assert(world.is_css_present('.xmodule_VideoModule'))
|
||||
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ unnamed_modules = 0
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Import the specified data directory into the default ModuleStore'
|
||||
help = 'Export the specified data directory into the default ModuleStore'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) != 2:
|
||||
raise CommandError("import requires two arguments: <course location> <output path>")
|
||||
raise CommandError("export requires two arguments: <course location> <output path>")
|
||||
|
||||
course_id = args[0]
|
||||
output_path = args[1]
|
||||
@@ -30,4 +30,4 @@ class Command(BaseCommand):
|
||||
root_dir = os.path.dirname(output_path)
|
||||
course_dir = os.path.splitext(os.path.basename(output_path))[0]
|
||||
|
||||
export_to_xml(modulestore('direct'), contentstore(), location, root_dir, course_dir)
|
||||
export_to_xml(modulestore('direct'), contentstore(), location, root_dir, course_dir, modulestore())
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
###
|
||||
### Script for exporting all courseware from Mongo to a directory
|
||||
###
|
||||
import os
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from xmodule.modulestore.xml_exporter import export_to_xml
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
|
||||
|
||||
unnamed_modules = 0
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Export all courses from mongo to the specified data directory'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) != 1:
|
||||
raise CommandError("export requires one argument: <output path>")
|
||||
|
||||
output_path = args[0]
|
||||
|
||||
cs = contentstore()
|
||||
ms = modulestore('direct')
|
||||
root_dir = output_path
|
||||
courses = ms.get_courses()
|
||||
|
||||
print "%d courses to export:" % len(courses)
|
||||
cids = [x.id for x in courses]
|
||||
print cids
|
||||
|
||||
for course_id in cids:
|
||||
|
||||
print "-"*77
|
||||
print "Exporting course id = {0} to {1}".format(course_id, output_path)
|
||||
|
||||
if 1:
|
||||
try:
|
||||
location = CourseDescriptor.id_to_location(course_id)
|
||||
course_dir = course_id.replace('/', '...')
|
||||
export_to_xml(ms, cs, location, root_dir, course_dir, modulestore())
|
||||
except Exception as err:
|
||||
print "="*30 + "> Oops, failed to export %s" % course_id
|
||||
print "Error:"
|
||||
print err
|
||||
@@ -1,10 +0,0 @@
|
||||
from xmodule.templates import update_templates
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Imports and updates the Studio component templates from the code pack and put in the DB'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
update_templates(modulestore('direct'))
|
||||
@@ -3,13 +3,13 @@ from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
from xmodule.modulestore import Location
|
||||
|
||||
|
||||
def get_module_info(store, location, parent_location=None, rewrite_static_links=False):
|
||||
def get_module_info(store, location, rewrite_static_links=False):
|
||||
try:
|
||||
module = store.get_item(location)
|
||||
except ItemNotFoundError:
|
||||
# create a new one
|
||||
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
|
||||
module = store.clone_item(template_location, location)
|
||||
store.create_and_save_xmodule(location)
|
||||
module = store.get_item(location)
|
||||
|
||||
data = module.data
|
||||
if rewrite_static_links:
|
||||
@@ -29,7 +29,8 @@ def get_module_info(store, location, parent_location=None, rewrite_static_links=
|
||||
'id': module.location.url(),
|
||||
'data': data,
|
||||
# TODO (cpennington): This really shouldn't have to do this much reaching in to get the metadata
|
||||
'metadata': module._model_data._kvs._metadata
|
||||
# what's the intent here? all metadata incl inherited & namespaced?
|
||||
'metadata': module.xblock_kvs._metadata
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +38,11 @@ def set_module_info(store, location, post_data):
|
||||
module = None
|
||||
try:
|
||||
module = store.get_item(location)
|
||||
except:
|
||||
pass
|
||||
|
||||
if module is None:
|
||||
# new module at this location
|
||||
# presume that we have an 'Empty' template
|
||||
template_location = Location(['i4x', 'edx', 'templates', location.category, 'Empty'])
|
||||
module = store.clone_item(template_location, location)
|
||||
except ItemNotFoundError:
|
||||
# new module at this location: almost always used for the course about pages; thus, no parent. (there
|
||||
# are quite a handful of about page types available for a course and only the overview is pre-created)
|
||||
store.create_and_save_xmodule(location)
|
||||
module = store.get_item(location)
|
||||
|
||||
if post_data.get('data') is not None:
|
||||
data = post_data['data']
|
||||
@@ -79,4 +77,4 @@ def set_module_info(store, location, post_data):
|
||||
|
||||
# commit to datastore
|
||||
# TODO (cpennington): This really shouldn't have to do this much reaching in to get the metadata
|
||||
store.update_metadata(location, module._model_data._kvs._metadata)
|
||||
store.update_metadata(location, module.xblock_kvs._metadata)
|
||||
|
||||
@@ -24,12 +24,11 @@ from auth.authz import add_user_to_creator_group
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore import Location, mongo
|
||||
from xmodule.modulestore.store_utilities import clone_course
|
||||
from xmodule.modulestore.store_utilities import delete_course
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.contentstore.django import contentstore, _CONTENTSTORE
|
||||
from xmodule.templates import update_templates
|
||||
from xmodule.modulestore.xml_exporter import export_to_xml
|
||||
from xmodule.modulestore.xml_importer import import_from_xml, perform_xlint
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
@@ -135,7 +134,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):
|
||||
@@ -183,7 +182,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
|
||||
html_module = draft_store.get_item(['i4x', 'edX', 'simple', 'html', 'test_html', None])
|
||||
|
||||
draft_store.clone_item(html_module.location, html_module.location)
|
||||
draft_store.convert_to_draft(html_module.location)
|
||||
|
||||
# now query get_items() to get this location with revision=None, this should just
|
||||
# return back a single item (not 2)
|
||||
@@ -215,7 +214,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.assertEqual(html_module.lms.graceperiod, course.lms.graceperiod)
|
||||
self.assertNotIn('graceperiod', own_metadata(html_module))
|
||||
|
||||
draft_store.clone_item(html_module.location, html_module.location)
|
||||
draft_store.convert_to_draft(html_module.location)
|
||||
|
||||
# refetch to check metadata
|
||||
html_module = draft_store.get_item(['i4x', 'edX', 'simple', 'html', 'test_html', None])
|
||||
@@ -233,7 +232,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
self.assertNotIn('graceperiod', own_metadata(html_module))
|
||||
|
||||
# put back in draft and change metadata and see if it's now marked as 'own_metadata'
|
||||
draft_store.clone_item(html_module.location, html_module.location)
|
||||
draft_store.convert_to_draft(html_module.location)
|
||||
html_module = draft_store.get_item(['i4x', 'edX', 'simple', 'html', 'test_html', None])
|
||||
|
||||
new_graceperiod = timedelta(hours=1)
|
||||
@@ -255,7 +254,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
draft_store.publish(html_module.location, 0)
|
||||
|
||||
# and re-read and verify 'own-metadata'
|
||||
draft_store.clone_item(html_module.location, html_module.location)
|
||||
draft_store.convert_to_draft(html_module.location)
|
||||
html_module = draft_store.get_item(['i4x', 'edX', 'simple', 'html', 'test_html', None])
|
||||
|
||||
self.assertIn('graceperiod', own_metadata(html_module))
|
||||
@@ -278,7 +277,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
)
|
||||
|
||||
# put into draft
|
||||
modulestore('draft').clone_item(problem.location, problem.location)
|
||||
modulestore('draft').convert_to_draft(problem.location)
|
||||
|
||||
# make sure we can query that item and verify that it is a draft
|
||||
draft_problem = modulestore('draft').get_item(
|
||||
@@ -309,12 +308,14 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
CourseFactory.create(org='edX', course='999', display_name='Robot Super Course')
|
||||
course_location = Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None])
|
||||
|
||||
ItemFactory.create(parent_location=course_location,
|
||||
template="i4x://edx/templates/static_tab/Empty",
|
||||
display_name="Static_1")
|
||||
ItemFactory.create(parent_location=course_location,
|
||||
template="i4x://edx/templates/static_tab/Empty",
|
||||
display_name="Static_2")
|
||||
ItemFactory.create(
|
||||
parent_location=course_location,
|
||||
category="static_tab",
|
||||
display_name="Static_1")
|
||||
ItemFactory.create(
|
||||
parent_location=course_location,
|
||||
category="static_tab",
|
||||
display_name="Static_2")
|
||||
|
||||
course = module_store.get_item(Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None]))
|
||||
|
||||
@@ -371,7 +372,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
course_location = Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None])
|
||||
|
||||
chapterloc = ItemFactory.create(parent_location=course_location, display_name="Chapter").location
|
||||
ItemFactory.create(parent_location=chapterloc, template='i4x://edx/templates/sequential/Empty', display_name="Sequential")
|
||||
ItemFactory.create(parent_location=chapterloc, category='sequential', display_name="Sequential")
|
||||
|
||||
sequential = direct_store.get_item(Location(['i4x', 'edX', '999', 'sequential', 'Sequential', None]))
|
||||
chapter = direct_store.get_item(Location(['i4x', 'edX', '999', 'chapter', 'Chapter', None]))
|
||||
@@ -574,7 +575,6 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
def test_clone_course(self):
|
||||
|
||||
course_data = {
|
||||
'template': 'i4x://edx/templates/course/Empty',
|
||||
'org': 'MITx',
|
||||
'number': '999',
|
||||
'display_name': 'Robot Super Course',
|
||||
@@ -614,10 +614,10 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||
|
||||
location = Location('i4x://MITx/999/chapter/neuvo')
|
||||
self.assertRaises(InvalidVersionError, draft_store.clone_item, 'i4x://edx/templates/chapter/Empty',
|
||||
location)
|
||||
direct_store.clone_item('i4x://edx/templates/chapter/Empty', location)
|
||||
self.assertRaises(InvalidVersionError, draft_store.clone_item, location, location)
|
||||
# Ensure draft mongo store does not allow us to create chapters either directly or via convert to draft
|
||||
self.assertRaises(InvalidVersionError, draft_store.create_and_save_xmodule, location)
|
||||
direct_store.create_and_save_xmodule(location)
|
||||
self.assertRaises(InvalidVersionError, draft_store.convert_to_draft, location)
|
||||
|
||||
self.assertRaises(InvalidVersionError, draft_store.update_item, location, 'chapter data')
|
||||
|
||||
@@ -652,9 +652,9 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
vertical = module_store.get_item(Location(['i4x', 'edX', 'toy',
|
||||
'vertical', 'vertical_test', None]), depth=1)
|
||||
|
||||
draft_store.clone_item(vertical.location, vertical.location)
|
||||
draft_store.convert_to_draft(vertical.location)
|
||||
for child in vertical.get_children():
|
||||
draft_store.clone_item(child.location, child.location)
|
||||
draft_store.convert_to_draft(child.location)
|
||||
|
||||
# delete the course
|
||||
delete_course(module_store, content_store, location, commit=True)
|
||||
@@ -687,26 +687,35 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
import_from_xml(module_store, 'common/test/data/', ['toy'])
|
||||
location = CourseDescriptor.id_to_location('edX/toy/2012_Fall')
|
||||
|
||||
# get a vertical (and components in it) to put into 'draft'
|
||||
vertical = module_store.get_item(Location(['i4x', 'edX', 'toy',
|
||||
'vertical', 'vertical_test', None]), depth=1)
|
||||
|
||||
draft_store.clone_item(vertical.location, vertical.location)
|
||||
|
||||
# get a vertical (and components in it) to copy into an orphan sub dag
|
||||
vertical = module_store.get_item(
|
||||
Location(['i4x', 'edX', 'toy', 'vertical', 'vertical_test', None]),
|
||||
depth=1
|
||||
)
|
||||
# We had a bug where orphaned draft nodes caused export to fail. This is here to cover that case.
|
||||
draft_store.clone_item(vertical.location, Location(['i4x', 'edX', 'toy',
|
||||
'vertical', 'no_references', 'draft']))
|
||||
vertical.location = mongo.draft.as_draft(vertical.location.replace(name='no_references'))
|
||||
draft_store.save_xmodule(vertical)
|
||||
orphan_vertical = draft_store.get_item(vertical.location)
|
||||
self.assertEqual(orphan_vertical.location.name, 'no_references')
|
||||
|
||||
# get the original vertical (and components in it) to put into 'draft'
|
||||
vertical = module_store.get_item(
|
||||
Location(['i4x', 'edX', 'toy', 'vertical', 'vertical_test', None]),
|
||||
depth=1)
|
||||
self.assertEqual(len(orphan_vertical.children), len(vertical.children))
|
||||
draft_store.convert_to_draft(vertical.location)
|
||||
for child in vertical.get_children():
|
||||
draft_store.clone_item(child.location, child.location)
|
||||
draft_store.convert_to_draft(child.location)
|
||||
|
||||
root_dir = path(mkdtemp_clean())
|
||||
|
||||
# now create a private vertical
|
||||
private_vertical = draft_store.clone_item(vertical.location,
|
||||
Location(['i4x', 'edX', 'toy', 'vertical', 'a_private_vertical', None]))
|
||||
# now create a new/different private (draft only) vertical
|
||||
vertical.location = mongo.draft.as_draft(Location(['i4x', 'edX', 'toy', 'vertical', 'a_private_vertical', None]))
|
||||
draft_store.save_xmodule(vertical)
|
||||
private_vertical = draft_store.get_item(vertical.location)
|
||||
vertical = None # blank out b/c i destructively manipulated its location 2 lines above
|
||||
|
||||
# add private to list of children
|
||||
# add the new private to list of children
|
||||
sequential = module_store.get_item(Location(['i4x', 'edX', 'toy',
|
||||
'sequential', 'vertical_sequential', None]))
|
||||
private_location_no_draft = private_vertical.location.replace(revision=None)
|
||||
@@ -792,6 +801,34 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
|
||||
|
||||
shutil.rmtree(root_dir)
|
||||
|
||||
def test_export_course_with_metadata_only_video(self):
|
||||
module_store = modulestore('direct')
|
||||
draft_store = modulestore('draft')
|
||||
content_store = contentstore()
|
||||
|
||||
import_from_xml(module_store, 'common/test/data/', ['toy'])
|
||||
location = CourseDescriptor.id_to_location('edX/toy/2012_Fall')
|
||||
|
||||
# create a new video module and add it as a child to a vertical
|
||||
# this re-creates a bug whereby since the video template doesn't have
|
||||
# anything in 'data' field, the export was blowing up
|
||||
verticals = module_store.get_items(['i4x', 'edX', 'toy', 'vertical', None, None])
|
||||
|
||||
self.assertGreater(len(verticals), 0)
|
||||
|
||||
parent = verticals[0]
|
||||
|
||||
ItemFactory.create(parent_location=parent.location, category="video", display_name="untitled")
|
||||
|
||||
root_dir = path(mkdtemp_clean())
|
||||
|
||||
print 'Exporting to tempdir = {0}'.format(root_dir)
|
||||
|
||||
# export out to a tempdir
|
||||
export_to_xml(module_store, content_store, location, root_dir, 'test_export', draft_modulestore=draft_store)
|
||||
|
||||
shutil.rmtree(root_dir)
|
||||
|
||||
def test_course_handouts_rewrites(self):
|
||||
module_store = modulestore('direct')
|
||||
|
||||
@@ -885,7 +922,6 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
self.client.login(username=uname, password=password)
|
||||
|
||||
self.course_data = {
|
||||
'template': 'i4x://edx/templates/course/Empty',
|
||||
'org': 'MITx',
|
||||
'number': '999',
|
||||
'display_name': 'Robot Super Course',
|
||||
@@ -1029,17 +1065,17 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
html=True
|
||||
)
|
||||
|
||||
def test_clone_item(self):
|
||||
def test_create_item(self):
|
||||
"""Test cloning an item. E.g. creating a new section"""
|
||||
CourseFactory.create(org='MITx', course='999', display_name='Robot Super Course')
|
||||
|
||||
section_data = {
|
||||
'parent_location': 'i4x://MITx/999/course/Robot_Super_Course',
|
||||
'template': 'i4x://edx/templates/chapter/Empty',
|
||||
'category': 'chapter',
|
||||
'display_name': 'Section One',
|
||||
}
|
||||
|
||||
resp = self.client.post(reverse('clone_item'), section_data)
|
||||
resp = self.client.post(reverse('create_item'), section_data)
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
data = parse_json(resp)
|
||||
@@ -1054,14 +1090,14 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
problem_data = {
|
||||
'parent_location': 'i4x://MITx/999/course/Robot_Super_Course',
|
||||
'template': 'i4x://edx/templates/problem/Blank_Common_Problem'
|
||||
'category': 'problem'
|
||||
}
|
||||
|
||||
resp = self.client.post(reverse('clone_item'), problem_data)
|
||||
resp = self.client.post(reverse('create_item'), problem_data)
|
||||
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
payload = parse_json(resp)
|
||||
problem_loc = payload['id']
|
||||
problem_loc = Location(payload['id'])
|
||||
problem = get_modulestore(problem_loc).get_item(problem_loc)
|
||||
# should be a CapaDescriptor
|
||||
self.assertIsInstance(problem, CapaDescriptor, "New problem is not a CapaDescriptor")
|
||||
@@ -1194,10 +1230,9 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
CourseFactory.create(org='edX', course='999', display_name='Robot Super Course')
|
||||
|
||||
new_component_location = Location('i4x', 'edX', '999', 'discussion', 'new_component')
|
||||
source_template_location = Location('i4x', 'edx', 'templates', 'discussion', 'Discussion_Tag')
|
||||
|
||||
# crate a new module and add it as a child to a vertical
|
||||
module_store.clone_item(source_template_location, new_component_location)
|
||||
module_store.create_and_save_xmodule(new_component_location)
|
||||
|
||||
new_discussion_item = module_store.get_item(new_component_location)
|
||||
|
||||
@@ -1218,10 +1253,9 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
module_store.modulestore_update_signal.connect(_signal_hander)
|
||||
|
||||
new_component_location = Location('i4x', 'edX', '999', 'html', 'new_component')
|
||||
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
|
||||
|
||||
# crate a new module
|
||||
module_store.clone_item(source_template_location, new_component_location)
|
||||
module_store.create_and_save_xmodule(new_component_location)
|
||||
|
||||
finally:
|
||||
module_store.modulestore_update_signal = None
|
||||
@@ -1239,14 +1273,14 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
# let's assert on the metadata_inheritance on an existing vertical
|
||||
for vertical in verticals:
|
||||
self.assertEqual(course.lms.xqa_key, vertical.lms.xqa_key)
|
||||
self.assertEqual(course.start, vertical.lms.start)
|
||||
|
||||
self.assertGreater(len(verticals), 0)
|
||||
|
||||
new_component_location = Location('i4x', 'edX', 'toy', 'html', 'new_component')
|
||||
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
|
||||
|
||||
# crate a new module and add it as a child to a vertical
|
||||
module_store.clone_item(source_template_location, new_component_location)
|
||||
module_store.create_and_save_xmodule(new_component_location)
|
||||
parent = verticals[0]
|
||||
module_store.update_children(parent.location, parent.children + [new_component_location.url()])
|
||||
|
||||
@@ -1256,6 +1290,8 @@ class ContentStoreTest(ModuleStoreTestCase):
|
||||
|
||||
# check for grace period definition which should be defined at the course level
|
||||
self.assertEqual(parent.lms.graceperiod, new_module.lms.graceperiod)
|
||||
self.assertEqual(parent.lms.start, new_module.lms.start)
|
||||
self.assertEqual(course.start, new_module.lms.start)
|
||||
|
||||
self.assertEqual(course.lms.xqa_key, new_module.lms.xqa_key)
|
||||
|
||||
@@ -1271,29 +1307,25 @@ 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)
|
||||
|
||||
class TemplateTestCase(ModuleStoreTestCase):
|
||||
|
||||
def test_template_cleanup(self):
|
||||
# by fetching
|
||||
module_store = modulestore('direct')
|
||||
|
||||
# insert a bogus template in the store
|
||||
bogus_template_location = Location('i4x', 'edx', 'templates', 'html', 'bogus')
|
||||
source_template_location = Location('i4x', 'edx', 'templates', 'html', 'Blank_HTML_Page')
|
||||
|
||||
module_store.clone_item(source_template_location, bogus_template_location)
|
||||
|
||||
verify_create = module_store.get_item(bogus_template_location)
|
||||
self.assertIsNotNone(verify_create)
|
||||
|
||||
# now run cleanup
|
||||
update_templates(modulestore('direct'))
|
||||
|
||||
# now try to find dangling template, it should not be in DB any longer
|
||||
asserted = False
|
||||
try:
|
||||
verify_create = module_store.get_item(bogus_template_location)
|
||||
except ItemNotFoundError:
|
||||
asserted = True
|
||||
|
||||
self.assertTrue(asserted)
|
||||
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)
|
||||
|
||||
@@ -19,6 +19,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
from models.settings.course_metadata import CourseMetadata
|
||||
from xmodule.modulestore.xml_importer import import_from_xml
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.fields import Date
|
||||
|
||||
from .utils import CourseTestCase
|
||||
@@ -36,7 +37,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 +49,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")
|
||||
|
||||
@@ -352,7 +351,7 @@ class CourseMetadataEditingTest(CourseTestCase):
|
||||
self.assertEqual(test_model['display_name'], 'Robot Super Course', "not expected value")
|
||||
self.assertIn('rerandomize', test_model, 'Missing rerandomize metadata field')
|
||||
# check for deletion effectiveness
|
||||
self.assertEqual('closed', test_model['showanswer'], 'showanswer field still in')
|
||||
self.assertEqual('finished', test_model['showanswer'], 'showanswer field still in')
|
||||
self.assertEqual(None, test_model['xqa_key'], 'xqa_key field still in')
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ class InternationalizationTest(ModuleStoreTestCase):
|
||||
self.user.save()
|
||||
|
||||
self.course_data = {
|
||||
'template': 'i4x://edx/templates/course/Empty',
|
||||
'org': 'MITx',
|
||||
'number': '999',
|
||||
'display_name': 'Robot Super Course',
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from contentstore.tests.test_course_settings import CourseTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from django.core.urlresolvers import reverse
|
||||
from xmodule.capa_module import CapaDescriptor
|
||||
import json
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
class DeleteItem(CourseTestCase):
|
||||
@@ -11,14 +14,199 @@ class DeleteItem(CourseTestCase):
|
||||
|
||||
def testDeleteStaticPage(self):
|
||||
# Add static tab
|
||||
data = {
|
||||
data = json.dumps({
|
||||
'parent_location': 'i4x://mitX/333/course/Dummy_Course',
|
||||
'template': 'i4x://edx/templates/static_tab/Empty'
|
||||
}
|
||||
'category': 'static_tab'
|
||||
})
|
||||
|
||||
resp = self.client.post(reverse('clone_item'), data)
|
||||
resp = self.client.post(reverse('create_item'), data,
|
||||
content_type="application/json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# Now delete it. There was a bug that the delete was failing (static tabs do not exist in draft modulestore).
|
||||
resp = self.client.post(reverse('delete_item'), resp.content, "application/json")
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
|
||||
class TestCreateItem(CourseTestCase):
|
||||
"""
|
||||
Test the create_item handler thoroughly
|
||||
"""
|
||||
def response_id(self, response):
|
||||
"""
|
||||
Get the id from the response payload
|
||||
:param response:
|
||||
"""
|
||||
parsed = json.loads(response.content)
|
||||
return parsed['id']
|
||||
|
||||
def test_create_nicely(self):
|
||||
"""
|
||||
Try the straightforward use cases
|
||||
"""
|
||||
# create a chapter
|
||||
display_name = 'Nicely created'
|
||||
resp = self.client.post(
|
||||
reverse('create_item'),
|
||||
json.dumps({
|
||||
'parent_location': self.course.location.url(),
|
||||
'display_name': display_name,
|
||||
'category': 'chapter'
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
# get the new item and check its category and display_name
|
||||
chap_location = self.response_id(resp)
|
||||
new_obj = modulestore().get_item(chap_location)
|
||||
self.assertEqual(new_obj.category, 'chapter')
|
||||
self.assertEqual(new_obj.display_name, display_name)
|
||||
self.assertEqual(new_obj.location.org, self.course.location.org)
|
||||
self.assertEqual(new_obj.location.course, self.course.location.course)
|
||||
|
||||
# get the course and ensure it now points to this one
|
||||
course = modulestore().get_item(self.course.location)
|
||||
self.assertIn(chap_location, course.children)
|
||||
|
||||
# use default display name
|
||||
resp = self.client.post(
|
||||
reverse('create_item'),
|
||||
json.dumps({
|
||||
'parent_location': chap_location,
|
||||
'category': 'vertical'
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
vert_location = self.response_id(resp)
|
||||
|
||||
# create problem w/ boilerplate
|
||||
template_id = 'multiplechoice.yaml'
|
||||
resp = self.client.post(
|
||||
reverse('create_item'),
|
||||
json.dumps({
|
||||
'parent_location': vert_location,
|
||||
'category': 'problem',
|
||||
'boilerplate': template_id
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
prob_location = self.response_id(resp)
|
||||
problem = modulestore('draft').get_item(prob_location)
|
||||
# ensure it's draft
|
||||
self.assertTrue(problem.is_draft)
|
||||
# check against the template
|
||||
template = CapaDescriptor.get_template(template_id)
|
||||
self.assertEqual(problem.data, template['data'])
|
||||
self.assertEqual(problem.display_name, template['metadata']['display_name'])
|
||||
self.assertEqual(problem.markdown, template['metadata']['markdown'])
|
||||
|
||||
def test_create_item_negative(self):
|
||||
"""
|
||||
Negative tests for create_item
|
||||
"""
|
||||
# non-existent boilerplate: creates a default
|
||||
resp = self.client.post(
|
||||
reverse('create_item'),
|
||||
json.dumps(
|
||||
{'parent_location': self.course.location.url(),
|
||||
'category': 'problem',
|
||||
'boilerplate': 'nosuchboilerplate.yaml'
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
class TestEditItem(CourseTestCase):
|
||||
"""
|
||||
Test contentstore.views.item.save_item
|
||||
"""
|
||||
def response_id(self, response):
|
||||
"""
|
||||
Get the id from the response payload
|
||||
:param response:
|
||||
"""
|
||||
parsed = json.loads(response.content)
|
||||
return parsed['id']
|
||||
|
||||
def setUp(self):
|
||||
""" Creates the test course structure and a couple problems to 'edit'. """
|
||||
super(TestEditItem, self).setUp()
|
||||
# create a chapter
|
||||
display_name = 'chapter created'
|
||||
resp = self.client.post(
|
||||
reverse('create_item'),
|
||||
json.dumps(
|
||||
{'parent_location': self.course.location.url(),
|
||||
'display_name': display_name,
|
||||
'category': 'chapter'
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
chap_location = self.response_id(resp)
|
||||
resp = self.client.post(
|
||||
reverse('create_item'),
|
||||
json.dumps(
|
||||
{'parent_location': chap_location,
|
||||
'category': 'vertical'
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
vert_location = self.response_id(resp)
|
||||
# create problem w/ boilerplate
|
||||
template_id = 'multiplechoice.yaml'
|
||||
resp = self.client.post(
|
||||
reverse('create_item'),
|
||||
json.dumps({'parent_location': vert_location,
|
||||
'category': 'problem',
|
||||
'boilerplate': template_id
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
self.problems = [self.response_id(resp)]
|
||||
|
||||
def test_delete_field(self):
|
||||
"""
|
||||
Sending null in for a field 'deletes' it
|
||||
"""
|
||||
self.client.post(
|
||||
reverse('save_item'),
|
||||
json.dumps({
|
||||
'id': self.problems[0],
|
||||
'metadata': {'rerandomize': 'onreset'}
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
problem = modulestore('draft').get_item(self.problems[0])
|
||||
self.assertEqual(problem.rerandomize, 'onreset')
|
||||
self.client.post(
|
||||
reverse('save_item'),
|
||||
json.dumps({
|
||||
'id': self.problems[0],
|
||||
'metadata': {'rerandomize': None}
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
problem = modulestore('draft').get_item(self.problems[0])
|
||||
self.assertEqual(problem.rerandomize, 'never')
|
||||
|
||||
|
||||
def test_null_field(self):
|
||||
"""
|
||||
Sending null in for a field 'deletes' it
|
||||
"""
|
||||
problem = modulestore('draft').get_item(self.problems[0])
|
||||
self.assertIsNotNone(problem.markdown)
|
||||
self.client.post(
|
||||
reverse('save_item'),
|
||||
json.dumps({
|
||||
'id': self.problems[0],
|
||||
'nullout': ['markdown']
|
||||
}),
|
||||
content_type="application/json"
|
||||
)
|
||||
problem = modulestore('draft').get_item(self.problems[0])
|
||||
self.assertIsNone(problem.markdown)
|
||||
|
||||
@@ -54,7 +54,6 @@ class CourseTestCase(ModuleStoreTestCase):
|
||||
self.client.login(username=uname, password=password)
|
||||
|
||||
self.course = CourseFactory.create(
|
||||
template='i4x://edx/templates/course/Empty',
|
||||
org='MITx',
|
||||
number='999',
|
||||
display_name='Robot Super Course',
|
||||
|
||||
@@ -9,23 +9,24 @@ import copy
|
||||
import logging
|
||||
import re
|
||||
from xmodule.modulestore.draft import DIRECT_ONLY_CATEGORIES
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# In order to instantiate an open ended tab automatically, need to have this data
|
||||
OPEN_ENDED_PANEL = {"name": "Open Ended Panel", "type": "open_ended"}
|
||||
NOTES_PANEL = {"name": "My Notes", "type": "notes"}
|
||||
OPEN_ENDED_PANEL = {"name": _("Open Ended Panel"), "type": "open_ended"}
|
||||
NOTES_PANEL = {"name": _("My Notes"), "type": "notes"}
|
||||
EXTRA_TAB_PANELS = dict([(p['type'], p) for p in [OPEN_ENDED_PANEL, NOTES_PANEL]])
|
||||
|
||||
|
||||
def get_modulestore(location):
|
||||
def get_modulestore(category_or_location):
|
||||
"""
|
||||
Returns the correct modulestore to use for modifying the specified location
|
||||
"""
|
||||
if not isinstance(location, Location):
|
||||
location = Location(location)
|
||||
if isinstance(category_or_location, Location):
|
||||
category_or_location = category_or_location.category
|
||||
|
||||
if location.category in DIRECT_ONLY_CATEGORIES:
|
||||
if category_or_location in DIRECT_ONLY_CATEGORIES:
|
||||
return modulestore('direct')
|
||||
else:
|
||||
return modulestore()
|
||||
|
||||
@@ -7,11 +7,11 @@ from django.views.decorators.http import require_http_methods
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
from xmodule.modulestore.inheritance import own_metadata
|
||||
|
||||
from ..utils import get_modulestore, get_url_reverse
|
||||
from .access import get_location_and_verify_access
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
|
||||
__all__ = ['get_checklists', 'update_checklist']
|
||||
|
||||
@@ -28,13 +28,11 @@ def get_checklists(request, org, course, name):
|
||||
|
||||
modulestore = get_modulestore(location)
|
||||
course_module = modulestore.get_item(location)
|
||||
new_course_template = Location('i4x', 'edx', 'templates', 'course', 'Empty')
|
||||
template_module = modulestore.get_item(new_course_template)
|
||||
|
||||
# If course was created before checklists were introduced, copy them over from the template.
|
||||
copied = False
|
||||
if not course_module.checklists:
|
||||
course_module.checklists = template_module.checklists
|
||||
course_module.checklists = CourseDescriptor.checklists.default
|
||||
copied = True
|
||||
|
||||
checklists, modified = expand_checklist_action_urls(course_module)
|
||||
|
||||
@@ -26,6 +26,8 @@ from models.settings.course_grading import CourseGradingModel
|
||||
|
||||
from .requests import _xmodule_recurse
|
||||
from .access import has_access
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
from xblock.plugin import PluginMissingError
|
||||
|
||||
__all__ = ['OPEN_ENDED_COMPONENT_TYPES',
|
||||
'ADVANCED_COMPONENT_POLICY_KEY',
|
||||
@@ -101,7 +103,7 @@ def edit_subsection(request, location):
|
||||
return render_to_response('edit_subsection.html',
|
||||
{'subsection': item,
|
||||
'context_course': course,
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
|
||||
'new_unit_category': 'vertical',
|
||||
'lms_link': lms_link,
|
||||
'preview_link': preview_link,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
@@ -134,10 +136,26 @@ def edit_unit(request, location):
|
||||
item = modulestore().get_item(location, depth=1)
|
||||
except ItemNotFoundError:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
lms_link = get_lms_link_for_item(item.location, course_id=course.location.course_id)
|
||||
|
||||
component_templates = defaultdict(list)
|
||||
for category in COMPONENT_TYPES:
|
||||
component_class = XModuleDescriptor.load_class(category)
|
||||
# add the default template
|
||||
component_templates[category].append((
|
||||
component_class.display_name.default or 'Blank',
|
||||
category,
|
||||
False, # No defaults have markdown (hardcoded current default)
|
||||
None # no boilerplate for overrides
|
||||
))
|
||||
# add boilerplates
|
||||
for template in component_class.templates():
|
||||
component_templates[category].append((
|
||||
template['metadata'].get('display_name'),
|
||||
category,
|
||||
template['metadata'].get('markdown') is not None,
|
||||
template.get('template_id')
|
||||
))
|
||||
|
||||
# Check if there are any advanced modules specified in the course policy. These modules
|
||||
# should be specified as a list of strings, where the strings are the names of the modules
|
||||
@@ -145,29 +163,29 @@ def edit_unit(request, location):
|
||||
course_advanced_keys = course.advanced_modules
|
||||
|
||||
# Set component types according to course policy file
|
||||
component_types = list(COMPONENT_TYPES)
|
||||
if isinstance(course_advanced_keys, list):
|
||||
course_advanced_keys = [c for c in course_advanced_keys if c in ADVANCED_COMPONENT_TYPES]
|
||||
if len(course_advanced_keys) > 0:
|
||||
component_types.append(ADVANCED_COMPONENT_CATEGORY)
|
||||
for category in course_advanced_keys:
|
||||
if category in ADVANCED_COMPONENT_TYPES:
|
||||
# Do I need to allow for boilerplates or just defaults on the class? i.e., can an advanced
|
||||
# have more than one entry in the menu? one for default and others for prefilled boilerplates?
|
||||
try:
|
||||
component_class = XModuleDescriptor.load_class(category)
|
||||
|
||||
component_templates['advanced'].append((
|
||||
component_class.display_name.default or category,
|
||||
category,
|
||||
False,
|
||||
None # don't override default data
|
||||
))
|
||||
except PluginMissingError:
|
||||
# dhm: I got this once but it can happen any time the course author configures
|
||||
# an advanced component which does not exist on the server. This code here merely
|
||||
# prevents any authors from trying to instantiate the non-existent component type
|
||||
# by not showing it in the menu
|
||||
pass
|
||||
else:
|
||||
log.error("Improper format for course advanced keys! {0}".format(course_advanced_keys))
|
||||
|
||||
templates = modulestore().get_items(Location('i4x', 'edx', 'templates'))
|
||||
for template in templates:
|
||||
category = template.location.category
|
||||
|
||||
if category in course_advanced_keys:
|
||||
category = ADVANCED_COMPONENT_CATEGORY
|
||||
|
||||
if category in component_types:
|
||||
# This is a hack to create categories for different xmodules
|
||||
component_templates[category].append((
|
||||
template.display_name_with_default,
|
||||
template.location.url(),
|
||||
hasattr(template, 'markdown') and template.markdown is not None
|
||||
))
|
||||
|
||||
components = [
|
||||
component.location.url()
|
||||
for component
|
||||
@@ -219,7 +237,7 @@ def edit_unit(request, location):
|
||||
'subsection': containing_subsection,
|
||||
'release_date': get_default_time_display(containing_subsection.lms.start) if containing_subsection.lms.start is not None else None,
|
||||
'section': containing_section,
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty'),
|
||||
'new_unit_category': 'vertical',
|
||||
'unit_state': unit_state,
|
||||
'published_date': get_default_time_display(item.cms.published_date) if item.cms.published_date is not None else None
|
||||
})
|
||||
@@ -253,7 +271,7 @@ def create_draft(request):
|
||||
|
||||
# This clones the existing item location to a draft location (the draft is implicit,
|
||||
# because modulestore is a Draft modulestore)
|
||||
modulestore().clone_item(location, location)
|
||||
modulestore().convert_to_draft(location)
|
||||
|
||||
return HttpResponse()
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ from .component import (
|
||||
from django_comment_common.utils import seed_permissions_roles
|
||||
import datetime
|
||||
from django.utils.timezone import UTC
|
||||
from xmodule.html_module import AboutDescriptor
|
||||
__all__ = ['course_index', 'create_new_course', 'course_info',
|
||||
'course_info_updates', 'get_course_settings',
|
||||
'course_config_graders_page',
|
||||
@@ -82,10 +83,11 @@ def course_index(request, org, course, name):
|
||||
'sections': sections,
|
||||
'course_graders': json.dumps(CourseGradingModel.fetch(course.location).graders),
|
||||
'parent_location': course.location,
|
||||
'new_section_template': Location('i4x', 'edx', 'templates', 'chapter', 'Empty'),
|
||||
'new_subsection_template': Location('i4x', 'edx', 'templates', 'sequential', 'Empty'), # for now they are the same, but the could be different at some point...
|
||||
'new_section_category': 'chapter',
|
||||
'new_subsection_category': 'sequential',
|
||||
'upload_asset_callback_url': upload_asset_callback_url,
|
||||
'create_new_unit_template': Location('i4x', 'edx', 'templates', 'vertical', 'Empty')
|
||||
'new_unit_category': 'vertical',
|
||||
'category': 'vertical'
|
||||
})
|
||||
|
||||
|
||||
@@ -98,12 +100,6 @@ def create_new_course(request):
|
||||
if not is_user_in_creator_group(request.user):
|
||||
raise PermissionDenied()
|
||||
|
||||
# This logic is repeated in xmodule/modulestore/tests/factories.py
|
||||
# so if you change anything here, you need to also change it there.
|
||||
# TODO: write a test that creates two courses, one with the factory and
|
||||
# the other with this method, then compare them to make sure they are
|
||||
# equivalent.
|
||||
template = Location(request.POST['template'])
|
||||
org = request.POST.get('org')
|
||||
number = request.POST.get('number')
|
||||
display_name = request.POST.get('display_name')
|
||||
@@ -121,29 +117,31 @@ def create_new_course(request):
|
||||
existing_course = modulestore('direct').get_item(dest_location)
|
||||
except ItemNotFoundError:
|
||||
pass
|
||||
|
||||
if existing_course is not None:
|
||||
return JsonResponse({'ErrMsg': 'There is already a course defined with this name.'})
|
||||
|
||||
course_search_location = ['i4x', dest_location.org, dest_location.course, 'course', None]
|
||||
courses = modulestore().get_items(course_search_location)
|
||||
|
||||
if len(courses) > 0:
|
||||
return JsonResponse({'ErrMsg': 'There is already a course defined with the same organization and course number.'})
|
||||
|
||||
new_course = modulestore('direct').clone_item(template, dest_location)
|
||||
# instantiate the CourseDescriptor and then persist it
|
||||
# note: no system to pass
|
||||
if display_name is None:
|
||||
metadata = {}
|
||||
else:
|
||||
metadata = {'display_name': display_name}
|
||||
modulestore('direct').create_and_save_xmodule(dest_location, metadata=metadata)
|
||||
new_course = modulestore('direct').get_item(dest_location)
|
||||
|
||||
# clone a default 'about' module as well
|
||||
|
||||
about_template_location = Location(['i4x', 'edx', 'templates', 'about', 'overview'])
|
||||
dest_about_location = dest_location._replace(category='about', name='overview')
|
||||
modulestore('direct').clone_item(about_template_location, dest_about_location)
|
||||
|
||||
if display_name is not None:
|
||||
new_course.display_name = display_name
|
||||
|
||||
# set a default start date to now
|
||||
new_course.start = datetime.datetime.now(UTC())
|
||||
# clone a default 'about' overview module as well
|
||||
dest_about_location = dest_location.replace(category='about', name='overview')
|
||||
overview_template = AboutDescriptor.get_template('overview.yaml')
|
||||
modulestore('direct').create_and_save_xmodule(
|
||||
dest_about_location,
|
||||
system=new_course.system,
|
||||
definition_data=overview_template.get('data')
|
||||
)
|
||||
|
||||
initialize_course_tabs(new_course)
|
||||
|
||||
|
||||
@@ -13,16 +13,26 @@ from util.json_request import expect_json
|
||||
from ..utils import get_modulestore
|
||||
from .access import has_access
|
||||
from .requests import _xmodule_recurse
|
||||
from xmodule.x_module import XModuleDescriptor
|
||||
|
||||
__all__ = ['save_item', 'clone_item', 'delete_item']
|
||||
__all__ = ['save_item', 'create_item', 'delete_item']
|
||||
|
||||
# cdodge: these are categories which should not be parented, they are detached from the hierarchy
|
||||
DETACHED_CATEGORIES = ['about', 'static_tab', 'course_info']
|
||||
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def save_item(request):
|
||||
"""
|
||||
Will carry a json payload with these possible fields
|
||||
:id (required): the id
|
||||
:data (optional): the new value for the data
|
||||
:metadata (optional): new values for the metadata fields.
|
||||
Any whose values are None will be deleted not set to None! Absent ones will be left alone
|
||||
:nullout (optional): which metadata fields to set to None
|
||||
"""
|
||||
# The nullout is a bit of a temporary copout until we can make module_edit.coffee and the metadata editors a
|
||||
# little smarter and able to pass something more akin to {unset: [field, field]}
|
||||
item_location = request.POST['id']
|
||||
|
||||
# check permissions for this user within this course
|
||||
@@ -42,30 +52,25 @@ def save_item(request):
|
||||
children = request.POST['children']
|
||||
store.update_children(item_location, children)
|
||||
|
||||
# cdodge: also commit any metadata which might have been passed along in the
|
||||
# POST from the client, if it is there
|
||||
# NOTE, that the postback is not the complete metadata, as there's system metadata which is
|
||||
# not presented to the end-user for editing. So let's fetch the original and
|
||||
# 'apply' the submitted metadata, so we don't end up deleting system metadata
|
||||
if request.POST.get('metadata') is not None:
|
||||
posted_metadata = request.POST['metadata']
|
||||
# fetch original
|
||||
# cdodge: also commit any metadata which might have been passed along
|
||||
if request.POST.get('nullout') is not None or request.POST.get('metadata') is not None:
|
||||
# the postback is not the complete metadata, as there's system metadata which is
|
||||
# not presented to the end-user for editing. So let's fetch the original and
|
||||
# 'apply' the submitted metadata, so we don't end up deleting system metadata
|
||||
existing_item = modulestore().get_item(item_location)
|
||||
for metadata_key in request.POST.get('nullout', []):
|
||||
setattr(existing_item, metadata_key, None)
|
||||
|
||||
# update existing metadata with submitted metadata (which can be partial)
|
||||
# IMPORTANT NOTE: if the client passed pack 'null' (None) for a piece of metadata that means 'remove it'
|
||||
for metadata_key, value in posted_metadata.items():
|
||||
# IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
|
||||
# the intent is to make it None, use the nullout field
|
||||
for metadata_key, value in request.POST.get('metadata', {}).items():
|
||||
|
||||
if posted_metadata[metadata_key] is None:
|
||||
# remove both from passed in collection as well as the collection read in from the modulestore
|
||||
if metadata_key in existing_item._model_data:
|
||||
del existing_item._model_data[metadata_key]
|
||||
del posted_metadata[metadata_key]
|
||||
if value is None:
|
||||
delattr(existing_item, metadata_key)
|
||||
else:
|
||||
existing_item._model_data[metadata_key] = value
|
||||
|
||||
setattr(existing_item, metadata_key, value)
|
||||
# commit to datastore
|
||||
# TODO (cpennington): This really shouldn't have to do this much reaching in to get the metadata
|
||||
store.update_metadata(item_location, own_metadata(existing_item))
|
||||
|
||||
return HttpResponse()
|
||||
@@ -73,28 +78,38 @@ def save_item(request):
|
||||
|
||||
@login_required
|
||||
@expect_json
|
||||
def clone_item(request):
|
||||
def create_item(request):
|
||||
parent_location = Location(request.POST['parent_location'])
|
||||
template = Location(request.POST['template'])
|
||||
category = request.POST['category']
|
||||
|
||||
display_name = request.POST.get('display_name')
|
||||
|
||||
if not has_access(request.user, parent_location):
|
||||
raise PermissionDenied()
|
||||
|
||||
parent = get_modulestore(template).get_item(parent_location)
|
||||
dest_location = parent_location._replace(category=template.category, name=uuid4().hex)
|
||||
parent = get_modulestore(category).get_item(parent_location)
|
||||
dest_location = parent_location.replace(category=category, name=uuid4().hex)
|
||||
|
||||
new_item = get_modulestore(template).clone_item(template, dest_location)
|
||||
# get the metadata, display_name, and definition from the request
|
||||
metadata = {}
|
||||
data = None
|
||||
template_id = request.POST.get('boilerplate')
|
||||
if template_id is not None:
|
||||
clz = XModuleDescriptor.load_class(category)
|
||||
if clz is not None:
|
||||
template = clz.get_template(template_id)
|
||||
if template is not None:
|
||||
metadata = template.get('metadata', {})
|
||||
data = template.get('data')
|
||||
|
||||
# replace the display name with an optional parameter passed in from the caller
|
||||
if display_name is not None:
|
||||
new_item.display_name = display_name
|
||||
metadata['display_name'] = display_name
|
||||
|
||||
get_modulestore(template).update_metadata(new_item.location.url(), own_metadata(new_item))
|
||||
get_modulestore(category).create_and_save_xmodule(dest_location, definition_data=data,
|
||||
metadata=metadata, system=parent.system)
|
||||
|
||||
if new_item.location.category not in DETACHED_CATEGORIES:
|
||||
get_modulestore(parent.location).update_children(parent_location, parent.children + [new_item.location.url()])
|
||||
if category not in DETACHED_CATEGORIES:
|
||||
get_modulestore(parent.location).update_children(parent_location, parent.children + [dest_location.url()])
|
||||
|
||||
return HttpResponse(json.dumps({'id': dest_location.url()}))
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from xmodule.modulestore.django import modulestore
|
||||
from ..utils import get_course_for_item, get_modulestore
|
||||
from .access import get_location_and_verify_access
|
||||
|
||||
__all__ = ['edit_tabs', 'reorder_static_tabs', 'static_pages', 'edit_static']
|
||||
__all__ = ['edit_tabs', 'reorder_static_tabs', 'static_pages']
|
||||
|
||||
|
||||
def initialize_course_tabs(course):
|
||||
@@ -127,7 +127,3 @@ def static_pages(request, org, course, coursename):
|
||||
return render_to_response('static-pages.html', {
|
||||
'context_course': course,
|
||||
})
|
||||
|
||||
|
||||
def edit_static(request, org, course, coursename):
|
||||
return render_to_response('edit-static-page.html', {})
|
||||
|
||||
@@ -2,6 +2,7 @@ from django.conf import settings
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import ugettext as _
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
|
||||
@@ -26,6 +27,7 @@ def index(request):
|
||||
# filter out courses that we don't have access too
|
||||
def course_filter(course):
|
||||
return (has_access(request.user, course.location)
|
||||
# TODO remove this condition when templates purged from db
|
||||
and course.location.course != 'templates'
|
||||
and course.location.org != ''
|
||||
and course.location.course != ''
|
||||
@@ -33,7 +35,6 @@ def index(request):
|
||||
courses = filter(course_filter, courses)
|
||||
|
||||
return render_to_response('index.html', {
|
||||
'new_course_template': Location('i4x', 'edx', 'templates', 'course', 'Empty'),
|
||||
'courses': [(course.display_name,
|
||||
get_url_reverse('CourseOutline', course),
|
||||
get_lms_link_for_item(course.location, course_id=course.location.course_id))
|
||||
@@ -78,7 +79,7 @@ def add_user(request, location):
|
||||
if not email:
|
||||
msg = {
|
||||
'Status': 'Failed',
|
||||
'ErrMsg': 'Please specify an email address.',
|
||||
'ErrMsg': _('Please specify an email address.'),
|
||||
}
|
||||
return JsonResponse(msg, 400)
|
||||
|
||||
@@ -92,7 +93,7 @@ def add_user(request, location):
|
||||
if user is None:
|
||||
msg = {
|
||||
'Status': 'Failed',
|
||||
'ErrMsg': "Could not find user by email address '{0}'.".format(email),
|
||||
'ErrMsg': _("Could not find user by email address '{email}'.").format(email=email),
|
||||
}
|
||||
return JsonResponse(msg, 404)
|
||||
|
||||
@@ -100,7 +101,7 @@ def add_user(request, location):
|
||||
if not user.is_active:
|
||||
msg = {
|
||||
'Status': 'Failed',
|
||||
'ErrMsg': 'User {0} has registered but has not yet activated his/her account.'.format(email),
|
||||
'ErrMsg': _('User {email} has registered but has not yet activated his/her account.').format(email=email),
|
||||
}
|
||||
return JsonResponse(msg, 400)
|
||||
|
||||
@@ -129,7 +130,7 @@ def remove_user(request, location):
|
||||
if user is None:
|
||||
msg = {
|
||||
'Status': 'Failed',
|
||||
'ErrMsg': "Could not find user by email address '{0}'.".format(email),
|
||||
'ErrMsg': _("Could not find user by email address '{email}'.").format(email=email),
|
||||
}
|
||||
return JsonResponse(msg, 404)
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class CourseGradingModel(object):
|
||||
"""
|
||||
def __init__(self, course_descriptor):
|
||||
self.course_location = course_descriptor.location
|
||||
self.graders = [CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)] # weights transformed to ints [0..100]
|
||||
self.graders = [CourseGradingModel.jsonize_grader(i, grader) for i, grader in enumerate(course_descriptor.raw_grader)] # weights transformed to ints [0..100]
|
||||
self.grade_cutoffs = course_descriptor.grade_cutoffs
|
||||
self.grace_period = CourseGradingModel.convert_set_grace_period(course_descriptor)
|
||||
|
||||
@@ -81,7 +81,7 @@ class CourseGradingModel(object):
|
||||
Decode the json into CourseGradingModel and save any changes. Returns the modified model.
|
||||
Probably not the usual path for updates as it's too coarse grained.
|
||||
"""
|
||||
course_location = jsondict['course_location']
|
||||
course_location = Location(jsondict['course_location'])
|
||||
descriptor = get_modulestore(course_location).get_item(course_location)
|
||||
|
||||
graders_parsed = [CourseGradingModel.parse_grader(jsonele) for jsonele in jsondict['graders']]
|
||||
@@ -89,7 +89,7 @@ class CourseGradingModel(object):
|
||||
descriptor.raw_grader = graders_parsed
|
||||
descriptor.grade_cutoffs = jsondict['grade_cutoffs']
|
||||
|
||||
get_modulestore(course_location).update_item(course_location, descriptor._model_data._kvs._data)
|
||||
get_modulestore(course_location).update_item(course_location, descriptor.xblock_kvs._data)
|
||||
CourseGradingModel.update_grace_period_from_json(course_location, jsondict['grace_period'])
|
||||
|
||||
return CourseGradingModel.fetch(course_location)
|
||||
@@ -209,7 +209,7 @@ class CourseGradingModel(object):
|
||||
descriptor = get_modulestore(location).get_item(location)
|
||||
return {"graderType": descriptor.lms.format if descriptor.lms.format is not None else 'Not Graded',
|
||||
"location": location,
|
||||
"id": 99 # just an arbitrary value to
|
||||
"id": 99 # just an arbitrary value to
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -232,7 +232,7 @@ class CourseGradingModel(object):
|
||||
# 5 hours 59 minutes 59 seconds => converted to iso format
|
||||
rawgrace = descriptor.lms.graceperiod
|
||||
if rawgrace:
|
||||
hours_from_days = rawgrace.days*24
|
||||
hours_from_days = rawgrace.days * 24
|
||||
seconds = rawgrace.seconds
|
||||
hours_from_seconds = int(seconds / 3600)
|
||||
hours = hours_from_days + hours_from_seconds
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
class="course-checklist"
|
||||
<% } %>
|
||||
id="<%= 'course-checklist' + checklistIndex %>">
|
||||
<% var widthPercentage = 'width:' + percentChecked + '%;'; %>
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value" style="<%= widthPercentage %>">
|
||||
<span class="int"><%= percentChecked %></span>% of checklist completed</span></span>
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value" style="width: <%= percentChecked %>%;">
|
||||
<%= _.template(gettext("{number}% of checklists completed"), {number: '<span class="int">' + percentChecked + '</span>'}, {interpolate: /\{(.+?)\}/g}) %>
|
||||
</span></span>
|
||||
<header>
|
||||
<h3 class="checklist-title title-2 is-selectable" title="Collapse/Expand this Checklist">
|
||||
<i class="icon-caret-down ui-toggle-expansion"></i>
|
||||
<%= checklistShortDescription %></h3>
|
||||
<span class="checklist-status status">
|
||||
Tasks Completed: <span class="status-count"><%= itemsChecked %></span>/<span class="status-amount"><%= items.length %></span>
|
||||
<%= gettext("Tasks Completed:") %> <span class="status-count"><%= itemsChecked %></span>/<span class="status-amount"><%= items.length %></span>
|
||||
<i class="icon-ok"></i>
|
||||
</span>
|
||||
</header>
|
||||
@@ -47,7 +47,7 @@
|
||||
<li class="action-item">
|
||||
<a href="<%= item['action_url'] %>" class="action action-primary"
|
||||
<% if (item['action_external']) { %>
|
||||
rel="external" title="This link will open in a new browser window/tab"
|
||||
rel="external" title="<%= gettext("This link will open in a new browser window/tab") %>"
|
||||
<% } %>
|
||||
><%= item['action_text'] %></a>
|
||||
</li>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<a href="#" class="edit-button"><span class="edit-icon"></span>Edit</a>
|
||||
|
||||
<h2>Course Handouts</h2>
|
||||
<h2 class="title">Course Handouts</h2>
|
||||
<%if (model.get('data') != null) { %>
|
||||
<div class="handouts-content">
|
||||
<%= model.get('data') %>
|
||||
</div>
|
||||
<% } else {%>
|
||||
<p>You have no handouts defined</p>
|
||||
<p>${_("You have no handouts defined")}</p>
|
||||
<% } %>
|
||||
<form class="edit-handouts-form" style="display: block;">
|
||||
<div class="row">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"static_files": [
|
||||
"../jsi18n/",
|
||||
"js/vendor/RequireJS.js",
|
||||
"js/vendor/jquery.min.js",
|
||||
"js/vendor/jquery-ui.min.js",
|
||||
|
||||
@@ -56,14 +56,15 @@ class CMS.Views.ModuleEdit extends Backbone.View
|
||||
changedMetadata: ->
|
||||
return _.extend(@metadataEditor.getModifiedMetadataValues(), @customMetadata())
|
||||
|
||||
cloneTemplate: (parent, template) ->
|
||||
$.post("/clone_item", {
|
||||
parent_location: parent
|
||||
template: template
|
||||
}, (data) =>
|
||||
@model.set(id: data.id)
|
||||
@$el.data('id', data.id)
|
||||
@render()
|
||||
createItem: (parent, payload) ->
|
||||
payload.parent_location = parent
|
||||
$.post(
|
||||
"/create_item"
|
||||
payload
|
||||
(data) =>
|
||||
@model.set(id: data.id)
|
||||
@$el.data('id', data.id)
|
||||
@render()
|
||||
)
|
||||
|
||||
render: ->
|
||||
|
||||
@@ -55,9 +55,9 @@ class CMS.Views.TabsEdit extends Backbone.View
|
||||
editor.$el.removeClass('new')
|
||||
, 500)
|
||||
|
||||
editor.cloneTemplate(
|
||||
editor.createItem(
|
||||
@model.get('id'),
|
||||
'i4x://edx/templates/static_tab/Empty'
|
||||
{category: 'static_tab'}
|
||||
)
|
||||
|
||||
analytics.track "Added Static Page",
|
||||
|
||||
@@ -89,9 +89,9 @@ class CMS.Views.UnitEdit extends Backbone.View
|
||||
|
||||
@$newComponentItem.before(editor.$el)
|
||||
|
||||
editor.cloneTemplate(
|
||||
editor.createItem(
|
||||
@$el.data('id'),
|
||||
$(event.currentTarget).data('location')
|
||||
$(event.currentTarget).data()
|
||||
)
|
||||
|
||||
analytics.track "Added a Component",
|
||||
|
||||
@@ -79,10 +79,10 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
// general link management - new window/tab
|
||||
$('a[rel="external"]').attr('title', 'This link will open in a new browser window/tab').bind('click', linkNewWindow);
|
||||
$('a[rel="external"]').attr('title', gettext('This link will open in a new browser window/tab')).bind('click', linkNewWindow);
|
||||
|
||||
// general link management - lean modal window
|
||||
$('a[rel="modal"]').attr('title', 'This link will open in a modal window').leanModal({
|
||||
$('a[rel="modal"]').attr('title', gettext('This link will open in a modal window')).leanModal({
|
||||
overlay: 0.50,
|
||||
closeButton: '.action-modal-close'
|
||||
});
|
||||
@@ -199,8 +199,10 @@ function toggleSections(e) {
|
||||
$section = $('.courseware-section');
|
||||
sectionCount = $section.length;
|
||||
$button = $(this);
|
||||
$labelCollapsed = $('<i class="icon-arrow-up"></i> <span class="label">Collapse All Sections</span>');
|
||||
$labelExpanded = $('<i class="icon-arrow-down"></i> <span class="label">Expand All Sections</span>');
|
||||
$labelCollapsed = $('<i class="icon-arrow-up"></i> <span class="label">' +
|
||||
gettext('Collapse All Sections') + '</span>');
|
||||
$labelExpanded = $('<i class="icon-arrow-down"></i> <span class="label">' +
|
||||
gettext('Expand All Sections') + '</span>');
|
||||
|
||||
var buttonLabel = $button.hasClass('is-activated') ? $labelCollapsed : $labelExpanded;
|
||||
$button.toggleClass('is-activated').html(buttonLabel);
|
||||
@@ -326,7 +328,7 @@ function saveSubsection() {
|
||||
$changedInput = null;
|
||||
},
|
||||
error: function() {
|
||||
showToastMessage('There has been an error while saving your changes.');
|
||||
showToastMessage(gettext('There has been an error while saving your changes.'));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -336,7 +338,7 @@ function createNewUnit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var parent = $(this).data('parent');
|
||||
var template = $(this).data('template');
|
||||
var category = $(this).data('category');
|
||||
|
||||
analytics.track('Created a Unit', {
|
||||
'course': course_location_analytics,
|
||||
@@ -344,9 +346,9 @@ function createNewUnit(e) {
|
||||
});
|
||||
|
||||
|
||||
$.post('/clone_item', {
|
||||
$.post('/create_item', {
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'category': category,
|
||||
'display_name': 'New Unit'
|
||||
},
|
||||
|
||||
@@ -372,7 +374,7 @@ function deleteSection(e) {
|
||||
}
|
||||
|
||||
function _deleteItem($el) {
|
||||
if (!confirm('Are you sure you wish to delete this item. It cannot be reversed!')) return;
|
||||
if (!confirm(gettext('Are you sure you wish to delete this item. It cannot be reversed!'))) return;
|
||||
|
||||
var id = $el.data('id');
|
||||
|
||||
@@ -549,7 +551,7 @@ function saveNewSection(e) {
|
||||
|
||||
var $saveButton = $(this).find('.new-section-name-save');
|
||||
var parent = $saveButton.data('parent');
|
||||
var template = $saveButton.data('template');
|
||||
var category = $saveButton.data('category');
|
||||
var display_name = $(this).find('.new-section-name').val();
|
||||
|
||||
analytics.track('Created a Section', {
|
||||
@@ -557,9 +559,9 @@ function saveNewSection(e) {
|
||||
'display_name': display_name
|
||||
});
|
||||
|
||||
$.post('/clone_item', {
|
||||
$.post('/create_item', {
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'category': category,
|
||||
'display_name': display_name,
|
||||
},
|
||||
|
||||
@@ -593,13 +595,12 @@ function saveNewCourse(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var $newCourse = $(this).closest('.new-course');
|
||||
var template = $(this).find('.new-course-save').data('template');
|
||||
var org = $newCourse.find('.new-course-org').val();
|
||||
var number = $newCourse.find('.new-course-number').val();
|
||||
var display_name = $newCourse.find('.new-course-name').val();
|
||||
|
||||
if (org == '' || number == '' || display_name == '') {
|
||||
alert('You must specify all fields in order to create a new course.');
|
||||
alert(gettext('You must specify all fields in order to create a new course.'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -610,7 +611,6 @@ function saveNewCourse(e) {
|
||||
});
|
||||
|
||||
$.post('/create_new_course', {
|
||||
'template': template,
|
||||
'org': org,
|
||||
'number': number,
|
||||
'display_name': display_name
|
||||
@@ -644,7 +644,7 @@ function addNewSubsection(e) {
|
||||
var parent = $(this).parents("section.branch").data("id");
|
||||
|
||||
$saveButton.data('parent', parent);
|
||||
$saveButton.data('template', $(this).data('template'));
|
||||
$saveButton.data('category', $(this).data('category'));
|
||||
|
||||
$newSubsection.find('.new-subsection-form').bind('submit', saveNewSubsection);
|
||||
$cancelButton.bind('click', cancelNewSubsection);
|
||||
@@ -657,7 +657,7 @@ function saveNewSubsection(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var parent = $(this).find('.new-subsection-name-save').data('parent');
|
||||
var template = $(this).find('.new-subsection-name-save').data('template');
|
||||
var category = $(this).find('.new-subsection-name-save').data('category');
|
||||
var display_name = $(this).find('.new-subsection-name-input').val();
|
||||
|
||||
analytics.track('Created a Subsection', {
|
||||
@@ -666,9 +666,9 @@ function saveNewSubsection(e) {
|
||||
});
|
||||
|
||||
|
||||
$.post('/clone_item', {
|
||||
$.post('/create_item', {
|
||||
'parent_location': parent,
|
||||
'template': template,
|
||||
'category': category,
|
||||
'display_name': display_name
|
||||
},
|
||||
|
||||
@@ -730,18 +730,16 @@ function saveSetSectionScheduleDate(e) {
|
||||
})
|
||||
}).success(function() {
|
||||
var $thisSection = $('.courseware-section[data-id="' + id + '"]');
|
||||
var format = gettext('<strong>Will Release:</strong> %(date)s at %(time)s UTC');
|
||||
var willReleaseAt = interpolate(format, {
|
||||
'date': input_date,
|
||||
'time': input_time
|
||||
},
|
||||
true);
|
||||
$thisSection.find('.section-published-date').html(
|
||||
'<span class="published-status">' + willReleaseAt + '</span>' +
|
||||
'<a href="#" class="edit-button" ' +
|
||||
'" data-date="' + input_date +
|
||||
'" data-time="' + input_time +
|
||||
'" data-id="' + id + '">' + gettext('Edit') + '</a>');
|
||||
var html = _.template(
|
||||
'<span class="published-status">' +
|
||||
'<strong>' + gettext("Will Release: ") + '</strong>' +
|
||||
gettext("<%= date %> at <%= time %> UTC") +
|
||||
'</span>' +
|
||||
'<a href="#" class="edit-button" data-date="<%= date %>" data-time="<%= time %>" data-id="<%= id %>">' +
|
||||
gettext("Edit") +
|
||||
'</a>',
|
||||
{date: input_date, time: input_time, id: id});
|
||||
$thisSection.find('.section-published-date').html(html);
|
||||
hideModal();
|
||||
saving.hide();
|
||||
});
|
||||
|
||||
@@ -38,23 +38,23 @@ CMS.Models.Settings.CourseDetails = Backbone.Model.extend({
|
||||
// A bit funny in that the video key validation is asynchronous; so, it won't stop the validation.
|
||||
var errors = {};
|
||||
if (newattrs.start_date === null) {
|
||||
errors.start_date = "The course must have an assigned start date.";
|
||||
errors.start_date = gettext("The course must have an assigned start date.");
|
||||
}
|
||||
if (newattrs.start_date && newattrs.end_date && newattrs.start_date >= newattrs.end_date) {
|
||||
errors.end_date = "The course end date cannot be before the course start date.";
|
||||
errors.end_date = gettext("The course end date cannot be before the course start date.");
|
||||
}
|
||||
if (newattrs.start_date && newattrs.enrollment_start && newattrs.start_date < newattrs.enrollment_start) {
|
||||
errors.enrollment_start = "The course start date cannot be before the enrollment start date.";
|
||||
errors.enrollment_start = gettext("The course start date cannot be before the enrollment start date.");
|
||||
}
|
||||
if (newattrs.enrollment_start && newattrs.enrollment_end && newattrs.enrollment_start >= newattrs.enrollment_end) {
|
||||
errors.enrollment_end = "The enrollment start date cannot be after the enrollment end date.";
|
||||
errors.enrollment_end = gettext("The enrollment start date cannot be after the enrollment end date.");
|
||||
}
|
||||
if (newattrs.end_date && newattrs.enrollment_end && newattrs.end_date < newattrs.enrollment_end) {
|
||||
errors.enrollment_end = "The enrollment end date cannot be after the course end date.";
|
||||
errors.enrollment_end = gettext("The enrollment end date cannot be after the course end date.");
|
||||
}
|
||||
if (newattrs.intro_video && newattrs.intro_video !== this.get('intro_video')) {
|
||||
if (this._videokey_illegal_chars.exec(newattrs.intro_video)) {
|
||||
errors.intro_video = "Key should only contain letters, numbers, _, or -";
|
||||
errors.intro_video = gettext("Key should only contain letters, numbers, _, or -");
|
||||
}
|
||||
// TODO check if key points to a real video using google's youtube api
|
||||
}
|
||||
|
||||
@@ -79,14 +79,14 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
|
||||
// FIXME somehow this.collection is unbound sometimes. I can't track down when
|
||||
var existing = this.collection && this.collection.some(function(other) { return (other.cid != this.cid) && (other.get('type') == attrs['type']);}, this);
|
||||
if (existing) {
|
||||
errors.type = "There's already another assignment type with this name.";
|
||||
errors.type = gettext("There's already another assignment type with this name.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_.has(attrs, 'weight')) {
|
||||
var intWeight = parseInt(attrs.weight); // see if this ensures value saved is int
|
||||
if (!isFinite(intWeight) || /\D+/.test(attrs.weight) || intWeight < 0 || intWeight > 100) {
|
||||
errors.weight = "Please enter an integer between 0 and 100.";
|
||||
errors.weight = gettext("Please enter an integer between 0 and 100.");
|
||||
}
|
||||
else {
|
||||
attrs.weight = intWeight;
|
||||
@@ -100,18 +100,20 @@ CMS.Models.Settings.CourseGrader = Backbone.Model.extend({
|
||||
}}
|
||||
if (_.has(attrs, 'min_count')) {
|
||||
if (!isFinite(attrs.min_count) || /\D+/.test(attrs.min_count)) {
|
||||
errors.min_count = "Please enter an integer.";
|
||||
errors.min_count = gettext("Please enter an integer.");
|
||||
}
|
||||
else attrs.min_count = parseInt(attrs.min_count);
|
||||
}
|
||||
if (_.has(attrs, 'drop_count')) {
|
||||
if (!isFinite(attrs.drop_count) || /\D+/.test(attrs.drop_count)) {
|
||||
errors.drop_count = "Please enter an integer.";
|
||||
errors.drop_count = gettext("Please enter an integer.");
|
||||
}
|
||||
else attrs.drop_count = parseInt(attrs.drop_count);
|
||||
}
|
||||
if (_.has(attrs, 'min_count') && _.has(attrs, 'drop_count') && attrs.drop_count > attrs.min_count) {
|
||||
errors.drop_count = "Cannot drop more " + attrs.type + " than will assigned.";
|
||||
errors.drop_count = _.template(
|
||||
gettext("Cannot drop more <% attrs.types %> than will assigned."),
|
||||
attrs, {variable: 'attrs'});
|
||||
}
|
||||
if (!_.isEmpty(errors)) return errors;
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ CMS.Views.ShowTextbook = Backbone.View.extend({
|
||||
if(e && e.preventDefault) { e.preventDefault(); }
|
||||
var textbook = this.model, collection = this.model.collection;
|
||||
var msg = new CMS.Views.Prompt.Warning({
|
||||
title: _.str.sprintf(gettext("Delete “%s”?"),
|
||||
textbook.escape('name')),
|
||||
title: _.template(gettext("Delete “<%= name %>”?"),
|
||||
{name: textbook.escape('name')}),
|
||||
message: gettext("Deleting a textbook cannot be undone and once deleted any reference to it in your courseware's navigation will also be removed."),
|
||||
actions: {
|
||||
primary: {
|
||||
@@ -241,8 +241,8 @@ CMS.Views.EditChapter = Backbone.View.extend({
|
||||
asset_path: this.$("input.chapter-asset-path").val()
|
||||
});
|
||||
var msg = new CMS.Models.FileUpload({
|
||||
title: _.str.sprintf(gettext("Upload a new asset to %s"),
|
||||
section.escape('name')),
|
||||
title: _.template(gettext("Upload a new asset to “<%= name %>”"),
|
||||
{name: section.escape('name')}),
|
||||
message: "Files must be in PDF format."
|
||||
});
|
||||
var view = new CMS.Views.UploadDialog({model: msg, chapter: this.model});
|
||||
@@ -260,7 +260,7 @@ CMS.Views.UploadDialog = Backbone.View.extend({
|
||||
this.listenTo(this.model, "change", this.render);
|
||||
},
|
||||
render: function() {
|
||||
var isValid = this.model.isValid()
|
||||
var isValid = this.model.isValid();
|
||||
var selectedFile = this.model.get('selectedFile');
|
||||
var oldInput = this.$("input[type=file]").get(0);
|
||||
this.$el.html(this.template({
|
||||
|
||||
@@ -576,7 +576,7 @@ p, ul, ol, dl {
|
||||
|
||||
// misc
|
||||
hr.divide {
|
||||
@extend .text-sr;
|
||||
@extend .cont-text-sr;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
@@ -824,7 +824,7 @@ hr.divide {
|
||||
|
||||
// basic utility
|
||||
.sr {
|
||||
@extend .text-sr;
|
||||
@extend .cont-text-sr;
|
||||
}
|
||||
|
||||
.fake-link {
|
||||
@@ -877,7 +877,7 @@ body.js {
|
||||
text-align: center;
|
||||
|
||||
.label {
|
||||
@extend .text-sr;
|
||||
@extend .cont-text-sr;
|
||||
}
|
||||
|
||||
[class^="icon-"] {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
../../../common/static/sass/_mixins-inherited.scss
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
// gray primary button
|
||||
.btn-primary-gray {
|
||||
@extend .btn-primary;
|
||||
@extend .ui-btn-primary;
|
||||
background: $gray-l1;
|
||||
border-color: $gray-l2;
|
||||
color: $white;
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
// blue primary button
|
||||
.btn-primary-blue {
|
||||
@extend .btn-primary;
|
||||
@extend .ui-btn-primary;
|
||||
background: $blue;
|
||||
border-color: $blue-s1;
|
||||
color: $white;
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
// green primary button
|
||||
.btn-primary-green {
|
||||
@extend .btn-primary;
|
||||
@extend .ui-btn-primary;
|
||||
background: $green;
|
||||
border-color: $green;
|
||||
color: $white;
|
||||
@@ -71,7 +71,7 @@
|
||||
|
||||
// gray secondary button
|
||||
.btn-secondary-gray {
|
||||
@extend .btn-secondary;
|
||||
@extend .ui-btn-secondary;
|
||||
border-color: $gray-l3;
|
||||
color: $gray-l1;
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
|
||||
// blue secondary button
|
||||
.btn-secondary-blue {
|
||||
@extend .btn-secondary;
|
||||
@extend .ui-btn-secondary;
|
||||
border-color: $blue-l3;
|
||||
color: $blue;
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
|
||||
// green secondary button
|
||||
.btn-secondary-green {
|
||||
@extend .btn-secondary;
|
||||
@extend .ui-btn-secondary;
|
||||
border-color: $green-l4;
|
||||
color: $green-l2;
|
||||
|
||||
@@ -148,9 +148,9 @@
|
||||
// ====================
|
||||
|
||||
// simple dropdown button styling - should we move this elsewhere?
|
||||
.btn-dd {
|
||||
@extend .btn;
|
||||
@extend .btn-pill;
|
||||
.ui-btn-dd {
|
||||
@extend .ui-btn;
|
||||
@extend .ui-btn-pill;
|
||||
padding:($baseline/4) ($baseline/2);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
@@ -158,7 +158,7 @@
|
||||
text-align: center;
|
||||
|
||||
&:hover, &:active {
|
||||
@extend .fake-link;
|
||||
@extend .ui-fake-link;
|
||||
border-color: $gray-l3;
|
||||
}
|
||||
|
||||
@@ -169,8 +169,8 @@
|
||||
}
|
||||
|
||||
// layout-based buttons - nav dd
|
||||
.btn-dd-nav-primary {
|
||||
@extend .btn-dd;
|
||||
.ui-btn-dd-nav-primary {
|
||||
@extend .ui-btn-dd;
|
||||
background: $white;
|
||||
border-color: $white;
|
||||
color: $gray-d1;
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// ====================
|
||||
|
||||
.wrapper-header {
|
||||
@extend .depth3;
|
||||
box-shadow: 0 1px 2px 0 $shadow-l1;
|
||||
@extend .ui-depth3;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-shadow: 0 1px 2px 0 $shadow-l1;
|
||||
margin: 0;
|
||||
padding: 0 $baseline;
|
||||
background: $white;
|
||||
@@ -22,7 +22,6 @@
|
||||
// ====================
|
||||
|
||||
// basic layout
|
||||
|
||||
.wrapper-l, .wrapper-r {
|
||||
background: $white;
|
||||
}
|
||||
@@ -76,7 +75,7 @@
|
||||
|
||||
.title {
|
||||
@extend .t-action2;
|
||||
@extend .btn-dd-nav-primary;
|
||||
@extend .ui-btn-dd-nav-primary;
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
|
||||
.label, .icon-caret-down {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// ====================
|
||||
|
||||
.modal-cover {
|
||||
@extend .depth3;
|
||||
@extend .ui-depth3;
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
.modal {
|
||||
@extend .depth4;
|
||||
@extend .ui-depth4;
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 60px;
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
// lean modal alternative
|
||||
#lean_overlay {
|
||||
@extend .depth4;
|
||||
@extend .ui-depth4;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
nav {
|
||||
|
||||
ol, ul {
|
||||
@extend .no-list;
|
||||
@extend .cont-no-list;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
.wrapper-inner {
|
||||
@include linear-gradient($gray-d3 0%, $gray-d3 98%, $black 100%);
|
||||
@extend .depth0;
|
||||
@extend .ui-depth0;
|
||||
display: none;
|
||||
width: 100% !important;
|
||||
border-bottom: 1px solid $white;
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
// sock - actions
|
||||
.list-cta {
|
||||
@extend .depth1;
|
||||
@extend .ui-depth1;
|
||||
position: absolute;
|
||||
top: -($baseline*0.75);
|
||||
width: 100%;
|
||||
@@ -27,7 +27,7 @@
|
||||
text-align: center;
|
||||
|
||||
.cta-show-sock {
|
||||
@extend .btn-pill;
|
||||
@extend .ui-btn-pill;
|
||||
@extend .t-action4;
|
||||
background: $gray-l5;
|
||||
padding: ($baseline/2) $baseline;
|
||||
|
||||
@@ -144,8 +144,8 @@
|
||||
|
||||
// prompts
|
||||
.wrapper-prompt {
|
||||
@extend .depth5;
|
||||
@include transition(all $tmg-f3 ease-in-out 0s);
|
||||
@extend .ui-depth5;
|
||||
@include transition(all $tmg-f3 ease-in-out 0s);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
background: $black-t0;
|
||||
@@ -242,7 +242,7 @@
|
||||
|
||||
// notifications
|
||||
.wrapper-notification {
|
||||
@extend .depth5;
|
||||
@extend .ui-depth5;
|
||||
@include clearfix();
|
||||
box-shadow: 0 -1px 3px $shadow, inset 0 3px 1px $blue;
|
||||
position: fixed;
|
||||
@@ -444,7 +444,7 @@
|
||||
}
|
||||
|
||||
.copy p {
|
||||
@extend .text-sr;
|
||||
@extend .cont-text-sr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -453,7 +453,7 @@
|
||||
|
||||
// alerts
|
||||
.wrapper-alert {
|
||||
@extend .depth2;
|
||||
@extend .ui-depth2;
|
||||
@include box-sizing(border-box);
|
||||
box-shadow: 0 1px 1px $white, inset 0 2px 2px $shadow-d1, inset 0 -4px 1px $blue;
|
||||
position: relative;
|
||||
@@ -599,7 +599,7 @@
|
||||
text-align: center;
|
||||
|
||||
.label {
|
||||
@extend .text-sr;
|
||||
@extend .cont-text-sr;
|
||||
}
|
||||
|
||||
[class^="icon"] {
|
||||
@@ -696,7 +696,7 @@ body.uxdesign.alerts {
|
||||
}
|
||||
|
||||
.content-primary {
|
||||
@extend .window;
|
||||
@extend .ui-window;
|
||||
width: flex-grid(12, 12);
|
||||
margin-right: flex-gutter();
|
||||
padding: $baseline ($baseline*1.5);
|
||||
|
||||
@@ -14,7 +14,7 @@ body.course.checklists {
|
||||
|
||||
// checklists - general
|
||||
.course-checklist {
|
||||
@extend .window;
|
||||
@extend .ui-window;
|
||||
margin: 0 0 ($baseline*2) 0;
|
||||
|
||||
&:last-child {
|
||||
@@ -23,7 +23,7 @@ body.course.checklists {
|
||||
|
||||
// visual status
|
||||
.viz-checklist-status {
|
||||
@extend .text-hide;
|
||||
@extend .cont-text-hide;
|
||||
@include size(100%,($baseline/4));
|
||||
position: relative;
|
||||
display: block;
|
||||
@@ -40,7 +40,7 @@ body.course.checklists {
|
||||
background: $green;
|
||||
|
||||
.int {
|
||||
@extend .text-sr;
|
||||
@extend .cont-text-sr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// ====================
|
||||
|
||||
body.course.export {
|
||||
|
||||
|
||||
.export-overview {
|
||||
@extend .window;
|
||||
@extend .ui-window;
|
||||
@include clearfix;
|
||||
padding: 30px 40px;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ body.course.export {
|
||||
}
|
||||
|
||||
.export-form-wrapper {
|
||||
|
||||
|
||||
.export-form {
|
||||
float: left;
|
||||
width: 35%;
|
||||
@@ -122,4 +122,4 @@ body.course.export {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
cms/static/sass/views/_import.scss
vendored
6
cms/static/sass/views/_import.scss
vendored
@@ -2,9 +2,9 @@
|
||||
// ====================
|
||||
|
||||
body.course.import {
|
||||
|
||||
|
||||
.import-overview {
|
||||
@extend .window;
|
||||
@extend .ui-window;
|
||||
@include clearfix;
|
||||
padding: 30px 40px;
|
||||
}
|
||||
@@ -103,4 +103,4 @@ body.course.import {
|
||||
color: #fff;
|
||||
line-height: 48px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ body.course.settings {
|
||||
}
|
||||
|
||||
.content-primary {
|
||||
@extend .window;
|
||||
@extend .ui-window;
|
||||
width: flex-grid(9, 12);
|
||||
margin-right: flex-gutter();
|
||||
padding: $baseline ($baseline*1.5);
|
||||
|
||||
@@ -171,7 +171,7 @@ body.course.static-pages {
|
||||
}
|
||||
|
||||
.static-page-details {
|
||||
@extend .window;
|
||||
@extend .ui-window;
|
||||
padding: 32px 40px;
|
||||
|
||||
.row {
|
||||
|
||||
@@ -115,7 +115,7 @@ body.course.textbooks {
|
||||
}
|
||||
|
||||
.delete {
|
||||
@extend .btn-non;
|
||||
@extend .ui-btn-non;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ body.course.textbooks {
|
||||
|
||||
.chapters-fields,
|
||||
.textbook-fields {
|
||||
@extend .no-list;
|
||||
@extend .cont-no-list;
|
||||
|
||||
.field {
|
||||
margin: 0 0 ($baseline*0.75) 0;
|
||||
@@ -320,7 +320,7 @@ body.course.textbooks {
|
||||
}
|
||||
|
||||
.action-upload {
|
||||
@extend .btn-flat-outline;
|
||||
@extend .ui-btn-flat-outline;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 0;
|
||||
@@ -348,7 +348,7 @@ body.course.textbooks {
|
||||
|
||||
|
||||
.action-add-chapter {
|
||||
@extend .btn-flat-outline;
|
||||
@extend .ui-btn-flat-outline;
|
||||
@include font-size(16);
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -365,7 +365,7 @@ body.course.textbooks {
|
||||
|
||||
// dialog
|
||||
.wrapper-dialog {
|
||||
@extend .depth5;
|
||||
@extend .ui-depth5;
|
||||
@include transition(all 0.05s ease-in-out);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
// ====================
|
||||
|
||||
body.course.updates {
|
||||
|
||||
h2 {
|
||||
margin-bottom: 24px;
|
||||
font-size: 22px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.course-info-wrapper {
|
||||
display: table;
|
||||
@@ -180,9 +174,10 @@ body.course.updates {
|
||||
border-left: none;
|
||||
background: $lightGrey;
|
||||
|
||||
h2 {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
.title {
|
||||
margin-bottom: 24px;
|
||||
font-size: 22px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.edit-button {
|
||||
@@ -220,4 +215,4 @@ body.course.updates {
|
||||
textarea {
|
||||
height: 300px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Page Not Found</%block>
|
||||
<%block name="title">${_("Page Not Found")}</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
|
||||
<h1>Page not found</h1>
|
||||
<p>The page that you were looking for was not found. Go back to the <a href="/">homepage</a> or let us know about any pages that may have been moved at <a href="mailto:technical@edx.org">technical@edx.org</a>.</p>
|
||||
<h1>${_("Page not found")}</h1>
|
||||
<p>${_('The page that you were looking for was not found.')}
|
||||
${_('Go back to the {homepage} or let us know about any pages that may have been moved at {email}.').format(
|
||||
homepage='<a href="/">homepage</a>',
|
||||
email='<a href="mailto:technical@edx.org">technical@edx.org</a>')}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="title">Studio Server Error</%block>
|
||||
<%block name="title">${_("Studio Server Error")}</%block>
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<h1>The <em>Studio</em> servers encountered an error</h1>
|
||||
<h1>${_("The <em>Studio</em> servers encountered an error")}</h1>
|
||||
<p>
|
||||
An error occurred in Studio and the page could not be loaded. Please try again in a few moments.
|
||||
We've logged the error and our staff is currently working to resolve this error as soon as possible.
|
||||
If the problem persists, please email us at <a href="mailto:technical@edx.org">technical@edx.org</a>.
|
||||
${_("An error occurred in Studio and the page could not be loaded. Please try again in a few moments.")}
|
||||
${_("We've logged the error and our staff is currently working to resolve this error as soon as possible.")}
|
||||
${_('If the problem persists, please email us at {email}.').format(email='<a href="mailto:technical@edx.org">technical@edx.org</a>')}
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="content">
|
||||
@@ -6,9 +7,9 @@
|
||||
<div>
|
||||
|
||||
<section class="activation">
|
||||
<h1>Account already active!</h1>
|
||||
<p> This account has already been activated. <a href="/signin">Log in here</a>.</p>
|
||||
<h1>${_("Account already active!")}</h1>
|
||||
<p>${_('This account has already been activated.')}<a href="/signin">${_("Log in here.")}</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="content">
|
||||
|
||||
<section class="tos">
|
||||
<div>
|
||||
<h1>Activation Complete!</h1>
|
||||
<p>Thanks for activating your account. <a href="/signin">Log in here</a>.</p>
|
||||
<h1>${_("Activation Complete!")}</h1>
|
||||
<p>${_('Thanks for activating your account.')}<a href="/signin">${_("Log in here.")}</a></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
|
||||
<%block name="content">
|
||||
<section class="tos">
|
||||
<div>
|
||||
<h1>Activation Invalid</h1>
|
||||
<h1>${_("Activation Invalid")}</h1>
|
||||
|
||||
<p>Something went wrong. Check to make sure the URL you went to was
|
||||
correct -- e-mail programs will sometimes split it into two
|
||||
lines. If you still have issues, e-mail us to let us know what happened
|
||||
at <a href="mailto:bugs@mitx.mit.edu">bugs@mitx.mit.edu</a>.</p>
|
||||
<p>${_('Something went wrong. Check to make sure the URL you went to was correct -- e-mail programs will sometimes split it into two lines. If you still have issues, e-mail us to let us know what happened at {email}.').format(email='<a href="mailto:bugs@mitx.mit.edu">bugs@mitx.mit.edu</a>')}</p>
|
||||
|
||||
<p>Or you can go back to the <a href="/">home page</a>.</p>
|
||||
<p>${_('Or you can go back to the {link_start}home page{link_end}.').format(
|
||||
link_start='<a href="/">', link_end='</a>')}</p>
|
||||
</div>
|
||||
</section>
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%block name="bodyclass">is-signedin course uploads</%block>
|
||||
<%block name="title">Files & Uploads</%block>
|
||||
<%block name="title">${_("Files & Uploads")}</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button upload-button new-button"><i class="icon-plus"></i> Upload New File</a>
|
||||
<a href="#" class="button upload-button new-button"><i class="icon-plus"></i> ${_("Upload New File")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
## -*- coding: utf-8 -*-
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<!doctype html>
|
||||
@@ -17,6 +18,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="path_prefix" content="${MITX_ROOT_URL}">
|
||||
|
||||
<script type="text/javascript" src="/jsi18n/"></script>
|
||||
<%static:css group='base-style'/>
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/skins/simple/style.css')}" />
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/markitup/sets/wiki/style.css')}" />
|
||||
@@ -35,7 +37,6 @@
|
||||
</script>
|
||||
|
||||
## javascript
|
||||
<script type="text/javascript" src="/jsi18n/"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/json2.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore.string.min.js')}"></script>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Course Checklists</%block>
|
||||
@@ -30,8 +31,8 @@
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Tools</small>
|
||||
<span class="sr">> </span>Course Checklists
|
||||
<small class="subtitle">${_("Tools")}</small>
|
||||
<span class="sr">> </span>${_("Course Checklists")}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
@@ -40,18 +41,18 @@
|
||||
<section class="content">
|
||||
<article class="content-primary" role="main">
|
||||
<form id="course-checklists" class="course-checklists" method="post" action="">
|
||||
<h2 class="title title-3 sr">Current Checklists</h2>
|
||||
<h2 class="title title-3 sr">${_("Current Checklists")}</h2>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<aside class="content-supplementary" role="complimentary">
|
||||
<div class="bit">
|
||||
<h3 class="title title-3">What are checklists?</h3>
|
||||
<h3 class="title title-3">${_("What are checklists?")}</h3>
|
||||
<p>
|
||||
Running a course on edX is a complex undertaking. Course checklists are designed to help you understand and keep track of all the steps necessary to get your course ready for students.
|
||||
${_("Running a course on edX is a complex undertaking. Course checklists are designed to help you understand and keep track of all the steps necessary to get your course ready for students.")}
|
||||
</p>
|
||||
<p>
|
||||
These checklists are shared among your course team, and any changes you make are immediately visible to other members of the team and saved automatically.
|
||||
${_("These checklists are shared among your course team, and any changes you make are immediately visible to other members of the team and saved automatically.")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<!-- TODO decode course # from context_course into title -->
|
||||
<%block name="title">Course Updates</%block>
|
||||
<%block name="title">${_("Course Updates")}</%block>
|
||||
<%block name="bodyclass">is-signedin course course-info updates</%block>
|
||||
|
||||
|
||||
@@ -44,15 +45,15 @@
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Content</small>
|
||||
<span class="sr">> </span>Course Updates
|
||||
<small class="subtitle">${_("Content")}</small>
|
||||
<span class="sr">> </span>${_("Course Updates")}
|
||||
</h1>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<h3 class="sr">${_('Page Actions')}</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class=" button new-button new-update-button"><i class="icon-plus"></i> New Update</a>
|
||||
<a href="#" class=" button new-button new-update-button"><i class="icon-plus"></i> ${_('New Update')}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -62,7 +63,7 @@
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<div class="introduction">
|
||||
<p clas="copy">Course updates are announcements or notifications you want to share with your class. Other course authors have used them for important exam/date reminders, change in schedules, and to call out any important steps students need to be aware of.</p>
|
||||
<p clas="copy">${_('Course updates are announcements or notifications you want to share with your class. Other course authors have used them for important exam/date reminders, change in schedules, and to call out any important steps students need to be aware of.')}</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Static Pages</%block>
|
||||
<%block name="bodyclass">is-signedin course pages static-pages</%block>
|
||||
@@ -19,15 +20,15 @@
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Content</small>
|
||||
<span class="sr">> </span>Static Pages
|
||||
<small class="subtitle">${_("Content")}</small>
|
||||
<span class="sr">> </span>${_("Static Pages")}
|
||||
</h1>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<h3 class="sr">${_("Page Actions")}</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button new-button new-tab"><i class="icon-plus"></i> New Page</a>
|
||||
<a href="#" class="button new-button new-tab"><i class="icon-plus"></i> ${_("New Page")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -37,11 +38,11 @@
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<div class="introduction has-links">
|
||||
<p class="copy">Static Pages are additional pages that supplement your Courseware. Other course authors have used them to share a syllabus, calendar, handouts, and more.</p>
|
||||
<p class="copy">${_("Static Pages are additional pages that supplement your Courseware. Other course authors have used them to share a syllabus, calendar, handouts, and more.")}</p>
|
||||
<nav class="nav-introduction-supplementary">
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a rel="modal" href="#preview-lms-staticpages"><i class="icon-question-sign"></i>How do Static Pages look to students in my course?</a>
|
||||
<a rel="modal" href="#preview-lms-staticpages"><i class="icon-question-sign"></i>${_("How do Static Pages look to students in my course?")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -69,15 +70,15 @@
|
||||
</div>
|
||||
|
||||
<div class="content-modal" id="preview-lms-staticpages">
|
||||
<h3 class="title">How Static Pages are Used in Your Course</h3>
|
||||
<h3 class="title">${_("How Static Pages are Used in Your Course")}</h3>
|
||||
<figure>
|
||||
<img src="/static/img/preview-lms-staticpages.png" alt="Preview of how Static Pages are used in your course" />
|
||||
<figcaption class="description">These pages will be presented in your course's main navigation alongside Courseware, Course Info, Discussion, etc.</figcaption>
|
||||
<img src="/static/img/preview-lms-staticpages.png" alt="${_('Preview of how Static Pages are used in your course')}" />
|
||||
<figcaption class="description">${_("These pages will be presented in your course's main navigation alongside Courseware, Course Info, Discussion, etc.")}</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="#" rel="view" class="action action-modal-close">
|
||||
<i class="icon-remove-sign"></i>
|
||||
<span class="label">close modal</span>
|
||||
<span class="label">${_("close modal")}</span>
|
||||
</a>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%!
|
||||
import logging
|
||||
@@ -5,7 +6,7 @@
|
||||
%>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">CMS Subsection</%block>
|
||||
<%block name="title">${_("CMS Subsection")}</%block>
|
||||
<%block name="bodyclass">is-signedin course subsection</%block>
|
||||
|
||||
|
||||
@@ -18,11 +19,11 @@
|
||||
<div class="main-column">
|
||||
<article class="subsection-body window" data-id="${subsection.location}">
|
||||
<div class="subsection-name-input">
|
||||
<label>Display Name:</label>
|
||||
<label>${_("Display Name:")}</label>
|
||||
<input type="text" value="${subsection.display_name_with_default | h}" class="subsection-display-name-input" data-metadata-name="display_name"/>
|
||||
</div>
|
||||
<div class="sortable-unit-list">
|
||||
<label>Units:</label>
|
||||
<label>${_("Units:")}</label>
|
||||
${units.enum_units(subsection, subsection_units=subsection_units)}
|
||||
</div>
|
||||
</article>
|
||||
@@ -30,63 +31,61 @@
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="unit-settings window id-holder" data-id="${subsection.location}">
|
||||
<h4 class="header">Subsection Settings</h4>
|
||||
<h4 class="header">${_("Subsection Settings")}</h4>
|
||||
<div class="window-contents">
|
||||
<div class="scheduled-date-input row">
|
||||
<div class="datepair" data-language="javascript">
|
||||
<div class="field field-start-date">
|
||||
<label for="start_date">Release Day</label>
|
||||
<input type="text" id="start_date" name="start_date"
|
||||
value="${subsection.lms.start.strftime('%m/%d/%Y') if subsection.lms.start else ''}"
|
||||
<label for="start_date">${_("Release Day")}</label>
|
||||
<input type="text" id="start_date" name="start_date"
|
||||
value="${subsection.lms.start.strftime('%m/%d/%Y') if subsection.lms.start else ''}"
|
||||
placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
|
||||
</div>
|
||||
<div class="field field-start-time">
|
||||
<label for="start_time">Release Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label>
|
||||
<input type="text" id="start_time" name="start_time"
|
||||
value="${subsection.lms.start.strftime('%H:%M') if subsection.lms.start else ''}"
|
||||
<label for="start_time">${_("Release Time")} (<abbr title="${_("Coordinated Universal Time")}">${_("UTC")}</abbr>)</label>
|
||||
<input type="text" id="start_time" name="start_time"
|
||||
value="${subsection.lms.start.strftime('%H:%M') if subsection.lms.start else ''}"
|
||||
placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
% if subsection.lms.start and not almost_same_datetime(subsection.lms.start, parent_item.lms.start):
|
||||
% if parent_item.lms.start is None:
|
||||
<p class="notice">The date above differs from the release date of
|
||||
${parent_item.display_name_with_default}, which is unset.
|
||||
<p class="notice">${_("The date above differs from the release date of {name}, which is unset.").format(name=parent_item.display_name_with_default)}
|
||||
% else:
|
||||
<p class="notice">The date above differs from the release date of ${parent_item.display_name_with_default} –
|
||||
${get_default_time_display(parent_item.lms.start)}.
|
||||
<p class="notice">${_("The date above differs from the release date of {name} - {start_time}").format(name=parent_item.display_name_with_default, start_time=get_default_time_display(parent_item.lms.start))}.
|
||||
% endif
|
||||
<a href="#" class="sync-date no-spinner">Sync to ${parent_item.display_name_with_default}.</a></p>
|
||||
<a href="#" class="sync-date no-spinner">${_("Sync to {name}.").format(name=parent_item.display_name_with_default)}</a></p>
|
||||
% endif
|
||||
</div>
|
||||
|
||||
<div class="row gradable">
|
||||
<label>Graded as:</label>
|
||||
<label>${_("Graded as:")}</label>
|
||||
|
||||
<div class="gradable-status" data-initial-status="${subsection.lms.format if subsection.lms.format is not None else 'Not Graded'}">
|
||||
<div class="gradable-status" data-initial-status="${subsection.lms.format if subsection.lms.format is not None else _('Not Graded')}">
|
||||
</div>
|
||||
|
||||
<div class="due-date-input row">
|
||||
<a href="#" class="set-date">Set a due date</a>
|
||||
<a href="#" class="set-date">${_("Set a due date")}</a>
|
||||
<div class="datepair date-setter">
|
||||
<div class="field field-start-date">
|
||||
<label for="due_date">Due Day</label>
|
||||
<input type="text" id="due_date" name="due_date"
|
||||
value="${subsection.lms.due.strftime('%m/%d/%Y') if subsection.lms.due else ''}"
|
||||
<label for="due_date">${_("Due Day")}</label>
|
||||
<input type="text" id="due_date" name="due_date"
|
||||
value="${subsection.lms.due.strftime('%m/%d/%Y') if subsection.lms.due else ''}"
|
||||
placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
|
||||
</div>
|
||||
<div class="field field-start-time">
|
||||
<label for="due_time">Due Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label>
|
||||
<input type="text" id="due_time" name="due_time"
|
||||
value="${subsection.lms.due.strftime('%H:%M') if subsection.lms.due else ''}"
|
||||
<label for="due_time">${_("Due Time")} (<abbr title="${_('Coordinated Universal Time')}">UTC</abbr>)</label>
|
||||
<input type="text" id="due_time" name="due_time"
|
||||
value="${subsection.lms.due.strftime('%H:%M') if subsection.lms.due else ''}"
|
||||
placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
|
||||
</div>
|
||||
<a href="#" class="remove-date">Remove due date</a>
|
||||
<a href="#" class="remove-date">${_("Remove due date")}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row unit-actions">
|
||||
<a href="${preview_link}" target="_blank" class="preview-button">Preview Drafts</a>
|
||||
<a href="${preview_link}" target="_blank" class="preview-button">${_("Preview Drafts")}</a>
|
||||
%if can_view_live:
|
||||
<a href="${lms_link}" target="_blank" class="preview-button">View Live</a>
|
||||
<a href="${lms_link}" target="_blank" class="preview-button">${_("View Live")}</a>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,19 +118,19 @@
|
||||
// TODO figure out whether these should be in window or someplace else or whether they're only needed as local vars
|
||||
// I believe that current (New Section/New Subsection) cause full page reloads which means these aren't needed globally
|
||||
// but we really should change that behavior.
|
||||
if (!window.graderTypes) {
|
||||
window.graderTypes = new CMS.Models.Settings.CourseGraderCollection();
|
||||
window.graderTypes.course_location = new CMS.Models.Location('${parent_location}');
|
||||
window.graderTypes.reset(${course_graders|n});
|
||||
}
|
||||
if (!window.graderTypes) {
|
||||
window.graderTypes = new CMS.Models.Settings.CourseGraderCollection();
|
||||
window.graderTypes.course_location = new CMS.Models.Location('${parent_location}');
|
||||
window.graderTypes.reset(${course_graders|n});
|
||||
}
|
||||
|
||||
$(".gradable-status").each(function(index, ele) {
|
||||
var gradeView = new CMS.Views.OverviewAssignmentGrader({
|
||||
el : ele,
|
||||
graders : window.graderTypes,
|
||||
hideSymbol : true
|
||||
});
|
||||
});
|
||||
$(".gradable-status").each(function(index, ele) {
|
||||
var gradeView = new CMS.Views.OverviewAssignmentGrader({
|
||||
el : ele,
|
||||
graders : window.graderTypes,
|
||||
hideSymbol : true
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Thank you for signing up for edX Studio! To activate your account,
|
||||
please copy and paste this address into your web browser's
|
||||
address bar:
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
${_("Thank you for signing up for edX Studio! To activate your account, please copy and paste this address into your web browser's address bar:")}
|
||||
|
||||
% if is_secure:
|
||||
https://${ site }/activate/${ key }
|
||||
@@ -8,6 +8,4 @@ address bar:
|
||||
http://${ site }/activate/${ key }
|
||||
% endif
|
||||
|
||||
If you didn't request this, you don't need to do anything; you won't
|
||||
receive any more email from us. Please do not reply to this e-mail; if
|
||||
you require assistance, check the help section of the edX web site.
|
||||
${_("If you didn't request this, you don't need to do anything; you won't receive any more email from us. Please do not reply to this e-mail; if you require assistance, check the help section of the edX web site.")}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
Your account for edX Studio
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
${_("Your account for edX Studio")}
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="bodyclass">error</%block>
|
||||
<%block name="title">
|
||||
% if error == '404':
|
||||
404 - Page Not Found
|
||||
404 - ${_("Page Not Found")}
|
||||
% elif error == '500':
|
||||
500 - Internal Server Error
|
||||
500 - ${_("Internal Server Error")}
|
||||
% endif
|
||||
</%block>
|
||||
|
||||
<%block name="content">
|
||||
<article class="error-prompt">
|
||||
% if error == '404':
|
||||
<h1>Hmm…</h1>
|
||||
<p class="description">we can't find that page.</p>
|
||||
% elif error == '500':
|
||||
<h1>Oops…</h1>
|
||||
<p class="description">there was a problem with the server.</p>
|
||||
% endif
|
||||
<a href="/" class="back-button">Back to dashboard</a>
|
||||
<h1>${_("The Page You Requested Page Cannot be Found")}</h1>
|
||||
<p class="description">${_("We're sorry. We couldn't find the Studio page you're looking for. You may want to return to the Studio Dashboard and try again. If you are still having problems accessing things, please feel free to {link_start}contact Studio support{link_end} for further help.").format(
|
||||
link_start='<a href="http://help.edge.edx.org/discussion/new" class="show-tender" title="{title}">'.format(title=_("Use our feedback tool, Tender, to share your feedback")),
|
||||
link_end='</a>',
|
||||
)}</p>
|
||||
% elif error == '500':
|
||||
<h1>${_("The Server Encountered an Error")}</h1>
|
||||
<p class="description">${_("We're sorry. There was a problem with the server while trying to process your last request. You may want to return to the Studio Dashboard or try this request again. If you are still having problems accessing things, please feel free to {link_start}contact Studio support{link_end} for further help.").format(
|
||||
link_start='<a href="http://help.edge.edx.org/discussion/new" class="show-tender" title="{title}">'.format(title=_("Use our feedback tool, Tender, to share your feedback")),
|
||||
link_end='</a>',
|
||||
)}</p>
|
||||
% endif
|
||||
<a href="/" class="back-button">${_("Back to dashboard")}</a>
|
||||
</article>
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Course Export</%block>
|
||||
<%block name="title">${_("Course Export")}</%block>
|
||||
<%block name="bodyclass">is-signedin course tools export</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Tools</small>
|
||||
<span class="sr">> </span>Course Export
|
||||
<small class="subtitle">${_("Tools")}</small>
|
||||
<span class="sr">> </span>${_("Course Export")}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
@@ -19,28 +20,29 @@
|
||||
<div class="inner-wrapper">
|
||||
<article class="export-overview">
|
||||
<div class="description">
|
||||
<h2>About Exporting Courses</h2>
|
||||
<p>When exporting your course, you will receive a .tar.gz formatted file that contains the following course data:</p>
|
||||
<h2>${_("About Exporting Courses")}</h2>
|
||||
## Translators: ".tar.gz" is a file extension, and should not be translated
|
||||
<p>${_("When exporting your course, you will receive a .tar.gz formatted file that contains the following course data:")}</p>
|
||||
|
||||
<ul>
|
||||
<li>Course Structure (Sections and sub-section ordering)</li>
|
||||
<li>Individual Units</li>
|
||||
<li>Individual Problems</li>
|
||||
<li>Static Pages</li>
|
||||
<li>Course Assets</li>
|
||||
<li>${_("Course Structure (Sections and sub-section ordering)")}</li>
|
||||
<li>${_("Individual Units")}</li>
|
||||
<li>${_("Individual Problems")}</li>
|
||||
<li>${_("Static Pages")}</li>
|
||||
<li>${_("Course Assets")}</li>
|
||||
</ul>
|
||||
|
||||
<p>Your course export <strong>will not include</strong>: student data, forum/discussion data, course settings, certificates, grading information, or user data.</p>
|
||||
<p>${_("Your course export <strong>will not include</strong>: student data, forum/discussion data, course settings, certificates, grading information, or user data.")}</p>
|
||||
</div>
|
||||
|
||||
<!-- default state -->
|
||||
<div class="export-form-wrapper">
|
||||
<form action="${reverse('generate_export_course', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}" method="post" enctype="multipart/form-data" class="export-form">
|
||||
<h2>Export Course:</h2>
|
||||
<h2>${_("Export Course:")}</h2>
|
||||
|
||||
<p class="error-block"></p>
|
||||
|
||||
<a href="${reverse('generate_export_course', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}" class="button-export">Download Files</a>
|
||||
<a href="${reverse('generate_export_course', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}" class="button-export">${_("Download Files")}</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -48,12 +50,12 @@
|
||||
<%doc>
|
||||
<div class="export-form-wrapper is-downloading">
|
||||
<form action="${reverse('export_course', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}" method="post" enctype="multipart/form-data" class="export-form">
|
||||
<h2>Export Course:</h2>
|
||||
<h2>${_("Export Course:")}</h2>
|
||||
|
||||
<p class="error-block"></p>
|
||||
|
||||
<a href="#" class="button-export disabled">Files Downloading</a>
|
||||
<p class="message-status">Download not start? <a href="#" class="text-export">Try again</a></p>
|
||||
<p class="message-status">${_("Download not start?")} <a href="#" class="text-export">${_("Try again")}</a></p>
|
||||
</form>
|
||||
</div>
|
||||
</%doc>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
|
||||
<%block name="title">Welcome</%block>
|
||||
<%block name="title">${_("Welcome")}</%block>
|
||||
<%block name="bodyclass">not-signedin index howitworks</%block>
|
||||
|
||||
<%block name="content">
|
||||
@@ -9,8 +10,9 @@
|
||||
<div class="wrapper-content-header wrapper">
|
||||
<section class="content content-header">
|
||||
<header>
|
||||
<h1>Welcome to <span class="logo">edX Studio</span></h1>
|
||||
<p class="tagline">Studio helps manage your courses online, so you can focus on teaching them</p>
|
||||
## "edX Studio" should not be translated
|
||||
<h1>${_('Welcome to')}<span class="logo">edX Studio</span></h1>
|
||||
<p class="tagline">${_("Studio helps manage your courses online, so you can focus on teaching them")}</p>
|
||||
</header>
|
||||
</section>
|
||||
</div>
|
||||
@@ -18,15 +20,15 @@
|
||||
<div class="wrapper-content-features wrapper">
|
||||
<section class="content content-features">
|
||||
<header>
|
||||
<h2 class="sr">Studio's Many Features</h2>
|
||||
<h2 class="sr">${_("Studio's Many Features")}</h2>
|
||||
</header>
|
||||
|
||||
<ol class="list-features">
|
||||
<li class="feature">
|
||||
<figure class="img zoom">
|
||||
<a rel="modal" href="#hiw-feature1">
|
||||
<img src="/static/img/thumb-hiw-feature1.png" alt="Studio Helps You Keep Your Courses Organized" />
|
||||
<figcaption class="sr">Studio Helps You Keep Your Courses Organized</figcaption>
|
||||
<img src="/static/img/thumb-hiw-feature1.png" alt="${_('Studio Helps You Keep Your Courses Organized')}" />
|
||||
<figcaption class="sr">${_("Studio Helps You Keep Your Courses Organized")}</figcaption>
|
||||
<span class="action-zoom">
|
||||
<i class="icon-zoom-in"></i>
|
||||
</span>
|
||||
@@ -34,23 +36,23 @@
|
||||
</figure>
|
||||
|
||||
<div class="copy">
|
||||
<h3>Keeping Your Course Organized</h3>
|
||||
<p>The backbone of your course is how it is organized. Studio offers an <strong>Outline</strong> editor, providing a simple hierarchy and easy drag and drop to help you and your students stay organized.</p>
|
||||
<h3>${_("Keeping Your Course Organized")}</h3>
|
||||
<p>${_("The backbone of your course is how it is organized. Studio offers an <strong>Outline</strong> editor, providing a simple hierarchy and easy drag and drop to help you and your students stay organized.")}</p>
|
||||
|
||||
<ul class="list-proofpoints">
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Simple Organization For Content</h4>
|
||||
<p>Studio uses a simple hierarchy of <strong>sections</strong> and <strong>subsections</strong> to organize your content.</p>
|
||||
<h4 class="title">${_("Simple Organization For Content")}</h4>
|
||||
<p>${_("Studio uses a simple hierarchy of <strong>sections</strong> and <strong>subsections</strong> to organize your content.")}</p>
|
||||
</li>
|
||||
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Change Your Mind Anytime</h4>
|
||||
<p>Draft your outline and build content anywhere. Simple drag and drop tools let your reorganize quickly.</p>
|
||||
<h4 class="title">${_("Change Your Mind Anytime")}</h4>
|
||||
<p>${_("Draft your outline and build content anywhere. Simple drag and drop tools let your reorganize quickly.")}</p>
|
||||
</li>
|
||||
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Go A Week Or A Semester At A Time</h4>
|
||||
<p>Build and release <strong>sections</strong> to your students incrementally. You don't have to have it all done at once.</p>
|
||||
<h4 class="title">${_("Go A Week Or A Semester At A Time")}</h4>
|
||||
<p>${_("Build and release <strong>sections</strong> to your students incrementally. You don't have to have it all done at once.")}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -59,8 +61,8 @@
|
||||
<li class="feature">
|
||||
<figure class="img zoom">
|
||||
<a rel="modal" href="#hiw-feature2">
|
||||
<img src="/static/img/thumb-hiw-feature2.png" alt="Learning is More than Just Lectures" />
|
||||
<figcaption class="sr">Learning is More than Just Lectures</figcaption>
|
||||
<img src="/static/img/thumb-hiw-feature2.png" alt="${_('Learning is More than Just Lectures')}" />
|
||||
<figcaption class="sr">${_("Learning is More than Just Lectures")}</figcaption>
|
||||
<span class="action-zoom">
|
||||
<i class="icon-zoom-in"></i>
|
||||
</span>
|
||||
@@ -68,23 +70,23 @@
|
||||
</figure>
|
||||
|
||||
<div class="copy">
|
||||
<h3>Learning is More than Just Lectures</h3>
|
||||
<p>Studio lets you weave your content together in a way that reinforces learning — short video lectures interleaved with exercises and more. Insert videos and author a wide variety of exercise types with just a few clicks. </p>
|
||||
<h3>${_("Learning is More than Just Lectures")}</h3>
|
||||
<p>${_("Studio lets you weave your content together in a way that reinforces learning — short video lectures interleaved with exercises and more. Insert videos and author a wide variety of exercise types with just a few clicks.")} </p>
|
||||
|
||||
<ul class="list-proofpoints">
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Create Learning Pathways</h4>
|
||||
<p>Help your students understand a small interactive piece at a time with multimedia, HTML, and exercises.</p>
|
||||
<h4 class="title">${_("Create Learning Pathways")}</h4>
|
||||
<p>${_("Help your students understand a small interactive piece at a time with multimedia, HTML, and exercises.")}</p>
|
||||
</li>
|
||||
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Work Visually, Organize Quickly</h4>
|
||||
<p>Work visually and see exactly what your students will see. Reorganize all your content with drag and drop.</p>
|
||||
<h4 class="title">${_("Work Visually, Organize Quickly")}</h4>
|
||||
<p>${_("Work visually and see exactly what your students will see. Reorganize all your content with drag and drop.")}</p>
|
||||
</li>
|
||||
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">A Broad Library of Problem Types</h4>
|
||||
<p>It's more than just multiple choice. Studio has nearly a dozen types of problems to challenge your learners.</p>
|
||||
<h4 class="title">${_("A Broad Library of Problem Types")}</h4>
|
||||
<p>${_("It's more than just multiple choice. Studio has nearly a dozen types of problems to challenge your learners.")}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -93,8 +95,8 @@
|
||||
<li class="feature">
|
||||
<figure class="img zoom">
|
||||
<a rel="modal" href="#hiw-feature3">
|
||||
<img src="/static/img/thumb-hiw-feature3.png" alt="Studio Gives You Simple, Fast, and Incremental Publishing. With Friends." />
|
||||
<figcaption class="sr">Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.</figcaption>
|
||||
<img src="/static/img/thumb-hiw-feature3.png" alt="${_('Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.')}" />
|
||||
<figcaption class="sr">${_("Studio Gives You Simple, Fast, and Incremental Publishing. With Friends.")}</figcaption>
|
||||
<span class="action-zoom">
|
||||
<i class="icon-zoom-in"></i>
|
||||
</span>
|
||||
@@ -102,23 +104,23 @@
|
||||
</figure>
|
||||
|
||||
<div class="copy">
|
||||
<h3>Simple, Fast, and Incremental Publishing. With Friends.</h3>
|
||||
<p>Studio works like web applications you already know, yet understands how you build curriculum. Instant publishing to the web when you want it, incremental release when it makes sense. And with co-authors, you can have a whole team building a course, together.</p>
|
||||
<h3>${_("Simple, Fast, and Incremental Publishing. With Friends.")}</h3>
|
||||
<p>${_("Studio works like web applications you already know, yet understands how you build curriculum. Instant publishing to the web when you want it, incremental release when it makes sense. And with co-authors, you can have a whole team building a course, together.")}</p>
|
||||
|
||||
<ul class="list-proofpoints">
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Instant Changes</h4>
|
||||
<p>Caught a bug? No problem. When you want, your changes to live when you hit Save.</p>
|
||||
<h4 class="title">${_("Instant Changes")}</h4>
|
||||
<p>${_("Caught a bug? No problem. When you want, your changes to live when you hit Save.")}</p>
|
||||
</li>
|
||||
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Release-On Date Publishing</h4>
|
||||
<p>When you've finished a <strong>section</strong>, pick when you want it to go live and Studio takes care of the rest. Build your course incrementally.</p>
|
||||
<h4 class="title">${_("Release-On Date Publishing")}</h4>
|
||||
<p>${_("When you've finished a <strong>section</strong>, pick when you want it to go live and Studio takes care of the rest. Build your course incrementally.")}</p>
|
||||
</li>
|
||||
|
||||
<li class="proofpoint">
|
||||
<h4 class="title">Work in Teams</h4>
|
||||
<p>Co-authors have full access to all the same authoring tools. Make your course better through a team effort.</p>
|
||||
<h4 class="title">${_("Work in Teams")}</h4>
|
||||
<p>${_("Co-authors have full access to all the same authoring tools. Make your course better through a team effort.")}</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -130,56 +132,56 @@
|
||||
<div class="wrapper-content-cta wrapper">
|
||||
<section class="content content-cta">
|
||||
<header>
|
||||
<h2 class="sr">Sign Up for Studio Today!</h2>
|
||||
<h2 class="sr">${_("Sign Up for Studio Today!")}</h2>
|
||||
</header>
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action-item">
|
||||
<a href="${reverse('signup')}" class="action action-primary">Sign Up & Start Making an edX Course</a>
|
||||
<a href="${reverse('signup')}" class="action action-primary">${_("Sign Up & Start Making an edX Course")}</a>
|
||||
</li>
|
||||
<li class="action-item">
|
||||
<a href="${reverse('login')}" class="action action-secondary">Already have a Studio Account? Sign In</a>
|
||||
<a href="${reverse('login')}" class="action action-secondary">${_("Already have a Studio Account? Sign In")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="content-modal" id="hiw-feature1">
|
||||
<h3 class="title">Outlining Your Course</h3>
|
||||
<h3 class="title">${_("Outlining Your Course")}</h3>
|
||||
<figure>
|
||||
<img src="/static/img/hiw-feature1.png" alt="" />
|
||||
<figcaption class="description">Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.</figcaption>
|
||||
<figcaption class="description">${_("Simple two-level outline to organize your couse. Drag and drop, and see your course at a glance.")}</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="" rel="view" class="action action-modal-close">
|
||||
<i class="icon-remove-sign"></i>
|
||||
<span class="label">close modal</span>
|
||||
<span class="label">${_("close modal")}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="content-modal" id="hiw-feature2">
|
||||
<h3 class="title">More than Just Lectures</h3>
|
||||
<h3 class="title">${_("More than Just Lectures")}</h3>
|
||||
<figure>
|
||||
<img src="/static/img/hiw-feature2.png" alt="" />
|
||||
<figcaption class="description">Quickly create videos, text snippets, inline discussions, and a variety of problem types.</figcaption>
|
||||
<figcaption class="description">${_("Quickly create videos, text snippets, inline discussions, and a variety of problem types.")}</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="" rel="view" class="action action-modal-close">
|
||||
<i class="icon-remove-sign"></i>
|
||||
<span class="label">close modal</span>
|
||||
<span class="label">${_("close modal")}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="content-modal" id="hiw-feature3">
|
||||
<h3 class="title">Publishing on Date</h3>
|
||||
<h3 class="title">${_("Publishing on Date")}</h3>
|
||||
<figure>
|
||||
<img src="/static/img/hiw-feature3.png" alt="" />
|
||||
<figcaption class="description">Simply set the date of a section or subsection, and Studio will publish it to your students for you.</figcaption>
|
||||
<figcaption class="description">${_("Simply set the date of a section or subsection, and Studio will publish it to your students for you.")}</figcaption>
|
||||
</figure>
|
||||
|
||||
<a href="" rel="view" class="action action-modal-close">
|
||||
<i class="icon-remove-sign"></i>
|
||||
<span class="label">close modal</span>
|
||||
<span class="label">${_("close modal")}</span>
|
||||
</a>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Course Import</%block>
|
||||
<%block name="title">${_("Course Import")}</%block>
|
||||
<%block name="bodyclass">is-signedin course tools import</%block>
|
||||
|
||||
<%block name="content">
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Tools</small>
|
||||
<span class="sr">> </span>Course Import
|
||||
<small class="subtitle">${_("Tools")}</small>
|
||||
<span class="sr">> </span>${_("Course Import")}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
@@ -19,19 +20,18 @@
|
||||
<div class="inner-wrapper">
|
||||
<article class="import-overview">
|
||||
<div class="description">
|
||||
<p><strong>Importing a new course will delete all content currently associated with your course
|
||||
and replace it with the contents of the uploaded file.</strong></p>
|
||||
<p>File uploads must be gzipped tar files (.tar.gz) containing, at a minimum, a <code>course.xml</code> file.</p>
|
||||
<p>Please note that if your course has any problems with auto-generated <code>url_name</code> nodes,
|
||||
re-importing your course could cause the loss of student data associated with those problems.</p>
|
||||
<p><strong>${_("Importing a new course will delete all content currently associated with your course and replace it with the contents of the uploaded file.")}</strong></p>
|
||||
## Translators: ".tar.gz" is a file extension, and files with that extension are called "gzipped tar files": these terms should not be translated
|
||||
<p>${_("File uploads must be gzipped tar files (.tar.gz) containing, at a minimum, a {filename} file.").format(filename='<code>course.xml</code>')}</p>
|
||||
<p>${_("Please note that if your course has any problems with auto-generated {nodename} nodes, re-importing your course could cause the loss of student data associated with those problems.").format(nodename='<code>url_name</code>')}</p>
|
||||
</div>
|
||||
<form action="${reverse('import_course', kwargs=dict(org=context_course.location.org, course=context_course.location.course, name=context_course.location.name))}" method="post" enctype="multipart/form-data" class="import-form">
|
||||
<h2>Course to import:</h2>
|
||||
<h2>${_("Course to import:")}</h2>
|
||||
<p class="error-block"></p>
|
||||
<a href="#" class="choose-file-button">Choose File</a>
|
||||
<p class="file-name-block"><span class="file-name"></span><a href="#" class="choose-file-button-inline">change</a></p>
|
||||
<a href="#" class="choose-file-button">${_("Choose File")}</a>
|
||||
<p class="file-name-block"><span class="file-name"></span><a href="#" class="choose-file-button-inline">${_("change")}</a></p>
|
||||
<input type="file" name="course-data" class="file-input">
|
||||
<input type="submit" value="Replace my course with the one above" class="submit-button">
|
||||
<input type="submit" value="${_('Replace my course with the one above')}" class="submit-button">
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill"></div>
|
||||
<div class="percent">0%</div>
|
||||
@@ -68,11 +68,11 @@ $('form').ajaxForm({
|
||||
},
|
||||
complete: function(xhr) {
|
||||
if (xhr.status == 200) {
|
||||
alert('Your import was successful.');
|
||||
alert('${_("Your import was successful.")}');
|
||||
window.location = '${successful_import_redirect_url}';
|
||||
}
|
||||
else
|
||||
alert('Your import has failed.\n\n' + xhr.responseText);
|
||||
alert('${_("Your import has failed.")}\n\n' + xhr.responseText);
|
||||
submitBtn.show();
|
||||
bar.hide();
|
||||
}
|
||||
|
||||
@@ -11,22 +11,22 @@
|
||||
<div class="item-details">
|
||||
<form class="course-info">
|
||||
<div class="row">
|
||||
<label>Course Name</label>
|
||||
<label>${_("Course Name")}</label>
|
||||
<input type="text" class="new-course-name" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="column">
|
||||
<label>Organization</label>
|
||||
<label>${_("Organization")}</label>
|
||||
<input type="text" class="new-course-org" />
|
||||
</div>
|
||||
<div class="column">
|
||||
<label>Course Number</label>
|
||||
<label>${_("Course Number")}</label>
|
||||
<input type="text" class="new-course-number" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="submit" value="Save" class="new-course-save" data-template="${new_course_template}" />
|
||||
<input type="button" value="Cancel" class="new-course-cancel" />
|
||||
<input type="submit" value="${_('Save')}" class="new-course-save"/>
|
||||
<input type="button" value="${_('Cancel')}" class="new-course-cancel" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -76,7 +76,7 @@
|
||||
<a class="class-link" href="${url}" class="class-name">
|
||||
<span class="class-name">${course}</span>
|
||||
</a>
|
||||
<a href="${lms_link}" rel="external" class="button view-button view-live-button">View Live</a>
|
||||
<a href="${lms_link}" rel="external" class="button view-button view-live-button">${_("View Live")}</a>
|
||||
</li>
|
||||
%endfor
|
||||
</ul>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Sign In</%block>
|
||||
<%block name="title">${_("Sign In")}</%block>
|
||||
<%block name="bodyclass">not-signedin signin</%block>
|
||||
|
||||
<%block name="content">
|
||||
@@ -8,32 +9,32 @@
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<header>
|
||||
<h1 class="title title-1">Sign In to edX Studio</h1>
|
||||
<a href="${reverse('signup')}" class="action action-signin">Don't have a Studio Account? Sign up!</a>
|
||||
<h1 class="title title-1">${_("Sign In to edX Studio")}</h1>
|
||||
<a href="${reverse('signup')}" class="action action-signin">${_("Don't have a Studio Account? Sign up!")}</a>
|
||||
</header>
|
||||
|
||||
<article class="content-primary" role="main">
|
||||
<form id="login_form" method="post" action="login_post">
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend class="sr">Required Information to Sign In to edX Studio</legend>
|
||||
|
||||
<legend class="sr">${_("Required Information to Sign In to edX Studio")}</legend>
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field text required" id="field-email">
|
||||
<label for="email">Email Address</label>
|
||||
<input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
|
||||
<label for="email">${_("Email Address")}</label>
|
||||
<input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-password">
|
||||
<a href="${forgot_password_link}" class="action action-forgotpassword" tabindex="-1">Forgot password?</a>
|
||||
<label for="password">Password</label>
|
||||
<input id="password" type="password" name="password" />
|
||||
<a href="${forgot_password_link}" class="action action-forgotpassword" tabindex="-1">${_("Forgot password?")}</a>
|
||||
<label for="password">${_("Password")}</label>
|
||||
<input id="password" type="password" name="password" />
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" id="submit" name="submit" class="action action-primary">Sign In to edX Studio</button>
|
||||
<button type="submit" id="submit" name="submit" class="action action-primary">${_("Sign In to edX Studio")}</button>
|
||||
</div>
|
||||
|
||||
<!-- no honor code for CMS, but need it because we're using the lms student object -->
|
||||
@@ -42,11 +43,11 @@
|
||||
</article>
|
||||
|
||||
<aside class="content-supplementary" role="complimentary">
|
||||
<h2 class="sr">Studio Support</h2>
|
||||
<h2 class="sr">${_("Studio Support")}</h2>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title-3">Need Help?</h3>
|
||||
<p>Having trouble with your account? Use <a href="http://help.edge.edx.org" rel="external">our support center</a> to look over self help steps, find solutions others have found to the same problem, or let us know of your issue.</p>
|
||||
<h3 class="title-3">${_("Need Help?")}</h3>
|
||||
<p>${_('Having trouble with your account? Use {link_start}our support center{link_end} to look over self help steps, find solutions others have found to the same problem, or let us know of your issue.').format(link_start='<a href="http://help.edge.edx.org" rel="external">', link_end='</a>')}</p>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
@@ -94,4 +95,4 @@
|
||||
});
|
||||
})(this)
|
||||
</script>
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Course Team Settings</%block>
|
||||
<%block name="title">${_("Course Team Settings")}</%block>
|
||||
<%block name="bodyclass">is-signedin course users settings team</%block>
|
||||
|
||||
|
||||
@@ -7,16 +8,16 @@
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Course Settings</small>
|
||||
<span class="sr">> </span>Course Team
|
||||
<small class="subtitle">${_("Course Settings")}</small>
|
||||
<span class="sr">> </span>${_("Course Team")}
|
||||
</h1>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<h3 class="sr">${_("Page Actions")}</h3>
|
||||
<ul>
|
||||
%if allow_actions:
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button new-button new-user-button"><i class="icon-plus"></i> New User</a>
|
||||
<a href="#" class="button new-button new-user-button"><i class="icon-plus"></i> ${_("New User")}</a>
|
||||
</li>
|
||||
%endif
|
||||
</ul>
|
||||
@@ -28,7 +29,7 @@
|
||||
<div class="inner-wrapper">
|
||||
|
||||
<div class="details">
|
||||
<p>The following list of users have been designated as course staff. This means that these users will have permissions to modify course content. You may add additional course staff below, if you are the course instructor. Please note that they must have already registered and verified their account.</p>
|
||||
<p>${_("The following list of users have been designated as course staff. This means that these users will have permissions to modify course content. You may add additional course staff below, if you are the course instructor. Please note that they must have already registered and verified their account.")}</p>
|
||||
</div>
|
||||
|
||||
<article class="user-overview">
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
/<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%!
|
||||
import logging
|
||||
from xmodule.util import date_utils
|
||||
%>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Course Outline</%block>
|
||||
<%block name="title">${_("Course Outline")}</%block>
|
||||
<%block name="bodyclass">is-signedin course outline</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%namespace name="units" file="widgets/units.html" />
|
||||
|
||||
<%block name="jsextra">
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
|
||||
<script src="${static.url('js/vendor/timepicker/jquery.timepicker.js')}"></script>
|
||||
<script src="${static.url('js/vendor/timepicker/datepair.js')}"></script>
|
||||
<script src="${static.url('js/vendor/date.js')}"></script>
|
||||
@@ -60,13 +61,14 @@
|
||||
<script type="text/template" id="new-section-template">
|
||||
<section class="courseware-section branch new-section">
|
||||
<header>
|
||||
<a href="#" data-tooltip="Collapse/expand this section" class="expand-collapse-icon collapse"></a>
|
||||
<a href="#" data-tooltip="${_('Collapse/expand this section')}" class="expand-collapse-icon collapse"></a>
|
||||
<div class="item-details">
|
||||
<h3 class="section-name">
|
||||
<form class="section-name-form">
|
||||
<input type="text" value="New Section Name" class="new-section-name" />
|
||||
<input type="submit" class="new-section-name-save" data-parent="${parent_location}" data-template="${new_section_template}" value="Save" />
|
||||
<input type="button" class="new-section-name-cancel" value="Cancel" /></h3>
|
||||
<input type="text" value="${_('New Section Name')}" class="new-section-name" />
|
||||
<input type="submit" class="new-section-name-save" data-parent="${parent_location}"
|
||||
data-category="${new_section_category}" value="${_('Save')}" />
|
||||
<input type="button" class="new-section-name-cancel" value="${_('Cancel')}" /></h3>
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
@@ -76,19 +78,20 @@
|
||||
<script type="text/template" id="blank-slate-template">
|
||||
<section class="courseware-section branch new-section">
|
||||
<header>
|
||||
<a href="#" data-tooltip="Collapse/expand this section" class="expand-collapse-icon collapse"></a>
|
||||
<a href="#" data-tooltip="${_('Collapse/expand this section')}" class="expand-collapse-icon collapse"></a>
|
||||
<div class="item-details">
|
||||
<h3 class="section-name">
|
||||
<span class="section-name-span">Click here to set the section name</span>
|
||||
<form class="section-name-form">
|
||||
<input type="text" value="New Section Name" class="new-section-name" />
|
||||
<input type="submit" class="new-section-name-save" data-parent="${parent_location}" data-template="${new_section_template}" value="Save" />
|
||||
<input type="button" class="new-section-name-cancel" value="Cancel" /></h3>
|
||||
<input type="text" value="${_('New Section Name')}" class="new-section-name" />
|
||||
<input type="submit" class="new-section-name-save" data-parent="${parent_location}"
|
||||
data-category="${new_section_category}" value="${_('Save')}" />
|
||||
<input type="button" class="new-section-name-cancel" value="$(_('Cancel')}" /></h3>
|
||||
</form>
|
||||
</div>
|
||||
<div class="item-actions">
|
||||
<a href="#" data-tooltip="Delete this section" class="delete-button delete-section-button"><span class="delete-icon"></span></a>
|
||||
<a href="#" data-tooltip="Drag to re-order" class="drag-handle"></a>
|
||||
<a href="#" data-tooltip="${_('Delete this section')}" class="delete-button delete-section-button"><span class="delete-icon"></span></a>
|
||||
<a href="#" data-tooltip="${_('Drag to re-order')}" class="drag-handle"></a>
|
||||
</div>
|
||||
</header>
|
||||
</section>
|
||||
@@ -100,16 +103,16 @@
|
||||
<form class="new-subsection-form">
|
||||
<span class="folder-icon"></span>
|
||||
<span class="subsection-name">
|
||||
<input type="text" value="New Subsection" class="new-subsection-name-input" />
|
||||
<input type="text" value="${_('New Subsection')}" class="new-subsection-name-input" />
|
||||
</span>
|
||||
<input type="submit" value="Save" class="new-subsection-name-save" />
|
||||
<input type="button" value="Cancel" class="new-subsection-name-cancel" />
|
||||
<input type="submit" value="${_('Save')}" class="new-subsection-name-save" />
|
||||
<input type="button" value="${_('Cancel')}" class="new-subsection-name-cancel" />
|
||||
</form>
|
||||
</div>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="unit.html" class="new-unit-item">
|
||||
<span class="new-unit-icon"></span>New Unit
|
||||
<span class="new-unit-icon"></span>${_('New Unit')}
|
||||
</a>
|
||||
</li>
|
||||
</ol>
|
||||
@@ -121,21 +124,21 @@
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-actions has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Content</small>
|
||||
<span class="sr">> </span>Course Outline
|
||||
<small class="subtitle">${_("Content")}</small>
|
||||
<span class="sr">> </span>${_("Course Outline")}
|
||||
</h1>
|
||||
|
||||
<nav class="nav-actions">
|
||||
<h3 class="sr">Page Actions</h3>
|
||||
<h3 class="sr">${_("Page Actions")}</h3>
|
||||
<ul>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="toggle-button toggle-button-sections"><i class="icon-arrow-up"></i> <span class="label">Collapse All Sections</span></a>
|
||||
<a href="#" class="toggle-button toggle-button-sections"><i class="icon-arrow-up"></i> <span class="label">${_("Collapse All Sections")}</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="#" class="button new-button new-courseware-section-button"><i class="icon-plus"></i> New Section</a>
|
||||
<a href="#" class="button new-button new-courseware-section-button"><i class="icon-plus"></i> ${_("New Section")}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="${lms_link}" rel="external" class="button view-button view-live-button">View Live</a>
|
||||
<a href="${lms_link}" rel="external" class="button view-button view-live-button">${_("View Live")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -148,7 +151,7 @@
|
||||
% for section in sections:
|
||||
<section class="courseware-section branch" data-id="${section.location}">
|
||||
<header>
|
||||
<a href="#" data-tooltip="Expand/collapse this section" class="expand-collapse-icon collapse"></a>
|
||||
<a href="#" data-tooltip="${_('Expand/collapse this section')}" class="expand-collapse-icon collapse"></a>
|
||||
|
||||
<div class="item-details" data-id="${section.location}">
|
||||
<h3 class="section-name" data-name="${section.display_name_with_default | h}"></h3>
|
||||
@@ -162,26 +165,26 @@
|
||||
start_time_str = ''
|
||||
%>
|
||||
%if section.lms.start is None:
|
||||
<span class="published-status">This section has not been released.</span>
|
||||
<a href="#" class="schedule-button" data-date="" data-time="" data-id="${section.location}">Schedule</a>
|
||||
<span class="published-status">${_("This section has not been released.")}</span>
|
||||
<a href="#" class="schedule-button" data-date="" data-time="" data-id="${section.location}">${_("Schedule")}</a>
|
||||
%else:
|
||||
<span class="published-status"><strong>Will Release:</strong>
|
||||
<span class="published-status"><strong>${_("Will Release:")}</strong>
|
||||
${date_utils.get_default_time_display(section.lms.start)}</span>
|
||||
<a href="#" class="edit-button" data-date="${start_date_str}"
|
||||
data-time="${start_time_str}" data-id="${section.location}">Edit</a>
|
||||
data-time="${start_time_str}" data-id="${section.location}">${_("Edit")}</a>
|
||||
%endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="item-actions">
|
||||
<a href="#" data-tooltip="Delete this section" class="delete-button delete-section-button"><span class="delete-icon"></span></a>
|
||||
<a href="#" data-tooltip="Drag to reorder" class="drag-handle"></a>
|
||||
<a href="#" data-tooltip="${_('Delete this section')}" class="delete-button delete-section-button"><span class="delete-icon"></span></a>
|
||||
<a href="#" data-tooltip="${_('Drag to reorder')}" class="drag-handle"></a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="subsection-list">
|
||||
<div class="list-header">
|
||||
<a href="#" class="new-subsection-item" data-template="${new_subsection_template}">
|
||||
<span class="new-folder-icon"></span>New Subsection
|
||||
<a href="#" class="new-subsection-item" data-category="${new_subsection_category}">
|
||||
<span class="new-folder-icon"></span>${_("New Subsection")}
|
||||
</a>
|
||||
</div>
|
||||
<ol data-section-id="${section.location.url()}">
|
||||
@@ -189,19 +192,19 @@
|
||||
<li class="branch collapsed id-holder" data-id="${subsection.location}">
|
||||
<div class="section-item">
|
||||
<div class="details">
|
||||
<a href="#" data-tooltip="Expand/collapse this subsection" class="expand-collapse-icon expand"></a>
|
||||
<a href="#" data-tooltip="${_('Expand/collapse this subsection')}" class="expand-collapse-icon expand"></a>
|
||||
<a href="${reverse('edit_subsection', args=[subsection.location])}">
|
||||
<span class="folder-icon"></span>
|
||||
<span class="subsection-name"><span class="subsection-name-value">${subsection.display_name_with_default}</span></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="gradable-status" data-initial-status="${subsection.lms.format if subsection.lms.format is not None else 'Not Graded'}">
|
||||
<div class="gradable-status" data-initial-status="${subsection.lms.format if subsection.lms.format is not None else _('Not Graded')}">
|
||||
</div>
|
||||
|
||||
<div class="item-actions">
|
||||
<a href="#" data-tooltip="Delete this subsection" class="delete-button delete-subsection-button"><span class="delete-icon"></span></a>
|
||||
<a href="#" data-tooltip="Drag to reorder" class="drag-handle"></a>
|
||||
<a href="#" data-tooltip="${_('Delete this subsection')}" class="delete-button delete-subsection-button"><span class="delete-icon"></span></a>
|
||||
<a href="#" data-tooltip="${_('Drag to reorder')}" class="drag-handle"></a>
|
||||
</div>
|
||||
</div>
|
||||
${units.enum_units(subsection)}
|
||||
@@ -218,22 +221,22 @@
|
||||
|
||||
<div class="edit-subsection-publish-settings">
|
||||
<div class="settings">
|
||||
<h3>Section Release Date</h3>
|
||||
<h3>${_("Section Release Date")}</h3>
|
||||
<div class="picker datepair">
|
||||
<div class="field field-start-date">
|
||||
<label for="">Release Day</label>
|
||||
<label for="">${_("Release Day")}</label>
|
||||
<input class="start-date date" type="text" name="start_date" value="" placeholder="MM/DD/YYYY" class="date" size='15' autocomplete="off"/>
|
||||
</div>
|
||||
<div class="field field-start-time">
|
||||
<label for="">Release Time (<abbr title="Coordinated Universal Time">UTC</abbr>)</label>
|
||||
<label for="">${_("Release Time")} (<abbr title="${_("Coordinated Universal Time")}">UTC</abbr>)</label>
|
||||
<input class="start-time time" type="text" name="start_time" value="" placeholder="HH:MM" class="time" size='10' autocomplete="off"/>
|
||||
</div>
|
||||
|
||||
<div class="description">
|
||||
<p>On the date set above, this section – <strong class="section-name"></strong> – will be released to students. Any units marked private will only be visible to admins.</p>
|
||||
<p>${_('On the date set above, this section - {name} - will be released to students. Any units marked private will only be visible to admins.').format(name='<strong class="section-name"></strong>')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="#" class="save-button">Save</a><a href="#" class="cancel-button">Cancel</a>
|
||||
<a href="#" class="save-button">${_("Save")}</a><a href="#" class="cancel-button">${_("Cancel")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%inherit file="../base.html" />
|
||||
|
||||
<%namespace name='static' file='../static_content.html'/>
|
||||
|
||||
%if not user_logged_in:
|
||||
%if not user_logged_in:
|
||||
<%block name="bodyclass">
|
||||
not-signedin
|
||||
not-signedin
|
||||
</%block>
|
||||
%endif
|
||||
|
||||
@@ -16,15 +17,15 @@
|
||||
|
||||
<p style='padding-top:100px; text-align:center;'>
|
||||
%if not already_active:
|
||||
Thanks for activating your account.
|
||||
${_("Thanks for activating your account.")}
|
||||
%else:
|
||||
This account has already been activated.
|
||||
${_("This account has already been activated.")}
|
||||
%endif
|
||||
|
||||
%if user_logged_in:
|
||||
Visit your <a href="/">dashboard</a> to see your courses.
|
||||
${_("Visit your {link_start}dashboard{link_end} to see your courses.").format(link_start='<a href="/">', link_end='</a>')}
|
||||
%else:
|
||||
You can now <a href="${reverse('login')}">login</a>.
|
||||
${_("You can now {link_start}login{link_end}.").format(link_start='<a href="{url}">'.format(url=reverse('login')), link_end='</a>')}
|
||||
%endif
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<h1>Check your email</h1>
|
||||
<p>An activation link has been sent to ${ email }, along with
|
||||
instructions for activating your account.</p>
|
||||
<p>${_("An activation link has been sent to {emaiL}, along with instructions for activating your account.").format(email=email)}</p>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Schedule & Details Settings</%block>
|
||||
<%block name="title">${_("Schedule & Details Settings")}</%block>
|
||||
<%block name="bodyclass">is-signedin course schedule settings</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Advanced Settings</%block>
|
||||
<%block name="title">${_("Advanced Settings")}</%block>
|
||||
<%block name="bodyclass">is-signedin course advanced settings</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
@@ -44,8 +45,8 @@ editor.render();
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Settings</small>
|
||||
<span class="sr">> </span>Advanced Settings
|
||||
<small class="subtitle">${_("Settings")}</small>
|
||||
<span class="sr">> </span>${_("Advanced Settings")}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
@@ -56,20 +57,20 @@ editor.render();
|
||||
<form id="settings_advanced" class="settings-advanced" method="post" action="">
|
||||
|
||||
<div class="message message-status confirm">
|
||||
Your policy changes have been saved.
|
||||
${_("Your policy changes have been saved.")}
|
||||
</div>
|
||||
|
||||
<div class="message message-status error">
|
||||
There was an error saving your information. Please see below.
|
||||
${_("There was an error saving your information. Please see below.")}
|
||||
</div>
|
||||
|
||||
<section class="group-settings advanced-policies">
|
||||
<header>
|
||||
<h2 class="title-2">Manual Policy Definition</h2>
|
||||
<span class="tip">Manually Edit Course Policy Values (JSON Key / Value pairs)</span>
|
||||
<h2 class="title-2">${_("Manual Policy Definition")}</h2>
|
||||
<span class="tip">${_("Manually Edit Course Policy Values (JSON Key / Value pairs)")}</span>
|
||||
</header>
|
||||
|
||||
<p class="instructions"><strong>Warning</strong>: Do not modify these policies unless you are familiar with their purpose.</p>
|
||||
<p class="instructions">${_("<strong>Warning</strong>: Do not modify these policies unless you are familiar with their purpose.")}</p>
|
||||
|
||||
<ul class="list-input course-advanced-policy-list enum">
|
||||
|
||||
@@ -80,22 +81,22 @@ editor.render();
|
||||
|
||||
<aside class="content-supplementary" role="complimentary">
|
||||
<div class="bit">
|
||||
<h3 class="title-3">How will these settings be used?</h3>
|
||||
<p>Manual policies are JSON-based key and value pairs that give you control over specific course settings that edX Studio will use when displaying and running your course.</p>
|
||||
<h3 class="title-3">${_("How will these settings be used?")}</h3>
|
||||
<p>${_("Manual policies are JSON-based key and value pairs that give you control over specific course settings that edX Studio will use when displaying and running your course.")}</p>
|
||||
|
||||
<p>Any policies you modify here will override any other information you've defined elsewhere in Studio. With this in mind, please be very careful and do not edit policies that you are unfamiliar with (both their purpose and their syntax).</p>
|
||||
<p>${_("Any policies you modify here will override any other information you've defined elsewhere in Studio. With this in mind, please be very careful and do not edit policies that you are unfamiliar with (both their purpose and their syntax)")}.</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
% if context_course:
|
||||
<% ctx_loc = context_course.location %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<h3 class="title-3">Other Course Settings</h3>
|
||||
<h3 class="title-3">${_("Other Course Settings")}</h3>
|
||||
<nav class="nav-related">
|
||||
<ul>
|
||||
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Details & Schedule</a></li>
|
||||
<li class="nav-item"><a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Grading</a></li>
|
||||
<li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
|
||||
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("Details & Schedule")}</a></li>
|
||||
<li class="nav-item"><a href="${reverse('contentstore.views.course_config_graders_page', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">${_("Grading")}</a></li>
|
||||
<li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">${_("Course Team")}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
% endif
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<!-- NOTE not used currently but retained b/c it's yet-to-be-wired functionality -->
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Schedule and details</%block>
|
||||
<%block name="title">${_("Schedule and details")}</%block>
|
||||
<%block name="bodyclass">is-signedin course settings</%block>
|
||||
|
||||
|
||||
@@ -26,17 +27,17 @@ from contentstore import utils
|
||||
<!-- -->
|
||||
<div class="main-wrapper">
|
||||
<div class="inner-wrapper">
|
||||
<h1>Settings</h1>
|
||||
<h1>${_("Settings")}</h1>
|
||||
<article class="settings-overview">
|
||||
<div class="settings-page-section main-column">
|
||||
|
||||
<section class="settings-faculty">
|
||||
<h2 class="title">Faculty</h2>
|
||||
<h2 class="title">${_("Faculty")}</h2>
|
||||
|
||||
<section class="settings-faculty-members">
|
||||
<header>
|
||||
<h3>Faculty Members</h3>
|
||||
<span class="detail">Individuals instructing and help with this course</span>
|
||||
<h3>${_("Faculty Members")}</h3>
|
||||
<span class="detail">${_("Individuals instructing and help with this course")}</span>
|
||||
</header>
|
||||
|
||||
<div class="row">
|
||||
@@ -44,74 +45,74 @@ from contentstore import utils
|
||||
<ul class="input-list course-faculty-list">
|
||||
<li class="input input-existing multi course-faculty-list-item">
|
||||
<div class="row row-col2">
|
||||
<label for="course-faculty-1-firstname">Faculty First Name:</label>
|
||||
<label for="course-faculty-1-firstname">${_("Faculty First Name:")}</label>
|
||||
<div class="field">
|
||||
<input type="text" class="long" id="course-faculty-1-firstname">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<label for="course-faculty-1-lastname">Faculty Last Name:</label>
|
||||
<label for="course-faculty-1-lastname">${_("Faculty Last Name:")}</label>
|
||||
<div class="field">
|
||||
<input type="text" class="long" id="course-faculty-1-lastname">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<label for="course-faculty-1-photo">Faculty Photo</label>
|
||||
<label for="course-faculty-1-photo">${_("Faculty Photo")}</label>
|
||||
<div class="field">
|
||||
<div class="input input-existing">
|
||||
<div class="current current-faculty-1-photo">
|
||||
<a href="#" class="remove-item remove-faculty-photo remove-video-data"><span class="delete-icon"></span> Delete Faculty Photo</a>
|
||||
<a href="#" class="remove-item remove-faculty-photo remove-video-data"><span class="delete-icon"></span> ${_("Delete Faculty Photo")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label for="course-faculty-1-bio">Faculty Bio:</label>
|
||||
<label for="course-faculty-1-bio">${_("Faculty Bio:")}</label>
|
||||
<div class="field">
|
||||
<textarea class="long tall edit-box tinymce" id="course-faculty-1-bio"></textarea>
|
||||
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
|
||||
<span class="tip tip-stacked">${_("A brief description of your education, experience, and expertise")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="#" class="remove-item remove-faculty-data"><span class="delete-icon"></span> Delete Faculty Member</a>
|
||||
<a href="#" class="remove-item remove-faculty-data"><span class="delete-icon"></span> ${_("Delete Faculty Member")}</a>
|
||||
</li>
|
||||
|
||||
<li class="input multi course-faculty-list-item">
|
||||
<div class="row row-col2">
|
||||
<label for="course-faculty-2-firstname">Faculty First Name:</label>
|
||||
<label for="course-faculty-2-firstname">${_("Faculty First Name:")}</label>
|
||||
<div class="field">
|
||||
<input type="text" class="long" id="course-faculty-2-firstname">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<label for="course-faculty-2-lastname">Faculty Last Name:</label>
|
||||
<label for="course-faculty-2-lastname">${_("Faculty Last Name:")}</label>
|
||||
<div class="field">
|
||||
<input type="text" class="long" id="course-faculty-2-lastname">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<label for="course-faculty-2-photo">Faculty Photo</label>
|
||||
<label for="course-faculty-2-photo">${_("Faculty Photo")}</label>
|
||||
<div class="field">
|
||||
<div class="input">
|
||||
<a href="#" class="new-item new-faculty-photo add-faculty-photo-data" id="course-faculty-2-photo">
|
||||
<span class="upload-icon"></span>Upload Faculty Photo
|
||||
<span class="upload-icon"></span>${_("Upload Faculty Photo")}
|
||||
</a>
|
||||
<span class="tip tip-inline">Max size: 30KB</span>
|
||||
<span class="tip tip-inline">${_("Max size: 30KB")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<label for="course-faculty-2-bio">Faculty Bio:</label>
|
||||
<label for="course-faculty-2-bio">${_("Faculty Bio:")}</label>
|
||||
<div class="field">
|
||||
<div clas="input">
|
||||
<textarea class="long tall edit-box tinymce" id="course-faculty-2-bio"></textarea>
|
||||
<span class="tip tip-stacked">A brief description of your education, experience, and expertise</span>
|
||||
<span class="tip tip-stacked">${_("A brief description of your education, experience, and expertise")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,7 +120,7 @@ from contentstore import utils
|
||||
</ul>
|
||||
|
||||
<a href="#" class="new-item new-course-faculty-item add-faculty-data">
|
||||
<span class="plus-icon"></span>New Faculty Member
|
||||
<span class="plus-icon"></span>${_("New Faculty Member")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,78 +129,78 @@ from contentstore import utils
|
||||
</section><!-- .settings-staff -->
|
||||
|
||||
<section class="settings-problems">
|
||||
<h2 class="title">Problems</h2>
|
||||
<h2 class="title">${_("Problems")}</h2>
|
||||
|
||||
<section class="settings-problems-general">
|
||||
<header>
|
||||
<h3>General Settings</h3>
|
||||
<span class="detail">Course-wide settings for all problems</span>
|
||||
<h3>${_("General Settings")}</h3>
|
||||
<span class="detail">${_("Course-wide settings for all problems")}</span>
|
||||
</header>
|
||||
|
||||
<div class="row row-col2">
|
||||
<h4 class="label">Problem Randomization:</h4>
|
||||
<h4 class="label">${_("Problem Randomization:")}</h4>
|
||||
|
||||
<div class="field">
|
||||
<div class="input input-radio">
|
||||
<input checked="checked" type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-always" value="Always">
|
||||
<input checked="checked" type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-always" value="${_('Always')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-general-randomization-always">Always</label>
|
||||
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
|
||||
<label for="course-problems-general-randomization-always">${_("Always")}</label>
|
||||
<span class="tip tip-stacked">${_("<strong>randomize all</strong> problems")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-never" value="Never">
|
||||
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-never" value="${_('Never')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-general-randomization-never">Never</label>
|
||||
<span class="tip tip-stacked"><strong>do not randomize</strong> problems</span>
|
||||
<label for="course-problems-general-randomization-never">${_("Never")}</label>
|
||||
<span class="tip tip-stacked">${_("<strong>do not randomize</strong> problems")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-perstudent" value="Per Student">
|
||||
<input type="radio" name="course-problems-general-randomization" id="course-problems-general-randomization-perstudent" value="${_('Per Student')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-general-randomization-perstudent">Per Student</label>
|
||||
<span class="tip tip-stacked">randomize problems <strong>per student</strong></span>
|
||||
<label for="course-problems-general-randomization-perstudent">${_("Per Student")}</label>
|
||||
<span class="tip tip-stacked">${_("randomize problems <strong>per student</strong>")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<h4 class="label">Show Answers:</h4>
|
||||
<h4 class="label">${_("Show Answers:")}</h4>
|
||||
|
||||
<div class="field">
|
||||
<div class="input input-radio">
|
||||
<input checked="checked" type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-always" value="Always">
|
||||
<input checked="checked" type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-always" value="${_('Always')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-general-showanswer-always">Always</label>
|
||||
<span class="tip tip-stacked">Answers will be shown after the number of attempts has been met</span>
|
||||
<label for="course-problems-general-showanswer-always">${_("Always")}</label>
|
||||
<span class="tip tip-stacked">${_("Answers will be shown after the number of attempts has been met")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-never" value="Never">
|
||||
<input type="radio" name="course-problems-general-showanswer" id="course-problems-general-showanswer-never" value="${_('Never')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-general-showanswer-never">Never</label>
|
||||
<span class="tip tip-stacked">Answers will never be shown, regardless of attempts</span>
|
||||
<label for="course-problems-general-showanswer-never">${_("Never")}</label>
|
||||
<span class="tip tip-stacked">${_("Answers will never be shown, regardless of attempts")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<label for="pcourse-roblems-general-attempts">Number of Attempts <br /> Allowed on Problems: </label>
|
||||
<label for="pcourse-roblems-general-attempts">${_("Number of Attempts <br /> Allowed on Problems:")} </label>
|
||||
|
||||
<div class="field">
|
||||
<div class="input">
|
||||
<input type="text" class="short" id="course-problems-general-attempts" placeholder="0 or higher" value="0">
|
||||
<span class="tip tip-stacked">Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"</span>
|
||||
<span class="tip tip-stacked">${_('Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -207,73 +208,73 @@ from contentstore import utils
|
||||
|
||||
<section class="settings-problems-assignment-1 settings-extras">
|
||||
<header>
|
||||
<h3>[Assignment Type Name]</h3>
|
||||
<h3>[${_("Assignment Type Name")}]</h3>
|
||||
</header>
|
||||
|
||||
<div class="row row-col2">
|
||||
<h4 class="label">Problem Randomization:</h4>
|
||||
<h4 class="label">${_("Problem Randomization:")}</h4>
|
||||
|
||||
<div class="field">
|
||||
<div class="input input-radio">
|
||||
<input checked="checked" type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-always" value="Always">
|
||||
<input checked="checked" type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-always" value="${_('Always')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-assignment-1-randomization-always">Always</label>
|
||||
<span class="tip tip-stacked"><strong>randomize all</strong> problems</span>
|
||||
<label for="course-problems-assignment-1-randomization-always">${_("Always")}</label>
|
||||
<span class="tip tip-stacked">${_("<strong>randomize all</strong> problems")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-never" value="Never">
|
||||
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-never" value="${_('Never')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-assignment-1-randomization-never">Never</label>
|
||||
<span class="tip tip-stacked"><strong>do not randomize</strong> problems</span>
|
||||
<label for="course-problems-assignment-1-randomization-never">${_("Never")}</label>
|
||||
<span class="tip tip-stacked">${_("<strong>do not randomize</strong> problems")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-perstudent" value="Per Student">
|
||||
<input type="radio" name="course-problems-assignment-1-randomization" id="course-problems-assignment-1-randomization-perstudent" value="${_('Per Student')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-assignment-1-randomization-perstudent">Per Student</label>
|
||||
<span class="tip tip-stacked">randomize problems <strong>per student</strong></span>
|
||||
<label for="course-problems-assignment-1-randomization-perstudent">${_("Per Student")}</label>
|
||||
<span class="tip tip-stacked">${_("randomize problems <strong>per student</strong>")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<h4 class="label">Show Answers:</h4>
|
||||
<h4 class="label">${_("Show Answers:")}</h4>
|
||||
|
||||
<div class="field">
|
||||
<div class="input input-radio">
|
||||
<input checked="checked" type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-always" value="Always">
|
||||
<input checked="checked" type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-always" value="${_('Always')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-problems-assignment-1-showanswer-always">Always</label>
|
||||
<span class="tip tip-stacked">Answers will be shown after the number of attempts has been met</span>
|
||||
<label for="course-problems-assignment-1-showanswer-always">${_("Always")}</label>
|
||||
<span class="tip tip-stacked">${_("Answers will be shown after the number of attempts has been met")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-never" value="Never">
|
||||
<input type="radio" name="course-problems-assignment-1-showanswer" id="course-problems-assignment-1-showanswer-never" value="${_('Never')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="pcourse-roblems-assignment-1-showanswer-never">Never</label>
|
||||
<span class="tip tip-stacked">Answers will never be shown, regardless of attempts</span>
|
||||
<label for="pcourse-roblems-assignment-1-showanswer-never">${_("Never")}</label>
|
||||
<span class="tip tip-stacked">${_("Answers will never be shown, regardless of attempts")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<label for="course-problems-assignment-1-attempts">Number of Attempts <br /> Allowed on Problems: </label>
|
||||
<label for="course-problems-assignment-1-attempts">${_("Number of Attempts <br /> Allowed on Problems: ")}</label>
|
||||
|
||||
<div class="field">
|
||||
<div class="input">
|
||||
<input type="text" class="short" id="course-problems-assignment-1-attempts" placeholder="0 or higher" value="0">
|
||||
<span class="tip tip-stacked">Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"</span>
|
||||
<input type="text" class="short" id="course-problems-assignment-1-attempts" placeholder="${_('0 or higher')}" value="0">
|
||||
<span class="tip tip-stacked">${_('Students will this have this number of chances to answer a problem. To set infinite atttempts, use "0"')}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -281,71 +282,71 @@ from contentstore import utils
|
||||
</section><!-- .settings-problems -->
|
||||
|
||||
<section class="settings-discussions">
|
||||
<h2 class="title">Discussions</h2>
|
||||
<h2 class="title">${_("Discussions")}</h2>
|
||||
|
||||
<section class="settings-discussions-general">
|
||||
<header>
|
||||
<h3>General Settings</h3>
|
||||
<span class="detail">Course-wide settings for online discussion</span>
|
||||
<h3>${_("General Settings")}</h3>
|
||||
<span class="detail">${_("Course-wide settings for online discussion")}</span>
|
||||
</header>
|
||||
|
||||
<div class="row row-col2">
|
||||
<h4 class="label">Anonymous Discussions:</h4>
|
||||
<h4 class="label">${_("Anonymous Discussions:")}</h4>
|
||||
|
||||
<div class="field">
|
||||
<div class="input input-radio">
|
||||
<input type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
|
||||
<input type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="${_('Allow')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-discussions-anonymous-allow">Allow</label>
|
||||
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
|
||||
<label for="course-discussions-anonymous-allow">${_("Allow")}</label>
|
||||
<span class="tip tip-stacked">${_("Students and faculty <strong>will be able to post anonymously</strong>")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
|
||||
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="${_('Do Not Allow')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-discussions-anonymous-dontallow">Do not allow</label>
|
||||
<span class="tip tip-stacked"><strong>Posting anonymously is not allowed</strong>. Any previous anonymous posts <strong>will be reverted to non-anonymous</strong></span>
|
||||
<label for="course-discussions-anonymous-dontallow">${_("Do not allow")}</label>
|
||||
<span class="tip tip-stacked">${_("<strong>Posting anonymously is not allowed</strong>. Any previous anonymous posts <strong>will be reverted to non-anonymous</strong>")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<h4 class="label">Anonymous Discussions:</h4>
|
||||
<h4 class="label">${_("Anonymous Discussions:")}</h4>
|
||||
|
||||
<div class="field">
|
||||
<div class="input input-radio">
|
||||
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="Allow">
|
||||
<input checked="checked" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-allow" value="${_('Allow')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-discussions-anonymous-allow">Allow</label>
|
||||
<span class="tip tip-stacked">Students and faculty <strong>will be able to post anonymously</strong></span>
|
||||
<label for="course-discussions-anonymous-allow">${_("Allow")}</label>
|
||||
<span class="tip tip-stacked">${_("Students and faculty <strong>will be able to post anonymously</strong>")}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input input-radio">
|
||||
<input disabled="disabled" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="Do Not Allow">
|
||||
<input disabled="disabled" type="radio" name="course-discussions-anonymous" id="course-discussions-anonymous-dontallow" value="${_('Do Not Allow')}">
|
||||
|
||||
<div class="copy">
|
||||
<label for="course-discussions-anonymous-dontallow">Do not allow</label>
|
||||
<span class="tip tip-stacked">This option is disabled since there are previous discussions that are anonymous.</span>
|
||||
<label for="course-discussions-anonymous-dontallow">${_("Do not allow")}</label>
|
||||
<span class="tip tip-stacked">${_("This option is disabled since there are previous discussions that are anonymous.")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row row-col2">
|
||||
<h4 class="label">Discussion Categories</h4>
|
||||
<h4 class="label">${_("Discussion Categories")}</h4>
|
||||
|
||||
<div class="field enum">
|
||||
<ul class="input-list course-discussions-categories-list sortable">
|
||||
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
|
||||
<div class="group">
|
||||
<label for="course-discussions-categories-1-name">Category Name: </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-1-name" placeholder="" value="General" disabled="disabled">
|
||||
<label for="course-discussions-categories-1-name">${_("Category Name:")} </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-1-name" placeholder="" value="${_('General')}" disabled="disabled">
|
||||
</div>
|
||||
|
||||
<a href="#" class="drag-handle"></a>
|
||||
@@ -353,7 +354,7 @@ from contentstore import utils
|
||||
|
||||
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
|
||||
<div class="group">
|
||||
<label for="course-discussions-categories-2-name">Category Name: </label>
|
||||
<label for="course-discussions-categories-2-name">${_("Category Name:")} </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-2-name" placeholder="" value="Feedback" disabled="disabled">
|
||||
</div>
|
||||
|
||||
@@ -362,8 +363,8 @@ from contentstore import utils
|
||||
|
||||
<li class="input input-existing input-default course-discussions-categories-list-item sortable-item">
|
||||
<div class="group">
|
||||
<label for="course-discussions-categories-3-name">Category Name: </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-3-name" placeholder="" value="Troubleshooting" disabled="disabled">
|
||||
<label for="course-discussions-categories-3-name">${_("Category Name:")} </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-3-name" placeholder="" value="${_('Troubleshooting')}" disabled="disabled">
|
||||
</div>
|
||||
|
||||
<a href="#" class="drag-handle"></a>
|
||||
@@ -371,10 +372,10 @@ from contentstore import utils
|
||||
|
||||
<li class="input input-existing course-discussions-categories-list-item sortable-item">
|
||||
<div class="group">
|
||||
<label for="course-discussions-categories-4-name">Category Name: </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-4-name" placeholder="" value="Study Groups">
|
||||
<label for="course-discussions-categories-4-name">${_("Category Name:")} </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-4-name" placeholder="" value="${_('Study Groups')}">
|
||||
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> ${_("Delete Category")}</a>
|
||||
</div>
|
||||
|
||||
<a href="#" class="drag-handle"></a>
|
||||
@@ -382,40 +383,40 @@ from contentstore import utils
|
||||
|
||||
<li class="input input-existing course-discussions-categories-list-item sortable-item">
|
||||
<div class="group">
|
||||
<label for="course-discussions-categories-5-name">Category Name: </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-5-name" placeholder="" value="Lectures">
|
||||
<label for="course-discussions-categories-5-name">${_("Category Name:")} </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-5-name" placeholder="" value="${_('Lectures')}">
|
||||
</div>
|
||||
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> ${_("Delete Category")}</a>
|
||||
|
||||
<a href="#" class="drag-handle"></a>
|
||||
</li>
|
||||
|
||||
<li class="input input-existing course-discussions-categories-list-item sortable-item">
|
||||
<div class="group">
|
||||
<label for="course-discussions-categories-6-name">Category Name: </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-6-name" placeholder="" value="Labs">
|
||||
<label for="course-discussions-categories-6-name">${_("Category Name:")} </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-6-name" placeholder="" value="${_('Labs')}">
|
||||
</div>
|
||||
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> ${_("Delete Category")}</a>
|
||||
|
||||
<a href="#" class="drag-handle"></a>
|
||||
</li>
|
||||
|
||||
<li class="input input-existing course-discussions-categories-list-item sortable-item">
|
||||
<div class="group">
|
||||
<label for="course-discussions-categories-6-name">Category Name: </label>
|
||||
<label for="course-discussions-categories-6-name">${_("Category Name:")} </label>
|
||||
<input type="text" class="course-discussions-categories-name" id="course-discussions-categories-6-name" placeholder="" value="">
|
||||
</div>
|
||||
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> Delete Category</a>
|
||||
<a href="#" class="remove-item remove-course-discussions-categories-data"><span class="delete-icon"></span> ${_("Delete Category")}</a>
|
||||
|
||||
<a href="#" class="drag-handle"></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<a href="#" class="new-item new-course-discussions-categories-item add-categories-data">
|
||||
<span class="plus-icon"></span>New Discussion Category
|
||||
<span class="plus-icon"></span>${_("New Discussion Category")}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%block name="title">Grading Settings</%block>
|
||||
<%block name="title">${_("Grading Settings")}</%block>
|
||||
<%block name="bodyclass">is-signedin course grading settings</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
@@ -41,8 +42,8 @@ from contentstore import utils
|
||||
<div class="wrapper-mast wrapper">
|
||||
<header class="mast has-subtitle">
|
||||
<h1 class="page-header">
|
||||
<small class="subtitle">Settings</small>
|
||||
<span class="sr">> </span>Grading
|
||||
<small class="subtitle">${_("Settings")}</small>
|
||||
<span class="sr">> </span>${_("Grading")}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
@@ -53,8 +54,8 @@ from contentstore import utils
|
||||
<form id="settings_details" class="settings-grading" method="post" action="">
|
||||
<section class="group-settings grade-range">
|
||||
<header>
|
||||
<h2 class="title-2">Overall Grade Range</h2>
|
||||
<span class="tip">Your overall grading scale for student final grades</span>
|
||||
<h2 class="title-2">${_("Overall Grade Range")}</h2>
|
||||
<span class="tip">${_("Your overall grading scale for student final grades")}</span>
|
||||
</header>
|
||||
|
||||
<ol class="list-input">
|
||||
@@ -89,15 +90,15 @@ from contentstore import utils
|
||||
|
||||
<section class="group-settings grade-rules">
|
||||
<header>
|
||||
<h2 class="title-2">Grading Rules & Policies</h2>
|
||||
<span class="tip">Deadlines, requirements, and logistics around grading student work</span>
|
||||
<h2 class="title-2">${_("Grading Rules & Policies")}</h2>
|
||||
<span class="tip">${_("Deadlines, requirements, and logistics around grading student work")}</span>
|
||||
</header>
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field text" id="field-course-grading-graceperiod">
|
||||
<label for="course-grading-graceperiod">Grace Period on Deadline:</label>
|
||||
<label for="course-grading-graceperiod">${_("Grace Period on Deadline:")}</label>
|
||||
<input type="text" class="short time" id="course-grading-graceperiod" value="0:00" placeholder="HH:MM" autocomplete="off" />
|
||||
<span class="tip tip-inline">Leeway on due dates</span>
|
||||
<span class="tip tip-inline">${_("Leeway on due dates")}</span>
|
||||
</li>
|
||||
</ol>
|
||||
</section>
|
||||
@@ -106,8 +107,8 @@ from contentstore import utils
|
||||
|
||||
<section class="group-settings assignment-types">
|
||||
<header>
|
||||
<h2 class="title-2">Assignment Types</h2>
|
||||
<span class="tip">Categories and labels for any exercises that are gradable</span>
|
||||
<h2 class="title-2">${_("Assignment Types")}</h2>
|
||||
<span class="tip">${_("Categories and labels for any exercises that are gradable")}</span>
|
||||
</header>
|
||||
|
||||
<ol class="list-input course-grading-assignment-list enum">
|
||||
@@ -116,7 +117,7 @@ from contentstore import utils
|
||||
|
||||
<div class="actions">
|
||||
<a href="#" class="new-button new-course-grading-item add-grading-data">
|
||||
<i class="icon-plus"></i>New Assignment Type
|
||||
<i class="icon-plus"></i>${_("New Assignment Type")}
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -125,22 +126,22 @@ from contentstore import utils
|
||||
|
||||
<aside class="content-supplementary" role="complimentary">
|
||||
<div class="bit">
|
||||
<h3 class="title-3">How will these settings be used?</h3>
|
||||
<p>Your grading settings will be used to calculate students grades and performance.</p>
|
||||
<h3 class="title-3">${_("How will these settings be used?")}</h3>
|
||||
<p>${_("Your grading settings will be used to calculate students grades and performance.")}</p>
|
||||
|
||||
<p>Overall grade range will be used in students' final grades, which are calculated by the weighting you determine for each custom assignment type.</p>
|
||||
<p>${_("Overall grade range will be used in students' final grades, which are calculated by the weighting you determine for each custom assignment type.")}</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
% if context_course:
|
||||
<% ctx_loc = context_course.location %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<h3 class="title-3">Other Course Settings</h3>
|
||||
<h3 class="title-3">${_("Other Course Settings")}</h3>
|
||||
<nav class="nav-related">
|
||||
<ul>
|
||||
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">Details & Schedule</a></li>
|
||||
<li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">Course Team</a></li>
|
||||
<li class="nav-item"><a href="${reverse('course_advanced_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">Advanced Settings</a></li>
|
||||
<li class="nav-item"><a href="${reverse('contentstore.views.get_course_settings', kwargs=dict(org=ctx_loc.org, course=ctx_loc.course, name=ctx_loc.name))}">${_("Details & Schedule")}</a></li>
|
||||
<li class="nav-item"><a href="${reverse('manage_users', kwargs=dict(location=ctx_loc))}">${_("Course Team")}</a></li>
|
||||
<li class="nav-item"><a href="${reverse('course_advanced_settings', kwargs={'org' : ctx_loc.org, 'course' : ctx_loc.course, 'name': ctx_loc.name})}">${_("Advanced Settings")}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
% endif
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
|
||||
<%block name="title">Sign Up</%block>
|
||||
<%block name="title">${_("Sign Up")}</%block>
|
||||
<%block name="bodyclass">not-signedin signup</%block>
|
||||
|
||||
<%block name="content">
|
||||
@@ -9,63 +10,63 @@
|
||||
<div class="wrapper-content wrapper">
|
||||
<section class="content">
|
||||
<header>
|
||||
<h1 class="title title-1">Sign Up for edX Studio</h1>
|
||||
<a href="${reverse('login')}" class="action action-signin">Already have a Studio Account? Sign in</a>
|
||||
<h1 class="title title-1">${_("Sign Up for edX Studio")}</h1>
|
||||
<a href="${reverse('login')}" class="action action-signin">${_("Already have a Studio Account? Sign in")}</a>
|
||||
</header>
|
||||
|
||||
<p class="introduction">Ready to start creating online courses? Sign up below and start creating your first edX course today.</p>
|
||||
<p class="introduction">${_("Ready to start creating online courses? Sign up below and start creating your first edX course today.")}</p>
|
||||
|
||||
<article class="content-primary" role="main">
|
||||
<form id="register_form" method="post" action="register_post">
|
||||
<div id="register_error" name="register_error" class="message message-status message-status error">
|
||||
</div>
|
||||
|
||||
|
||||
<fieldset>
|
||||
<legend class="sr">Required Information to Sign Up for edX Studio</legend>
|
||||
|
||||
<legend class="sr">${_("Required Information to Sign Up for edX Studio")}</legend>
|
||||
|
||||
<ol class="list-input">
|
||||
<li class="field text required" id="field-email">
|
||||
<label for="email">Email Address</label>
|
||||
<input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
|
||||
<label for="email">${_("Email Address")}</label>
|
||||
<input id="email" type="email" name="email" placeholder="e.g. jane.doe@gmail.com" />
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-password">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" type="password" name="password" />
|
||||
<label for="password">${_("Password")}</label>
|
||||
<input id="password" type="password" name="password" />
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-username">
|
||||
<label for="username">Public Username</label>
|
||||
<label for="username">${_("Public Username")}</label>
|
||||
<input id="username" type="text" name="username" placeholder="e.g. janedoe" />
|
||||
<span class="tip tip-stacked">This will be used in public discussions with your courses and in our edX101 support forums</span>
|
||||
<span class="tip tip-stacked">${_("This will be used in public discussions with your courses and in our edX101 support forums")}</span>
|
||||
</li>
|
||||
|
||||
<li class="field text required" id="field-name">
|
||||
<label for="name">Full Name</label>
|
||||
<input id="name" type="text" name="name" placeholder="e.g. Jane Doe" />
|
||||
<label for="name">${_("Full Name")}</label>
|
||||
<input id="name" type="text" name="name" placeholder="e.g. Jane Doe" />
|
||||
</li>
|
||||
|
||||
<li class="field-group">
|
||||
<div class="field text" id="field-location">
|
||||
<label for="location">Your Location</label>
|
||||
<input class="short" id="location" type="text" name="location" />
|
||||
<label for="location">${_("Your Location")}</label>
|
||||
<input class="short" id="location" type="text" name="location" />
|
||||
</div>
|
||||
|
||||
<div class="field text" id="field-language">
|
||||
<label for="language">Preferred Language</label>
|
||||
<input class="short" id="language" type="text" name="language" />
|
||||
<label for="language">${_("Preferred Language")}</label>
|
||||
<input class="short" id="language" type="text" name="language" />
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li class="field checkbox required" id="field-tos">
|
||||
<input id="tos" name="terms_of_service" type="checkbox" value="true" />
|
||||
<label for="tos">I agree to the Terms of Service</label>
|
||||
<label for="tos">${_("I agree to the Terms of Service")}</label>
|
||||
</li>
|
||||
</ol>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" id="submit" name="submit" class="action action-primary">Create My Account & Start Authoring Courses</button>
|
||||
<button type="submit" id="submit" name="submit" class="action action-primary">${_("Create My Account & Start Authoring Courses")}</button>
|
||||
</div>
|
||||
|
||||
<!-- no honor code for CMS, but need it because we're using the lms student object -->
|
||||
@@ -74,21 +75,21 @@
|
||||
</article>
|
||||
|
||||
<aside class="content-supplementary" role="complimentary">
|
||||
<h2 class="sr">Common Studio Questions</h2>
|
||||
<h2 class="sr">${_("Common Studio Questions")}</h2>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title-3">Who is Studio for?</h3>
|
||||
<p>Studio is for anyone that wants to create online courses that leverage the global edX platform. Our users are often faculty members, teaching assistants and course staff, and members of instructional technology groups.</p>
|
||||
<h3 class="title-3">${_("Who is Studio for?")}</h3>
|
||||
<p>${_("Studio is for anyone that wants to create online courses that leverage the global edX platform. Our users are often faculty members, teaching assistants and course staff, and members of instructional technology groups.")}</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title-3">How technically savvy do I need to be to create courses in Studio?</h3>
|
||||
<p>Studio is designed to be easy to use by almost anyone familiar with common web-based authoring environments (Wordpress, Moodle, etc.). No programming knowledge is required, but for some of the more advanced features, a technical background would be helpful. As always, we are here to help, so don't hesitate to dive right in.</p>
|
||||
<h3 class="title-3">${_("How technically savvy do I need to be to create courses in Studio?")}</h3>
|
||||
<p>${_("Studio is designed to be easy to use by almost anyone familiar with common web-based authoring environments (Wordpress, Moodle, etc.). No programming knowledge is required, but for some of the more advanced features, a technical background would be helpful. As always, we are here to help, so don't hesitate to dive right in.")}</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title-3">I've never authored a course online before. Is there help?</h3>
|
||||
<p>Absolutely. We have created an online course, edX101, that describes some best practices: from filming video, creating exercises, to the basics of running an online course. Additionally, we're always here to help, just drop us a note.</p>
|
||||
<h3 class="title-3">${_("I've never authored a course online before. Is there help?")}</h3>
|
||||
<p>${_("Absolutely. We have created an online course, edX101, that describes some best practices: from filming video, creating exercises, to the basics of running an online course. Additionally, we're always here to help, just drop us a note.")}</p>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
@@ -104,7 +105,7 @@
|
||||
}).blur(function() {
|
||||
$("label").removeClass("is-focused");
|
||||
});
|
||||
|
||||
|
||||
|
||||
function getCookie(name) {
|
||||
return $.cookie(name);
|
||||
@@ -138,4 +139,4 @@
|
||||
});
|
||||
})(this)
|
||||
</script>
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%block name="title">Static Pages</%block>
|
||||
<%block name="title">${_("Static Pages")}</%block>
|
||||
<%block name="bodyclass">static-pages</%block>
|
||||
|
||||
<%block name="content">
|
||||
@@ -11,24 +12,24 @@
|
||||
|
||||
</div>
|
||||
<article class="static-page-overview">
|
||||
<a href="#" class="new-static-page-button wip-box"><span class="plus-icon"></span> New Static Page</a>
|
||||
<a href="#" class="new-static-page-button wip-box"><span class="plus-icon"></span> ${_("New Static Page")}</a>
|
||||
<ul class="static-page-list">
|
||||
<li class="static-page-item">
|
||||
<a href="#" class="page-name">Course Info</a>
|
||||
<a href="#" class="page-name">${_("Course Info")}</a>
|
||||
<div class="item-actions">
|
||||
<a href="#" class="edit-button wip"><span class="delete-icon"></span></a>
|
||||
<a href="#" class="drag-handle wip"></a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="static-page-item">
|
||||
<a href="#" class="page-name">Textbook</a>
|
||||
<a href="#" class="page-name">${_("Textbook")}</a>
|
||||
<div class="item-actions">
|
||||
<a href="#" class="edit-button wip"><span class="delete-icon"></span></a>
|
||||
<a href="#" class="drag-handle wip"></a>
|
||||
</div>
|
||||
</li>
|
||||
<li class="static-page-item">
|
||||
<a href="#" class="page-name">Syllabus</a>
|
||||
<a href="#" class="page-name">${_("Syllabus")}</a>
|
||||
<div class="item-actions">
|
||||
<a href="#" class="edit-button wip"><span class="delete-icon"></span></a>
|
||||
<a href="#" class="drag-handle wip"></a>
|
||||
@@ -38,4 +39,4 @@
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</%block>
|
||||
</%block>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%inherit file="base.html" />
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<%namespace name="units" file="widgets/units.html" />
|
||||
<%block name="title">Individual Unit</%block>
|
||||
<%block name="title">${_("Individual Unit")}</%block>
|
||||
<%block name="bodyclass">is-signedin course unit</%block>
|
||||
|
||||
<%block name="jsextra">
|
||||
@@ -35,31 +36,31 @@
|
||||
<div class="main-wrapper edit-state-${unit_state}" data-id="${unit_location}">
|
||||
<div class="inner-wrapper">
|
||||
<div class="alert editing-draft-alert">
|
||||
<p class="alert-message"><strong>You are editing a draft.</strong>
|
||||
<p class="alert-message"><strong>${_("You are editing a draft.")}</strong>
|
||||
% if published_date:
|
||||
This unit was originally published on ${published_date}.
|
||||
${_("This unit was originally published on {date}.").format(date=published_date)}
|
||||
% endif
|
||||
</p>
|
||||
<a href="${published_preview_link}" target="_blank" class="alert-action secondary">View the Live Version</a>
|
||||
<a href="${published_preview_link}" target="_blank" class="alert-action secondary">${_("View the Live Version")}</a>
|
||||
</div>
|
||||
<div class="main-column">
|
||||
<article class="unit-body window">
|
||||
<p class="unit-name-input"><label>Display Name:</label><input type="text" value="${unit.display_name_with_default | h}" class="unit-display-name-input" /></p>
|
||||
<p class="unit-name-input"><label>${_("Display Name:")}</label><input type="text" value="${unit.display_name_with_default | h}" class="unit-display-name-input" /></p>
|
||||
<ol class="components">
|
||||
% for id in components:
|
||||
<li class="component" data-id="${id}"/>
|
||||
% endfor
|
||||
<li class="new-component-item adding">
|
||||
<div class="new-component">
|
||||
<h5>Add New Component</h5>
|
||||
<h5>${_("Add New Component")}</h5>
|
||||
<ul class="new-component-type">
|
||||
% for type, templates in sorted(component_templates.items()):
|
||||
<li>
|
||||
% if type == 'advanced' or len(templates) > 1:
|
||||
<a href="#" class="multiple-templates" data-type="${type}">
|
||||
% else:
|
||||
% for _, location, _ in templates:
|
||||
<a href="#" class="single-template" data-type="${type}" data-location="${location}">
|
||||
% for __, category, __, __ in templates:
|
||||
<a href="#" class="single-template" data-type="${type}" data-category="${category}">
|
||||
% endfor
|
||||
% endif
|
||||
<span class="large-template-icon large-${type}-icon"></span>
|
||||
@@ -73,49 +74,60 @@
|
||||
% if len(templates) > 1 or type == 'advanced':
|
||||
<div class="new-component-templates new-component-${type}">
|
||||
% if type == "problem":
|
||||
<div class="tab-group tabs">
|
||||
<ul class="problem-type-tabs nav-tabs">
|
||||
<li class="current">
|
||||
<a class="link-tab" href="#tab1">Common Problem Types</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-tab" href="#tab2">Advanced</a>
|
||||
</li>
|
||||
</ul>
|
||||
% endif
|
||||
<div class="tab current" id="tab1">
|
||||
<ul class="new-component-template">
|
||||
% for name, location, has_markdown in templates:
|
||||
% if has_markdown or type != "problem":
|
||||
<li class="editor-md">
|
||||
<a href="#" id="${location}" data-location="${location}">
|
||||
<span class="name"> ${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
|
||||
%endfor
|
||||
<div class="tab-group tabs">
|
||||
<ul class="problem-type-tabs nav-tabs">
|
||||
<li class="current">
|
||||
<a class="link-tab" href="#tab1">${_("Common Problem Types")}</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="link-tab" href="#tab2">${_("Advanced")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
% if type == "problem":
|
||||
<div class="tab" id="tab2">
|
||||
<ul class="new-component-template">
|
||||
% for name, location, has_markdown in templates:
|
||||
% if not has_markdown:
|
||||
<li class="editor-manual">
|
||||
<a href="#" id="${location}" data-location="${location}">
|
||||
<span class="name"> ${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
</div>
|
||||
% endif
|
||||
% endif
|
||||
<div class="tab current" id="tab1">
|
||||
<ul class="new-component-template">
|
||||
% for name, category, has_markdown, boilerplate_name in sorted(templates):
|
||||
% if has_markdown or type != "problem":
|
||||
% if boilerplate_name is None:
|
||||
<li class="editor-md empty">
|
||||
<a href="#" data-category="${category}">
|
||||
<span class="name">${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
% else:
|
||||
<li class="editor-md">
|
||||
<a href="#" data-category="${category}"
|
||||
data-boilerplate="${boilerplate_name}">
|
||||
<span class="name">${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
% endif
|
||||
|
||||
%endfor
|
||||
</ul>
|
||||
</div>
|
||||
% if type == "problem":
|
||||
<div class="tab" id="tab2">
|
||||
<ul class="new-component-template">
|
||||
% for name, category, has_markdown, boilerplate_name in sorted(templates):
|
||||
% if not has_markdown:
|
||||
<li class="editor-manual">
|
||||
<a href="#" data-category="${category}"
|
||||
data-boilerplate="${boilerplate_name}">
|
||||
<span class="name">${name}</span>
|
||||
</a>
|
||||
</li>
|
||||
% endif
|
||||
% endfor
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<a href="#" class="cancel-button">Cancel</a>
|
||||
</div>
|
||||
% endif
|
||||
% endfor
|
||||
</li>
|
||||
</ol>
|
||||
@@ -124,35 +136,40 @@
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="unit-settings window">
|
||||
<h4 class="header">Unit Settings</h4>
|
||||
<h4 class="header">${_("Unit Settings")}</h4>
|
||||
<div class="window-contents">
|
||||
<div class="row visibility">
|
||||
<label class="inline-label">Visibility:</label>
|
||||
<label class="inline-label">${_("Visibility:")}</label>
|
||||
<select class='visibility-select'>
|
||||
<option value="public">Public</option>
|
||||
<option value="private">Private</option>
|
||||
<option value="public">${_("Public")}</option>
|
||||
<option value="private">${_("Private")}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="row published-alert">
|
||||
<p class="edit-draft-message">This unit has been published. To make changes, you must <a href="#" class="create-draft">edit a draft</a>.</p>
|
||||
<p class="publish-draft-message">This is a draft of the published unit. To update the live version, you must <a href="#" class="publish-draft">replace it with this draft</a>.</p>
|
||||
<p class="edit-draft-message">${_('This unit has been published. To make changes, you must {link_start}edit a draft{link_end}.').format(link_start='<a href="#" class="create-draft">', link_end='</a>')}</p>
|
||||
<p class="publish-draft-message">${_('This is a draft of the published unit. To update the live version, you must {link_start}replace it with this draft{link_end}.').format(link_start='<a href="#" class="publish-draft">', link_end='</a>')}</p>
|
||||
</div>
|
||||
<div class="row status">
|
||||
<p>This unit is scheduled to be released to <strong>students</strong>
|
||||
<p>${_("This unit is scheduled to be released to <strong>students</strong>")}
|
||||
% if release_date is not None:
|
||||
on <strong>${release_date}</strong>
|
||||
${_(u"on {date}").format(date=u"<strong>{}</strong>".format(release_date))}
|
||||
% endif
|
||||
with the subsection <a href="${reverse('edit_subsection', kwargs={'location': subsection.location})}">"${subsection.display_name_with_default}"</a></p>
|
||||
${_("with the subsection {link_start}{name}{link_end}").format(
|
||||
name=subsection.display_name_with_default,
|
||||
link_start='<a href="{url}">'.format(url=reverse('edit_subsection', kwargs={'location': subsection.location})),
|
||||
link_end='</a>',
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="row unit-actions">
|
||||
<a href="#" class="delete-draft delete-button">Delete Draft</a>
|
||||
<a href="${draft_preview_link}" target="_blank" class="preview-button">Preview</a>
|
||||
<a href="${published_preview_link}" target="_blank" class="view-button">View Live</a>
|
||||
<a href="#" class="delete-draft delete-button">${_("Delete Draft")}</a>
|
||||
<a href="${draft_preview_link}" target="_blank" class="preview-button">${_("Preview")}</a>
|
||||
<a href="${published_preview_link}" target="_blank" class="view-button">${_("View Live")}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="window unit-location">
|
||||
<h4 class="header">Unit Location</h4>
|
||||
<h4 class="header">${_("Unit Location")}</h4>
|
||||
<div class="window-contents">
|
||||
<div><input type="text" class="url" value="/courseware/${section.url_name}/${subsection.url_name}" disabled /></div>
|
||||
<ol>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<header class="primary" role="banner">
|
||||
|
||||
<div class="wrapper wrapper-l">
|
||||
## "edX Studio" should not be translated
|
||||
<h1 class="branding"><a href="/"><img src="/static/img/logo-edx-studio.png" alt="edX Studio" /></a></h1>
|
||||
|
||||
% if context_course:
|
||||
@@ -18,7 +19,7 @@
|
||||
</h2>
|
||||
|
||||
<nav class="nav-course nav-dd ui-left">
|
||||
<h2 class="sr">${_("{}'s Navigation:".format(context_course.display_name_with_default))}</h2>
|
||||
<h2 class="sr">${_("{course_name}'s Navigation:").format(course_name=context_course.display_name_with_default)}</h2>
|
||||
<ol>
|
||||
<li class="nav-item nav-course-courseware">
|
||||
<h3 class="title"><span class="label"><span class="label-prefix sr">${_("Course")} </span>${_("Content")}</span> <i class="icon-caret-down ui-toggle-dd"></i></h3>
|
||||
@@ -96,23 +97,23 @@
|
||||
<div class="wrapper wrapper-r">
|
||||
% if user.is_authenticated():
|
||||
<nav class="nav-account nav-is-signedin nav-dd ui-right">
|
||||
<h2 class="sr">Help & Account Navigation</h2>
|
||||
<h2 class="sr">${_("Help & Account Navigation")}</h2>
|
||||
|
||||
<ol>
|
||||
<li class="nav-item nav-account-help">
|
||||
<h3 class="title"><span class="label">Help</span> <i class="icon-caret-down ui-toggle-dd"></i></h3>
|
||||
<h3 class="title"><span class="label">${_("Help")}</span> <i class="icon-caret-down ui-toggle-dd"></i></h3>
|
||||
|
||||
<div class="wrapper wrapper-nav-sub">
|
||||
<div class="nav-sub">
|
||||
<ul>
|
||||
<li class="nav-item nav-help-documentation">
|
||||
<a href="http://files.edx.org/Getting_Started_with_Studio.pdf" title="This is a PDF Document">Studio Documentation</a>
|
||||
<a href="http://files.edx.org/Getting_Started_with_Studio.pdf" title="${_("This is a PDF Document")}">${_("Studio Documentation")}</a>
|
||||
</li>
|
||||
<li class="nav-item nav-help-helpcenter">
|
||||
<a href="http://help.edge.edx.org/" rel="external">Studio Help Center</a>
|
||||
<a href="http://help.edge.edx.org/" rel="external">${_("Studio Help Center")}</a>
|
||||
</li>
|
||||
<li class="nav-item nav-help-feedback">
|
||||
<a href="http://help.edge.edx.org/discussion/new" class="show-tender" title="Use our feedback tool, Tender, to share your feedback">Contact Us</a>
|
||||
<a href="http://help.edge.edx.org/discussion/new" class="show-tender" title="${_("Use our feedback tool, Tender, to share your feedback")}">${_("Contact Us")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -120,7 +121,7 @@
|
||||
</li>
|
||||
|
||||
<li class="nav-item nav-account-user">
|
||||
<h3 class="title"><span class="label"><span class="label-prefix sr">Currently signed in as:</span><span class="account-username" title="${ user.username }">${ user.username }</span></span> <i class="icon-caret-down ui-toggle-dd"></i></h3>
|
||||
<h3 class="title"><span class="label"><span class="label-prefix sr">${_("Currently signed in as:")}</span><span class="account-username" title="${ user.username }">${ user.username }</span></span> <i class="icon-caret-down ui-toggle-dd"></i></h3>
|
||||
|
||||
<div class="wrapper wrapper-nav-sub">
|
||||
<div class="nav-sub">
|
||||
|
||||
@@ -1,51 +1,52 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.core.urlresolvers import reverse %>
|
||||
<div class="wrapper-sock wrapper">
|
||||
<ul class="list-actions list-cta">
|
||||
<li class="action-item">
|
||||
<a href="#sock" class="cta cta-show-sock"><i class="icon-question-sign"></i> <span class="copy">Looking for Help with Studio?</span></a>
|
||||
<a href="#sock" class="cta cta-show-sock"><i class="icon-question-sign"></i> <span class="copy">${_("Looking for Help with Studio?")}</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="wrapper-inner wrapper">
|
||||
<section class="sock" id="sock">
|
||||
<header>
|
||||
<h2 class="title sr">edX Studio Help</h2>
|
||||
<h2 class="title sr">${_("edX Studio Help")}</h2>
|
||||
</header>
|
||||
|
||||
<div class="support">
|
||||
<h3 class="title">Studio Support</h3>
|
||||
<h3 class="title">${_("Studio Support")}</h3>
|
||||
|
||||
<div class="copy">
|
||||
<p>Need help with Studio? Creating a course is complex, so we're here to help. Take advantage of our documentation, help center, as well as our edX101 introduction course for course authors.</p>
|
||||
<p>${_("Need help with Studio? Creating a course is complex, so we're here to help. Take advantage of our documentation, help center, as well as our edX101 introduction course for course authors.")}</p>
|
||||
</div>
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action-item">
|
||||
<a href="http://files.edx.org/Getting_Started_with_Studio.pdf" class="action action-primary" title="This is a PDF Document">Download Studio Documentation</a>
|
||||
<span class="tip">How to use Studio to build your course</span>
|
||||
<a href="http://files.edx.org/Getting_Started_with_Studio.pdf" class="action action-primary" title='${_("This is a PDF Document")}'>${_("Download Studio Documentation")}</a>
|
||||
<span class="tip">${_("How to use Studio to build your course")}</span>
|
||||
</li>
|
||||
<li class="action-item">
|
||||
<a href="http://help.edge.edx.org/" rel="external" class="action action-primary">Studio Help Center</a>
|
||||
<span class="tip">Studio Help Center</span>
|
||||
<a href="http://help.edge.edx.org/" rel="external" class="action action-primary">${_("Studio Help Center")}</a>
|
||||
<span class="tip">${_("Studio Help Center")}</span>
|
||||
</li>
|
||||
<li class="action-item">
|
||||
<a href="https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about" rel="external" class="action action-primary">Enroll in edX101</a>
|
||||
<span class="tip">How to use Studio to build your course</span>
|
||||
<a href="https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about" rel="external" class="action action-primary">${_("Enroll in edX101")}</a>
|
||||
<span class="tip">${_("How to use Studio to build your course")}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="feedback">
|
||||
<h3 class="title">Contact us about Studio</h3>
|
||||
<h3 class="title">${_("Contact us about Studio")}</h3>
|
||||
|
||||
<div class="copy">
|
||||
<p>Have problems, questions, or suggestions about Studio? We're also here to listen to any feedback you want to share.</p>
|
||||
<p>${_("Have problems, questions, or suggestions about Studio? We're also here to listen to any feedback you want to share.")}</p>
|
||||
</div>
|
||||
|
||||
<ul class="list-actions">
|
||||
<li class="action-item">
|
||||
|
||||
<a href="http://help.edge.edx.org/discussion/new" class="action action-primary show-tender" title="Use our feedback tool, Tender, to share your feedback"><i class="icon-comments"></i> Contact Us</a>
|
||||
<a href="http://help.edge.edx.org/discussion/new" class="action action-primary show-tender" title='${_("Use our feedback tool, Tender, to share your feedback")}'><i class="icon-comments"></i>${_("Contact Us")}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@ This def will enumerate through a passed in subsection and list all of the units
|
||||
</li>
|
||||
% endfor
|
||||
<li>
|
||||
<a href="#" class="new-unit-item" data-template="${create_new_unit_template}" data-parent="${subsection.location}">
|
||||
<a href="#" class="new-unit-item" data-category="${new_unit_category}" data-parent="${subsection.location}">
|
||||
<span class="new-unit-icon"></span>New Unit
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -17,7 +17,7 @@ urlpatterns = ('', # nopep8
|
||||
url(r'^preview_component/(?P<location>.*?)$', 'contentstore.views.preview_component', name='preview_component'),
|
||||
url(r'^save_item$', 'contentstore.views.save_item', name='save_item'),
|
||||
url(r'^delete_item$', 'contentstore.views.delete_item', name='delete_item'),
|
||||
url(r'^clone_item$', 'contentstore.views.clone_item', name='clone_item'),
|
||||
url(r'^create_item$', 'contentstore.views.create_item', name='create_item'),
|
||||
url(r'^create_draft$', 'contentstore.views.create_draft', name='create_draft'),
|
||||
url(r'^publish_draft$', 'contentstore.views.publish_draft', name='publish_draft'),
|
||||
url(r'^unpublish_unit$', 'contentstore.views.unpublish_unit', name='unpublish_unit'),
|
||||
@@ -72,8 +72,6 @@ urlpatterns = ('', # nopep8
|
||||
url(r'^pages/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$',
|
||||
'contentstore.views.static_pages',
|
||||
name='static_pages'),
|
||||
url(r'^edit_static/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$',
|
||||
'contentstore.views.edit_static', name='edit_static'),
|
||||
url(r'^edit_tabs/(?P<org>[^/]+)/(?P<course>[^/]+)/course/(?P<coursename>[^/]+)$',
|
||||
'contentstore.views.edit_tabs', name='edit_tabs'),
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ class MakoMiddleware(object):
|
||||
module_directory=module_directory,
|
||||
output_encoding='utf-8',
|
||||
input_encoding='utf-8',
|
||||
default_filters=['decode.utf8'],
|
||||
encoding_errors='replace',
|
||||
)
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ from django.shortcuts import redirect
|
||||
from django_future.csrf import ensure_csrf_cookie
|
||||
from django.utils.http import cookie_date
|
||||
from django.utils.http import base36_to_int
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from mitxmako.shortcuts import render_to_response, render_to_string
|
||||
from bs4 import BeautifulSoup
|
||||
@@ -285,7 +286,7 @@ def dashboard(request):
|
||||
|
||||
# Get the 3 most recent news
|
||||
top_news = _get_news(top=3) if not settings.MITX_FEATURES.get('ENABLE_MKTG_SITE', False) else None
|
||||
|
||||
|
||||
# get info w.r.t ExternalAuthMap
|
||||
external_auth_map = None
|
||||
try:
|
||||
@@ -357,7 +358,7 @@ def change_enrollment(request):
|
||||
action = request.POST.get("enrollment_action")
|
||||
course_id = request.POST.get("course_id")
|
||||
if course_id is None:
|
||||
return HttpResponseBadRequest("Course id not specified")
|
||||
return HttpResponseBadRequest(_("Course id not specified"))
|
||||
|
||||
if action == "enroll":
|
||||
# Make sure the course exists
|
||||
@@ -367,10 +368,10 @@ def change_enrollment(request):
|
||||
except ItemNotFoundError:
|
||||
log.warning("User {0} tried to enroll in non-existent course {1}"
|
||||
.format(user.username, course_id))
|
||||
return HttpResponseBadRequest("Course id is invalid")
|
||||
return HttpResponseBadRequest(_("Course id is invalid"))
|
||||
|
||||
if not has_access(user, course, 'enroll'):
|
||||
return HttpResponseBadRequest("Enrollment is closed")
|
||||
return HttpResponseBadRequest(_("Enrollment is closed"))
|
||||
|
||||
org, course_num, run = course_id.split("/")
|
||||
statsd.increment("common.student.enrollment",
|
||||
@@ -399,9 +400,9 @@ def change_enrollment(request):
|
||||
|
||||
return HttpResponse()
|
||||
except CourseEnrollment.DoesNotExist:
|
||||
return HttpResponseBadRequest("You are not enrolled in this course")
|
||||
return HttpResponseBadRequest(_("You are not enrolled in this course"))
|
||||
else:
|
||||
return HttpResponseBadRequest("Enrollment action is invalid")
|
||||
return HttpResponseBadRequest(_("Enrollment action is invalid"))
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -416,7 +417,7 @@ def login_user(request, error=""):
|
||||
''' AJAX request to log in the user. '''
|
||||
if 'email' not in request.POST or 'password' not in request.POST:
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'value': 'There was an error receiving your login information. Please email us.'})) # TODO: User error message
|
||||
'value': _('There was an error receiving your login information. Please email us.')})) # TODO: User error message
|
||||
|
||||
email = request.POST['email']
|
||||
password = request.POST['password']
|
||||
@@ -425,14 +426,14 @@ def login_user(request, error=""):
|
||||
except User.DoesNotExist:
|
||||
log.warning(u"Login failed - Unknown user email: {0}".format(email))
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'value': 'Email or password is incorrect.'})) # TODO: User error message
|
||||
'value': _('Email or password is incorrect.')})) # TODO: User error message
|
||||
|
||||
username = user.username
|
||||
user = authenticate(username=username, password=password)
|
||||
if user is None:
|
||||
log.warning(u"Login failed - password for {0} is invalid".format(email))
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'value': 'Email or password is incorrect.'}))
|
||||
'value': _('Email or password is incorrect.')}))
|
||||
|
||||
if user is not None and user.is_active:
|
||||
try:
|
||||
@@ -450,7 +451,7 @@ def login_user(request, error=""):
|
||||
|
||||
try_change_enrollment(request)
|
||||
|
||||
statsd.increment("common.student.successful_login")
|
||||
statsd.increment(_("common.student.successful_login"))
|
||||
response = HttpResponse(json.dumps({'success': True}))
|
||||
|
||||
# set the login cookie for the edx marketing site
|
||||
@@ -477,9 +478,7 @@ def login_user(request, error=""):
|
||||
log.warning(u"Login failed - Account not active for user {0}, resending activation".format(username))
|
||||
|
||||
reactivation_email_for_user(user)
|
||||
not_activated_msg = "This account has not been activated. We have " + \
|
||||
"sent another activation message. Please check your " + \
|
||||
"e-mail for the activation instructions."
|
||||
not_activated_msg = _("This account has not been activated. We have sent another activation message. Please check your e-mail for the activation instructions.")
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'value': not_activated_msg}))
|
||||
|
||||
@@ -537,12 +536,12 @@ def _do_create_account(post_vars):
|
||||
js = {'success': False}
|
||||
# Figure out the cause of the integrity error
|
||||
if len(User.objects.filter(username=post_vars['username'])) > 0:
|
||||
js['value'] = "An account with the Public Username '" + post_vars['username'] + "' already exists."
|
||||
js['value'] = _("An account with the Public Username '{username}' already exists.").format(username=post_vars['username'])
|
||||
js['field'] = 'username'
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
if len(User.objects.filter(email=post_vars['email'])) > 0:
|
||||
js['value'] = "An account with the Email '" + post_vars['email'] + "' already exists."
|
||||
js['value'] = _("An account with the Email '{email}' already exists.").format(email=post_vars['email'])
|
||||
js['field'] = 'email'
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
@@ -566,7 +565,7 @@ def _do_create_account(post_vars):
|
||||
try:
|
||||
profile.save()
|
||||
except Exception:
|
||||
log.exception("UserProfile creation failed for user {0}.".format(user.id))
|
||||
log.exception("UserProfile creation failed for user {id}.".format(id=user.id))
|
||||
return (user, profile, registration)
|
||||
|
||||
|
||||
@@ -594,7 +593,7 @@ def create_account(request, post_override=None):
|
||||
if eamap.external_name.strip() == '':
|
||||
name = post_vars.get('name', '')
|
||||
else:
|
||||
name = eamap.external_name
|
||||
name = eamap.external_name
|
||||
password = eamap.internal_password
|
||||
post_vars = dict(post_vars.items())
|
||||
post_vars.update(dict(email=email, name=name, password=password))
|
||||
@@ -603,12 +602,12 @@ def create_account(request, post_override=None):
|
||||
# Confirm we have a properly formed request
|
||||
for a in ['username', 'email', 'password', 'name']:
|
||||
if a not in post_vars:
|
||||
js['value'] = "Error (401 {field}). E-mail us.".format(field=a)
|
||||
js['value'] = _("Error (401 {field}). E-mail us.").format(field=a)
|
||||
js['field'] = a
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
if post_vars.get('honor_code', 'false') != u'true':
|
||||
js['value'] = "To enroll, you must follow the honor code.".format(field=a)
|
||||
js['value'] = _("To enroll, you must follow the honor code.").format(field=a)
|
||||
js['field'] = 'honor_code'
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
@@ -619,7 +618,7 @@ def create_account(request, post_override=None):
|
||||
|
||||
if not tos_not_required:
|
||||
if post_vars.get('terms_of_service', 'false') != u'true':
|
||||
js['value'] = "You must accept the terms of service.".format(field=a)
|
||||
js['value'] = _("You must accept the terms of service.").format(field=a)
|
||||
js['field'] = 'terms_of_service'
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
@@ -648,14 +647,14 @@ def create_account(request, post_override=None):
|
||||
try:
|
||||
validate_email(post_vars['email'])
|
||||
except ValidationError:
|
||||
js['value'] = "Valid e-mail is required.".format(field=a)
|
||||
js['value'] = _("Valid e-mail is required.").format(field=a)
|
||||
js['field'] = 'email'
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
try:
|
||||
validate_slug(post_vars['username'])
|
||||
except ValidationError:
|
||||
js['value'] = "Username should only consist of A-Z and 0-9, with no spaces.".format(field=a)
|
||||
js['value'] = _("Username should only consist of A-Z and 0-9, with no spaces.").format(field=a)
|
||||
js['field'] = 'username'
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
@@ -685,7 +684,7 @@ def create_account(request, post_override=None):
|
||||
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
|
||||
except:
|
||||
log.warning('Unable to send activation email to user', exc_info=True)
|
||||
js['value'] = 'Could not send activation e-mail.'
|
||||
js['value'] = _('Could not send activation e-mail.')
|
||||
return HttpResponse(json.dumps(js))
|
||||
|
||||
# Immediately after a user creates an account, we log them in. They are only
|
||||
@@ -956,7 +955,7 @@ def activate_account(request, key):
|
||||
return resp
|
||||
if len(r) == 0:
|
||||
return render_to_response("registration/activation_invalid.html", {'csrf': csrf(request)['csrf_token']})
|
||||
return HttpResponse("Unknown error. Please e-mail us to let us know how it happened.")
|
||||
return HttpResponse(_("Unknown error. Please e-mail us to let us know how it happened."))
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@@ -975,7 +974,8 @@ def password_reset(request):
|
||||
'value': render_to_string('registration/password_reset_done.html', {})}))
|
||||
else:
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'error': 'Invalid e-mail or user'}))
|
||||
'error': _('Invalid e-mail or user')}))
|
||||
|
||||
|
||||
def password_reset_confirm_wrapper(request, uidb36=None, token=None):
|
||||
''' A wrapper around django.contrib.auth.views.password_reset_confirm.
|
||||
@@ -997,7 +997,7 @@ def reactivation_email_for_user(user):
|
||||
reg = Registration.objects.get(user=user)
|
||||
except Registration.DoesNotExist:
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'error': 'No inactive user with this e-mail exists'}))
|
||||
'error': _('No inactive user with this e-mail exists')}))
|
||||
|
||||
d = {'name': user.profile.name,
|
||||
'key': reg.activation_key}
|
||||
@@ -1010,7 +1010,7 @@ def reactivation_email_for_user(user):
|
||||
res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
|
||||
except:
|
||||
log.warning('Unable to send reactivation email', exc_info=True)
|
||||
return HttpResponse(json.dumps({'success': False, 'error': 'Unable to send reactivation email'}))
|
||||
return HttpResponse(json.dumps({'success': False, 'error': _('Unable to send reactivation email')}))
|
||||
|
||||
return HttpResponse(json.dumps({'success': True}))
|
||||
|
||||
@@ -1027,19 +1027,19 @@ def change_email_request(request):
|
||||
|
||||
if not user.check_password(request.POST['password']):
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'error': 'Invalid password'}))
|
||||
'error': _('Invalid password')}))
|
||||
|
||||
new_email = request.POST['new_email']
|
||||
try:
|
||||
validate_email(new_email)
|
||||
except ValidationError:
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'error': 'Valid e-mail address required.'}))
|
||||
'error': _('Valid e-mail address required.')}))
|
||||
|
||||
if User.objects.filter(email=new_email).count() != 0:
|
||||
## CRITICAL TODO: Handle case sensitivity for e-mails
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'error': 'An account with this e-mail already exists.'}))
|
||||
'error': _('An account with this e-mail already exists.')}))
|
||||
|
||||
pec_list = PendingEmailChange.objects.filter(user=request.user)
|
||||
if len(pec_list) == 0:
|
||||
@@ -1055,7 +1055,7 @@ def change_email_request(request):
|
||||
if pec.new_email == user.email:
|
||||
pec.delete()
|
||||
return HttpResponse(json.dumps({'success': False,
|
||||
'error': 'Old email is the same as the new email.'}))
|
||||
'error': _('Old email is the same as the new email.')}))
|
||||
|
||||
d = {'key': pec.activation_key,
|
||||
'old_email': user.email,
|
||||
@@ -1144,7 +1144,7 @@ def change_name_request(request):
|
||||
pnc.new_name = request.POST['new_name']
|
||||
pnc.rationale = request.POST['rationale']
|
||||
if len(pnc.new_name) < 2:
|
||||
return HttpResponse(json.dumps({'success': False, 'error': 'Name required'}))
|
||||
return HttpResponse(json.dumps({'success': False, 'error': _('Name required')}))
|
||||
pnc.save()
|
||||
|
||||
# The following automatically accepts name change requests. Remove this to
|
||||
@@ -1179,7 +1179,7 @@ def reject_name_change(request):
|
||||
try:
|
||||
pnc = PendingNameChange.objects.get(id=int(request.POST['id']))
|
||||
except PendingNameChange.DoesNotExist:
|
||||
return HttpResponse(json.dumps({'success': False, 'error': 'Invalid ID'}))
|
||||
return HttpResponse(json.dumps({'success': False, 'error': _('Invalid ID')}))
|
||||
|
||||
pnc.delete()
|
||||
return HttpResponse(json.dumps({'success': True}))
|
||||
@@ -1189,7 +1189,7 @@ def accept_name_change_by_id(id):
|
||||
try:
|
||||
pnc = PendingNameChange.objects.get(id=id)
|
||||
except PendingNameChange.DoesNotExist:
|
||||
return HttpResponse(json.dumps({'success': False, 'error': 'Invalid ID'}))
|
||||
return HttpResponse(json.dumps({'success': False, 'error': _('Invalid ID')}))
|
||||
|
||||
u = pnc.user
|
||||
up = UserProfile.objects.get(user=u)
|
||||
|
||||
@@ -12,7 +12,6 @@ from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from student.models import CourseEnrollment
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.templates import update_templates
|
||||
from urllib import quote_plus
|
||||
|
||||
|
||||
@@ -84,5 +83,4 @@ def clear_courses():
|
||||
# from the bash shell to drop it:
|
||||
# $ mongo test_xmodule --eval "db.dropDatabase()"
|
||||
modulestore().collection.drop()
|
||||
update_templates(modulestore('direct'))
|
||||
contentstore().fs_files.drop()
|
||||
|
||||
@@ -23,15 +23,15 @@ class TestXmoduleModfiers(ModuleStoreTestCase):
|
||||
number='313', display_name='histogram test')
|
||||
section = ItemFactory.create(
|
||||
parent_location=course.location, display_name='chapter hist',
|
||||
template='i4x://edx/templates/chapter/Empty')
|
||||
category='chapter')
|
||||
problem = ItemFactory.create(
|
||||
parent_location=section.location, display_name='problem hist 1',
|
||||
template='i4x://edx/templates/problem/Blank_Common_Problem')
|
||||
category='problem')
|
||||
problem.has_score = False # don't trip trying to retrieve db data
|
||||
|
||||
late_problem = ItemFactory.create(
|
||||
parent_location=section.location, display_name='problem hist 2',
|
||||
template='i4x://edx/templates/problem/Blank_Common_Problem')
|
||||
category='problem')
|
||||
late_problem.lms.start = datetime.datetime.now(UTC) + datetime.timedelta(days=32)
|
||||
late_problem.has_score = False
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -80,8 +80,6 @@ class ABTestModule(ABTestFields, XModule):
|
||||
class ABTestDescriptor(ABTestFields, RawDescriptor, XmlDescriptor):
|
||||
module_class = ABTestModule
|
||||
|
||||
template_dir_name = "abtest"
|
||||
|
||||
@classmethod
|
||||
def definition_from_xml(cls, xml_object, system):
|
||||
"""
|
||||
|
||||
@@ -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><annotation></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):
|
||||
@@ -125,5 +150,4 @@ class AnnotatableModule(AnnotatableFields, XModule):
|
||||
|
||||
class AnnotatableDescriptor(AnnotatableFields, RawDescriptor):
|
||||
module_class = AnnotatableModule
|
||||
template_dir_name = "annotatable"
|
||||
mako_template = "widgets/raw-edit.html"
|
||||
|
||||
@@ -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="finished",
|
||||
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="never",
|
||||
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,7 +146,7 @@ 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=None, scope=Scope.settings)
|
||||
source_code = String(
|
||||
help="Source code for LaTeX and Word problems. This feature is not well-supported.",
|
||||
scope=Scope.settings
|
||||
@@ -541,6 +553,16 @@ class CapaModule(CapaFields, XModule):
|
||||
'ungraded_response': self.handle_ungraded_response
|
||||
}
|
||||
|
||||
generic_error_message = (
|
||||
"We're sorry, there was an error with processing your request. "
|
||||
"Please try reloading your page and trying again."
|
||||
)
|
||||
|
||||
not_found_error_message = (
|
||||
"The state of this problem has changed since you loaded this page. "
|
||||
"Please refresh your page."
|
||||
)
|
||||
|
||||
if dispatch not in handlers:
|
||||
return 'Error'
|
||||
|
||||
@@ -548,9 +570,14 @@ class CapaModule(CapaFields, XModule):
|
||||
|
||||
try:
|
||||
result = handlers[dispatch](data)
|
||||
|
||||
except NotFoundError as err:
|
||||
_, _, traceback_obj = sys.exc_info()
|
||||
raise ProcessingError, (not_found_error_message, err), traceback_obj
|
||||
|
||||
except Exception as err:
|
||||
_, _, traceback_obj = sys.exc_info()
|
||||
raise ProcessingError(err.message, traceback_obj)
|
||||
raise ProcessingError, (generic_error_message, err), traceback_obj
|
||||
|
||||
after = self.get_progress()
|
||||
|
||||
@@ -1101,6 +1128,19 @@ class CapaDescriptor(CapaFields, RawDescriptor):
|
||||
path[8:],
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, xml_data, system, org=None, course=None):
|
||||
"""
|
||||
Augment regular translation w/ setting the pre-Studio defaults.
|
||||
"""
|
||||
problem = super(CapaDescriptor, cls).from_xml(xml_data, system, org, course)
|
||||
# pylint: disable=W0212
|
||||
if 'showanswer' not in problem._model_data:
|
||||
problem.showanswer = "closed"
|
||||
if 'rerandomize' not in problem._model_data:
|
||||
problem.rerandomize = "always"
|
||||
return problem
|
||||
|
||||
@property
|
||||
def non_editable_metadata_fields(self):
|
||||
non_editable_fields = super(CapaDescriptor, self).non_editable_metadata_fields
|
||||
|
||||
@@ -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):
|
||||
@@ -51,7 +84,8 @@ class CombinedOpenEndedFields(object):
|
||||
display_name = String(
|
||||
display_name="Display Name",
|
||||
help="This name appears in the horizontal navigation at the top of the page.",
|
||||
default="Open Ended Grading", scope=Scope.settings
|
||||
default="Open Ended Grading",
|
||||
scope=Scope.settings
|
||||
)
|
||||
current_task_number = Integer(help="Current task that the student is on.", default=0, scope=Scope.user_state)
|
||||
task_states = List(help="List of state dictionaries of each task within this module.", scope=Scope.user_state)
|
||||
@@ -85,13 +119,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):
|
||||
@@ -240,7 +291,6 @@ class CombinedOpenEndedDescriptor(CombinedOpenEndedFields, RawDescriptor):
|
||||
|
||||
has_score = True
|
||||
always_recalculate_grades = True
|
||||
template_dir_name = "combinedopenended"
|
||||
|
||||
#Specify whether or not to pass in S3 interface
|
||||
needs_s3_interface = True
|
||||
|
||||
@@ -5,7 +5,6 @@ from lxml import etree
|
||||
from path import path # NOTE (THK): Only used for detecting presence of syllabus
|
||||
import requests
|
||||
from datetime import datetime
|
||||
|
||||
import dateutil.parser
|
||||
|
||||
from xmodule.modulestore import Location
|
||||
@@ -146,16 +145,56 @@ class TextbookList(List):
|
||||
|
||||
|
||||
class CourseFields(object):
|
||||
textbooks = TextbookList(help="List of pairs of (title, url) for textbooks used in this course", scope=Scope.content)
|
||||
textbooks = TextbookList(help="List of pairs of (title, url) for textbooks used in this course",
|
||||
default=[], scope=Scope.content)
|
||||
wiki_slug = String(help="Slug that points to the wiki for this course", scope=Scope.content)
|
||||
enrollment_start = Date(help="Date that enrollment for this class is opened", scope=Scope.settings)
|
||||
enrollment_end = Date(help="Date that enrollment for this class is closed", 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",
|
||||
# using now(UTC()) resulted in fractional seconds which screwed up comparisons and anyway w/b the
|
||||
# time of first invocation of this stmt on the server
|
||||
default=datetime.fromtimestamp(0, UTC()),
|
||||
scope=Scope.settings)
|
||||
end = Date(help="Date that this class ends", scope=Scope.settings)
|
||||
advertised_start = String(help="Date that this course is advertised to start", scope=Scope.settings)
|
||||
grading_policy = Dict(help="Grading policy definition for this class", scope=Scope.content)
|
||||
grading_policy = Dict(help="Grading policy definition for this class",
|
||||
default={"GRADER": [
|
||||
{
|
||||
"type": "Homework",
|
||||
"min_count": 12,
|
||||
"drop_count": 2,
|
||||
"short_label": "HW",
|
||||
"weight": 0.15
|
||||
},
|
||||
{
|
||||
"type": "Lab",
|
||||
"min_count": 12,
|
||||
"drop_count": 2,
|
||||
"weight": 0.15
|
||||
},
|
||||
{
|
||||
"type": "Midterm Exam",
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"drop_count": 0,
|
||||
"weight": 0.3
|
||||
},
|
||||
{
|
||||
"type": "Final Exam",
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"drop_count": 0,
|
||||
"weight": 0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.5
|
||||
}},
|
||||
scope=Scope.content)
|
||||
show_calculator = Boolean(help="Whether to show the calculator in this course", default=False, scope=Scope.settings)
|
||||
display_name = String(help="Display name for this module", scope=Scope.settings)
|
||||
display_name = String(
|
||||
help="Display name for this module", default="Empty",
|
||||
display_name="Display Name", scope=Scope.settings)
|
||||
tabs = List(help="List of tabs to enable in this course", scope=Scope.settings)
|
||||
end_of_course_survey_url = String(help="Url for the end-of-course survey", scope=Scope.settings)
|
||||
discussion_blackouts = List(help="List of pairs of start/end dates for discussion blackouts", scope=Scope.settings)
|
||||
@@ -176,7 +215,125 @@ class CourseFields(object):
|
||||
allow_anonymous_to_peers = Boolean(scope=Scope.settings, default=False)
|
||||
advanced_modules = List(help="Beta modules used in your course", scope=Scope.settings)
|
||||
has_children = True
|
||||
checklists = List(scope=Scope.settings)
|
||||
checklists = List(scope=Scope.settings,
|
||||
default=[
|
||||
{"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 & 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 & 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 & 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 & 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 & Details",
|
||||
"action_external": False}]
|
||||
}
|
||||
])
|
||||
info_sidebar_name = String(scope=Scope.settings, default='Course Handouts')
|
||||
show_timezone = Boolean(help="True if timezones should be shown on dates in the courseware", scope=Scope.settings, default=True)
|
||||
enrollment_domain = String(help="External login method associated with user accounts allowed to register in course",
|
||||
@@ -209,8 +366,6 @@ class CourseFields(object):
|
||||
class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
module_class = SequenceModule
|
||||
|
||||
template_dir_name = 'course'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Expects the same arguments as XModuleDescriptor.__init__
|
||||
@@ -221,18 +376,16 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
self.wiki_slug = self.location.course
|
||||
|
||||
msg = None
|
||||
if self.start is None:
|
||||
msg = "Course loaded without a valid start date. id = %s" % self.id
|
||||
self.start = datetime.now(UTC())
|
||||
log.critical(msg)
|
||||
self.system.error_tracker(msg)
|
||||
|
||||
# NOTE: relies on the modulestore to call set_grading_policy() right after
|
||||
# init. (Modulestore is in charge of figuring out where to load the policy from)
|
||||
|
||||
# NOTE (THK): This is a last-minute addition for Fall 2012 launch to dynamically
|
||||
# disable the syllabus content for courses that do not provide a syllabus
|
||||
self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))
|
||||
if self.system.resources_fs is None:
|
||||
self.syllabus_present = False
|
||||
else:
|
||||
self.syllabus_present = self.system.resources_fs.exists(path('syllabus'))
|
||||
self._grading_policy = {}
|
||||
|
||||
self.set_grading_policy(self.grading_policy)
|
||||
@@ -253,42 +406,33 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
log.error(msg)
|
||||
continue
|
||||
|
||||
def default_grading_policy(self):
|
||||
"""
|
||||
Return a dict which is a copy of the default grading policy
|
||||
"""
|
||||
return {"GRADER": [
|
||||
{
|
||||
"type": "Homework",
|
||||
"min_count": 12,
|
||||
"drop_count": 2,
|
||||
"short_label": "HW",
|
||||
"weight": 0.15
|
||||
},
|
||||
{
|
||||
"type": "Lab",
|
||||
"min_count": 12,
|
||||
"drop_count": 2,
|
||||
"weight": 0.15
|
||||
},
|
||||
{
|
||||
"type": "Midterm Exam",
|
||||
"short_label": "Midterm",
|
||||
"min_count": 1,
|
||||
"drop_count": 0,
|
||||
"weight": 0.3
|
||||
},
|
||||
{
|
||||
"type": "Final Exam",
|
||||
"short_label": "Final",
|
||||
"min_count": 1,
|
||||
"drop_count": 0,
|
||||
"weight": 0.4
|
||||
}
|
||||
],
|
||||
"GRADE_CUTOFFS": {
|
||||
"Pass": 0.5
|
||||
}}
|
||||
# TODO check that this is still needed here and can't be by defaults.
|
||||
if self.tabs is None:
|
||||
# When calling the various _tab methods, can omit the 'type':'blah' from the
|
||||
# first arg, since that's only used for dispatch
|
||||
tabs = []
|
||||
tabs.append({'type': 'courseware'})
|
||||
tabs.append({'type': 'course_info', 'name': 'Course Info'})
|
||||
|
||||
if self.syllabus_present:
|
||||
tabs.append({'type': 'syllabus'})
|
||||
|
||||
tabs.append({'type': 'textbooks'})
|
||||
|
||||
# # If they have a discussion link specified, use that even if we feature
|
||||
# # flag discussions off. Disabling that is mostly a server safety feature
|
||||
# # at this point, and we don't need to worry about external sites.
|
||||
if self.discussion_link:
|
||||
tabs.append({'type': 'external_discussion', 'link': self.discussion_link})
|
||||
else:
|
||||
tabs.append({'type': 'discussion', 'name': 'Discussion'})
|
||||
|
||||
tabs.append({'type': 'wiki', 'name': 'Wiki'})
|
||||
|
||||
if not self.hide_progress_tab:
|
||||
tabs.append({'type': 'progress', 'name': 'Progress'})
|
||||
|
||||
self.tabs = tabs
|
||||
|
||||
def set_grading_policy(self, course_policy):
|
||||
"""
|
||||
@@ -299,7 +443,13 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
course_policy = {}
|
||||
|
||||
# Load the global settings as a dictionary
|
||||
grading_policy = self.default_grading_policy()
|
||||
grading_policy = self.grading_policy
|
||||
# BOY DO I HATE THIS grading_policy CODE ACROBATICS YET HERE I ADD MORE (dhm)--this fixes things persisted w/
|
||||
# defective grading policy values (but not None)
|
||||
if 'GRADER' not in grading_policy:
|
||||
grading_policy['GRADER'] = CourseFields.grading_policy.default['GRADER']
|
||||
if 'GRADE_CUTOFFS' not in grading_policy:
|
||||
grading_policy['GRADE_CUTOFFS'] = CourseFields.grading_policy.default['GRADE_CUTOFFS']
|
||||
|
||||
# Override any global settings with the course settings
|
||||
grading_policy.update(course_policy)
|
||||
@@ -355,10 +505,6 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
system.error_tracker("Unable to decode grading policy as json")
|
||||
policy = {}
|
||||
|
||||
# cdodge: import the grading policy information that is on disk and put into the
|
||||
# descriptor 'definition' bucket as a dictionary so that it is persisted in the DB
|
||||
instance.grading_policy = policy
|
||||
|
||||
# now set the current instance. set_grading_policy() will apply some inheritance rules
|
||||
instance.set_grading_policy(policy)
|
||||
|
||||
@@ -662,6 +808,7 @@ class CourseDescriptor(CourseFields, SequenceDescriptor):
|
||||
if isinstance(self.advertised_start, basestring):
|
||||
return try_parse_iso_8601(self.advertised_start)
|
||||
elif self.advertised_start is None and self.start is None:
|
||||
# TODO this is an impossible state since the init function forces start to have a value
|
||||
return 'TBD'
|
||||
else:
|
||||
return (self.advertised_start or self.start).strftime("%b %d, %Y")
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user