AA-151 dates banner on course outline and refactor

- use new dates banner template on course outline page
- remove old banner from main.html
- let dates tab use new dates banner template
- remove dates banner completely from the courseware problem view
  on the web app
- use new banner on the courseware problem view on the mobile app
- update banner util to use get_course_blocks
This commit is contained in:
Nicholas D'Alfonso
2020-05-14 19:04:35 -04:00
parent 1e103b2fdd
commit b454f9be1d
20 changed files with 321 additions and 254 deletions

View File

@@ -13,15 +13,24 @@ from django.utils import timezone
from django.utils.translation import gettext as _
from django.utils.translation import ngettext
from lms.djangoapps.courseware.access import has_access
from openedx.core.djangolib.markup import HTML, Text
from openedx.features.course_experience import RELATIVE_DATES_FLAG
%>
<%
course_sections = blocks.get('children')
self_paced = context.get('self_paced', False)
reset_deadlines_banner_displayed = False
relative_dates_flag_is_enabled = RELATIVE_DATES_FLAG.is_enabled(str(course_key))
is_course_staff = bool(user and course and has_access(user, 'staff', course, course.id))
dates_banner_displayed = False
%>
<main role="main" class="course-outline" id="main" tabindex="-1">
<div class="dates-banner-wrapper">
% if enrollment_mode and relative_dates_flag_is_enabled and self_paced and not is_course_staff:
<%include file="/dates_banner.html" />
% endif
</div>
% if course_sections is not None:
<button class="btn btn-primary"
id="expand-collapse-outline-all-button"
@@ -63,10 +72,10 @@ reset_deadlines_banner_displayed = False
due_date = subsection.get('due')
overdue = due_date is not None and due_date < timezone.now() and not subsection.get('complete', True)
%>
% if graded and overdue and not reset_deadlines_banner_displayed:
<% reset_deadlines_banner_displayed = True %>
% if graded and overdue and not dates_banner_displayed:
<% dates_banner_displayed = True %>
<script type="text/javascript">
$('.reset-deadlines-banner').css('display', 'flex');
$('.dates-banner-wrapper').css('display', 'block');
</script>
% endif
<li class="subsection accordion ${ 'current' if subsection.get('resume_block') else '' } ${graded} ${scored}">

View File

