Commit 435a6f52 by Andy Armstrong Committed by Brian Jacobel

Make course_experience a feature

parent 6f5249d3
......@@ -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 #########################################
......
../../openedx/features/course_experience/static/course_experience
\ No newline at end of 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'},
......
......@@ -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',
......
......@@ -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
......@@ -381,20 +380,6 @@ urlpatterns += (
),
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"]:
......
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.
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),
}
......
"""
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',
),
]
"""
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)
"""
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"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment