Commit 8c93eac6 by Andy Armstrong

Add an empty course message to the course home page

LEARNER-27
parent 8fbf01cd
...@@ -25,4 +25,4 @@ ...@@ -25,4 +25,4 @@
// Features // Features
@import 'features/bookmarks'; @import 'features/bookmarks';
@import 'features/course-outline'; @import 'features/course-experience';
// Course sidebar
.course-sidebar {
@include margin-left(0);
@include padding-left($baseline);
}
// Course outline
.course-outline { .course-outline {
color: $lms-gray; color: $lms-gray;
......
...@@ -64,7 +64,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG ...@@ -64,7 +64,7 @@ from openedx.features.course_experience import UNIFIED_COURSE_EXPERIENCE_FLAG
<main class="layout-col layout-col-b"> <main class="layout-col layout-col-b">
${HTML(outline_fragment.body_html())} ${HTML(outline_fragment.body_html())}
</main> </main>
<aside class="layout-col layout-col-a"> <aside class="course-sidebar layout-col layout-col-a">
<div class="section section-tools"> <div class="section section-tools">
<h3 class="hd-6">${_("Course Tools")}</h3> <h3 class="hd-6">${_("Course Tools")}</h3>
<ul class="list-unstyled"> <ul class="list-unstyled">
......
...@@ -5,7 +5,11 @@ ...@@ -5,7 +5,11 @@
<%namespace name='static' file='../static_content.html'/> <%namespace name='static' file='../static_content.html'/>
<%! <%!
from datetime import date
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from openedx.core.djangolib.markup import HTML, Text
%> %>
<%static:require_module_async module_name="course_experience/js/course_outline_factory" class_name="CourseOutlineFactory"> <%static:require_module_async module_name="course_experience/js/course_outline_factory" class_name="CourseOutlineFactory">
...@@ -13,65 +17,77 @@ from django.utils.translation import ugettext as _ ...@@ -13,65 +17,77 @@ from django.utils.translation import ugettext as _
</%static:require_module_async> </%static:require_module_async>
<main role="main" class="course-outline" id="main" tabindex="-1"> <main role="main" class="course-outline" id="main" tabindex="-1">
<ol class="block-tree" role="tree"> % if blocks.get('children'):
% for section in blocks.get('children') or []: <ol class="block-tree" role="tree">
<li % for section in blocks.get('children'):
aria-expanded="true" <li
class="outline-item focusable section" aria-expanded="true"
id="${ section['id'] }" class="outline-item focusable section"
role="treeitem" id="${ section['id'] }"
tabindex="0" role="treeitem"
> tabindex="0"
<div class="section-name"> >
<h3>${ section['display_name'] }</h3> <div class="section-name">
</div> <h3>${ section['display_name'] }</h3>
<ol class="outline-item focusable" role="group" tabindex="0"> </div>
% for subsection in section.get('children') or []: <ol class="outline-item focusable" role="group" tabindex="0">
<li % for subsection in section.get('children') or []:
class="subsection ${ 'current' if subsection['current'] else '' }" <li
role="treeitem" class="subsection ${ 'current' if subsection['current'] else '' }"
tabindex="-1" role="treeitem"
aria-expanded="true" tabindex="-1"
> aria-expanded="true"
<a
class="outline-item focusable"
href="${ subsection['lms_web_url'] }"
id="${ subsection['id'] }"
> >
<div class="subsection-text"> <a
## Subsection title class="outline-item focusable"
<span class="subsection-title">${ subsection['display_name'] }</span> href="${ subsection['lms_web_url'] }"
id="${ subsection['id'] }"
>
<div class="subsection-text">
## Subsection title
<span class="subsection-title">${ subsection['display_name'] }</span>
<div class="details"> <div class="details">
## There are behavior differences between rendering of subsections which have ## There are behavior differences between rendering of subsections which have
## exams (timed, graded, etc) and those that do not. ## exams (timed, graded, etc) and those that do not.
## ##
## Exam subsections expose exam status message field as well as a status icon ## Exam subsections expose exam status message field as well as a status icon
<% <%
if subsection.get('due') is None: if subsection.get('due') is None:
# examples: Homework, Lab, etc. # examples: Homework, Lab, etc.
data_string = subsection.get('format') data_string = subsection.get('format')
else:
if 'special_exam_info' in subsection:
data_string = _('due {date}')
else: else:
data_string = _("{subsection_format} due {{date}}").format(subsection_format=subsection.get('format')) if 'special_exam_info' in subsection:
%> data_string = _('due {date}')
% if subsection.get('format') or 'special_exam_info' in subsection: else:
<span class="subtitle"> data_string = _("{subsection_format} due {{date}}").format(subsection_format=subsection.get('format'))
% if 'special_exam' in subsection: %>
## Display the exam status icon and status message % if subsection.get('format') or 'special_exam_info' in subsection:
<span <span class="subtitle">
class="menu-icon icon fa ${subsection['special_exam_info'].get('suggested_icon', 'fa-pencil-square-o')} ${subsection['special_exam_info'].get('status', 'eligible')}" % if 'special_exam' in subsection:
aria-hidden="true" ## Display the exam status icon and status message
></span> <span
<span class="subtitle-name"> class="menu-icon icon fa ${subsection['special_exam_info'].get('suggested_icon', 'fa-pencil-square-o')} ${subsection['special_exam_info'].get('status', 'eligible')}"
${subsection['special_exam_info'].get('short_description', '')} aria-hidden="true"
</span> ></span>
<span class="subtitle-name">
${subsection['special_exam_info'].get('short_description', '')}
</span>
## completed exam statuses should not show the due date ## completed exam statuses should not show the due date
## since the exam has already been submitted by the user ## since the exam has already been submitted by the user
% if not subsection['special_exam_info'].get('in_completed_state', False): % if not subsection['special_exam_info'].get('in_completed_state', False):
<span
class="localized-datetime subtitle-name"
data-datetime="${subsection.get('due')}"
data-string="${data_string}"
data-timezone="${user_timezone}"
data-language="${user_language}"
></span>
% endif
% else:
## non-graded section, we just show the exam format and the due date
## this is the standard case in edx-platform
<span <span
class="localized-datetime subtitle-name" class="localized-datetime subtitle-name"
data-datetime="${subsection.get('due')}" data-datetime="${subsection.get('due')}"
...@@ -79,47 +95,69 @@ from django.utils.translation import ugettext as _ ...@@ -79,47 +95,69 @@ from django.utils.translation import ugettext as _
data-timezone="${user_timezone}" data-timezone="${user_timezone}"
data-language="${user_language}" data-language="${user_language}"
></span> ></span>
% endif
% else:
## non-graded section, we just show the exam format and the due date
## this is the standard case in edx-platform
<span
class="localized-datetime subtitle-name"
data-datetime="${subsection.get('due')}"
data-string="${data_string}"
data-timezone="${user_timezone}"
data-language="${user_language}"
></span>
% if 'graded' in subsection and subsection['graded']: % if 'graded' in subsection and subsection['graded']:
<span <span
class="menu-icon icon fa fa-pencil-square-o" class="menu-icon icon fa fa-pencil-square-o"
aria-hidden="true" aria-hidden="true"
></span> ></span>
<span class="sr">${_("This content is graded")}</span> <span class="sr">${_("This content is graded")}</span>
% endif
% endif % endif
</span>
% endif % endif
</span> </div> <!-- /details -->
% endif </div> <!-- /subsection-text -->
</div> <!-- /details --> <div class="subsection-actions">
</div> <!-- /subsection-text --> ## Resume button (if last visited section)
<div class="subsection-actions"> % if subsection['current']:
## Resume button (if last visited section) <span class="sr-only">${ _("This is your last visited course section.") }</span>
% if subsection['current']: <span class="resume-right">
<span class="sr-only">${ _("This is your last visited course section.") }</span> <b>${ _("Resume Course") }</b>
<span class="resume-right"> <span class="icon fa fa-arrow-circle-right" aria-hidden="true"></span>
<b>${ _("Resume Course") }</b> </span>
<span class="icon fa fa-arrow-circle-right" aria-hidden="true"></span> %endif
</span> </div>
%endif </a>
</div> </li>
</a> % endfor
</li> </ol>
% endfor </li>
</ol> % endfor
</li> </ol>
% endfor % else:
</ol> <div class="well depth-0 message-area">
<div class="copy-large">
<span class="icon fa fa-calendar-o" aria-hidden="true"></span>
<%
course_started = course.start.date() <= date.today()
%>
% if course.start_date_is_still_default:
${_("This course has not started yet.")}
% elif course_started:
${_("We're still working on course content.")}
% else:
${Text(_("This course has not started yet, and will launch on {launch_date_html}.")).format(
launch_date_html=HTML(
'<span'
' class="localized-datetime start-date"'
' data-datetime="{start_date}"'
' data-format="shortDate"'
' data-timezone="{user_timezone}"'
' data-language="{user_language}"'
'></span>'
).format(
start_date=course.start,
user_timezone=user_timezone,
user_language=user_language,
),
)}
% endif
</div>
</div>
% endif
</main> </main>
<%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory"> <%static:require_module_async module_name="js/dateutil_factory" class_name="DateUtilFactory">
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
Tests for the Course Outline view and supporting views. Tests for the Course Outline view and supporting views.
""" """
import datetime import datetime
import ddt
from mock import patch from mock import patch
import json import json
...@@ -12,10 +13,13 @@ from student.models import CourseEnrollment ...@@ -12,10 +13,13 @@ from student.models import CourseEnrollment
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.course_module import DEFAULT_START_DATE
from .test_course_home import course_home_url from .test_course_home import course_home_url
TEST_PASSWORD = 'test' TEST_PASSWORD = 'test'
FUTURE_DAY = datetime.datetime.now() + datetime.timedelta(days=30)
PAST_DAY = datetime.datetime.now() - datetime.timedelta(days=30)
class TestCourseOutlinePage(SharedModuleStoreTestCase): class TestCourseOutlinePage(SharedModuleStoreTestCase):
...@@ -160,3 +164,25 @@ class TestCourseOutlinePreview(SharedModuleStoreTestCase): ...@@ -160,3 +164,25 @@ class TestCourseOutlinePreview(SharedModuleStoreTestCase):
response = self.client.get(url) response = self.client.get(url)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertNotContains(response, 'Future Chapter') self.assertNotContains(response, 'Future Chapter')
@ddt.ddt
class TestEmptyCourseOutlinePage(SharedModuleStoreTestCase):
"""
Test the new course outline view.
"""
@ddt.data(
(FUTURE_DAY, 'This course has not started yet, and will launch on'),
(PAST_DAY, "We're still working on course content."),
(DEFAULT_START_DATE, 'This course has not started yet.'),
)
@ddt.unpack
def test_empty_course_rendering(self, start_date, expected_text):
course = CourseFactory.create(start=start_date)
test_user = UserFactory(password=TEST_PASSWORD)
CourseEnrollment.enroll(test_user, course.id)
self.client.login(username=test_user.username, password=TEST_PASSWORD)
url = course_home_url(course)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertContains(response, expected_text)
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