Merge pull request #14953 from edx/andy/course-updates-page
Implement new course updates page
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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">
|
||||
<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),
|
||||
}
|
||||
|
||||
@@ -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'<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',
|
||||
),
|
||||
]
|
||||
|
||||
64
openedx/features/course_experience/views/course_updates.py
Normal file
64
openedx/features/course_experience/views/course_updates.py
Normal file
@@ -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)
|
||||
Reference in New Issue
Block a user