Make course_experience a feature

This commit is contained in:
Andy Armstrong
2017-03-06 17:32:52 -05:00
committed by Diana Huang
parent 9ca051c527
commit a3f32fea5a
24 changed files with 169 additions and 107 deletions

View File

@@ -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')

View File

@@ -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)

View File

@@ -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 #########################################

View File

@@ -0,0 +1 @@
../../openedx/features/course_experience/static/course_experience

View File

@@ -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'},

View File

@@ -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',

View File

@@ -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',

View File

@@ -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"]:

View 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.

View File

View 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');
});

View File

@@ -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),
}

View 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',
),
]

View 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)

View 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)

View File

@@ -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"