Merge pull request #10642 from edx/feature/self-paced

Course home page improvements.
This commit is contained in:
Peter Fogg
2016-02-03 15:06:59 -05:00
50 changed files with 668 additions and 274 deletions

View File

@@ -424,12 +424,12 @@ class CourseFields(object):
)
has_children = True
info_sidebar_name = String(
display_name=_("Course Info Sidebar Name"),
display_name=_("Course Home Sidebar Name"),
help=_(
"Enter the heading that you want students to see above your course handouts on the Course Info page. "
"Enter the heading that you want students to see above your course handouts on the Course Home page. "
"Your course handouts appear in the right panel of the page."
),
scope=Scope.settings, default='Course Handouts')
scope=Scope.settings, default=_('Course Handouts'))
show_timezone = Boolean(
help=_(
"True if timezones should be shown on dates in the courseware. "

View File

@@ -1,13 +1,14 @@
import os
import sys
import re
import copy
import logging
import textwrap
from lxml import etree
from path import Path as path
from datetime import datetime
from fs.errors import ResourceNotFoundError
import logging
from lxml import etree
import os
from path import Path as path
from pkg_resources import resource_string
import re
import sys
import textwrap
import dogstats_wrapper as dog_stats_api
from xmodule.util.misc import escape_html_characters
@@ -75,10 +76,10 @@ class HtmlBlock(object):
return Fragment(self.get_html())
def get_html(self):
"""
When we switch this to an XBlock, we can merge this with student_view,
but for now the XModule mixin requires that this method be defined.
"""
""" Returns html required for rendering XModule. """
# When we switch this to an XBlock, we can merge this with student_view,
# but for now the XModule mixin requires that this method be defined.
# pylint: disable=no-member
if self.system.anonymous_student_id:
return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id)
@@ -417,6 +418,35 @@ class CourseInfoModule(CourseInfoFields, HtmlModuleMixin):
# statuses
STATUS_VISIBLE = 'visible'
STATUS_DELETED = 'deleted'
TEMPLATE_DIR = 'courseware'
@XBlock.supports("multi_device")
def student_view(self, _context):
"""
Return a fragment that contains the html for the student view
"""
return Fragment(self.get_html())
def get_html(self):
""" Returns html required for rendering XModule. """
# When we switch this to an XBlock, we can merge this with student_view,
# but for now the XModule mixin requires that this method be defined.
# pylint: disable=no-member
if self.data != "":
if self.system.anonymous_student_id:
return self.data.replace("%%USER_ID%%", self.system.anonymous_student_id)
return self.data
else:
course_updates = [item for item in self.items if item.get('status') == self.STATUS_VISIBLE]
course_updates.sort(key=lambda item: datetime.strptime(item['date'], '%B %d, %Y'), reverse=True)
context = {
'visible_updates': course_updates[:3],
'hidden_updates': course_updates[3:],
}
return self.system.render_template("{0}/course_updates.html".format(self.TEMPLATE_DIR), context)
@XBlock.tag("detached")

View File

@@ -305,8 +305,8 @@ class CourseTabList(List):
"""
course.tabs.extend([
CourseTab.load('courseware'),
CourseTab.load('course_info')
CourseTab.load('course_info'),
CourseTab.load('courseware')
])
# Presence of syllabus tab is indicated by a course attribute
@@ -389,6 +389,19 @@ class CourseTabList(List):
else:
yield tab
@classmethod
def upgrade_tabs(cls, tabs):
"""
Reverse and Rename Courseware to Course and Course Info to Home Tabs.
"""
if tabs and len(tabs) > 1:
if tabs[0].get('type') == 'courseware' and tabs[1].get('type') == 'course_info':
tabs[0], tabs[1] = tabs[1], tabs[0]
tabs[0]['name'] = _('Home')
tabs[1]['name'] = _('Course')
return tabs
@classmethod
def validate_tabs(cls, tabs):
"""
@@ -406,13 +419,13 @@ class CourseTabList(List):
if len(tabs) < 2:
raise InvalidTabsException("Expected at least two tabs. tabs: '{0}'".format(tabs))
if tabs[0].get('type') != 'courseware':
if tabs[0].get('type') != 'course_info':
raise InvalidTabsException(
"Expected first tab to have type 'courseware'. tabs: '{0}'".format(tabs))
"Expected first tab to have type 'course_info'. tabs: '{0}'".format(tabs))
if tabs[1].get('type') != 'course_info':
if tabs[1].get('type') != 'courseware':
raise InvalidTabsException(
"Expected second tab to have type 'course_info'. tabs: '{0}'".format(tabs))
"Expected second tab to have type 'courseware'. tabs: '{0}'".format(tabs))
# the following tabs should appear only once
# TODO: don't import openedx capabilities from common
@@ -455,6 +468,7 @@ class CourseTabList(List):
"""
Overrides the from_json method to de-serialize the CourseTab objects from a json-like representation.
"""
self.upgrade_tabs(values)
self.validate_tabs(values)
tabs = []
for tab_dict in values:

View File

@@ -21,7 +21,7 @@ class TabNavPage(PageObject):
Navigate to the tab `tab_name`.
"""
if tab_name not in ['Courseware', 'Course Info', 'Discussion', 'Wiki', 'Progress']:
if tab_name not in ['Course', 'Home', 'Discussion', 'Wiki', 'Progress']:
self.warning("'{0}' is not a valid tab name".format(tab_name))
# The only identifier for individual tabs is the link href

View File

@@ -206,7 +206,7 @@ class CertificateProgressPageTest(UniqueCourseTest):
Problems were added in the setUp
"""
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
# Navigate to Test Subsection in Test Section Section
self.course_nav.go_to_section('Test Section', 'Test Subsection')

View File

@@ -127,9 +127,9 @@ class LibraryContentTestBase(UniqueCourseTest):
Open library page in LMS
"""
self.courseware_page.visit()
paragraphs = self.courseware_page.q(css='.course-content p')
if paragraphs and "You were most recently in" in paragraphs.text[0]:
paragraphs[0].find_element_by_tag_name('a').click()
paragraphs = self.courseware_page.q(css='.course-content p').results
if not paragraphs:
self.courseware_page.q(css='.menu-item a').results[0].click()
block_id = block_id if block_id is not None else self.lib_block.locator
#pylint: disable=attribute-defined-outside-init
self.library_content_page = LibraryContentXBlockWrapper(self.browser, block_id)

View File

@@ -597,7 +597,7 @@ class HighLevelTabTest(UniqueCourseTest):
# Navigate to the course info page from the progress page
self.progress_page.visit()
self.tab_nav.go_to_tab('Course Info')
self.tab_nav.go_to_tab('Home')
# Expect just one update
self.assertEqual(self.course_info_page.num_updates, 1)
@@ -667,13 +667,13 @@ class HighLevelTabTest(UniqueCourseTest):
def test_courseware_nav(self):
"""
Navigate to a particular unit in the courseware.
Navigate to a particular unit in the course.
"""
# Navigate to the courseware page from the info page
# Navigate to the course page from the info page
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
# Check that the courseware navigation appears correctly
# Check that the course navigation appears correctly
EXPECTED_SECTIONS = {
'Test Section': ['Test Subsection'],
'Test Section 2': ['Test Subsection 2', 'Test Subsection 3']
@@ -862,7 +862,7 @@ class TooltipTest(UniqueCourseTest):
Verify that tooltips are displayed when you hover over the sequence nav bar.
"""
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
self.courseware_page.verify_tooltips_displayed()
@@ -1011,7 +1011,7 @@ class ProblemExecutionTest(UniqueCourseTest):
def test_python_execution_in_problem(self):
# Navigate to the problem page
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
self.course_nav.go_to_section('Test Section', 'Test Subsection')
problem_page = ProblemPage(self.browser)
@@ -1061,14 +1061,14 @@ class EntranceExamTest(UniqueCourseTest):
def test_entrance_exam_section(self):
"""
Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at courseware
Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at course
page.
Given that I am on the courseware page
When I view the courseware that has an entrance exam
Given that I am on the course page
When I view the course that has an entrance exam
Then there should be an "Entrance Exam" chapter.'
"""
entrance_exam_link_selector = '.accordion .course-navigation .chapter .group-heading'
# visit courseware page and make sure there is not entrance exam chapter.
# visit course page and make sure there is not entrance exam chapter.
self.courseware_page.visit()
self.courseware_page.wait_for_page()
self.assertFalse(element_has_text(

View File

@@ -75,7 +75,7 @@ class XBlockAcidNoChildTest(XBlockAcidBase):
"""
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
acid_block = AcidView(self.browser, '.xblock-student_view[data-block-type=acid]')
self.validate_acid_block_view(acid_block)
@@ -119,7 +119,7 @@ class XBlockAcidChildTest(XBlockAcidBase):
"""
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
acid_parent_block = AcidView(self.browser, '.xblock-student_view[data-block-type=acid_parent]')
self.validate_acid_parent_block_view(acid_parent_block)
@@ -159,7 +159,7 @@ class XBlockAcidAsideTest(XBlockAcidBase):
"""
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
acid_aside = AcidView(self.browser, '.xblock_asides-v1-student_view[data-block-type=acid_aside]')
self.validate_acid_aside_view(acid_aside)

View File

@@ -132,7 +132,7 @@ class VideoBaseTest(UniqueCourseTest):
self.auth_page.visit()
self.user_info = self.auth_page.user_info
self.course_info_page.visit()
self.tab_nav.go_to_tab('Courseware')
self.tab_nav.go_to_tab('Course')
def _navigate_to_courseware_video_and_render(self):
""" Wait for the video player to render """