@@ -219,7 +219,7 @@ class TestCourseHomePage(CourseHomePageTestCase):
# Fetch the view and verify the query counts
# TODO: decrease query count as part of REVO-28
with self.assertNumQueries(75, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
with self.assertNumQueries(77, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST):
with check_mongo_calls(4):
url = course_home_url(self.course)
self.client.get(url)
@@ -1022,10 +1022,3 @@ class CourseHomeFragmentViewTests(ModuleStoreTestCase):
response = self.client.get(self.url)
self.assertContains(response, "<span>DISCOUNT_PRICE</span>")
@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">')

View File

@@ -7,6 +7,7 @@ import datetime
import json
import re
import ddt
import six
from completion import waffle
from completion.models import BlockCompletion
@@ -23,6 +24,7 @@ from six import text_type
from waffle.models import Switch
from waffle.testutils import override_switch
from course_modes.models import CourseMode
from lms.djangoapps.courseware.tests.factories import StaffFactory
from lms.urls import RESET_COURSE_DEADLINES_NAME
from gating import api as lms_gating_api
@@ -30,6 +32,7 @@ from lms.djangoapps.course_api.blocks.transformers.milestones import MilestonesA
from openedx.core.djangoapps.schedules.models import Schedule
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
from openedx.core.lib.gating import api as gating_api
from openedx.features.course_experience import RELATIVE_DATES_FLAG
from openedx.features.course_experience.views.course_outline import (
DEFAULT_COMPLETION_TRACKING_START,
CourseOutlineFragmentView
@@ -46,6 +49,7 @@ TEST_PASSWORD = 'test'
GATING_NAMESPACE_QUALIFIER = '.gating'
@ddt.ddt
class TestCourseOutlinePage(SharedModuleStoreTestCase):
"""
Test the course outline view.
@@ -164,6 +168,35 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
self.assertRegex(content, sequential2.display_name + r'\s*\(1 Question\)\s*</h4>')
self.assertRegex(content, sequential3.display_name + r'\s*\(2 Questions\)\s*</h4>')
@RELATIVE_DATES_FLAG.override(active=True)
@ddt.data(
(CourseMode.AUDIT, False, True),
(CourseMode.VERIFIED, False, True),
(CourseMode.MASTERS, False, False),
(CourseMode.VERIFIED, True, False),
)
@ddt.unpack
def test_reset_course_deadlines_banner_shows_for_self_paced_course(
self,
enrollment_mode,
is_course_staff,
should_display
):
course = self.courses[0]
enrollment = CourseEnrollment.objects.get(course_id=course.id)
enrollment.mode = enrollment_mode
enrollment.save()
self.user.is_staff = is_course_staff
self.user.save()
url = course_home_url(course)
response = self.client.get(url)
if should_display:
self.assertContains(response, '<div class="dates-banner-text"')
else:
self.assertNotContains(response, '<div class="dates-banner-text"')
def test_reset_course_deadlines(self):
course = self.courses[0]
enrollment = CourseEnrollment.objects.get(course_id=course.id)

View File

@@ -13,10 +13,12 @@ from six.moves import range
from course_modes.models import CourseMode
from lms.djangoapps.course_api.blocks.api import get_blocks
from lms.djangoapps.course_blocks.api import get_course_blocks
from lms.djangoapps.course_blocks.utils import get_student_module_as_dict
from lms.djangoapps.courseware.access import has_access
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.cache_utils import request_cached
from openedx.features.course_experience import RELATIVE_DATES_FLAG
from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore
@@ -253,38 +255,43 @@ def get_resume_block(block):
return block
def reset_deadlines_banner_should_display(course_key, request):
def dates_banner_should_display(course_key, request):
"""
Return whether or not the reset banner should display,
determined by whether or not a course has any past-due,
incomplete sequentials
incomplete sequentials and which enrollment mode is being
dealt with for the current user and course.
"""
display_reset_dates_banner = False
course_overview = CourseOverview.objects.get(id=str(course_key))
course_end_date = getattr(course_overview, 'end_date', None)
is_self_paced = getattr(course_overview, 'self_paced', False)
is_course_staff = bool(
request.user and course_overview and has_access(request.user, 'staff', course_overview, course_overview.id)
)
if is_self_paced and (not is_course_staff) and (not course_end_date or timezone.now() < course_end_date):
if CourseEnrollment.objects.filter(
course=course_overview, user=request.user,
).filter(
Q(mode=CourseMode.AUDIT) | Q(mode=CourseMode.VERIFIED)
).exists():
course_block_tree = get_course_outline_block_tree(
request, str(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('graded', False)
and subsection.get('due', timezone.now() + timedelta(1)) < timezone.now()
):
display_reset_dates_banner = True
missed_deadlines = False
course_enrollment = None
if RELATIVE_DATES_FLAG.is_enabled(str(course_key)):
course_overview = CourseOverview.objects.get(id=str(course_key))
course_end_date = getattr(course_overview, 'end_date', None)
is_self_paced = getattr(course_overview, 'self_paced', False)
is_course_staff = bool(
request.user and course_overview and has_access(request.user, 'staff', course_overview, course_overview.id)
)
if is_self_paced and (not is_course_staff) and (not course_end_date or timezone.now() < course_end_date):
course_enrollment = CourseEnrollment.objects.filter(
course=course_overview, user=request.user,
).filter(
Q(mode=CourseMode.AUDIT) | Q(mode=CourseMode.VERIFIED)
).first()
if course_enrollment:
store = modulestore()
course_usage_key = store.make_course_usage_key(course_key)
block_data = get_course_blocks(request.user, course_usage_key, include_completion=True)
for section_key in block_data.get_children(course_usage_key):
if missed_deadlines:
break
return display_reset_dates_banner
for subsection_key in block_data.get_children(section_key):
if (
not block_data.get_xblock_field(subsection_key, 'complete', False)
and block_data.get_xblock_field(subsection_key, 'graded', False)
and block_data.get_xblock_field(
subsection_key, 'due', timezone.now() + timedelta(1)) < timezone.now()
):
missed_deadlines = True
break
return missed_deadlines, getattr(course_enrollment, 'mode', None)

View File

@@ -9,10 +9,12 @@ import six
from completion import waffle as completion_waffle
from django.contrib.auth.models import User
from django.db.models import Q
from django.shortcuts import redirect
from django.template.context_processors import csrf
from django.template.loader import render_to_string
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.csrf import ensure_csrf_cookie
import edx_when.api as edx_when_api
from opaque_keys.edx.keys import CourseKey
@@ -20,11 +22,16 @@ from pytz import UTC
from waffle.models import Switch
from web_fragments.fragment import Fragment
from course_modes.models import CourseMode
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.courses import get_course_overview_with_access
from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link
from lms.djangoapps.courseware.masquerade import setup_masquerade
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.djangoapps.plugin_api.views import EdxFragmentView
from openedx.core.djangoapps.schedules.utils import reset_self_paced_schedule
from openedx.features.course_experience import RELATIVE_DATES_FLAG
from student.models import CourseEnrollment
from util.milestones_helpers import get_course_content_milestones
from xmodule.course_module import COURSE_VISIBILITY_PUBLIC
from xmodule.modulestore.django import modulestore
@@ -43,6 +50,9 @@ class CourseOutlineFragmentView(EdxFragmentView):
"""
Renders the course outline as a fragment.
"""
from lms.urls import RESET_COURSE_DEADLINES_NAME
from openedx.features.course_experience.urls import COURSE_HOME_VIEW_NAME
course_key = CourseKey.from_string(course_id)
course_overview = get_course_overview_with_access(
request.user, 'load', course_key, check_if_enrolled=user_is_enrolled
@@ -61,6 +71,7 @@ class CourseOutlineFragmentView(EdxFragmentView):
'due_date_display_format': course.due_date_display_format,
'blocks': course_block_tree,
'enable_links': user_is_enrolled or course.course_visibility == COURSE_VISIBILITY_PUBLIC,
'course_key': course_key,
}
resume_block = get_resume_block(course_block_tree) if user_is_enrolled else None
@@ -82,6 +93,21 @@ class CourseOutlineFragmentView(EdxFragmentView):
# managed by edx-when.
context['in_edx_when'] = edx_when_api.is_enabled_for_course(course_key)
reset_deadlines_url = reverse(RESET_COURSE_DEADLINES_NAME)
reset_deadlines_redirect_url_base = COURSE_HOME_VIEW_NAME
course_enrollment = None
if not request.user.is_anonymous:
course_enrollment = CourseEnrollment.objects.filter(course=course_overview, user=request.user).filter(
Q(mode=CourseMode.AUDIT) | Q(mode=CourseMode.VERIFIED)).first()
context['reset_deadlines_url'] = reset_deadlines_url
context['reset_deadlines_redirect_url_base'] = reset_deadlines_redirect_url_base
context['reset_deadlines_redirect_url_id_dict'] = {'course_id': str(course.id)}
context['enrollment_mode'] = getattr(course_enrollment, 'mode', None)
context['verified_upgrade_link'] = verified_upgrade_deadline_link(request.user, course=course),
context['on_course_outline_page'] = True,
html = render_to_string('course_experience/course-outline-fragment.html', context)
return Fragment(html)