diff --git a/lms/djangoapps/courseware/tests/test_views.py b/lms/djangoapps/courseware/tests/test_views.py
index 1e61ac540e..898c91aac9 100644
--- a/lms/djangoapps/courseware/tests/test_views.py
+++ b/lms/djangoapps/courseware/tests/test_views.py
@@ -273,8 +273,8 @@ class IndexQueryTestCase(ModuleStoreTestCase):
NUM_PROBLEMS = 20
@ddt.data(
- (ModuleStoreEnum.Type.mongo, 10, 172),
- (ModuleStoreEnum.Type.split, 4, 170),
+ (ModuleStoreEnum.Type.mongo, 10, 174),
+ (ModuleStoreEnum.Type.split, 4, 172),
)
@ddt.unpack
def test_index_query_counts(self, store_type, expected_mongo_query_count, expected_mysql_query_count):
@@ -2600,6 +2600,33 @@ class TestIndexView(ModuleStoreTestCase):
expected_should_show_enroll_button
)
+ def test_reset_deadlines_banner_is_present_when_viewing_courseware(self):
+ user = UserFactory()
+ course = CourseFactory.create(self_paced=True)
+ with self.store.bulk_operations(course.id):
+ chapter = ItemFactory.create(parent=course, category='chapter')
+ section = ItemFactory.create(
+ parent=chapter, category='sequential',
+ display_name="Sequence",
+ due=datetime.today() - timedelta(1),
+ )
+
+ CourseOverview.load_from_module_store(course.id)
+ CourseEnrollmentFactory(user=user, course_id=course.id, mode=CourseMode.VERIFIED)
+ self.client.login(username=user.username, password='test')
+ response = self.client.get(
+ reverse(
+ 'courseware_section',
+ kwargs={
+ 'course_id': six.text_type(course.id),
+ 'chapter': chapter.url_name,
+ 'section': section.url_name,
+ }
+ ) + '?activate_block_id=test_block_id'
+ )
+
+ self.assertContains(response, '
')
+
@ddt.ddt
class TestIndexViewCompleteOnView(ModuleStoreTestCase, CompletionWaffleTestMixin):
diff --git a/lms/djangoapps/courseware/views/index.py b/lms/djangoapps/courseware/views/index.py
index e1f26bfd9c..a0b83ac1c4 100644
--- a/lms/djangoapps/courseware/views/index.py
+++ b/lms/djangoapps/courseware/views/index.py
@@ -7,6 +7,7 @@ View for Courseware Index
import logging
+from datetime import timedelta
import six
import six.moves.urllib as urllib # pylint: disable=import-error
import six.moves.urllib.error # pylint: disable=import-error
@@ -18,6 +19,7 @@ from django.contrib.auth.views import redirect_to_login
from django.http import Http404
from django.template.context_processors import csrf
from django.urls import reverse
+from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.functional import cached_property
from django.utils.translation import ugettext as _
@@ -29,6 +31,7 @@ from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey, UsageKey
from web_fragments.fragment import Fragment
+from course_modes.models import CourseMode
from edxmako.shortcuts import render_to_response, render_to_string
from lms.djangoapps.courseware.exceptions import CourseAccessRedirect, Redirect
from lms.djangoapps.experiments.utils import get_experiment_user_metadata_context
@@ -44,11 +47,14 @@ from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_experience import (
COURSE_ENABLE_UNENROLLED_ACCESS_FLAG,
COURSE_OUTLINE_PAGE_FLAG,
- default_course_url_name
+ default_course_url_name,
+ RELATIVE_DATES_FLAG,
)
+from openedx.features.course_experience.utils import get_course_outline_block_tree
from openedx.features.course_experience.views.course_sock import CourseSockFragmentView
from openedx.features.enterprise_support.api import data_sharing_consent_required
from shoppingcart.models import CourseRegistrationCode
+from student.models import CourseEnrollment
from student.views import is_course_blocked
from util.views import ensure_valid_course_key
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC
@@ -446,6 +452,32 @@ class CoursewareIndex(View):
)
staff_access = self.is_staff
+ reset_deadlines_url = reverse(
+ 'openedx.course_experience.reset_course_deadlines', kwargs={'course_id': six.text_type(self.course.id)}
+ )
+
+ allow_anonymous = allow_public_access(self.course, [COURSE_VISIBILITY_PUBLIC])
+ display_reset_dates_banner = False
+ if not allow_anonymous: # pylint: disable=too-many-nested-blocks
+ course_overview = CourseOverview.objects.get(id=str(self.course_key))
+ end_date = getattr(course_overview, 'end_date')
+ if not end_date or timezone.now() < end_date:
+ if (CourseEnrollment.objects.filter(
+ course=course_overview, user=request.user, mode=CourseMode.VERIFIED
+ ).exists()):
+ course_block_tree = get_course_outline_block_tree(
+ request, str(self.course_key), request.user
+ )
+ course_sections = course_block_tree.get('children')
+ for section in course_sections:
+ if display_reset_dates_banner:
+ break
+ for subsection in section.get('children', []):
+ if (not subsection.get('complete', True)
+ and subsection.get('due', timezone.now() + timedelta(1)) < timezone.now()):
+ display_reset_dates_banner = True
+ break
+
courseware_context = {
'csrf': csrf(self.request)['csrf_token'],
'course': self.course,
@@ -467,6 +499,9 @@ class CoursewareIndex(View):
'sequence_title': None,
'disable_accordion': COURSE_OUTLINE_PAGE_FLAG.is_enabled(self.course.id),
'show_search': show_search,
+ 'relative_dates_is_enabled': RELATIVE_DATES_FLAG.is_enabled(self.course.id),
+ 'reset_deadlines_url': reset_deadlines_url,
+ 'display_reset_dates_banner': display_reset_dates_banner,
}
courseware_context.update(
get_experiment_user_metadata_context(
diff --git a/lms/djangoapps/courseware/views/views.py b/lms/djangoapps/courseware/views/views.py
index c190efeff4..9a7d67de64 100644
--- a/lms/djangoapps/courseware/views/views.py
+++ b/lms/djangoapps/courseware/views/views.py
@@ -24,6 +24,7 @@ from django.http import Http404, HttpResponse, HttpResponseBadRequest, HttpRespo
from django.shortcuts import redirect
from django.template.context_processors import csrf
from django.urls import reverse
+from django.utils import timezone
from django.utils.decorators import method_decorator
from django.utils.http import urlquote_plus
from django.utils.text import slugify
@@ -734,6 +735,15 @@ class CourseTabView(EdxFragmentView):
'openedx.course_experience.reset_course_deadlines', kwargs={'course_id': text_type(course.id)}
)
+ display_reset_dates_banner = False
+ if RELATIVE_DATES_FLAG.is_enabled(course.id):
+ course_overview = CourseOverview.get_from_id(course.id)
+ end_date = getattr(course_overview, 'end_date', None)
+ if (not end_date or timezone.now() < end_date and CourseEnrollment.objects.filter(
+ course=course_overview, user=request.user, mode=CourseMode.VERIFIED
+ ).exists()):
+ display_reset_dates_banner = True
+
context = {
'course': course,
'tab': tab,
@@ -744,8 +754,8 @@ class CourseTabView(EdxFragmentView):
'uses_bootstrap': uses_bootstrap,
'uses_pattern_library': not uses_bootstrap,
'disable_courseware_js': True,
- 'relative_dates_is_enabled': RELATIVE_DATES_FLAG.is_enabled(course.id),
'reset_deadlines_url': reset_deadlines_url,
+ 'display_reset_dates_banner': display_reset_dates_banner,
}
# Avoid Multiple Mathjax loading on the 'user_profile'
if 'profile_page_context' in kwargs:
diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss
index ede0085a9e..61aaf3103b 100644
--- a/lms/static/sass/base/_base.scss
+++ b/lms/static/sass/base/_base.scss
@@ -304,3 +304,26 @@ mark {
}
}
}
+
+div.reset-deadlines-banner {
+ background-color: theme-color("primary");
+ display: none;
+ flex-wrap: wrap;
+ padding: 15px 20px;
+ margin-top: 5px;
+
+ div.reset-deadlines-text {
+ color: theme-color("inverse");
+ padding-top: 10px;
+ margin-right: 10px;
+ flex: 0 0 auto;
+ }
+
+ form {
+ button {
+ color: #0075b4;
+ background-color: theme-color("inverse");
+ cursor: pointer;
+ }
+ }
+}
diff --git a/lms/static/sass/course/layout/_reset_deadlines.scss b/lms/static/sass/course/layout/_reset_deadlines.scss
index 24c915e4ac..b931bad5ee 100644
--- a/lms/static/sass/course/layout/_reset_deadlines.scss
+++ b/lms/static/sass/course/layout/_reset_deadlines.scss
@@ -11,12 +11,13 @@ div.reset-deadlines-banner {
&.reset-deadlines-text {
color: theme-color("inverse");
- padding-top: 2px;
+ padding-top: 10px;
margin-right: 10px;
}
&.reset-deadlines-button {
- border-radius: 5px;
+ color: #0075b4;
+ background-color: theme-color("inverse");
cursor: pointer;
}
}
diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html
index 6dba5cd21c..4167364ef4 100644
--- a/lms/templates/courseware/courseware.html
+++ b/lms/templates/courseware/courseware.html
@@ -22,6 +22,12 @@ from openedx.features.course_experience import course_home_page_title, COURSE_OU
(course.enable_proctored_exams or course.enable_timed_exams)
)
%>
+
+% if display_reset_dates_banner:
+
+% endif
<%def name="course_name()">
<% return _("{course_number} Courseware").format(course_number=course.display_number_with_default) %>
%def>
diff --git a/lms/templates/main.html b/lms/templates/main.html
index 83d4195bc2..efeeca1718 100644
--- a/lms/templates/main.html
+++ b/lms/templates/main.html
@@ -186,7 +186,7 @@ from pipeline_mako import render_require_js_path_overrides
% endif
<% is_course_staff = bool(user and course and has_access(user, 'staff', course, course.id)) %>
- % if course and course.self_paced and tab and not is_course_staff and relative_dates_is_enabled:
+ % if course and course.self_paced and display_reset_dates_banner and not is_course_staff:
<%include file="/reset_deadlines_banner.html" />
% endif
diff --git a/lms/templates/reset_deadlines_banner.html b/lms/templates/reset_deadlines_banner.html
index 39cc944037..868bcf869d 100644
--- a/lms/templates/reset_deadlines_banner.html
+++ b/lms/templates/reset_deadlines_banner.html
@@ -8,6 +8,6 @@ from django.utils.translation import ugettext as _
${_("It looks like you've missed some important deadlines. Reset your deadlines and get started today.")}
diff --git a/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html b/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html
index a0f0bdaf19..75c6f871ae 100644
--- a/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html
+++ b/openedx/features/course_experience/templates/course_experience/course-outline-fragment.html
@@ -58,8 +58,10 @@ reset_deadlines_banner_displayed = False
needs_prereqs = not gated_content[subsection['id']]['completed_prereqs'] if gated_subsection else False
scored = 'scored' if subsection.get('scored', False) else ''
graded = 'graded' if subsection.get('graded') else ''
+ due_date = subsection.get('due')
+ overdue = due_date is not None and due_date < timezone.now() and not subsection.get('complete', True)
%>
- % if not subsection.get('complete', True) and subsection.get('due', timezone.now() + timedelta(1)) < timezone.now() and not reset_deadlines_banner_displayed:
+ % if overdue and not reset_deadlines_banner_displayed:
<% reset_deadlines_banner_displayed = True %>