Merge pull request #23373 from edx/AA-59-reset-dates-banner-on-courseware

AA-59 show reset dates banner on courseware page
This commit is contained in:
Nick
2020-03-13 08:14:00 -04:00
committed by GitHub
10 changed files with 115 additions and 9 deletions

View File

@@ -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, '<div class="reset-deadlines-banner">')
@ddt.ddt
class TestIndexViewCompleteOnView(ModuleStoreTestCase, CompletionWaffleTestMixin):

View File

@@ -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(

View File

@@ -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:

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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:
<script type="text/javascript">
$('.reset-deadlines-banner').css('display', 'flex');
</script>
% endif
<%def name="course_name()">
<% return _("{course_number} Courseware").format(course_number=course.display_number_with_default) %>
</%def>

View File

@@ -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

View File

@@ -8,6 +8,6 @@ from django.utils.translation import ugettext as _
<div class="reset-deadlines-text">${_("It looks like you've missed some important deadlines. Reset your deadlines and get started today.")}</div>
<form method="post" action="${reset_deadlines_url}">
<input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="${csrf_token}">
<button class="reset-deadlines-button">${_("Reset my deadlines")}</button>
<button class="btn reset-deadlines-button">${_("Reset my deadlines")}</button>
</form>
</div>

View File

@@ -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 %>
<script type="text/javascript">
$('.reset-deadlines-banner').css('display', 'flex');

View File

@@ -1025,5 +1025,7 @@ class CourseHomeFragmentViewTests(ModuleStoreTestCase):
@RELATIVE_DATES_FLAG.override(active=True)
def test_reset_deadline_banner_is_present_on_course_tab(self):
CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) # pylint: disable=no-member
response = self.client.get(self.url)
self.assertContains(response, '<div class="reset-deadlines-banner">')