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:
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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">')
|
||||
|
||||
Reference in New Issue
Block a user