Remove CMS Course Checklists feature
This commit is contained in:
@@ -1,33 +0,0 @@
|
||||
@shard_1
|
||||
Feature: CMS.Course checklists
|
||||
|
||||
Scenario: A course author sees checklists defined by edX
|
||||
Given I have opened a new course in Studio
|
||||
When I select Checklists from the Tools menu
|
||||
Then I see the four default edX checklists
|
||||
|
||||
Scenario: A course author can mark tasks as complete
|
||||
Given I have opened Checklists
|
||||
Then I can check and uncheck tasks in a checklist
|
||||
And I reload the page
|
||||
Then the tasks are correctly selected
|
||||
|
||||
# There are issues getting link to be active in browsers other than chrome
|
||||
@skip_firefox
|
||||
@skip_internetexplorer
|
||||
@skip_safari
|
||||
Scenario: A task can link to a location within Studio
|
||||
Given I have opened Checklists
|
||||
When I select a link to the course outline
|
||||
Then I am brought to the course outline page
|
||||
And I press the browser back button
|
||||
Then I am brought back to the course outline in the correct state
|
||||
|
||||
# There are issues getting link to be active in browsers other than chrome
|
||||
@skip_firefox
|
||||
@skip_internetexplorer
|
||||
@skip_safari
|
||||
Scenario: A task can link to a location outside Studio
|
||||
Given I have opened Checklists
|
||||
When I select a link to help page
|
||||
Then I am brought to the help page in a new window
|
||||
@@ -1,125 +0,0 @@
|
||||
# pylint: disable=missing-docstring
|
||||
# pylint: disable=redefined-outer-name
|
||||
|
||||
from lettuce import world, step
|
||||
from nose.tools import assert_true, assert_equal
|
||||
from selenium.common.exceptions import StaleElementReferenceException
|
||||
|
||||
|
||||
############### ACTIONS ####################
|
||||
@step('I select Checklists from the Tools menu$')
|
||||
def i_select_checklists(step):
|
||||
world.click_tools()
|
||||
link_css = 'li.nav-course-tools-checklists a'
|
||||
world.css_click(link_css)
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
@step('I have opened Checklists$')
|
||||
def i_have_opened_checklists(step):
|
||||
step.given('I have opened a new course in Studio')
|
||||
step.given('I select Checklists from the Tools menu')
|
||||
|
||||
|
||||
@step('I see the four default edX checklists$')
|
||||
def i_see_default_checklists(step):
|
||||
checklists = world.css_find('.checklist-title')
|
||||
assert_equal(4, len(checklists))
|
||||
assert_true(checklists[0].text.endswith('Getting Started With Studio'))
|
||||
assert_true(checklists[1].text.endswith('Draft a Rough Course Outline'))
|
||||
assert_true(checklists[2].text.endswith("Explore edX\'s Support Tools"))
|
||||
assert_true(checklists[3].text.endswith('Draft Your Course About Page'))
|
||||
|
||||
|
||||
@step('I can check and uncheck tasks in a checklist$')
|
||||
def i_can_check_and_uncheck_tasks(step):
|
||||
# Use the 2nd checklist as a reference
|
||||
verifyChecklist2Status(0, 7, 0)
|
||||
toggleTask(1, 0)
|
||||
verifyChecklist2Status(1, 7, 14)
|
||||
toggleTask(1, 3)
|
||||
verifyChecklist2Status(2, 7, 29)
|
||||
toggleTask(1, 6)
|
||||
verifyChecklist2Status(3, 7, 43)
|
||||
toggleTask(1, 3)
|
||||
verifyChecklist2Status(2, 7, 29)
|
||||
|
||||
|
||||
@step('the tasks are correctly selected$')
|
||||
def tasks_correctly_selected(step):
|
||||
verifyChecklist2Status(2, 7, 29)
|
||||
# verify that task 7 is still selected by toggling its checkbox state and making sure that it deselects
|
||||
world.browser.execute_script("window.scrollBy(0,1000)")
|
||||
toggleTask(1, 6)
|
||||
verifyChecklist2Status(1, 7, 14)
|
||||
|
||||
|
||||
@step('I select a link to the course outline$')
|
||||
def i_select_a_link_to_the_course_outline(step):
|
||||
clickActionLink(1, 0, 'Edit Course Outline')
|
||||
|
||||
|
||||
@step('I am brought to the course outline page$')
|
||||
def i_am_brought_to_course_outline(step):
|
||||
assert world.is_css_present('body.view-outline')
|
||||
assert_equal(1, len(world.browser.windows))
|
||||
|
||||
|
||||
@step('I am brought back to the course outline in the correct state$')
|
||||
def i_am_brought_back_to_course_outline(step):
|
||||
step.given('I see the four default edX checklists')
|
||||
# In a previous step, we selected (1, 0) in order to click the 'Edit Course Outline' link.
|
||||
# Make sure the task is still showing as selected (there was a caching bug with the collection).
|
||||
verifyChecklist2Status(1, 7, 14)
|
||||
|
||||
|
||||
@step('I select a link to help page$')
|
||||
def i_select_a_link_to_the_help_page(step):
|
||||
clickActionLink(2, 0, 'Visit Studio Help')
|
||||
|
||||
|
||||
@step('I am brought to the help page in a new window$')
|
||||
def i_am_brought_to_help_page_in_new_window(step):
|
||||
step.given('I see the four default edX checklists')
|
||||
windows = world.browser.windows
|
||||
assert_equal(2, len(windows))
|
||||
world.browser.switch_to_window(windows[1])
|
||||
assert_equal('http://help.edge.edx.org/', world.browser.url)
|
||||
|
||||
|
||||
############### HELPER METHODS ####################
|
||||
def verifyChecklist2Status(completed, total, percentage):
|
||||
def verify_count(driver):
|
||||
try:
|
||||
statusCount = world.css_find('#course-checklist1 .status-count').first
|
||||
return statusCount.text == str(completed)
|
||||
except StaleElementReferenceException:
|
||||
return False
|
||||
|
||||
world.wait_for(verify_count)
|
||||
assert_equal(str(total), world.css_find('#course-checklist1 .status-amount').first.text)
|
||||
# Would like to check the CSS width, but not sure how to do that.
|
||||
assert_equal(str(percentage), world.css_find('#course-checklist1 .viz-checklist-status-value .int').first.text)
|
||||
|
||||
|
||||
def toggleTask(checklist, task):
|
||||
world.css_click('#course-checklist' + str(checklist) + '-task' + str(task))
|
||||
world.wait_for_ajax_complete()
|
||||
|
||||
|
||||
# TODO: figure out a way to do this in phantom and firefox
|
||||
# For now we will mark the scenerios that use this method as skipped
|
||||
def clickActionLink(checklist, task, actionText):
|
||||
# text will be empty initially, wait for it to populate
|
||||
def verify_action_link_text(driver):
|
||||
actualText = world.css_text('#course-checklist' + str(checklist) + ' a', index=task)
|
||||
if actualText == actionText:
|
||||
return True
|
||||
else:
|
||||
# toggle checklist item to make sure that the link button is showing
|
||||
toggleTask(checklist, task)
|
||||
return False
|
||||
|
||||
world.wait_for(verify_action_link_text)
|
||||
world.css_click('#course-checklist' + str(checklist) + ' a', index=task)
|
||||
world.wait_for_ajax_complete()
|
||||
@@ -1510,7 +1510,6 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
|
||||
test_get_html('export_handler')
|
||||
test_get_html('course_team_handler')
|
||||
test_get_html('course_info_handler')
|
||||
test_get_html('checklists_handler')
|
||||
test_get_html('assets_handler')
|
||||
test_get_html('tabs_handler')
|
||||
test_get_html('settings_handler')
|
||||
@@ -1694,7 +1693,6 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
|
||||
self.assertEqual(course.textbooks, [])
|
||||
self.assertIn('GRADER', course.grading_policy)
|
||||
self.assertIn('GRADE_CUTOFFS', course.grading_policy)
|
||||
self.assertGreaterEqual(len(course.checklists), 4)
|
||||
|
||||
# by fetching
|
||||
fetched_course = self.store.get_item(course.location)
|
||||
@@ -1703,8 +1701,6 @@ class ContentStoreTest(ContentStoreTestCase, XssTestMixin):
|
||||
self.assertEqual(course.start, fetched_course.start)
|
||||
self.assertEqual(fetched_course.start, fetched_item.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)
|
||||
|
||||
def test_image_import(self):
|
||||
"""Test backwards compatibilty of course image."""
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
# Disable warnings about import from wildcard
|
||||
# All files below declare exports with __all__
|
||||
from .assets import *
|
||||
from .checklist import *
|
||||
from .component import *
|
||||
from .course import *
|
||||
from .entrance_exam import *
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
import json
|
||||
import copy
|
||||
|
||||
from util.json_request import JsonResponse
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from contentstore.utils import reverse_course_url
|
||||
|
||||
from student.auth import has_course_author_access
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
|
||||
from django.utils.translation import ugettext
|
||||
|
||||
__all__ = ['checklists_handler']
|
||||
|
||||
|
||||
@require_http_methods(("GET", "POST", "PUT"))
|
||||
@login_required
|
||||
@ensure_csrf_cookie
|
||||
def checklists_handler(request, course_key_string, checklist_index=None):
|
||||
"""
|
||||
The restful handler for checklists.
|
||||
|
||||
GET
|
||||
html: return html page for all checklists
|
||||
json: return json representing all checklists. checklist_index is not supported for GET at this time.
|
||||
POST or PUT
|
||||
json: updates the checked state for items within a particular checklist. checklist_index is required.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_key_string)
|
||||
if not has_course_author_access(request.user, course_key):
|
||||
raise PermissionDenied()
|
||||
|
||||
course_module = modulestore().get_course(course_key)
|
||||
|
||||
json_request = 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json')
|
||||
if request.method == 'GET':
|
||||
# If course was created before checklists were introduced, copy them over
|
||||
# from the template.
|
||||
if not course_module.checklists:
|
||||
course_module.checklists = CourseDescriptor.checklists.default
|
||||
modulestore().update_item(course_module, request.user.id)
|
||||
|
||||
expanded_checklists = expand_all_action_urls(course_module)
|
||||
if json_request:
|
||||
return JsonResponse(expanded_checklists)
|
||||
else:
|
||||
handler_url = reverse_course_url('checklists_handler', course_key)
|
||||
return render_to_response('checklists.html',
|
||||
{
|
||||
'handler_url': handler_url,
|
||||
# context_course is used by analytics
|
||||
'context_course': course_module,
|
||||
'checklists': expanded_checklists
|
||||
})
|
||||
elif json_request:
|
||||
# Can now assume POST or PUT because GET handled above.
|
||||
if checklist_index is not None and 0 <= int(checklist_index) < len(course_module.checklists):
|
||||
index = int(checklist_index)
|
||||
persisted_checklist = course_module.checklists[index]
|
||||
modified_checklist = json.loads(request.body)
|
||||
# Only thing the user can modify is the "checked" state.
|
||||
# We don't want to persist what comes back from the client because it will
|
||||
# include the expanded action URLs (which are non-portable).
|
||||
for item_index, item in enumerate(modified_checklist.get('items')):
|
||||
persisted_checklist['items'][item_index]['is_checked'] = item['is_checked']
|
||||
# seeming noop which triggers kvs to record that the metadata is
|
||||
# not default
|
||||
course_module.checklists = course_module.checklists
|
||||
course_module.save()
|
||||
modulestore().update_item(course_module, request.user.id)
|
||||
expanded_checklist = expand_checklist_action_url(course_module, persisted_checklist)
|
||||
return JsonResponse(localize_checklist_text(expanded_checklist))
|
||||
else:
|
||||
return HttpResponseBadRequest(
|
||||
("Could not save checklist state because the checklist index "
|
||||
"was out of range or unspecified."),
|
||||
content_type="text/plain"
|
||||
)
|
||||
else:
|
||||
return HttpResponseNotFound()
|
||||
|
||||
|
||||
def expand_all_action_urls(course_module):
|
||||
"""
|
||||
Gets the checklists out of the course module and expands their action urls.
|
||||
|
||||
Returns a copy of the checklists with modified urls, without modifying the persisted version
|
||||
of the checklists.
|
||||
"""
|
||||
expanded_checklists = []
|
||||
for checklist in course_module.checklists:
|
||||
expanded_checklists.append(localize_checklist_text(expand_checklist_action_url(course_module, checklist)))
|
||||
return expanded_checklists
|
||||
|
||||
|
||||
def expand_checklist_action_url(course_module, checklist):
|
||||
"""
|
||||
Expands the action URLs for a given checklist and returns the modified version.
|
||||
|
||||
The method does a copy of the input checklist and does not modify the input argument.
|
||||
"""
|
||||
expanded_checklist = copy.deepcopy(checklist)
|
||||
|
||||
urlconf_map = {
|
||||
"ManageUsers": "course_team_handler",
|
||||
"CourseOutline": "course_handler",
|
||||
"SettingsDetails": "settings_handler",
|
||||
"SettingsGrading": "grading_handler",
|
||||
}
|
||||
|
||||
for item in expanded_checklist.get('items'):
|
||||
action_url = item.get('action_url')
|
||||
if action_url in urlconf_map:
|
||||
item['action_url'] = reverse_course_url(urlconf_map[action_url], course_module.id)
|
||||
|
||||
return expanded_checklist
|
||||
|
||||
|
||||
def localize_checklist_text(checklist):
|
||||
"""
|
||||
Localize texts for a given checklist and returns the modified version.
|
||||
|
||||
The method does an in-place operation so the input checklist is modified directly.
|
||||
"""
|
||||
# Localize checklist name
|
||||
checklist['short_description'] = ugettext(checklist['short_description']) # pylint: disable=translation-of-non-string
|
||||
|
||||
# Localize checklist items
|
||||
for item in checklist.get('items'):
|
||||
item['short_description'] = ugettext(item['short_description']) # pylint: disable=translation-of-non-string
|
||||
item['long_description'] = ugettext(item['long_description']) # pylint: disable=translation-of-non-string
|
||||
item['action_text'] = ugettext(item['action_text']) # pylint: disable=translation-of-non-string
|
||||
|
||||
return checklist
|
||||
@@ -1,153 +0,0 @@
|
||||
""" Unit tests for checklist methods in views.py. """
|
||||
from contentstore.utils import reverse_course_url
|
||||
from contentstore.views.checklist import expand_checklist_action_url
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
import json
|
||||
from contentstore.tests.utils import CourseTestCase
|
||||
|
||||
|
||||
class ChecklistTestCase(CourseTestCase):
|
||||
""" Test for checklist get and put methods. """
|
||||
def setUp(self):
|
||||
""" Creates the test course. """
|
||||
super(ChecklistTestCase, self).setUp()
|
||||
self.course = CourseFactory.create(org='mitX', number='333', display_name='Checklists Course')
|
||||
self.checklists_url = self.get_url()
|
||||
|
||||
def get_url(self, checklist_index=None):
|
||||
url_args = {'checklist_index': checklist_index} if checklist_index else None
|
||||
return reverse_course_url('checklists_handler', self.course.id, kwargs=url_args)
|
||||
|
||||
def get_persisted_checklists(self):
|
||||
""" Returns the checklists as persisted in the modulestore. """
|
||||
return modulestore().get_item(self.course.location).checklists
|
||||
|
||||
def compare_checklists(self, persisted, request):
|
||||
"""
|
||||
Handles url expansion as possible difference and descends into guts
|
||||
"""
|
||||
self.assertEqual(persisted['short_description'], request['short_description'])
|
||||
expanded_checklist = expand_checklist_action_url(self.course, persisted)
|
||||
for pers, req in zip(expanded_checklist['items'], request['items']):
|
||||
self.assertEqual(pers['short_description'], req['short_description'])
|
||||
self.assertEqual(pers['long_description'], req['long_description'])
|
||||
self.assertEqual(pers['is_checked'], req['is_checked'])
|
||||
self.assertEqual(pers['action_url'], req['action_url'])
|
||||
self.assertEqual(pers['action_text'], req['action_text'])
|
||||
self.assertEqual(pers['action_external'], req['action_external'])
|
||||
|
||||
def test_get_checklists(self):
|
||||
""" Tests the get checklists method and URL expansion. """
|
||||
response = self.client.get(self.checklists_url)
|
||||
self.assertContains(response, "Getting Started With Studio")
|
||||
# Verify expansion of action URL happened.
|
||||
self.assertContains(response, 'course_team/mitX/333/Checklists_Course')
|
||||
# Verify persisted checklist does NOT have expanded URL.
|
||||
checklist_0 = self.get_persisted_checklists()[0]
|
||||
self.assertEqual('ManageUsers', get_action_url(checklist_0, 0))
|
||||
payload = response.content
|
||||
|
||||
# Now delete the checklists from the course and verify they get repopulated (for courses
|
||||
# created before checklists were introduced).
|
||||
self.course.checklists = None
|
||||
# Save the changed `checklists` to the underlying KeyValueStore before updating the modulestore
|
||||
self.course.save()
|
||||
modulestore().update_item(self.course, self.user.id)
|
||||
self.assertEqual(self.get_persisted_checklists(), None)
|
||||
response = self.client.get(self.checklists_url)
|
||||
self.assertEqual(payload, response.content)
|
||||
|
||||
def test_get_checklists_html(self):
|
||||
""" Tests getting the HTML template for the checklists page). """
|
||||
response = self.client.get(self.checklists_url, HTTP_ACCEPT='text/html')
|
||||
self.assertContains(response, "Getting Started With Studio")
|
||||
# The HTML generated will define the handler URL (for use by the Backbone model).
|
||||
self.assertContains(response, self.checklists_url)
|
||||
|
||||
def test_update_checklists_no_index(self):
|
||||
""" No checklist index, should return all of them. """
|
||||
returned_checklists = json.loads(self.client.get(self.checklists_url).content)
|
||||
# Verify that persisted checklists do not have expanded action URLs.
|
||||
# compare_checklists will verify that returned_checklists DO have expanded action URLs.
|
||||
pers = self.get_persisted_checklists()
|
||||
self.assertEqual('CourseOutline', get_first_item(pers[1]).get('action_url'))
|
||||
for pay, resp in zip(pers, returned_checklists):
|
||||
self.compare_checklists(pay, resp)
|
||||
|
||||
def test_update_checklists_index_ignored_on_get(self):
|
||||
""" Checklist index ignored on get. """
|
||||
update_url = self.get_url(1)
|
||||
|
||||
returned_checklists = json.loads(self.client.get(update_url).content)
|
||||
for pay, resp in zip(self.get_persisted_checklists(), returned_checklists):
|
||||
self.compare_checklists(pay, resp)
|
||||
|
||||
def test_update_checklists_post_no_index(self):
|
||||
""" No checklist index, will error on post. """
|
||||
response = self.client.post(self.checklists_url)
|
||||
self.assertContains(response, 'Could not save checklist', status_code=400)
|
||||
|
||||
def test_update_checklists_index_out_of_range(self):
|
||||
""" Checklist index out of range, will error on post. """
|
||||
update_url = self.get_url(100)
|
||||
|
||||
response = self.client.post(update_url)
|
||||
self.assertContains(response, 'Could not save checklist', status_code=400)
|
||||
|
||||
def test_update_checklists_index(self):
|
||||
""" Check that an update of a particular checklist works. """
|
||||
update_url = self.get_url(1)
|
||||
|
||||
payload = self.course.checklists[1]
|
||||
self.assertFalse(get_first_item(payload).get('is_checked'))
|
||||
self.assertEqual('CourseOutline', get_first_item(payload).get('action_url'))
|
||||
get_first_item(payload)['is_checked'] = True
|
||||
|
||||
returned_checklist = json.loads(self.client.ajax_post(update_url, payload).content)
|
||||
self.assertTrue(get_first_item(returned_checklist).get('is_checked'))
|
||||
persisted_checklist = self.get_persisted_checklists()[1]
|
||||
# Verify that persisted checklist does not have expanded action URLs.
|
||||
# compare_checklists will verify that returned_checklist DOES have expanded action URLs.
|
||||
self.assertEqual('CourseOutline', get_first_item(persisted_checklist).get('action_url'))
|
||||
self.compare_checklists(persisted_checklist, returned_checklist)
|
||||
|
||||
def test_update_checklists_delete_unsupported(self):
|
||||
""" Delete operation is not supported. """
|
||||
update_url = self.get_url(100)
|
||||
response = self.client.delete(update_url)
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
def test_expand_checklist_action_url(self):
|
||||
"""
|
||||
Tests the method to expand checklist action url.
|
||||
"""
|
||||
|
||||
def test_expansion(checklist, index, stored, expanded):
|
||||
"""
|
||||
Tests that the expected expanded value is returned for the item at the given index.
|
||||
|
||||
Also verifies that the original checklist is not modified.
|
||||
"""
|
||||
self.assertEqual(get_action_url(checklist, index), stored)
|
||||
expanded_checklist = expand_checklist_action_url(self.course, checklist)
|
||||
self.assertEqual(get_action_url(expanded_checklist, index), expanded)
|
||||
# Verify no side effect in the original list.
|
||||
self.assertEqual(get_action_url(checklist, index), stored)
|
||||
|
||||
test_expansion(self.course.checklists[0], 0, 'ManageUsers', '/course_team/mitX/333/Checklists_Course')
|
||||
test_expansion(self.course.checklists[1], 1, 'CourseOutline', '/course/mitX/333/Checklists_Course')
|
||||
test_expansion(self.course.checklists[2], 0, 'http://help.edge.edx.org/', 'http://help.edge.edx.org/')
|
||||
|
||||
|
||||
def get_first_item(checklist):
|
||||
""" Returns the first item from the checklist. """
|
||||
return checklist['items'][0]
|
||||
|
||||
|
||||
def get_action_url(checklist, index):
|
||||
"""
|
||||
Returns the action_url for the item at the specified index in the given checklist.
|
||||
"""
|
||||
return checklist['items'][index]['action_url']
|
||||
@@ -26,7 +26,6 @@ class CourseMetadata(object):
|
||||
'enrollment_end',
|
||||
'tabs',
|
||||
'graceperiod',
|
||||
'checklists',
|
||||
'show_timezone',
|
||||
'format',
|
||||
'graded',
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
modules: getModulesList([
|
||||
'js/factories/asset_index',
|
||||
'js/factories/base',
|
||||
'js/factories/checklists',
|
||||
'js/factories/container',
|
||||
'js/factories/course',
|
||||
'js/factories/course_create_rerun',
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
define(["backbone", "underscore", "js/models/checklist"],
|
||||
function(Backbone, _, ChecklistModel) {
|
||||
var ChecklistCollection = Backbone.Collection.extend({
|
||||
model : ChecklistModel,
|
||||
|
||||
parse: function(response) {
|
||||
_.each(response,
|
||||
function( element, idx ) {
|
||||
element.id = idx;
|
||||
});
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
// Disable caching so the browser back button will work (checklists have links to other
|
||||
// places within Studio).
|
||||
fetch: function (options) {
|
||||
options.cache = false;
|
||||
return Backbone.Collection.prototype.fetch.call(this, options);
|
||||
}
|
||||
});
|
||||
return ChecklistCollection;
|
||||
});
|
||||
@@ -1,16 +0,0 @@
|
||||
define([
|
||||
'jquery', 'js/collections/checklist', 'js/views/checklist'
|
||||
], function($, ChecklistCollection, ChecklistView) {
|
||||
'use strict';
|
||||
return function (handlerUrl) {
|
||||
var checklistCollection = new ChecklistCollection(),
|
||||
editor;
|
||||
|
||||
checklistCollection.url = handlerUrl;
|
||||
editor = new ChecklistView({
|
||||
el: $('.course-checklists'),
|
||||
collection: checklistCollection
|
||||
});
|
||||
checklistCollection.fetch({reset: true});
|
||||
};
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
define(["js/views/baseview", "underscore", "jquery"], function(BaseView, _, $) {
|
||||
var ChecklistView = BaseView.extend({
|
||||
// takes CMS.Models.Checklists as model
|
||||
|
||||
events : {
|
||||
'click .course-checklist .checklist-title' : "toggleChecklist",
|
||||
'click .course-checklist .task input' : "toggleTask",
|
||||
'click a[rel="external"]' : "popup"
|
||||
},
|
||||
|
||||
initialize : function() {
|
||||
var self = this;
|
||||
this.template = this.loadTemplate('checklist');
|
||||
this.collection.fetch({
|
||||
reset: true,
|
||||
complete: function() {
|
||||
self.render();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// catch potential outside call before template loaded
|
||||
if (!this.template) return this;
|
||||
|
||||
this.$el.empty();
|
||||
|
||||
var self = this;
|
||||
_.each(this.collection.models,
|
||||
function(checklist, index) {
|
||||
self.$el.append(self.renderTemplate(checklist, index));
|
||||
});
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
renderTemplate: function (checklist, index) {
|
||||
var checklistItems = checklist.attributes['items'];
|
||||
var itemsChecked = 0;
|
||||
_.each(checklistItems,
|
||||
function(checklist) {
|
||||
if (checklist['is_checked']) {
|
||||
itemsChecked +=1;
|
||||
}
|
||||
});
|
||||
var percentChecked = Math.round((itemsChecked/checklistItems.length)*100);
|
||||
return this.template({
|
||||
checklistIndex : index,
|
||||
checklistShortDescription : checklist.attributes['short_description'],
|
||||
items: checklistItems,
|
||||
itemsChecked: itemsChecked,
|
||||
percentChecked: percentChecked});
|
||||
},
|
||||
|
||||
toggleChecklist : function(e) {
|
||||
e.preventDefault();
|
||||
$(e.target).closest('.course-checklist').toggleClass('is-collapsed');
|
||||
},
|
||||
|
||||
toggleTask : function (e) {
|
||||
var self = this;
|
||||
|
||||
var completed = 'is-completed';
|
||||
var $checkbox = $(e.target);
|
||||
var $task = $checkbox.closest('.task');
|
||||
$task.toggleClass(completed);
|
||||
|
||||
var checklist_index = $checkbox.data('checklist');
|
||||
var task_index = $checkbox.data('task');
|
||||
var model = this.collection.at(checklist_index);
|
||||
model.attributes.items[task_index].is_checked = $task.hasClass(completed);
|
||||
|
||||
model.save({},
|
||||
{
|
||||
success : function() {
|
||||
var updatedTemplate = self.renderTemplate(model, checklist_index);
|
||||
self.$el.find('#course-checklist'+checklist_index).first().replaceWith(updatedTemplate);
|
||||
|
||||
analytics.track('Toggled a Checklist Task', {
|
||||
'course': course_location_analytics,
|
||||
'task': model.attributes.items[task_index].short_description,
|
||||
'state': model.attributes.items[task_index].is_checked
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
popup: function(e) {
|
||||
e.preventDefault();
|
||||
window.open($(e.target).attr('href'));
|
||||
}
|
||||
});
|
||||
return ChecklistView;
|
||||
});
|
||||
@@ -61,7 +61,6 @@
|
||||
@import 'views/unit';
|
||||
@import 'views/container';
|
||||
@import 'views/users';
|
||||
@import 'views/checklists';
|
||||
@import 'views/textbooks';
|
||||
@import 'views/export-git';
|
||||
@import 'views/group-configuration';
|
||||
|
||||
@@ -368,12 +368,10 @@ body.course.view-certificates .nav-course-settings-certificates,
|
||||
// course tools
|
||||
body.course.view-import .nav-course-tools .title,
|
||||
body.course.view-export .nav-course-tools .title,
|
||||
body.course.view-checklists .nav-course-tools .title,
|
||||
body.course.view-export-git .nav-course-tools .title,
|
||||
|
||||
body.course.view-import .nav-course-tools-import,
|
||||
body.course.view-export .nav-course-tools-export,
|
||||
body.course.view-checklists .nav-course-tools-checklists,
|
||||
body.course.view-export-git .nav-course-tools-export-git,
|
||||
|
||||
// content library settings
|
||||
|
||||
@@ -1,341 +0,0 @@
|
||||
// Studio - Course Settings
|
||||
// ====================
|
||||
|
||||
.view-checklists {
|
||||
|
||||
.content-primary, .content-supplementary {
|
||||
@include box-sizing(border-box);
|
||||
}
|
||||
|
||||
.content-primary {
|
||||
@extend .ui-col-wide;
|
||||
}
|
||||
|
||||
// checklists - general
|
||||
.course-checklist {
|
||||
@extend %ui-window;
|
||||
margin: 0 0 ($baseline*2) 0;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// visual status
|
||||
.viz-checklist-status {
|
||||
@extend %cont-text-hide;
|
||||
@include size(100%,($baseline/4));
|
||||
position: relative;
|
||||
display: block;
|
||||
margin: 0;
|
||||
background: $gray-l4;
|
||||
|
||||
.viz-checklist-status-value {
|
||||
@include transition(width $tmg-s2 ease-in-out .25s);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0%;
|
||||
height: ($baseline/4);
|
||||
background: $green;
|
||||
|
||||
.int {
|
||||
@extend %cont-text-sr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// header/title
|
||||
header {
|
||||
@include clearfix();
|
||||
box-shadow: inset 0 -1px 1px $shadow-l1;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid $gray-l3;
|
||||
padding: $baseline ($baseline*1.5);
|
||||
|
||||
.checklist-title {
|
||||
@include transition(color $tmg-f2 ease-in-out 0s);
|
||||
width: flex-grid(6, 9);
|
||||
margin: 0;
|
||||
@include margin-right(flex-gutter());
|
||||
@include float(left);
|
||||
|
||||
.ui-toggle-expansion {
|
||||
@include transition(all $tmg-f2 ease-in-out 0s);
|
||||
@extend %t-action1;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
@include margin-right($baseline/2);
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
&.is-selectable {
|
||||
@extend %ui-fake-link;
|
||||
|
||||
&:hover {
|
||||
color: $blue;
|
||||
|
||||
.ui-toggle-expansion {
|
||||
color: $blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.checklist-status {
|
||||
@extend %t-copy-sub1;
|
||||
width: flex-grid(3, 9);
|
||||
@include float(right);
|
||||
margin-top: ($baseline/2);
|
||||
@include text-align(right);
|
||||
color: $gray-l2;
|
||||
|
||||
|
||||
.fa-check-square-o {
|
||||
@extend %t-icon4;
|
||||
display: inline-block;
|
||||
margin-left: ($baseline/2);
|
||||
color: $gray-l4;
|
||||
}
|
||||
|
||||
.status-count {
|
||||
@extend %t-copy-base;
|
||||
@extend %t-strong;
|
||||
margin-left: ($baseline/4);
|
||||
margin-right: ($baseline/4);
|
||||
color: $gray-d3;
|
||||
}
|
||||
|
||||
.status-amount {
|
||||
@extend %t-copy-base;
|
||||
@extend %t-strong;
|
||||
margin-left: ($baseline/4);
|
||||
color: $gray-d3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checklist actions
|
||||
.course-checklist-actions {
|
||||
@include clearfix();
|
||||
@include transition(border $tmg-f2 ease-in-out .25s);
|
||||
box-shadow: inset 0 1px 1px $shadow-l1;
|
||||
border-top: 1px solid $gray-l2;
|
||||
padding: $baseline ($baseline*1.5);
|
||||
background: $gray-l4;
|
||||
|
||||
.action-primary {
|
||||
@include green-button();
|
||||
@include float(left);
|
||||
|
||||
.fa-plus {
|
||||
@extend %t-icon7;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
}
|
||||
|
||||
.action-secondary {
|
||||
@include grey-button();
|
||||
@extend %t-action3;
|
||||
@extend %t-regular;
|
||||
@include float(right);
|
||||
|
||||
.fa-trash-o {
|
||||
@extend %t-icon7;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: ($baseline/4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// state - collapsed
|
||||
&.is-collapsed {
|
||||
|
||||
header {
|
||||
box-shadow: none;
|
||||
|
||||
.checklist-title {
|
||||
|
||||
.ui-toggle-expansion {
|
||||
@include transform(rotate(-90deg));
|
||||
@include transform-origin(50% 50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-tasks {
|
||||
height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// state - completed
|
||||
&.is-completed {
|
||||
|
||||
.viz-checklist-status {
|
||||
|
||||
.viz-checklist-status-value {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
|
||||
.checklist-title, .fa-caret-down {
|
||||
color: $green;
|
||||
}
|
||||
|
||||
.checklist-status {
|
||||
|
||||
.status-count, .status-amount, .fa-check-square-o {
|
||||
color: $green;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// state - not available
|
||||
.is-not-available {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// list of tasks
|
||||
.list-tasks {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
|
||||
.task {
|
||||
@include transition(background $tmg-f2 ease-in-out 0s, border $tmg-f3 ease-in-out 0s);
|
||||
@include clearfix();
|
||||
position: relative;
|
||||
border-top: 1px solid $white;
|
||||
border-bottom: 1px solid $gray-l5;
|
||||
padding: $baseline ($baseline*1.5);
|
||||
background: $white;
|
||||
opacity: 1.0;
|
||||
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.task-input {
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
@include float(left);
|
||||
margin-top: ($baseline/2);
|
||||
@include margin-right(flex-gutter());
|
||||
}
|
||||
|
||||
.task-details {
|
||||
@extend %t-strong;
|
||||
display: inline-block;
|
||||
vertical-align: text-top;
|
||||
@include float(left);
|
||||
width: flex-grid(6,9);
|
||||
|
||||
.task-name {
|
||||
@include transition(color $tmg-f2 ease-in-out 0s);
|
||||
@extend %ui-fake-link;
|
||||
vertical-align: baseline;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.task-description {
|
||||
@extend %t-copy-sub1;
|
||||
@include transition(color $tmg-f2 ease-in-out 0s);
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.task-support {
|
||||
@extend %t-copy-sub2;
|
||||
@include transition(opacity $tmg-f2 ease-in-out 0s);
|
||||
opacity: 0.0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
@include transition(opacity $tmg-f2 ease-in-out $tmg-f2);
|
||||
@include clearfix();
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
@include float(right);
|
||||
width: flex-grid(2,9);
|
||||
margin: ($baseline/2) 0 0 flex-gutter();
|
||||
opacity: 0.0;
|
||||
pointer-events: none;
|
||||
text-align: right;
|
||||
|
||||
.action-primary {
|
||||
@extend %btn-primary-blue;
|
||||
@extend %t-action3;
|
||||
}
|
||||
|
||||
.action-secondary {
|
||||
@extend %t-action4;
|
||||
margin-top: ($baseline/2);
|
||||
}
|
||||
}
|
||||
|
||||
// state - hover
|
||||
&:hover {
|
||||
background: $blue-l5;
|
||||
border-bottom-color: $blue-l4;
|
||||
border-top-color: $blue-l4;
|
||||
opacity: 1.0;
|
||||
|
||||
.task-details {
|
||||
|
||||
.task-support {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
opacity: 1.0;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// state - completed
|
||||
&.is-completed {
|
||||
background: $gray-l6;
|
||||
border-top-color: $gray-l5;
|
||||
border-bottom-color: $gray-l5;
|
||||
|
||||
.task-name {
|
||||
color: $gray-l2;
|
||||
}
|
||||
|
||||
.task-actions {
|
||||
|
||||
.action-primary {
|
||||
@extend %btn-secondary-blue;
|
||||
@extend %t-action3;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $gray-l5;
|
||||
border-bottom-color: $gray-l4;
|
||||
border-top-color: $gray-l4;
|
||||
|
||||
.task-details {
|
||||
opacity:1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-supplementary {
|
||||
@extend .ui-col-narrow;
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
<%inherit file="base.html" />
|
||||
<%def name="online_help_token()"><% return "checklist" %></%def>
|
||||
<%!
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
<%block name="title">${_("Course Checklists")}</%block>
|
||||
<%block name="bodyclass">is-signedin course view-checklists</%block>
|
||||
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in ["checklist"]:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="js/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<%block name="requirejs">
|
||||
require(["js/factories/checklists"], function (ChecklistsFactory) {
|
||||
ChecklistsFactory("${handler_url}");
|
||||
});
|
||||
</%block>
|
||||
|
||||
|
||||
<%block name="content">
|
||||
<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")}
|
||||
</h1>
|
||||
</header>
|
||||
</div>
|
||||
|
||||
<div class="wrapper-content wrapper">
|
||||
<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>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<aside class="content-supplementary" role="complementary">
|
||||
<div class="bit">
|
||||
<h3 class="title title-3">${_("What are course checklists?")}</h3>
|
||||
<p>
|
||||
${_("Course checklists are tools to help you understand and keep track of all the steps necessary to get your course ready for students.")}
|
||||
</p>
|
||||
<p>
|
||||
${_("Any changes you make to these checklists are saved automatically and are immediately visible to other course team members.")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="bit">
|
||||
<h3 class="title title-3">${_("{studio_name} checklists").format(studio_name=settings.STUDIO_SHORT_NAME)}</h3>
|
||||
<nav class="nav-page checklists-current">
|
||||
<ol>
|
||||
% for checklist in checklists:
|
||||
<li class="nav-item">
|
||||
<a rel="view" href="${'#course-checklist' + str(loop.index)}">${checklist['short_description']}</a>
|
||||
</li>
|
||||
% endfor
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
</div>
|
||||
</%block>
|
||||
@@ -1,71 +0,0 @@
|
||||
<% var allChecked = itemsChecked == items.length; %>
|
||||
<section
|
||||
<% if (allChecked) { %>
|
||||
class="course-checklist is-completed"
|
||||
<% } else { %>
|
||||
class="course-checklist"
|
||||
<% } %>
|
||||
id="<%= 'course-checklist' + checklistIndex %>">
|
||||
<span class="viz viz-checklist-status"><span class="viz value viz-checklist-status-value" style="width: <%= percentChecked %>%;">
|
||||
<%= _.template(
|
||||
// Translators: The {pct_sign} here represents the percent sign, i.e., '%'
|
||||
// in many languages. This is used to avoid Transifex's misinterpreting of
|
||||
// '% o'. The percent sign is also translatable as a standalone string.
|
||||
gettext("{number}{pct_sign} of checklists completed"),
|
||||
// Translators: This is the percent sign. It will be used to represent a
|
||||
// percent value out of 100, e.g. "58%" means "58/100".
|
||||
{number: '<span class="int">' + percentChecked + '</span>', pct_sign: gettext('%')},
|
||||
{interpolate: /\{(.+?)\}/g}
|
||||
)
|
||||
%>
|
||||
</span></span>
|
||||
<header>
|
||||
<h3 class="checklist-title title-2 is-selectable" title="Collapse/Expand this Checklist">
|
||||
<i class="icon fa fa-caret-down ui-toggle-expansion"></i>
|
||||
<%= checklistShortDescription %></h3>
|
||||
<span class="checklist-status status">
|
||||
<%= gettext("Tasks Completed:") %> <span class="status-count"><%= itemsChecked %></span>/<span class="status-amount"><%= items.length %></span>
|
||||
<i class="icon fa fa-check-square-o"></i>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<ul class="list list-tasks">
|
||||
<% var taskIndex = 0; %>
|
||||
<% _.each(items, function(item) { %>
|
||||
<% var checked = item['is_checked']; %>
|
||||
<li
|
||||
<% if (checked) { %>
|
||||
class="task is-completed"
|
||||
<% } else { %>
|
||||
class="task"
|
||||
<% } %>
|
||||
>
|
||||
<% var taskId = 'course-checklist' + checklistIndex + '-task' + taskIndex; %>
|
||||
<input type="checkbox" class="task-input" data-checklist="<%= checklistIndex %>" data-task="<%= taskIndex %>"
|
||||
name="<%= taskId %>" id="<%= taskId %>"
|
||||
<% if (checked) { %>
|
||||
checked="checked"
|
||||
<% } %>
|
||||
>
|
||||
<label class="task-details" for="<%= taskId %>">
|
||||
<h4 class="task-name title title-3"><%= item['short_description'] %></h4>
|
||||
<p class="task-description"><%= item['long_description'] %></p>
|
||||
</label>
|
||||
|
||||
<% if (item['action_text'] !== '' && item['action_url'] !== '') { %>
|
||||
<ul class="list-actions task-actions">
|
||||
<li class="action-item">
|
||||
<a href="<%= item['action_url'] %>" class="action action-primary"
|
||||
<% if (item['action_external']) { %>
|
||||
rel="external" title="<%= gettext("This link will open in a new browser window/tab") %>"
|
||||
<% } %>
|
||||
><%= item['action_text'] %></a>
|
||||
</li>
|
||||
</ul>
|
||||
<% } %>
|
||||
</li>
|
||||
|
||||
<% taskIndex+=1; }) %>
|
||||
|
||||
</ul>
|
||||
</section>
|
||||
@@ -19,7 +19,6 @@
|
||||
<%
|
||||
course_key = context_course.id
|
||||
index_url = reverse('contentstore.views.course_handler', kwargs={'course_key_string': unicode(course_key)})
|
||||
checklists_url = reverse('contentstore.views.checklists_handler', kwargs={'course_key_string': unicode(course_key)})
|
||||
course_team_url = reverse('contentstore.views.course_team_handler', kwargs={'course_key_string': unicode(course_key)})
|
||||
assets_url = reverse('contentstore.views.assets_handler', kwargs={'course_key_string': unicode(course_key)})
|
||||
textbooks_url = reverse('contentstore.views.textbooks_list_handler', kwargs={'course_key_string': unicode(course_key)})
|
||||
@@ -113,9 +112,6 @@
|
||||
<div class="wrapper wrapper-nav-sub">
|
||||
<div class="nav-sub">
|
||||
<ul>
|
||||
<li class="nav-item nav-course-tools-checklists">
|
||||
<a href="${checklists_url}">${_("Checklists")}</a>
|
||||
</li>
|
||||
<li class="nav-item nav-course-tools-import">
|
||||
<a href="${import_url}">${_("Import")}</a>
|
||||
</li>
|
||||
|
||||
@@ -93,7 +93,6 @@ urlpatterns += patterns(
|
||||
'course_notifications_handler'),
|
||||
url(r'^course_rerun/{}$'.format(settings.COURSE_KEY_PATTERN), 'course_rerun_handler', name='course_rerun_handler'),
|
||||
url(r'^container/{}$'.format(settings.USAGE_KEY_PATTERN), 'container_handler'),
|
||||
url(r'^checklists/{}/(?P<checklist_index>\d+)?$'.format(settings.COURSE_KEY_PATTERN), 'checklists_handler'),
|
||||
url(r'^orphan/{}$'.format(settings.COURSE_KEY_PATTERN), 'orphan_handler'),
|
||||
url(r'^assets/{}/{}?$'.format(settings.COURSE_KEY_PATTERN, settings.ASSET_KEY_PATTERN), 'assets_handler'),
|
||||
url(r'^import/{}$'.format(COURSELIKE_KEY_PATTERN), 'import_handler'),
|
||||
|
||||
@@ -414,210 +414,6 @@ class CourseFields(object):
|
||||
scope=Scope.settings
|
||||
)
|
||||
has_children = True
|
||||
checklists = List(
|
||||
help=_("Checklist to Follow When Developing a Course"),
|
||||
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(
|
||||
display_name=_("Course Info Sidebar Name"),
|
||||
help=_(
|
||||
|
||||
@@ -141,7 +141,7 @@ class XmlParserMixin(object):
|
||||
# Used for storing xml attributes between import and export, for roundtrips
|
||||
'xml_attributes')
|
||||
|
||||
metadata_to_export_to_policy = ('discussion_topics', 'checklists')
|
||||
metadata_to_export_to_policy = ('discussion_topics',)
|
||||
|
||||
@staticmethod
|
||||
def _get_metadata_from_xml(xml_object, remove=True):
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
"""
|
||||
Course checklists page.
|
||||
"""
|
||||
|
||||
from .course_page import CoursePage
|
||||
|
||||
|
||||
class ChecklistsPage(CoursePage):
|
||||
"""
|
||||
Course Checklists page.
|
||||
"""
|
||||
|
||||
url_path = "checklists"
|
||||
|
||||
def is_browser_on_page(self):
|
||||
return self.q(css='body.view-checklists').present
|
||||
@@ -7,7 +7,6 @@ from bok_choy.web_app_test import WebAppTest
|
||||
|
||||
from ...pages.studio.asset_index import AssetIndexPage
|
||||
from ...pages.studio.auto_auth import AutoAuthPage
|
||||
from ...pages.studio.checklists import ChecklistsPage
|
||||
from ...pages.studio.course_info import CourseUpdatesPage
|
||||
from ...pages.studio.edit_tabs import PagesPage
|
||||
from ...pages.studio.import_export import ExportCoursePage, ImportCoursePage
|
||||
@@ -81,7 +80,7 @@ class CoursePagesTest(StudioCourseTest):
|
||||
self.pages = [
|
||||
clz(self.browser, self.course_info['org'], self.course_info['number'], self.course_info['run'])
|
||||
for clz in [
|
||||
AssetIndexPage, ChecklistsPage, CourseUpdatesPage,
|
||||
AssetIndexPage, CourseUpdatesPage,
|
||||
PagesPage, ExportCoursePage, ImportCoursePage, CourseTeamPage, CourseOutlinePage, SettingsPage,
|
||||
AdvancedSettingsPage, GradingPage, TextbooksPage
|
||||
]
|
||||
|
||||
@@ -38,164 +38,5 @@
|
||||
},
|
||||
"video/Welcome": {
|
||||
"display_name": "Welcome"
|
||||
},
|
||||
"checklists": [
|
||||
{
|
||||
"short_description": "Getting Started With Studio",
|
||||
"items": [
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Add Course Team Members",
|
||||
"action_url": "ManageUsers",
|
||||
"action_text": "Edit Course Team",
|
||||
"is_checked": true,
|
||||
"long_description": "Grant your collaborators permission to edit your course so you can work together."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Set Important Dates for Your Course",
|
||||
"action_url": "SettingsDetails",
|
||||
"action_text": "Edit Course Details & Schedule",
|
||||
"is_checked": false,
|
||||
"long_description": "Establish your course's student enrollment and launch dates on the Schedule and Details page."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Draft Your Course's Grading Policy",
|
||||
"action_url": "SettingsGrading",
|
||||
"action_text": "Edit Grading Settings",
|
||||
"is_checked": false,
|
||||
"long_description": "Set up your assignment types and grading policy even if you haven't created all your assignments."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Explore the Other Studio Checklists",
|
||||
"action_url": "",
|
||||
"action_text": "",
|
||||
"is_checked": false,
|
||||
"long_description": "Discover other available course authoring tools, and find help when you need it."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"short_description": "Draft a Rough Course Outline",
|
||||
"items": [
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Create Your First Section and Subsection",
|
||||
"action_url": "CourseOutline",
|
||||
"action_text": "Edit Course Outline",
|
||||
"is_checked": false,
|
||||
"long_description": "Use your course outline to build your first Section and Subsection."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Set Section Release Dates",
|
||||
"action_url": "CourseOutline",
|
||||
"action_text": "Edit Course Outline",
|
||||
"is_checked": false,
|
||||
"long_description": "Specify the release dates for each Section in your course. Sections become visible to students on their release dates."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Designate a Subsection as Graded",
|
||||
"action_url": "CourseOutline",
|
||||
"action_text": "Edit Course Outline",
|
||||
"is_checked": false,
|
||||
"long_description": "Set a Subsection to be graded as a specific assignment type. Assignments within graded Subsections count toward a student's final grade."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Reordering Course Content",
|
||||
"action_url": "CourseOutline",
|
||||
"action_text": "Edit Course Outline",
|
||||
"is_checked": false,
|
||||
"long_description": "Use drag and drop to reorder the content in your course."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Renaming Sections",
|
||||
"action_url": "CourseOutline",
|
||||
"action_text": "Edit Course Outline",
|
||||
"is_checked": true,
|
||||
"long_description": "Rename Sections by clicking the Section name from the Course Outline."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Deleting Course Content",
|
||||
"action_url": "CourseOutline",
|
||||
"action_text": "Edit Course Outline",
|
||||
"is_checked": false,
|
||||
"long_description": "Delete Sections, Subsections, or Units you don't need anymore. Be careful, as there is no Undo function."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Add an Instructor-Only Section to Your Outline",
|
||||
"action_url": "CourseOutline",
|
||||
"action_text": "Edit Course Outline",
|
||||
"is_checked": false,
|
||||
"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."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"short_description": "Explore edX's Support Tools",
|
||||
"items": [
|
||||
{
|
||||
"action_external": true,
|
||||
"short_description": "Explore the Studio Help Forum",
|
||||
"action_url": "http://help.edge.edx.org/",
|
||||
"action_text": "Visit Studio Help",
|
||||
"is_checked": false,
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"action_external": true,
|
||||
"short_description": "Enroll in edX 101",
|
||||
"action_url": "https://edge.edx.org/courses/edX/edX101/How_to_Create_an_edX_Course/about",
|
||||
"action_text": "Register for edX 101",
|
||||
"is_checked": false,
|
||||
"long_description": "Register for edX 101, edX's primer for course creation."
|
||||
},
|
||||
{
|
||||
"action_external": true,
|
||||
"short_description": "Download the Studio Documentation",
|
||||
"action_url": "http://files.edx.org/Getting_Started_with_Studio.pdf",
|
||||
"action_text": "Download Documentation",
|
||||
"is_checked": false,
|
||||
"long_description": "Download the searchable Studio reference documentation in PDF form."
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"short_description": "Draft Your Course About Page",
|
||||
"items": [
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Draft a Course Description",
|
||||
"action_url": "SettingsDetails",
|
||||
"action_text": "Edit Course Schedule & Details",
|
||||
"is_checked": false,
|
||||
"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."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Add Staff Bios",
|
||||
"action_url": "SettingsDetails",
|
||||
"action_text": "Edit Course Schedule & Details",
|
||||
"is_checked": false,
|
||||
"long_description": "Showing prospective students who their instructor will be is helpful. Include staff bios on the course About page."
|
||||
},
|
||||
{
|
||||
"action_external": false,
|
||||
"short_description": "Add Course FAQs",
|
||||
"action_url": "SettingsDetails",
|
||||
"action_text": "Edit Course Schedule & Details",
|
||||
"is_checked": false,
|
||||
"long_description": "Include a short list of frequently asked questions about your course."
|
||||
},
|
||||
{
|
||||
"action_external": false, "short_description": "Add Course Prerequisites", "action_url": "SettingsDetails", "action_text": "Edit Course Schedule & Details", "is_checked": false, "long_description": "Let students know what knowledge and/or skills they should have before they enroll in your course."}]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,8 @@ from xblock.fields import Scope
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
|
||||
FILTER_LIST = ['xml_attributes', 'checklists']
|
||||
INHERITED_FILTER_LIST = ['children', 'xml_attributes', 'checklists']
|
||||
FILTER_LIST = ['xml_attributes']
|
||||
INHERITED_FILTER_LIST = ['children', 'xml_attributes']
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
Reference in New Issue
Block a user