Make course_experience a feature
This commit is contained in:
committed by
Diana Huang
parent
9ca051c527
commit
a3f32fea5a
@@ -44,7 +44,7 @@ class CoursewareTab(EnrolledTab):
|
||||
"""
|
||||
request = RequestCache.get_current_request()
|
||||
if waffle.flag_is_active(request, 'unified_course_view'):
|
||||
return link_reverse_func('unified_course_view')
|
||||
return link_reverse_func('edx.course_experience.course_home')
|
||||
else:
|
||||
return link_reverse_func('courseware')
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ from lms.djangoapps.grades.new.course_grade import CourseGradeFactory
|
||||
from lms.djangoapps.instructor.enrollment import uses_shib
|
||||
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification
|
||||
from lms.djangoapps.ccx.custom_exception import CCXLocatorValidationException
|
||||
from lms.djangoapps.course_api.blocks.api import get_blocks
|
||||
|
||||
import shoppingcart
|
||||
import survey.utils
|
||||
@@ -1625,84 +1624,3 @@ def financial_assistance_form(request):
|
||||
}
|
||||
],
|
||||
})
|
||||
|
||||
|
||||
class UnifiedCourseView(View):
|
||||
"""
|
||||
Unified view for a course.
|
||||
"""
|
||||
@method_decorator(login_required)
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
|
||||
@method_decorator(ensure_valid_course_key)
|
||||
def get(self, request, course_id):
|
||||
"""
|
||||
Displays the main view for the specified course.
|
||||
|
||||
Arguments:
|
||||
request: HTTP request
|
||||
course_id (unicode): course id
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
|
||||
# Render the outline as a fragment
|
||||
outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id)
|
||||
|
||||
# Render the entire unified course view
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
'outline_fragment': outline_fragment,
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True,
|
||||
}
|
||||
return render_to_response('courseware/unified-course-view.html', context)
|
||||
|
||||
|
||||
class CourseOutlineFragmentView(FragmentView):
|
||||
"""
|
||||
Course outline fragment to be shown in the unified course view.
|
||||
"""
|
||||
|
||||
def populate_children(self, block, all_blocks):
|
||||
"""
|
||||
For a passed block, replace each id in its children array with the full representation of that child,
|
||||
which will be looked up by id in the passed all_blocks dict.
|
||||
Recursively do the same replacement for children of those children.
|
||||
"""
|
||||
children = block.get('children') or []
|
||||
|
||||
for i in range(len(children)):
|
||||
child_id = block['children'][i]
|
||||
child_detail = self.populate_children(all_blocks[child_id], all_blocks)
|
||||
block['children'][i] = child_detail
|
||||
|
||||
return block
|
||||
|
||||
def render_to_fragment(self, request, course_id=None, **kwargs):
|
||||
"""
|
||||
Renders the course outline as a fragment.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
course_usage_key = modulestore().make_course_usage_key(course_key)
|
||||
all_blocks = get_blocks(
|
||||
request,
|
||||
course_usage_key,
|
||||
user=request.user,
|
||||
nav_depth=3,
|
||||
requested_fields=['children', 'display_name', 'type'],
|
||||
block_types_filter=['course', 'chapter', 'vertical', 'sequential']
|
||||
)
|
||||
|
||||
course_block_tree = all_blocks['blocks'][all_blocks['root']] # Get the root of the block tree
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
# Recurse through the block tree, fleshing out each child object
|
||||
'blocks': self.populate_children(course_block_tree, all_blocks['blocks'])
|
||||
}
|
||||
html = render_to_string('courseware/course_outline.html', context)
|
||||
return Fragment(html)
|
||||
|
||||
@@ -1736,7 +1736,6 @@ REQUIRE_JS_PATH_OVERRIDES = {
|
||||
'js/student_account/logistration_factory': 'js/student_account/logistration_factory.js',
|
||||
'js/student_profile/views/learner_profile_factory': 'js/student_profile/views/learner_profile_factory.js',
|
||||
'js/courseware/courseware_factory': 'js/courseware/courseware_factory.js',
|
||||
'js/courseware/course_outline_factory': 'js/courseware/course_outline_factory.js',
|
||||
'js/groups/views/cohorts_dashboard_factory': 'js/groups/views/cohorts_dashboard_factory.js',
|
||||
'draggabilly': 'js/vendor/draggabilly.js'
|
||||
}
|
||||
@@ -2174,6 +2173,9 @@ INSTALLED_APPS = (
|
||||
|
||||
# Unusual migrations
|
||||
'database_fixups',
|
||||
|
||||
# Features
|
||||
'openedx.features.course_experience',
|
||||
)
|
||||
|
||||
######################### CSRF #########################################
|
||||
|
||||
1
lms/static/course_experience
Symbolic link
1
lms/static/course_experience
Symbolic link
@@ -0,0 +1 @@
|
||||
../../openedx/features/course_experience/static/course_experience
|
||||
@@ -27,6 +27,7 @@ var options = {
|
||||
// Otherwise Istanbul which is used for coverage tracking will cause tests to not run.
|
||||
sourceFiles: [
|
||||
{pattern: 'coffee/src/**/!(*spec).js'},
|
||||
{pattern: 'course_experience/js/**/!(*spec).js'},
|
||||
{pattern: 'discussion/js/**/!(*spec).js'},
|
||||
{pattern: 'js/**/!(*spec|djangojs).js'},
|
||||
{pattern: 'lms/js/**/!(*spec).js'},
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
* done.
|
||||
*/
|
||||
modules: getModulesList([
|
||||
'course_experience/js/course_outline_factory',
|
||||
'discussion/js/discussion_board_factory',
|
||||
'discussion/js/discussion_profile_page_factory',
|
||||
'js/api_admin/catalog_preview_factory',
|
||||
|
||||
@@ -679,6 +679,7 @@
|
||||
});
|
||||
|
||||
testFiles = [
|
||||
'course_experience/js/spec/course_outline_factory_spec.js',
|
||||
'discussion/js/spec/discussion_board_factory_spec.js',
|
||||
'discussion/js/spec/discussion_profile_page_factory_spec.js',
|
||||
'discussion/js/spec/discussion_board_view_spec.js',
|
||||
@@ -694,7 +695,6 @@
|
||||
'js/spec/courseware/course_home_events_spec.js',
|
||||
'js/spec/courseware/link_clicked_events_spec.js',
|
||||
'js/spec/courseware/updates_visibility_spec.js',
|
||||
'js/spec/courseware/course_outline_factory_spec.js',
|
||||
'js/spec/dashboard/donation.js',
|
||||
'js/spec/dashboard/dropdown_spec.js',
|
||||
'js/spec/dashboard/track_events_spec.js',
|
||||
|
||||
24
lms/urls.py
24
lms/urls.py
@@ -11,7 +11,6 @@ from django.conf.urls.static import static
|
||||
from courseware.views.views import CourseTabView, EnrollStaffView, StaticCourseTabView
|
||||
from config_models.views import ConfigurationModelCurrentAPIView
|
||||
from courseware.views.index import CoursewareIndex
|
||||
from courseware.views.views import UnifiedCourseView, CourseOutlineFragmentView
|
||||
from openedx.core.djangoapps.auth_exchange.views import LoginWithAccessTokenView
|
||||
from openedx.core.djangoapps.catalog.models import CatalogIntegration
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
@@ -380,20 +379,6 @@ urlpatterns += (
|
||||
name='html_book',
|
||||
),
|
||||
|
||||
url(
|
||||
r'^courses/{}/course/?$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
UnifiedCourseView.as_view(),
|
||||
name='unified_course_view',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/course/outline?$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
CourseOutlineFragmentView.as_view(),
|
||||
name='course_outline_fragment_view',
|
||||
),
|
||||
url(
|
||||
r'^courses/{}/courseware/?$'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
@@ -616,10 +601,19 @@ urlpatterns += (
|
||||
name='edxnotes_endpoints',
|
||||
),
|
||||
|
||||
# Branding API
|
||||
url(
|
||||
r'^api/branding/v1/',
|
||||
include('branding.api_urls')
|
||||
),
|
||||
|
||||
# Course experience
|
||||
url(
|
||||
r'^courses/{}/course/'.format(
|
||||
settings.COURSE_ID_PATTERN,
|
||||
),
|
||||
include('openedx.features.course_experience.urls'),
|
||||
),
|
||||
)
|
||||
|
||||
if settings.FEATURES["ENABLE_TEAMS"]:
|
||||
|
||||
6
openedx/features/README.rst
Normal file
6
openedx/features/README.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
Open EdX Features
|
||||
-----------------
|
||||
|
||||
This is the root package for Open edX features that extend the edX platform.
|
||||
The intention is that these features would ideally live in an external
|
||||
repository, but for now they live in edx-platform but are cleanly modularized.
|
||||
0
openedx/features/__init__.py
Normal file
0
openedx/features/__init__.py
Normal file
0
openedx/features/course_experience/__init__.py
Normal file
0
openedx/features/course_experience/__init__.py
Normal file
@@ -1,7 +1,7 @@
|
||||
define([
|
||||
'jquery',
|
||||
'edx-ui-toolkit/js/utils/constants',
|
||||
'js/courseware/course_outline_factory'
|
||||
'course_experience/js/course_outline_factory'
|
||||
],
|
||||
function($, constants, CourseOutlineFactory) {
|
||||
'use strict';
|
||||
@@ -19,7 +19,7 @@ define([
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
loadFixtures('js/fixtures/courseware/course_outline.html');
|
||||
loadFixtures('course_experience/fixtures/course-outline-fragment.html');
|
||||
CourseOutlineFactory('.block-tree');
|
||||
});
|
||||
|
||||
@@ -55,7 +55,7 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
def test_render(self):
|
||||
for course in self.courses:
|
||||
url = reverse(
|
||||
'unified_course_view',
|
||||
'edx.course_experience.course_home',
|
||||
kwargs={
|
||||
'course_id': unicode(course.id),
|
||||
}
|
||||
21
openedx/features/course_experience/urls.py
Normal file
21
openedx/features/course_experience/urls.py
Normal file
@@ -0,0 +1,21 @@
|
||||
"""
|
||||
Defines URLs for the course experience.
|
||||
"""
|
||||
|
||||
from django.conf.urls import url
|
||||
|
||||
from views.course_home import CourseHomeView
|
||||
from views.course_outline import CourseOutlineFragmentView
|
||||
|
||||
urlpatterns = [
|
||||
url(
|
||||
r'^$',
|
||||
CourseHomeView.as_view(),
|
||||
name='edx.course_experience.course_home',
|
||||
),
|
||||
url(
|
||||
r'^outline_fragment$',
|
||||
CourseOutlineFragmentView.as_view(),
|
||||
name='edx.course_experience.course_outline_fragment_view',
|
||||
),
|
||||
]
|
||||
50
openedx/features/course_experience/views/course_home.py
Normal file
50
openedx/features/course_experience/views/course_home.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""
|
||||
Views for the course home page.
|
||||
"""
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.context_processors import csrf
|
||||
from django.shortcuts import render_to_response
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.cache import cache_control
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.views.generic import View
|
||||
|
||||
from courseware.courses import get_course_with_access
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from util.views import ensure_valid_course_key
|
||||
|
||||
from course_outline import CourseOutlineFragmentView
|
||||
|
||||
|
||||
class CourseHomeView(View):
|
||||
"""
|
||||
The home page for a course.
|
||||
"""
|
||||
@method_decorator(login_required)
|
||||
@method_decorator(ensure_csrf_cookie)
|
||||
@method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
|
||||
@method_decorator(ensure_valid_course_key)
|
||||
def get(self, request, course_id):
|
||||
"""
|
||||
Displays the home page for the specified course.
|
||||
|
||||
Arguments:
|
||||
request: HTTP request
|
||||
course_id (unicode): course id
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
|
||||
# Render the outline as a fragment
|
||||
outline_fragment = CourseOutlineFragmentView().render_to_fragment(request, course_id=course_id)
|
||||
|
||||
# Render the entire unified course view
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
'outline_fragment': outline_fragment,
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True,
|
||||
}
|
||||
return render_to_response('course_experience/course-home.html', context)
|
||||
61
openedx/features/course_experience/views/course_outline.py
Normal file
61
openedx/features/course_experience/views/course_outline.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""
|
||||
Views to show a course outline.
|
||||
"""
|
||||
|
||||
from django.core.context_processors import csrf
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
from courseware.courses import get_course_with_access
|
||||
from lms.djangoapps.course_api.blocks.api import get_blocks
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from web_fragments.fragment import Fragment
|
||||
from web_fragments.views import FragmentView
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
class CourseOutlineFragmentView(FragmentView):
|
||||
"""
|
||||
Course outline fragment to be shown in the unified course view.
|
||||
"""
|
||||
|
||||
def populate_children(self, block, all_blocks):
|
||||
"""
|
||||
For a passed block, replace each id in its children array with the full representation of that child,
|
||||
which will be looked up by id in the passed all_blocks dict.
|
||||
Recursively do the same replacement for children of those children.
|
||||
"""
|
||||
children = block.get('children') or []
|
||||
|
||||
for i in range(len(children)):
|
||||
child_id = block['children'][i]
|
||||
child_detail = self.populate_children(all_blocks[child_id], all_blocks)
|
||||
block['children'][i] = child_detail
|
||||
|
||||
return block
|
||||
|
||||
def render_to_fragment(self, request, course_id=None, **kwargs):
|
||||
"""
|
||||
Renders the course outline as a fragment.
|
||||
"""
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True)
|
||||
course_usage_key = modulestore().make_course_usage_key(course_key)
|
||||
all_blocks = get_blocks(
|
||||
request,
|
||||
course_usage_key,
|
||||
user=request.user,
|
||||
nav_depth=3,
|
||||
requested_fields=['children', 'display_name', 'type'],
|
||||
block_types_filter=['course', 'chapter', 'vertical', 'sequential']
|
||||
)
|
||||
|
||||
course_block_tree = all_blocks['blocks'][all_blocks['root']] # Get the root of the block tree
|
||||
|
||||
context = {
|
||||
'csrf': csrf(request)['csrf_token'],
|
||||
'course': course,
|
||||
# Recurse through the block tree, fleshing out each child object
|
||||
'blocks': self.populate_children(course_block_tree, all_blocks['blocks'])
|
||||
}
|
||||
html = render_to_string('course_experience/course-outline-fragment.html', context)
|
||||
return Fragment(html)
|
||||
@@ -5,13 +5,20 @@ from paver.easy import sh, task, cmdopts, needs, BuildFailure
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from string import join
|
||||
|
||||
from openedx.core.djangolib.markup import HTML
|
||||
|
||||
from .utils.envs import Env
|
||||
from .utils.timer import timed
|
||||
|
||||
ALL_SYSTEMS = 'lms,cms,common,openedx,pavelib'
|
||||
ALL_SYSTEMS = [
|
||||
'cms',
|
||||
'common',
|
||||
'lms',
|
||||
'openedx',
|
||||
'pavelib',
|
||||
]
|
||||
|
||||
|
||||
def top_python_dirs(dirname):
|
||||
@@ -45,7 +52,7 @@ def find_fixme(options):
|
||||
Run pylint on system code, only looking for fixme items.
|
||||
"""
|
||||
num_fixme = 0
|
||||
systems = getattr(options, 'system', ALL_SYSTEMS).split(',')
|
||||
systems = getattr(options, 'system', '').split(',') or ALL_SYSTEMS
|
||||
|
||||
for system in systems:
|
||||
# Directory to put the pylint report in.
|
||||
@@ -93,7 +100,7 @@ def run_pylint(options):
|
||||
num_violations = 0
|
||||
violations_limit = int(getattr(options, 'limit', -1))
|
||||
errors = getattr(options, 'errors', False)
|
||||
systems = getattr(options, 'system', ALL_SYSTEMS).split(',')
|
||||
systems = getattr(options, 'system', '').split(',') or ALL_SYSTEMS
|
||||
|
||||
# Make sure the metrics subdirectory exists
|
||||
Env.METRICS_DIR.makedirs_p()
|
||||
@@ -234,7 +241,7 @@ def run_complexity():
|
||||
Uses radon to examine cyclomatic complexity.
|
||||
For additional details on radon, see http://radon.readthedocs.org/
|
||||
"""
|
||||
system_string = 'cms/ lms/ common/ openedx/'
|
||||
system_string = join(ALL_SYSTEMS, '/ ') + '/'
|
||||
complexity_report_dir = (Env.REPORT_DIR / "complexity")
|
||||
complexity_report = complexity_report_dir / "python_complexity.log"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user