Last-accessed courseware on the home page.
ECOM-2806
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -19,10 +19,3 @@ Feature: LMS.Navigate Course
|
||||
When I navigate to an item in a sequence
|
||||
Then I see the content of the sequence item
|
||||
And a "seq_goto" browser event is emitted
|
||||
|
||||
Scenario: I can return to the last section I visited
|
||||
Given I am viewing a course with multiple sections
|
||||
When I navigate to a section
|
||||
And I see the content of the section
|
||||
And I return to the course
|
||||
Then I see that I was most recently in the subsection
|
||||
|
||||
@@ -136,12 +136,6 @@ def and_i_return_to_the_course(step):
|
||||
world.css_click(course)
|
||||
|
||||
|
||||
@step(u'I see that I was most recently in the subsection')
|
||||
def then_i_see_that_i_was_most_recently_in_the_subsection(step):
|
||||
message = world.css_text('section.course-content > p')
|
||||
assert_in("You were most recently in Test Subsection 2", message)
|
||||
|
||||
|
||||
def create_course():
|
||||
world.clear_courses()
|
||||
world.scenario_dict['COURSE'] = world.CourseFactory.create(
|
||||
|
||||
@@ -228,11 +228,16 @@ class FieldOverrideProvider(object):
|
||||
@abstractmethod
|
||||
def enabled_for(self, course): # pragma no cover
|
||||
"""
|
||||
Return True if this provider should be enabled for a given course
|
||||
Return True if this provider should be enabled for a given course,
|
||||
and False otherwise.
|
||||
|
||||
Return False otherwise
|
||||
Concrete implementations are responsible for implementing this method.
|
||||
|
||||
Concrete implementations are responsible for implementing this method
|
||||
Arguments:
|
||||
course (CourseModule or None)
|
||||
|
||||
Returns:
|
||||
bool
|
||||
"""
|
||||
return False
|
||||
|
||||
|
||||
@@ -25,4 +25,4 @@ class SelfPacedDateOverrideProvider(FieldOverrideProvider):
|
||||
@classmethod
|
||||
def enabled_for(cls, course):
|
||||
"""This provider is enabled for self-paced courses only."""
|
||||
return SelfPacedConfiguration.current().enabled and course.self_paced
|
||||
return course is not None and course.self_paced and SelfPacedConfiguration.current().enabled
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.core.urlresolvers import reverse
|
||||
from django.test.utils import override_settings
|
||||
from opaque_keys.edx.locations import SlashSeparatedCourseKey
|
||||
|
||||
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
||||
from util.date_utils import strftime_localized
|
||||
from xmodule.modulestore.tests.django_utils import (
|
||||
ModuleStoreTestCase,
|
||||
@@ -92,6 +93,33 @@ class CourseInfoTestCase(LoginEnrollmentTestCase, ModuleStoreTestCase):
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_last_accessed_courseware_not_shown(self):
|
||||
SelfPacedConfiguration(enable_course_home_improvements=True).save()
|
||||
url = reverse('info', args=(unicode(self.course.id),))
|
||||
response = self.client.get(url)
|
||||
self.assertNotIn('Jump back to where you were last:', response.content)
|
||||
|
||||
def test_last_accessed_shown(self):
|
||||
SelfPacedConfiguration(enable_course_home_improvements=True).save()
|
||||
chapter = ItemFactory.create(
|
||||
category="chapter", parent_location=self.course.location
|
||||
)
|
||||
section = ItemFactory.create(
|
||||
category='section', parent_location=chapter.location
|
||||
)
|
||||
section_url = reverse(
|
||||
'courseware_section',
|
||||
kwargs={
|
||||
'section': section.url_name,
|
||||
'chapter': chapter.url_name,
|
||||
'course_id': self.course.id
|
||||
}
|
||||
)
|
||||
self.client.get(section_url)
|
||||
info_url = reverse('info', args=(unicode(self.course.id),))
|
||||
info_page_response = self.client.get(info_url)
|
||||
self.assertIn('Jump back to where you were last:', info_page_response.content)
|
||||
|
||||
|
||||
class CourseInfoTestCaseCCX(SharedModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
@@ -169,6 +197,7 @@ class SelfPacedCourseInfoTestCase(LoginEnrollmentTestCase, SharedModuleStoreTest
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
SelfPacedConfiguration(enabled=True).save()
|
||||
super(SelfPacedCourseInfoTestCase, self).setUp()
|
||||
self.instructor_paced_course = CourseFactory.create(self_paced=False)
|
||||
self.self_paced_course = CourseFactory.create(self_paced=True)
|
||||
|
||||
@@ -72,6 +72,7 @@ from openedx.core.djangoapps.credit.api import (
|
||||
)
|
||||
from shoppingcart.models import CourseRegistrationCode
|
||||
from shoppingcart.utils import is_shopping_cart_enabled
|
||||
from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
||||
from student.models import UserTestGroup, CourseEnrollment
|
||||
from student.views import is_course_blocked
|
||||
from util.cache import cache, cache_if_anonymous
|
||||
@@ -549,8 +550,6 @@ def _index_bulk_op(request, course_key, chapter, section, position):
|
||||
context['fragment'] = section_module.render(STUDENT_VIEW, section_render_context)
|
||||
context['section_title'] = section_descriptor.display_name_with_default_escaped
|
||||
else:
|
||||
# section is none, so display a message
|
||||
studio_url = get_studio_url(course, 'course')
|
||||
prev_section = get_current_child(chapter_module)
|
||||
if prev_section is None:
|
||||
# Something went wrong -- perhaps this chapter has no sections visible to the user.
|
||||
@@ -559,22 +558,6 @@ def _index_bulk_op(request, course_key, chapter, section, position):
|
||||
course_module.position = None
|
||||
course_module.save()
|
||||
return redirect(reverse('courseware', args=[course.id.to_deprecated_string()]))
|
||||
prev_section_url = reverse('courseware_section', kwargs={
|
||||
'course_id': course_key.to_deprecated_string(),
|
||||
'chapter': chapter_descriptor.url_name,
|
||||
'section': prev_section.url_name
|
||||
})
|
||||
context['fragment'] = Fragment(content=render_to_string(
|
||||
'courseware/welcome-back.html',
|
||||
{
|
||||
'course': course,
|
||||
'studio_url': studio_url,
|
||||
'chapter_module': chapter_module,
|
||||
'prev_section': prev_section,
|
||||
'prev_section_url': prev_section_url
|
||||
}
|
||||
))
|
||||
|
||||
result = render_to_response('courseware/courseware.html', context)
|
||||
except Exception as e:
|
||||
|
||||
@@ -729,6 +712,14 @@ def course_info(request, course_id):
|
||||
'url_to_enroll': url_to_enroll,
|
||||
}
|
||||
|
||||
# Get the URL of the user's last position in order to display the 'where you were last' message
|
||||
context['last_accessed_courseware'] = None
|
||||
if SelfPacedConfiguration.current().enable_course_home_improvements:
|
||||
(section_module, section_url) = get_last_accessed_courseware(course, request)
|
||||
if section_module is not None and section_url is not None:
|
||||
context['last_accessed_courseware'] = section_module
|
||||
context['last_accessed_url'] = section_url
|
||||
|
||||
now = datetime.now(UTC())
|
||||
effective_start = _adjust_start_date_for_beta_testers(user, course, course_key)
|
||||
if not in_preview_mode() and staff_access and now < effective_start:
|
||||
@@ -739,6 +730,30 @@ def course_info(request, course_id):
|
||||
return render_to_response('courseware/info.html', context)
|
||||
|
||||
|
||||
def get_last_accessed_courseware(course, request):
|
||||
"""
|
||||
Return a pair of the last-accessed courseware for this request's
|
||||
user, and a URL for that module.
|
||||
"""
|
||||
field_data_cache = FieldDataCache.cache_for_descriptor_descendents(
|
||||
course.id, request.user, course, depth=2
|
||||
)
|
||||
course_module = get_module_for_descriptor(
|
||||
request.user, request, course, field_data_cache, course.id, course=course
|
||||
)
|
||||
chapter_module = get_current_child(course_module)
|
||||
if chapter_module is not None:
|
||||
section_module = get_current_child(chapter_module)
|
||||
if section_module is not None:
|
||||
url = reverse('courseware_section', kwargs={
|
||||
'course_id': unicode(course.id),
|
||||
'chapter': chapter_module.url_name,
|
||||
'section': section_module.url_name
|
||||
})
|
||||
return (section_module, url)
|
||||
return (None, None)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@ensure_valid_course_key
|
||||
def static_tab(request, course_id, tab_slug):
|
||||
|
||||
@@ -465,6 +465,7 @@ $courseware-navigation-color: $blue !default;
|
||||
$homepage__header--gradient__color--alpha: lighten($gray, 15%) !default;
|
||||
$homepage__header--gradient__color--bravo: saturate($gray, 30%) !default;
|
||||
$homepage__header--background: lighten($gray, 15%) !default;
|
||||
$homepage-background: rgb(252, 252, 252);
|
||||
$course-card-height: ($baseline*18) !default;
|
||||
$course-image-height: ($baseline*8) !default;
|
||||
$course-info-height: ($baseline*10) !default;
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
.home-wrapper {
|
||||
max-width: 1180px;
|
||||
.home {
|
||||
@include clearfix();
|
||||
max-width: 1140px;
|
||||
margin: 0 auto;
|
||||
padding: $baseline $baseline ($baseline/2) $baseline;
|
||||
|
||||
.home {
|
||||
margin: $baseline;
|
||||
.page-header-main {
|
||||
display: inline-block;
|
||||
width: flex-grid(8, 12);
|
||||
margin: 0;
|
||||
|
||||
.page-title {
|
||||
margin-bottom: 5px;
|
||||
@@ -17,11 +21,29 @@
|
||||
text-transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.page-header-secondary {
|
||||
display: inline-block;
|
||||
width: flex-grid(4, 12);
|
||||
margin: 0;
|
||||
padding: ($baseline/2) ($baseline*0.75);
|
||||
border: 1px solid $blue;
|
||||
background-color: $homepage-background;
|
||||
@extend %t-title8;
|
||||
color: $blue-d1;
|
||||
@extend %cont-truncated;
|
||||
vertical-align: text-bottom;
|
||||
|
||||
.last-accessed-message {
|
||||
display: inline-block;
|
||||
@include margin-left($baseline*0.75);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.info-wrapper {
|
||||
|
||||
background-color: rgb(252, 252, 252);
|
||||
background-color: $homepage-background;
|
||||
|
||||
section.updates {
|
||||
@extend .content;
|
||||
@@ -63,7 +85,7 @@ div.info-wrapper {
|
||||
margin-bottom: ($baseline/4);
|
||||
text-transform: none;
|
||||
background: url('#{$static-path}/images/calendar-icon.png') 0 center no-repeat;
|
||||
padding-left: $baseline;
|
||||
@include padding-left($baseline);
|
||||
}
|
||||
|
||||
section.update-description {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
list-style: none;
|
||||
|
||||
&.prominent {
|
||||
margin-right: 16px;
|
||||
@include margin-right(16px);
|
||||
background: rgba(255, 255, 255, 0.5);
|
||||
border-radius: 3px;
|
||||
|
||||
@@ -45,11 +45,18 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration
|
||||
|
||||
<%block name="bodyclass">view-in-course view-course-info ${course.css_class or ''}</%block>
|
||||
<section class="container">
|
||||
<div class="home-wrapper">
|
||||
<section class="home">
|
||||
<div class="home">
|
||||
<div class="page-header-main">
|
||||
<h1 class="page-title">${_("Welcome to {org}'s {course_name}!").format(org=course.id.org, course_name=course.id.course) | h}</h1>
|
||||
<h2 class="page-subtitle">${course.display_name | h}</h2>
|
||||
</section>
|
||||
</div>
|
||||
% if last_accessed_courseware:
|
||||
<div class="page-header-secondary">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
<p class="last-accessed-message">${_("Jump back to where you were last:")}</p>
|
||||
<a href="${last_accessed_url}">${last_accessed_courseware.display_name | h}</a>
|
||||
</div>
|
||||
% endif
|
||||
</div>
|
||||
<div class="info-wrapper">
|
||||
% if user.is_authenticated():
|
||||
|
||||
Reference in New Issue
Block a user