Commit b06d5bed by Andy Armstrong Committed by GitHub

Merge pull request #14953 from edx/andy/course-updates-page

Implement new course updates page
parents a0129ccb 6b8b79ff
......@@ -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'
......
......@@ -150,6 +150,13 @@
}
}
.section {
.icon {
width: 20px;
text-align: center;
}
}
.section:not(:first-child) {
margin-top: $baseline;
}
......@@ -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):
......
......@@ -74,6 +74,12 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
${_("Bookmarks")}
</a>
</li>
<li>
<a class="action-show-bookmarks" href="${reverse('openedx.course_experience.course_updates', args=[course.id])}">
<span class="icon fa fa-newspaper-o" aria-hidden="true"></span>
${_("Updates")}
</a>
</li>
</ul>
</div>
% if handouts_html:
......
## 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">
<div class="course-updates container" id="course-container">
<header class="page-header has-secondary">
## Breadcrumb navigation
<div class="page-header-main">
<nav aria-label="${_('Course Updates')}" class="sr-is-focusable" tabindex="-1">
<div class="has-breadcrumbs">
<div class="breadcrumbs">
<span class="nav-item">
<a href="${course_url}">Course</a>
</span>
<span class="icon fa fa-angle-right" aria-hidden="true"></span>
<span class="nav-item">${_('Course Updates')}</span>
</div>
</div>
</nav>
</div>
</header>
<div class="page-content">
${HTML(updates_html)}
</div>
</div>
</%block>
......@@ -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),
}
......
"""
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'<ol><li><a href="test">Test Update</a></li></ol>'
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('<a href="test">Test Update</a>', 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)
......@@ -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',
),
]
"""
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)
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