diff --git a/lms/djangoapps/courseware/tabs.py b/lms/djangoapps/courseware/tabs.py
index 6fd2f425bf..c8df7669f9 100644
--- a/lms/djangoapps/courseware/tabs.py
+++ b/lms/djangoapps/courseware/tabs.py
@@ -45,7 +45,7 @@ class CoursewareTab(EnrolledTab):
Returns the main course URL for the current user.
"""
if waffle.flag_is_active(request, UNIFIED_COURSE_VIEW_FLAG):
- return 'edx.course_experience.course_home'
+ return 'openedx.course_experience.course_home'
else:
return 'courseware'
diff --git a/lms/static/sass/shared-v2/_components.scss b/lms/static/sass/shared-v2/_components.scss
index 5f36120e42..bafb0dc175 100644
--- a/lms/static/sass/shared-v2/_components.scss
+++ b/lms/static/sass/shared-v2/_components.scss
@@ -150,6 +150,13 @@
}
}
+.section {
+ .icon {
+ width: 20px;
+ text-align: center;
+ }
+}
+
.section:not(:first-child) {
margin-top: $baseline;
}
diff --git a/openedx/features/course_bookmarks/views/course_bookmarks.py b/openedx/features/course_bookmarks/views/course_bookmarks.py
index f44f05770f..7d8d2a5e98 100644
--- a/openedx/features/course_bookmarks/views/course_bookmarks.py
+++ b/openedx/features/course_bookmarks/views/course_bookmarks.py
@@ -18,7 +18,6 @@ from opaque_keys.edx.keys import CourseKey
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from util.views import ensure_valid_course_key
from web_fragments.fragment import Fragment
-from xmodule.modulestore.django import modulestore
class CourseBookmarksView(View):
diff --git a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
index fdb0967e99..99429120f5 100644
--- a/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
+++ b/openedx/features/course_experience/templates/course_experience/course-home-fragment.html
@@ -74,6 +74,12 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
${_("Bookmarks")}
+
+
+
+ ${_("Updates")}
+
+
% if handouts_html:
diff --git a/openedx/features/course_experience/templates/course_experience/course-updates-fragment.html b/openedx/features/course_experience/templates/course_experience/course-updates-fragment.html
new file mode 100644
index 0000000000..993b996acf
--- /dev/null
+++ b/openedx/features/course_experience/templates/course_experience/course-updates-fragment.html
@@ -0,0 +1,42 @@
+## mako
+
+<%page expression_filter="h"/>
+<%namespace name='static' file='../static_content.html'/>
+
+<%!
+import json
+
+from django.conf import settings
+from django.utils.translation import ugettext as _
+from django.template.defaultfilters import escapejs
+from django.core.urlresolvers import reverse
+
+from django_comment_client.permissions import has_permission
+from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
+from openedx.core.djangolib.markup import HTML
+from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
+%>
+
+<%block name="content">
+
+
+
+ ${HTML(updates_html)}
+
+
+%block>
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py
index 84d984a88e..3094022d45 100644
--- a/openedx/features/course_experience/tests/views/test_course_home.py
+++ b/openedx/features/course_experience/tests/views/test_course_home.py
@@ -18,7 +18,7 @@ def course_home_url(course):
Returns the URL for the course's home page
"""
return reverse(
- 'edx.course_experience.course_home',
+ 'openedx.course_experience.course_home',
kwargs={
'course_id': unicode(course.id),
}
diff --git a/openedx/features/course_experience/tests/views/test_course_updates.py b/openedx/features/course_experience/tests/views/test_course_updates.py
new file mode 100644
index 0000000000..4afcca0956
--- /dev/null
+++ b/openedx/features/course_experience/tests/views/test_course_updates.py
@@ -0,0 +1,90 @@
+"""
+Tests for the course updates page.
+"""
+from django.core.urlresolvers import reverse
+
+from student.models import CourseEnrollment
+from student.tests.factories import UserFactory
+from xmodule.html_module import CourseInfoModule
+from xmodule.modulestore import ModuleStoreEnum
+from xmodule.modulestore.django import modulestore
+from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
+
+TEST_PASSWORD = 'test'
+
+
+def course_updates_url(course):
+ """
+ Returns the URL for the course's home page
+ """
+ return reverse(
+ 'openedx.course_experience.course_updates',
+ kwargs={
+ 'course_id': unicode(course.id),
+ }
+ )
+
+
+class TestCourseUpdatesPage(SharedModuleStoreTestCase):
+ """
+ Test the course updates page.
+ """
+ @classmethod
+ def setUpClass(cls):
+ """Set up the simplest course possible."""
+ # setUpClassAndTestData() already calls setUpClass on SharedModuleStoreTestCase
+ # pylint: disable=super-method-not-called
+ with super(TestCourseUpdatesPage, cls).setUpClassAndTestData():
+ with cls.store.default_store(ModuleStoreEnum.Type.split):
+ cls.course = CourseFactory.create()
+ with cls.store.bulk_operations(cls.course.id):
+ # Create a basic course structure
+ chapter = ItemFactory.create(category='chapter', parent_location=cls.course.location)
+ section = ItemFactory.create(category='sequential', parent_location=chapter.location)
+ ItemFactory.create(category='vertical', parent_location=section.location)
+
+ @classmethod
+ def setUpTestData(cls):
+ """Set up and enroll our fake user in the course."""
+ cls.user = UserFactory(password=TEST_PASSWORD)
+ CourseEnrollment.enroll(cls.user, cls.course.id)
+
+ # Create course updates
+ cls.create_course_updates(cls.course, cls.user)
+
+ @classmethod
+ def create_course_updates(cls, course, user, count=5):
+ """
+ Create some test course updates.
+ """
+ updates_usage_key = course.id.make_usage_key('course_info', 'updates')
+ course_updates = modulestore().create_item(
+ user.id,
+ updates_usage_key.course_key,
+ updates_usage_key.block_type,
+ block_id=updates_usage_key.block_id
+ )
+ course_updates.data = u'- Test Update
'
+ modulestore().update_item(course_updates, user.id)
+
+ def setUp(self):
+ """
+ Set up for the tests.
+ """
+ super(TestCourseUpdatesPage, self).setUp()
+ self.client.login(username=self.user.username, password=TEST_PASSWORD)
+
+ def test_view(self):
+ url = course_updates_url(self.course)
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ response_content = response.content.decode("utf-8")
+ self.assertIn('Test Update', response_content)
+
+ def test_queries(self):
+ # Fetch the view and verify that the query counts haven't changed
+ with self.assertNumQueries(34):
+ with check_mongo_calls(4):
+ url = course_updates_url(self.course)
+ self.client.get(url)
diff --git a/openedx/features/course_experience/urls.py b/openedx/features/course_experience/urls.py
index 6dded24c98..8f8ed091bb 100644
--- a/openedx/features/course_experience/urls.py
+++ b/openedx/features/course_experience/urls.py
@@ -6,21 +6,32 @@ from django.conf.urls import url
from views.course_home import CourseHomeView, CourseHomeFragmentView
from views.course_outline import CourseOutlineFragmentView
+from views.course_updates import CourseUpdatesFragmentView, CourseUpdatesView
urlpatterns = [
url(
r'^$',
CourseHomeView.as_view(),
- name='edx.course_experience.course_home',
+ name='openedx.course_experience.course_home',
+ ),
+ url(
+ r'^updates$',
+ CourseUpdatesView.as_view(),
+ name='openedx.course_experience.course_updates',
),
url(
r'^home_fragment$',
CourseHomeFragmentView.as_view(),
- name='edx.course_experience.course_home_fragment_view',
+ name='openedx.course_experience.course_home_fragment_view',
),
url(
r'^outline_fragment$',
CourseOutlineFragmentView.as_view(),
- name='edx.course_experience.course_outline_fragment_view',
+ name='openedx.course_experience.course_outline_fragment_view',
+ ),
+ url(
+ r'^updates_fragment$',
+ CourseUpdatesFragmentView.as_view(),
+ name='openedx.course_experience.course_updates_fragment_view',
),
]
diff --git a/openedx/features/course_experience/views/course_updates.py b/openedx/features/course_experience/views/course_updates.py
new file mode 100644
index 0000000000..1080a1016f
--- /dev/null
+++ b/openedx/features/course_experience/views/course_updates.py
@@ -0,0 +1,64 @@
+"""
+Views that handle course updates.
+"""
+
+from django.contrib.auth.decorators import login_required
+from django.core.context_processors import csrf
+from django.core.urlresolvers import reverse
+from django.template.loader import render_to_string
+from django.utils.decorators import method_decorator
+from django.views.decorators.cache import cache_control
+
+from courseware.courses import get_course_info_section, get_course_with_access
+from lms.djangoapps.courseware.tabs import CoursewareTab
+from lms.djangoapps.courseware.views.views import CourseTabView
+from opaque_keys.edx.keys import CourseKey
+from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
+from web_fragments.fragment import Fragment
+
+
+class CourseUpdatesView(CourseTabView):
+ """
+ The course updates page.
+ """
+ @method_decorator(login_required)
+ @method_decorator(cache_control(no_cache=True, no_store=True, must_revalidate=True))
+ def get(self, request, course_id, **kwargs):
+ """
+ Displays the home page for the specified course.
+ """
+ return super(CourseUpdatesView, self).get(request, course_id, 'courseware', **kwargs)
+
+ def render_to_fragment(self, request, course=None, tab=None, **kwargs):
+ course_id = unicode(course.id)
+ updates_fragment_view = CourseUpdatesFragmentView()
+ return updates_fragment_view.render_to_fragment(request, course_id=course_id, **kwargs)
+
+
+class CourseUpdatesFragmentView(EdxFragmentView):
+ """
+ A fragment to render the home page for a course.
+ """
+ def render_to_fragment(self, request, course_id=None, **kwargs):
+ """
+ Renders the course's home page 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_url_name = CoursewareTab.main_course_url_name(request)
+ course_url = reverse(course_url_name, kwargs={'course_id': unicode(course.id)})
+
+ # Fetch the updates as HTML
+ updates_html = get_course_info_section(request, request.user, course, 'updates')
+
+ # Render the course home fragment
+ context = {
+ 'csrf': csrf(request)['csrf_token'],
+ 'course': course,
+ 'course_url': course_url,
+ 'updates_html': updates_html,
+ 'disable_courseware_js': True,
+ 'uses_pattern_library': True,
+ }
+ html = render_to_string('course_experience/course-updates-fragment.html', context)
+ return Fragment(html)