From fe91d63a0bee5b80b6d08807fe09ca1d5d7636d4 Mon Sep 17 00:00:00 2001 From: Dillon Dumesnil Date: Mon, 13 Jan 2020 16:00:41 -0500 Subject: [PATCH 1/3] Show relevant dates in course dates sidebar This includes (at least) upcoming assignments, FBE access expiration, and course end date. AA-4 --- lms/djangoapps/courseware/course_tools.py | 2 +- lms/djangoapps/courseware/courses.py | 59 +++++-- lms/djangoapps/courseware/date_summary.py | 114 +++++++----- lms/djangoapps/courseware/tests/helpers.py | 2 +- .../courseware/tests/test_date_summary.py | 167 +++++++++++++++++- lms/djangoapps/courseware/utils.py | 56 ++++++ lms/djangoapps/experiments/utils.py | 2 +- lms/djangoapps/experiments/views_custom.py | 2 +- .../sass/features/_course-experience.scss | 111 ++++++------ .../core/djangoapps/schedules/resolvers.py | 2 +- .../features/course_duration_limits/access.py | 2 +- .../course_experience/dates-summary.html | 40 ++--- .../tests/views/test_course_home.py | 4 +- .../course_experience/views/course_dates.py | 2 +- .../course_experience/views/course_sock.py | 2 +- openedx/features/discounts/utils.py | 2 +- 16 files changed, 422 insertions(+), 147 deletions(-) create mode 100644 lms/djangoapps/courseware/utils.py diff --git a/lms/djangoapps/courseware/course_tools.py b/lms/djangoapps/courseware/course_tools.py index ecad90aee0..b13116cd61 100644 --- a/lms/djangoapps/courseware/course_tools.py +++ b/lms/djangoapps/courseware/course_tools.py @@ -10,7 +10,7 @@ from crum import get_current_request from django.utils.translation import ugettext as _ from course_modes.models import CourseMode -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from openedx.features.course_experience.course_tools import CourseTool from student.models import CourseEnrollment diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index a7477fe279..bdeb662269 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -16,6 +16,7 @@ from django.db.models import Prefetch from django.http import Http404, QueryDict from django.urls import reverse from edx_django_utils.monitoring import function_trace +from edx_when.api import get_dates_for_course from fs.errors import ResourceNotFound from opaque_keys.edx.keys import UsageKey from path import Path as path @@ -27,7 +28,9 @@ from lms.djangoapps.courseware.access import has_access from lms.djangoapps.courseware.access_response import MilestoneAccessError, StartDateError from lms.djangoapps.courseware.date_summary import ( CertificateAvailableDate, + CourseAssignmentDate, CourseEndDate, + CourseExpiredDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, @@ -390,13 +393,14 @@ def get_course_info_section(request, user, course, section_key): return html -def get_course_date_blocks(course, user): +def get_course_date_blocks(course, user, request=None, include_past_dates=False, num_assignments=None): """ Return the list of blocks to display on the course info page, sorted by date. """ block_classes = [ CourseEndDate, + CourseExpiredDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, @@ -405,17 +409,50 @@ def get_course_date_blocks(course, user): if certs_api.get_active_web_certificate(course): block_classes.insert(0, CertificateAvailableDate) - blocks = (cls(course, user) for cls in block_classes) + blocks = [cls(course, user) for cls in block_classes] + blocks.extend(get_course_assignment_due_dates( + course, user, request, num_return=num_assignments, include_past_dates=include_past_dates)) - def block_key_fn(block): - """ - If the block's date is None, return the maximum datetime in order - to force it to the end of the list of displayed blocks. - """ - if block.date is None: - return datetime.max.replace(tzinfo=pytz.UTC) - return block.date - return sorted((b for b in blocks if b.is_enabled), key=block_key_fn) + return sorted((b for b in blocks if b.date and (b.is_enabled or include_past_dates)), key=date_block_key_fn) + + +def date_block_key_fn(block): + """ + If the block's date is None, return the maximum datetime in order + to force it to the end of the list of displayed blocks. + """ + return block.date or datetime.max.replace(tzinfo=pytz.UTC) + + +def get_course_assignment_due_dates(course, user, request, num_return=None, include_past_dates=False): + """ + Returns a list of assignment (at the subsection/sequential level) due date + blocks for the given course. Will return num_return results or all results + if num_return is None in date increasing order. + """ + store = modulestore() + all_course_dates = get_dates_for_course(course.id, user) + date_blocks = [] + for (block_key, date_type), date in all_course_dates.items(): + if date_type == 'due' and block_key.block_type == 'sequential': + item = store.get_item(block_key) + if item.graded: + date_block = CourseAssignmentDate(course, user) + date_block.date = date + + block_url = None + now = datetime.now().replace(tzinfo=pytz.UTC) + assignment_released = item.start < now + if assignment_released: + block_url = reverse('jump_to', args=[course.id, block_key]) + block_url = request.build_absolute_uri(block_url) if request else None + date_block.set_title(item.display_name, block_url) + + date_blocks.append(date_block) + date_blocks = sorted((b for b in date_blocks if b.is_enabled or include_past_dates), key=date_block_key_fn) + if num_return: + return date_blocks[:num_return] + return date_blocks # TODO: Fix this such that these are pulled in as extra course-specific tabs. diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index ffcb774209..a431983b0c 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -20,11 +20,14 @@ from lazy import lazy from pytz import utc from course_modes.models import CourseMode, get_cosmetic_verified_display_price -from lms.djangoapps.commerce.utils import EcommerceService +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid from lms.djangoapps.verify_student.models import VerificationDeadline from lms.djangoapps.verify_student.services import IDVerificationService +from openedx.core.djangoapps.catalog.utils import get_course_run_details from openedx.core.djangoapps.certificates.api import can_show_certificate_available_date_field from openedx.core.djangolib.markup import HTML, Text +from openedx.features.course_duration_limits.access import get_user_course_expiration_date +from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_experience import UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages from student.models import CourseEnrollment @@ -287,6 +290,14 @@ class CourseEndDate(DateSummary): @property def date(self): + if self.course.self_paced: + weeks_to_complete = get_course_run_details(self.course.id, ['weeks_to_complete']).get('weeks_to_complete') + if weeks_to_complete: + course_duration = datetime.timedelta(weeks=weeks_to_complete) + if self.course.end < (self.current_time + course_duration): + return self.course.end + return None + return self.course.end def register_alerts(self, request, course): @@ -318,6 +329,60 @@ class CourseEndDate(DateSummary): ) +class CourseAssignmentDate(DateSummary): + """ + Displays due dates for homework assignments with a link to the homework + assignment if the link is provided. + """ + css_class = 'assignment' + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.assignment_date = None + self.assignment_title = None + + @property + def date(self): + return self.assignment_date + + @date.setter + def date(self, date): + self.assignment_date = date + + @property + def title(self): + return self.assignment_title + + def set_title(self, title, link=None): + if link: + self.assignment_title = HTML( + '{assignment_title}' + ).format(assignment_link=link, assignment_title=title) + else: + self.assignment_title = title + + +class CourseExpiredDate(DateSummary): + """ + Displays the course expiration date for Audit learners (if enabled) + """ + css_class = 'course-expired' + + @property + def date(self): + if not CourseDurationLimitConfig.enabled_for_enrollment(user=self.user, course_key=self.course_id): + return + return get_user_course_expiration_date(self.user, self.course) + + @property + def description(self): + return _('You lose all access to this course, including your progress.') + + @property + def title(self): + return _('Audit Access Expires') + + class CertificateAvailableDate(DateSummary): """ Displays the certificate available date of the course. @@ -384,53 +449,6 @@ class CertificateAvailableDate(DateSummary): ) -def verified_upgrade_deadline_link(user, course=None, course_id=None): - """ - Format the correct verified upgrade link for the specified ``user`` - in a course. - - One of ``course`` or ``course_id`` must be supplied. If both are specified, - ``course`` will take priority. - - Arguments: - user (:class:`~django.contrib.auth.models.User`): The user to display - the link for. - course (:class:`.CourseOverview`): The course to render a link for. - course_id (:class:`.CourseKey`): The course_id of the course to render for. - - Returns: - The formatted link that will allow the user to upgrade to verified - in this course. - """ - if course is not None: - course_id = course.id - return EcommerceService().upgrade_url(user, course_id) - - -def verified_upgrade_link_is_valid(enrollment=None): - """ - Return whether this enrollment can be upgraded. - - Arguments: - enrollment (:class:`.CourseEnrollment`): The enrollment under consideration. - If None, then the enrollment is considered to be upgradeable. - """ - # Return `true` if user is not enrolled in course - if enrollment is None: - return False - - upgrade_deadline = enrollment.upgrade_deadline - - if upgrade_deadline is None: - return False - - if datetime.datetime.now(utc).date() > upgrade_deadline.date(): - return False - - # Show the summary if user enrollment is in which allow user to upsell - return enrollment.is_active and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES - - class VerifiedUpgradeDeadlineDate(DateSummary): """ Displays the date before which learners must upgrade to the diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index 3918d30576..4a7d86e865 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -21,7 +21,7 @@ from xblock.field_data import DictFieldData from edxmako.shortcuts import render_to_string from lms.djangoapps.courseware.access import has_access -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from lms.djangoapps.courseware.masquerade import handle_ajax, setup_masquerade from lms.djangoapps.lms_xblock.field_data import LmsFieldData from openedx.core.djangoapps.content.course_overviews.models import CourseOverview diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 7f39f211dc..49908e8697 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -18,7 +18,9 @@ from course_modes.tests.factories import CourseModeFactory from lms.djangoapps.courseware.courses import get_course_date_blocks from lms.djangoapps.courseware.date_summary import ( CertificateAvailableDate, + CourseAssignmentDate, CourseEndDate, + CourseExpiredDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, @@ -38,10 +40,11 @@ from openedx.core.djangoapps.self_paced.models import SelfPacedConfiguration from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangoapps.user_api.preferences.api import set_user_preference from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag +from openedx.features.course_duration_limits.models import CourseDurationLimitConfig from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages from student.tests.factories import TEST_PASSWORD, CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @ddt.ddt @@ -129,6 +132,138 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) self.assert_block_types(course, user, expected_blocks) + def test_enabled_block_types_with_assignments(self): + """ + Creates a course with multiple subsections to test all of the different + cases for assignment dates showing up. Mocks out calling the edx-when + service and then validates the correct data is set and returned. + """ + course = create_course_run(days_till_start=-100) + user = create_user() + request = RequestFactory().request() + request.user = user + CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) + now = datetime.now(utc) + assignment_title_html = [''] + with self.store.bulk_operations(course.id): + section = ItemFactory.create(category='chapter', parent_location=course.location) + subsection_1 = ItemFactory.create( + category='sequential', + display_name='Released', + parent_location=section.location, + start=now - timedelta(days=1), + due=now + timedelta(days=6), + graded=True, + ) + subsection_2 = ItemFactory.create( + category='sequential', + display_name='Not released', + parent_location=section.location, + start=now + timedelta(days=1), + due=now + timedelta(days=7), + graded=True, + ) + subsection_3 = ItemFactory.create( + category='sequential', + display_name='Third nearest assignment', + parent_location=section.location, + start=now + timedelta(days=1), + due=now + timedelta(days=8), + graded=True, + ) + subsection_4 = ItemFactory.create( + category='sequential', + display_name='Past due date', + parent_location=section.location, + start=now - timedelta(days=14), + due=now - timedelta(days=7), + graded=True, + ) + subsection_5 = ItemFactory.create( + category='sequential', + display_name='Not returned since we do not get non-graded subsections', + parent_location=section.location, + start=now + timedelta(days=1), + due=now - timedelta(days=7), + graded=False, + ) + + with patch('lms.djangoapps.courseware.courses.get_dates_for_course') as mock_get_dates: + mock_get_dates.return_value = { + (subsection_1.location, 'due'): subsection_1.due, + (subsection_1.location, 'start'): subsection_1.start, + (subsection_2.location, 'due'): subsection_2.due, + (subsection_2.location, 'start'): subsection_2.start, + (subsection_3.location, 'due'): subsection_3.due, + (subsection_3.location, 'start'): subsection_3.start, + (subsection_4.location, 'due'): subsection_4.due, + (subsection_4.location, 'start'): subsection_4.start, + (subsection_5.location, 'due'): subsection_5.due, + (subsection_5.location, 'start'): subsection_5.start, + } + # Standard widget case where we restrict the number of assignments. + expected_blocks = ( + TodaysDate, CourseAssignmentDate, CourseAssignmentDate, CourseEndDate, VerificationDeadlineDate + ) + blocks = get_course_date_blocks(course, user, request, num_assignments=2) + self.assertEqual(len(blocks), len(expected_blocks)) + self.assertEqual(set(type(b) for b in blocks), set(expected_blocks)) + assignment_blocks = filter(lambda b: isinstance(b, CourseAssignmentDate), blocks) + for assignment in assignment_blocks: + assignment_title = str(assignment.title) + self.assertNotEqual(assignment_title, 'Third nearest assignment') + self.assertNotEqual(assignment_title, 'Past due date') + self.assertNotEqual(assignment_title, 'Not returned since we do not get non-graded subsections') + # checking if it is _in_ the title instead of being the title since released assignments + # are actually links. Unreleased assignments are just the string of the title. + if 'Released' in assignment_title: + for html_tag in assignment_title_html: + self.assertIn(html_tag, assignment_title) + elif assignment_title == 'Not released': + for html_tag in assignment_title_html: + self.assertNotIn(html_tag, assignment_title) + + # No restrictions on number of assignments to return + expected_blocks = ( + CourseStartDate, TodaysDate, CourseAssignmentDate, CourseAssignmentDate, CourseAssignmentDate, + CourseAssignmentDate, CourseEndDate, VerificationDeadlineDate + ) + blocks = get_course_date_blocks(course, user, request, include_past_dates=True) + self.assertEqual(len(blocks), len(expected_blocks)) + self.assertEqual(set(type(b) for b in blocks), set(expected_blocks)) + assignment_blocks = filter(lambda b: isinstance(b, CourseAssignmentDate), blocks) + for assignment in assignment_blocks: + assignment_title = str(assignment.title) + self.assertNotEqual(assignment_title, 'Not returned since we do not get non-graded subsections') + # checking if it is _in_ the title instead of being the title since released assignments + # are actually links. Unreleased assignments are just the string of the title. + if 'Released' in assignment_title: + for html_tag in assignment_title_html: + self.assertIn(html_tag, assignment_title) + elif assignment_title == 'Not released': + for html_tag in assignment_title_html: + self.assertNotIn(html_tag, assignment_title) + elif assignment_title == 'Third nearest assignment': + # It's still not released + for html_tag in assignment_title_html: + self.assertNotIn(html_tag, assignment_title) + elif 'Past due date' in assignment_title: + self.assertGreater(now, assignment.date) + for html_tag in assignment_title_html: + self.assertIn(html_tag, assignment_title) + + def test_enabled_block_types_with_expired_course(self): + course = create_course_run(days_till_start=-100) + user = create_user() + # These two lines are to trigger the course expired block to be rendered + CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.AUDIT) + CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=utc)) + + expected_blocks = ( + TodaysDate, CourseEndDate, CourseExpiredDate, VerifiedUpgradeDeadlineDate + ) + self.assert_block_types(course, user, expected_blocks) + @ddt.data( # Course not started ({}, (CourseStartDate, TodaysDate, CourseEndDate)), @@ -177,7 +312,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): html_elements = [ '

Upcoming Dates

', - '
', + '
Upcoming Dates', - '
', + '
time til end (end date shown) + {'weeks_to_complete': 4}, # Weeks to complete < time til end (end date not shown) + ) + def test_course_end_date_self_paced(self, cr_details): + """ + In self-paced courses, the end date will now only show up if the learner + views the course within the course's weeks to complete (as defined in + the course-discovery service). E.g. if the weeks to complete is 5 weeks + and the course doesn't end for 10 weeks, there will be no end date, but + if the course ends in 3 weeks, the end date will appear. + """ + now = datetime.now(utc) + end_timedelta_number = 5 + course = CourseFactory.create( + start=now + timedelta(days=-7), end=now + timedelta(weeks=end_timedelta_number), self_paced=True) + user = create_user() + with patch('lms.djangoapps.courseware.date_summary.get_course_run_details') as mock_get_cr_details: + mock_get_cr_details.return_value = cr_details + block = CourseEndDate(course, user) + self.assertEqual(block.title, 'Course End') + if cr_details['weeks_to_complete'] > end_timedelta_number: + self.assertEqual(block.date, course.end) + else: + self.assertIsNone(block.date) + def test_ecommerce_checkout_redirect(self): """Verify the block link redirects to ecommerce checkout if it's enabled.""" sku = 'TESTSKU' diff --git a/lms/djangoapps/courseware/utils.py b/lms/djangoapps/courseware/utils.py new file mode 100644 index 0000000000..a378ace954 --- /dev/null +++ b/lms/djangoapps/courseware/utils.py @@ -0,0 +1,56 @@ +"""Utility functions that have to do with the courseware.""" + + +import datetime + +from lms.djangoapps.commerce.utils import EcommerceService +from pytz import utc + +from course_modes.models import CourseMode + + +def verified_upgrade_deadline_link(user, course=None, course_id=None): + """ + Format the correct verified upgrade link for the specified ``user`` + in a course. + + One of ``course`` or ``course_id`` must be supplied. If both are specified, + ``course`` will take priority. + + Arguments: + user (:class:`~django.contrib.auth.models.User`): The user to display + the link for. + course (:class:`.CourseOverview`): The course to render a link for. + course_id (:class:`.CourseKey`): The course_id of the course to render for. + + Returns: + The formatted link that will allow the user to upgrade to verified + in this course. + """ + if course is not None: + course_id = course.id + return EcommerceService().upgrade_url(user, course_id) + + +def verified_upgrade_link_is_valid(enrollment=None): + """ + Return whether this enrollment can be upgraded. + + Arguments: + enrollment (:class:`.CourseEnrollment`): The enrollment under consideration. + If None, then the enrollment is considered to be upgradeable. + """ + # Return `true` if user is not enrolled in course + if enrollment is None: + return False + + upgrade_deadline = enrollment.upgrade_deadline + + if upgrade_deadline is None: + return False + + if datetime.datetime.now(utc).date() > upgrade_deadline.date(): + return False + + # Show the summary if user enrollment is in which allow user to upsell + return enrollment.is_active and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES diff --git a/lms/djangoapps/experiments/utils.py b/lms/djangoapps/experiments/utils.py index c0a524f75f..3e4420e33f 100644 --- a/lms/djangoapps/experiments/utils.py +++ b/lms/djangoapps/experiments/utils.py @@ -13,7 +13,7 @@ from opaque_keys.edx.keys import CourseKey from course_modes.models import format_course_price, get_cosmetic_verified_display_price, CourseMode from lms.djangoapps.courseware.access import has_staff_access_to_preview_mode -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid from entitlements.models import CourseEntitlement from lms.djangoapps.commerce.utils import EcommerceService from openedx.core.djangoapps.catalog.utils import get_programs diff --git a/lms/djangoapps/experiments/views_custom.py b/lms/djangoapps/experiments/views_custom.py index 99d20d8fac..9e10d9d9cd 100644 --- a/lms/djangoapps/experiments/views_custom.py +++ b/lms/djangoapps/experiments/views_custom.py @@ -23,7 +23,7 @@ from openedx.core.lib.api.authentication import OAuth2AuthenticationAllowInactiv from openedx.core.lib.api.permissions import ApiKeyHeaderPermissionIsAuthenticated from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin -from lms.djangoapps.courseware.date_summary import verified_upgrade_link_is_valid +from lms.djangoapps.courseware.utils import verified_upgrade_link_is_valid from course_modes.models import get_cosmetic_verified_display_price from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group diff --git a/lms/static/sass/features/_course-experience.scss b/lms/static/sass/features/_course-experience.scss index 3f91244b4f..b1fe70780b 100644 --- a/lms/static/sass/features/_course-experience.scss +++ b/lms/static/sass/features/_course-experience.scss @@ -14,8 +14,8 @@ text-align: center; } - &:not(:first-child) { - margin-top: $baseline; + &:not(:last-child) { + margin-bottom: 32px; } } } @@ -412,64 +412,69 @@ } // date summary -.date-summary-container { - .date-summary { - @include clearfix; +.date-summary { + @include clearfix; - display: flex; - justify-content: space-between; - padding: $baseline/2 $baseline/2 $baseline/2 0; + display: flex; + justify-content: space-between; + padding: 12px 0; + &:last-of-type { + padding-bottom: 0; + } - .left-column { - flex: 5%; + .left-column { + flex: 0 0 24px; - .calendar-icon { - margin-top: 3px; - height: 1em; - width: auto; - background: url('#{$static-path}/images/calendar-alt-regular.svg'); - background-repeat: no-repeat; - } + .calendar-icon { + margin-top: 4px; + height: 1em; + width: 16px; + background: url('#{$static-path}/images/calendar-alt-regular.svg'); + background-repeat: no-repeat; + } + } + + .right-column { + flex: auto; + + .localized-datetime { + font-weight: $font-weight-bold; + margin-bottom: 8px; } - .right-column { - flex: 85%; - - .localized-datetime { - font-weight: $font-weight-bold; - margin-bottom: 8px; - } - - .heading { - font: -apple-system-body; - line-height: 1; - font-weight: $font-bold; - color: theme-color("dark"); - } - - .description { - margin-bottom: $baseline/2; - display: inline-block; - } - - .heading, .description { - font-size: 0.9rem; - } - - .date-summary-link { - font-weight: $font-semibold; - - a { - color: $link-color; - font-weight: $font-regular; - } - } - } - - .date { - color: theme-color("dark"); + .heading { font: -apple-system-body; + line-height: 1.5; + font-weight: $font-bold; + color: theme-color("dark"); + + a { + font-weight: $font-semibold; + } } + + .description { + margin-bottom: 0; + display: inline-block; + } + + .heading, .description { + font-size: 0.9rem; + } + + .date-summary-link { + font-weight: $font-semibold; + + a { + color: $link-color; + font-size: 0.9rem; + } + } + } + + .date { + color: theme-color("dark"); + font: -apple-system-body; } } diff --git a/openedx/core/djangoapps/schedules/resolvers.py b/openedx/core/djangoapps/schedules/resolvers.py index c7b22a07d0..6437e28238 100644 --- a/openedx/core/djangoapps/schedules/resolvers.py +++ b/openedx/core/djangoapps/schedules/resolvers.py @@ -14,7 +14,7 @@ from edx_ace.recipient import Recipient from edx_ace.recipient_resolver import RecipientResolver from edx_django_utils.monitoring import function_trace, set_custom_metric -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid from lms.djangoapps.discussion.notification_prefs.views import UsernameCipher from openedx.core.djangoapps.ace_common.template_context import get_base_template_context from openedx.core.djangoapps.schedules.config import COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH diff --git a/openedx/features/course_duration_limits/access.py b/openedx/features/course_duration_limits/access.py index a528416fb4..8f69656b5a 100644 --- a/openedx/features/course_duration_limits/access.py +++ b/openedx/features/course_duration_limits/access.py @@ -17,7 +17,7 @@ from web_fragments.fragment import Fragment from course_modes.models import CourseMode from lms.djangoapps.courseware.access_response import AccessError from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from lms.djangoapps.courseware.masquerade import get_course_masquerade, is_masquerading_as_specific_student from openedx.core.djangoapps.catalog.utils import get_course_run_details from openedx.core.djangoapps.content.course_overviews.models import CourseOverview diff --git a/openedx/features/course_experience/templates/course_experience/dates-summary.html b/openedx/features/course_experience/templates/course_experience/dates-summary.html index 36952923c8..e8a66c083b 100644 --- a/openedx/features/course_experience/templates/course_experience/dates-summary.html +++ b/openedx/features/course_experience/templates/course_experience/dates-summary.html @@ -2,26 +2,24 @@ from django.utils.translation import ugettext as _ %> <%page args="course_date" expression_filter="h"/> -
-
-
-
-
-
+
+
+
+
+
+ % if course_date.date: +

+ % endif + % if course_date.title: +
${course_date.title}
+ % endif + % if course_date.description: +

${course_date.description}

+ % endif + % if course_date.link and course_date.link_text: + + % endif
diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py index ea0e6e7a4b..74562c19b3 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -25,7 +25,7 @@ from experiments.models import ExperimentData from lms.djangoapps.commerce.models import CommerceConfiguration from lms.djangoapps.commerce.utils import EcommerceService from lms.djangoapps.course_goals.api import add_course_goal, remove_course_goal -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from lms.djangoapps.courseware.tests.factories import ( BetaTesterFactory, GlobalStaffFactory, @@ -218,7 +218,7 @@ class TestCourseHomePage(CourseHomePageTestCase): # Fetch the view and verify the query counts # TODO: decrease query count as part of REVO-28 - with self.assertNumQueries(74, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): + with self.assertNumQueries(76, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with check_mongo_calls(4): url = course_home_url(self.course) self.client.get(url) diff --git a/openedx/features/course_experience/views/course_dates.py b/openedx/features/course_experience/views/course_dates.py index 204a273494..91c2bbfd21 100644 --- a/openedx/features/course_experience/views/course_dates.py +++ b/openedx/features/course_experience/views/course_dates.py @@ -25,7 +25,7 @@ class CourseDatesFragmentView(EdxFragmentView): """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) - course_date_blocks = get_course_date_blocks(course, request.user) + course_date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=2) context = { 'course_date_blocks': [block for block in course_date_blocks if block.title != 'current_datetime'] diff --git a/openedx/features/course_experience/views/course_sock.py b/openedx/features/course_experience/views/course_sock.py index 695aef426b..a41d9b8f81 100644 --- a/openedx/features/course_experience/views/course_sock.py +++ b/openedx/features/course_experience/views/course_sock.py @@ -6,7 +6,7 @@ Fragment for rendering the course's sock and associated toggle button. from django.template.loader import render_to_string from web_fragments.fragment import Fragment -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link, verified_upgrade_link_is_valid from openedx.core.djangoapps.plugin_api.views import EdxFragmentView from openedx.features.discounts.utils import format_strikeout_price from student.models import CourseEnrollment diff --git a/openedx/features/discounts/utils.py b/openedx/features/discounts/utils.py index 8b9840e00e..53ac8a898e 100644 --- a/openedx/features/discounts/utils.py +++ b/openedx/features/discounts/utils.py @@ -11,7 +11,7 @@ from edx_django_utils.cache import RequestCache import pytz from course_modes.models import get_course_prices, format_course_price -from lms.djangoapps.courseware.date_summary import verified_upgrade_deadline_link +from lms.djangoapps.courseware.utils import verified_upgrade_deadline_link from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from experiments.models import ExperimentData From 5853cddfc197450e906ebef2dc5b0ad7e4642232 Mon Sep 17 00:00:00 2001 From: Dillon Dumesnil Date: Tue, 11 Feb 2020 11:07:47 -0500 Subject: [PATCH 2/3] AA-4: Extreme defensiveness over any fields accessed from modulestore --- .../contentstore/tests/test_request_event.py | 4 +- lms/djangoapps/courseware/courses.py | 6 ++- lms/djangoapps/courseware/date_summary.py | 16 +++++-- .../courseware/tests/test_date_summary.py | 42 +++++++++++++++++-- .../course_experience/dates-summary.html | 4 +- scripts/thresholds.sh | 2 +- 6 files changed, 61 insertions(+), 13 deletions(-) diff --git a/cms/djangoapps/contentstore/tests/test_request_event.py b/cms/djangoapps/contentstore/tests/test_request_event.py index 7ac72f25a0..9c5bebe58d 100644 --- a/cms/djangoapps/contentstore/tests/test_request_event.py +++ b/cms/djangoapps/contentstore/tests/test_request_event.py @@ -3,7 +3,7 @@ from django.test import TestCase from django.urls import reverse -from six import unichr # pylint: disable=W0622 +from six import unichr from contentstore.views.helpers import event as cms_user_track @@ -20,7 +20,7 @@ class CMSLogTest(TestCase): """ requests = [ {"event": "my_event", "event_type": "my_event_type", "page": "my_page"}, - {"event": "{'json': 'object'}", "event_type": unichr(512), "page": "my_page"} # pylint: disable=unicode-format-string + {"event": "{'json': 'object'}", "event_type": unichr(512), "page": "my_page"} ] for request_params in requests: response = self.client.post(reverse(cms_user_track), request_params) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index bdeb662269..2163d7f151 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -15,6 +15,7 @@ from django.conf import settings from django.db.models import Prefetch from django.http import Http404, QueryDict from django.urls import reverse +from django.utils.translation import ugettext as _ from edx_django_utils.monitoring import function_trace from edx_when.api import get_dates_for_course from fs.errors import ResourceNotFound @@ -442,11 +443,12 @@ def get_course_assignment_due_dates(course, user, request, num_return=None, incl block_url = None now = datetime.now().replace(tzinfo=pytz.UTC) - assignment_released = item.start < now + assignment_released = item.start < now if item.start else None if assignment_released: block_url = reverse('jump_to', args=[course.id, block_key]) block_url = request.build_absolute_uri(block_url) if request else None - date_block.set_title(item.display_name, block_url) + assignment_title = item.display_name if item.display_name else _('Assignment') + date_block.set_title(assignment_title, block_url) date_blocks.append(date_block) date_blocks = sorted((b for b in date_blocks if b.is_enabled or include_past_dates), key=date_block_key_fn) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index a431983b0c..d80a59da9a 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -62,6 +62,11 @@ class DateSummary(object): """The title of this summary.""" return '' + @property + def title_html(self): + """The title as html for this summary.""" + return '' + @property def description(self): """The detail text displayed by this summary.""" @@ -340,6 +345,7 @@ class CourseAssignmentDate(DateSummary): super().__init__(*args, **kwargs) self.assignment_date = None self.assignment_title = None + self.assignment_title_html = None @property def date(self): @@ -353,13 +359,17 @@ class CourseAssignmentDate(DateSummary): def title(self): return self.assignment_title + @property + def title_html(self): + return self.assignment_title_html + def set_title(self, title, link=None): + """ Used to set the title_html and title properties for the assignment date block """ if link: - self.assignment_title = HTML( + self.assignment_title_html = HTML( '{assignment_title}' ).format(assignment_link=link, assignment_title=title) - else: - self.assignment_title = title + self.assignment_title = title class CourseExpiredDate(DateSummary): diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 49908e8697..82f33a1c69 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -132,7 +132,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) self.assert_block_types(course, user, expected_blocks) - def test_enabled_block_types_with_assignments(self): + def test_enabled_block_types_with_assignments(self): # pylint: disable=too-many-statements """ Creates a course with multiple subsections to test all of the different cases for assignment dates showing up. Mocks out calling the edx-when @@ -187,6 +187,24 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): due=now - timedelta(days=7), graded=False, ) + subsection_6 = ItemFactory.create( + category='sequential', + display_name='No start date', + parent_location=section.location, + start=None, + due=now + timedelta(days=9), + graded=True, + ) + subsection_7 = ItemFactory.create( + category='sequential', + # Setting display name to None should set the assignment title to 'Assignment' + display_name=None, + parent_location=section.location, + start=now - timedelta(days=14), + due=now + timedelta(days=10), + graded=True, + ) + dummy_subsection = ItemFactory.create(category='sequential') with patch('lms.djangoapps.courseware.courses.get_dates_for_course') as mock_get_dates: mock_get_dates.return_value = { @@ -200,6 +218,12 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): (subsection_4.location, 'start'): subsection_4.start, (subsection_5.location, 'due'): subsection_5.due, (subsection_5.location, 'start'): subsection_5.start, + (subsection_6.location, 'due'): subsection_6.due, + (subsection_7.location, 'due'): subsection_7.due, + (subsection_7.location, 'start'): subsection_7.start, + # Adding this in for the case where we return a block that + # doesn't actually exist as part of the course. Should just be ignored. + (dummy_subsection.location, 'due'): dummy_subsection.due, } # Standard widget case where we restrict the number of assignments. expected_blocks = ( @@ -210,7 +234,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): self.assertEqual(set(type(b) for b in blocks), set(expected_blocks)) assignment_blocks = filter(lambda b: isinstance(b, CourseAssignmentDate), blocks) for assignment in assignment_blocks: - assignment_title = str(assignment.title) + assignment_title = str(assignment.title_html) or str(assignment.title) self.assertNotEqual(assignment_title, 'Third nearest assignment') self.assertNotEqual(assignment_title, 'Past due date') self.assertNotEqual(assignment_title, 'Not returned since we do not get non-graded subsections') @@ -226,14 +250,15 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): # No restrictions on number of assignments to return expected_blocks = ( CourseStartDate, TodaysDate, CourseAssignmentDate, CourseAssignmentDate, CourseAssignmentDate, - CourseAssignmentDate, CourseEndDate, VerificationDeadlineDate + CourseAssignmentDate, CourseAssignmentDate, CourseAssignmentDate, CourseEndDate, + VerificationDeadlineDate ) blocks = get_course_date_blocks(course, user, request, include_past_dates=True) self.assertEqual(len(blocks), len(expected_blocks)) self.assertEqual(set(type(b) for b in blocks), set(expected_blocks)) assignment_blocks = filter(lambda b: isinstance(b, CourseAssignmentDate), blocks) for assignment in assignment_blocks: - assignment_title = str(assignment.title) + assignment_title = str(assignment.title_html) or str(assignment.title) self.assertNotEqual(assignment_title, 'Not returned since we do not get non-graded subsections') # checking if it is _in_ the title instead of being the title since released assignments # are actually links. Unreleased assignments are just the string of the title. @@ -251,6 +276,15 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): self.assertGreater(now, assignment.date) for html_tag in assignment_title_html: self.assertIn(html_tag, assignment_title) + elif 'No start date' == assignment_title: + # Can't determine if it is released so it does not get a link + for html_tag in assignment_title_html: + self.assertNotIn(html_tag, assignment_title) + # This is the item with no display name where we set one ourselves. + elif 'Assignment' in assignment_title: + # Can't determine if it is released so it does not get a link + for html_tag in assignment_title_html: + self.assertIn(html_tag, assignment_title) def test_enabled_block_types_with_expired_course(self): course = create_course_run(days_till_start=-100) diff --git a/openedx/features/course_experience/templates/course_experience/dates-summary.html b/openedx/features/course_experience/templates/course_experience/dates-summary.html index e8a66c083b..0cfa204bcc 100644 --- a/openedx/features/course_experience/templates/course_experience/dates-summary.html +++ b/openedx/features/course_experience/templates/course_experience/dates-summary.html @@ -10,7 +10,9 @@ from django.utils.translation import ugettext as _ % if course_date.date:

% endif - % if course_date.title: + % if course_date.title_html: +
${course_date.title_html}
+ % elif course_date.title:
${course_date.title}
% endif % if course_date.description: diff --git a/scripts/thresholds.sh b/scripts/thresholds.sh index 6f8bfb89d4..4a96f5c01b 100755 --- a/scripts/thresholds.sh +++ b/scripts/thresholds.sh @@ -2,6 +2,6 @@ set -e export LOWER_PYLINT_THRESHOLD=1000 -export UPPER_PYLINT_THRESHOLD=2005 +export UPPER_PYLINT_THRESHOLD=1993 export ESLINT_THRESHOLD=5530 export STYLELINT_THRESHOLD=880 From be7ef9804df8e6e8ecd253da2729a76212ab2c1c Mon Sep 17 00:00:00 2001 From: Dillon Dumesnil Date: Tue, 18 Feb 2020 10:15:00 -0500 Subject: [PATCH 3/3] AA-4: Adding waffle flag for dates widget --- lms/djangoapps/courseware/courses.py | 11 ++++++----- lms/djangoapps/courseware/date_summary.py | 4 ++-- lms/djangoapps/courseware/tests/test_date_summary.py | 7 ++++++- openedx/features/course_experience/__init__.py | 4 ++++ .../course_experience/tests/views/test_course_home.py | 2 +- scripts/thresholds.sh | 2 +- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py index 2163d7f151..5fa7d181d1 100644 --- a/lms/djangoapps/courseware/courses.py +++ b/lms/djangoapps/courseware/courses.py @@ -49,7 +49,7 @@ from openedx.core.djangoapps.enrollments.api import get_course_enrollment_detail from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.lib.api.view_utils import LazySequence from openedx.features.course_duration_limits.access import AuditExpiredError -from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG +from openedx.features.course_experience import COURSE_ENABLE_UNENROLLED_ACCESS_FLAG, DATE_WIDGET_V2_FLAG from static_replace import replace_static_urls from student.models import CourseEnrollment from survey.utils import is_survey_required_and_unanswered @@ -401,7 +401,6 @@ def get_course_date_blocks(course, user, request=None, include_past_dates=False, """ block_classes = [ CourseEndDate, - CourseExpiredDate, CourseStartDate, TodaysDate, VerificationDeadlineDate, @@ -411,8 +410,10 @@ def get_course_date_blocks(course, user, request=None, include_past_dates=False, block_classes.insert(0, CertificateAvailableDate) blocks = [cls(course, user) for cls in block_classes] - blocks.extend(get_course_assignment_due_dates( - course, user, request, num_return=num_assignments, include_past_dates=include_past_dates)) + if DATE_WIDGET_V2_FLAG.is_enabled(course.id): + blocks.append(CourseExpiredDate(course, user)) + blocks.extend(get_course_assignment_due_dates( + course, user, request, num_return=num_assignments, include_past_dates=include_past_dates)) return sorted((b for b in blocks if b.date and (b.is_enabled or include_past_dates)), key=date_block_key_fn) @@ -448,7 +449,7 @@ def get_course_assignment_due_dates(course, user, request, num_return=None, incl block_url = reverse('jump_to', args=[course.id, block_key]) block_url = request.build_absolute_uri(block_url) if request else None assignment_title = item.display_name if item.display_name else _('Assignment') - date_block.set_title(assignment_title, block_url) + date_block.set_title(assignment_title, link=block_url) date_blocks.append(date_block) date_blocks = sorted((b for b in date_blocks if b.is_enabled or include_past_dates), key=date_block_key_fn) diff --git a/lms/djangoapps/courseware/date_summary.py b/lms/djangoapps/courseware/date_summary.py index d80a59da9a..5746495de7 100644 --- a/lms/djangoapps/courseware/date_summary.py +++ b/lms/djangoapps/courseware/date_summary.py @@ -28,7 +28,7 @@ from openedx.core.djangoapps.certificates.api import can_show_certificate_availa from openedx.core.djangolib.markup import HTML, Text from openedx.features.course_duration_limits.access import get_user_course_expiration_date from openedx.features.course_duration_limits.models import CourseDurationLimitConfig -from openedx.features.course_experience import UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages +from openedx.features.course_experience import DATE_WIDGET_V2_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages from student.models import CourseEnrollment from .context_processor import user_timezone_locale_prefs @@ -295,7 +295,7 @@ class CourseEndDate(DateSummary): @property def date(self): - if self.course.self_paced: + if DATE_WIDGET_V2_FLAG.is_enabled(self.course_id) and self.course.self_paced: weeks_to_complete = get_course_run_details(self.course.id, ['weeks_to_complete']).get('weeks_to_complete') if weeks_to_complete: course_duration = datetime.timedelta(weeks=weeks_to_complete) diff --git a/lms/djangoapps/courseware/tests/test_date_summary.py b/lms/djangoapps/courseware/tests/test_date_summary.py index 82f33a1c69..48774aac3c 100644 --- a/lms/djangoapps/courseware/tests/test_date_summary.py +++ b/lms/djangoapps/courseware/tests/test_date_summary.py @@ -41,7 +41,9 @@ from openedx.core.djangoapps.site_configuration.tests.factories import SiteFacto from openedx.core.djangoapps.user_api.preferences.api import set_user_preference from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag from openedx.features.course_duration_limits.models import CourseDurationLimitConfig -from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages +from openedx.features.course_experience import ( + DATE_WIDGET_V2_FLAG, UNIFIED_COURSE_TAB_FLAG, UPGRADE_DEADLINE_MESSAGE, CourseHomeMessages +) from student.tests.factories import TEST_PASSWORD, CourseEnrollmentFactory, UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory @@ -132,6 +134,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): CourseEnrollmentFactory(course_id=course.id, user=user, mode=CourseMode.VERIFIED) self.assert_block_types(course, user, expected_blocks) + @override_waffle_flag(DATE_WIDGET_V2_FLAG, active=True) def test_enabled_block_types_with_assignments(self): # pylint: disable=too-many-statements """ Creates a course with multiple subsections to test all of the different @@ -286,6 +289,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): for html_tag in assignment_title_html: self.assertIn(html_tag, assignment_title) + @override_waffle_flag(DATE_WIDGET_V2_FLAG, active=True) def test_enabled_block_types_with_expired_course(self): course = create_course_run(days_till_start=-100) user = create_user() @@ -460,6 +464,7 @@ class CourseDateSummaryTest(SharedModuleStoreTestCase): {'weeks_to_complete': 7}, # Weeks to complete > time til end (end date shown) {'weeks_to_complete': 4}, # Weeks to complete < time til end (end date not shown) ) + @override_waffle_flag(DATE_WIDGET_V2_FLAG, active=True) def test_course_end_date_self_paced(self, cr_details): """ In self-paced courses, the end date will now only show up if the learner diff --git a/openedx/features/course_experience/__init__.py b/openedx/features/course_experience/__init__.py index a419ca520e..034541e53e 100644 --- a/openedx/features/course_experience/__init__.py +++ b/openedx/features/course_experience/__init__.py @@ -81,6 +81,10 @@ COURSE_ENABLE_UNENROLLED_ACCESS_FLAG = CourseWaffleFlag(SEO_WAFFLE_FLAG_NAMESPAC # Waffle flag to enable relative dates for course content RELATIVE_DATES_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'relative_dates') +# Waffle flag to enable showing FBE messaging, assignment due dates, and modified +# end date logic (for self-paced courses) in the date widget +DATE_WIDGET_V2_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'date_widget_v2') + def course_home_page_title(course): # pylint: disable=unused-argument """ diff --git a/openedx/features/course_experience/tests/views/test_course_home.py b/openedx/features/course_experience/tests/views/test_course_home.py index 74562c19b3..8b88c99381 100644 --- a/openedx/features/course_experience/tests/views/test_course_home.py +++ b/openedx/features/course_experience/tests/views/test_course_home.py @@ -218,7 +218,7 @@ class TestCourseHomePage(CourseHomePageTestCase): # Fetch the view and verify the query counts # TODO: decrease query count as part of REVO-28 - with self.assertNumQueries(76, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): + with self.assertNumQueries(74, table_blacklist=QUERY_COUNT_TABLE_BLACKLIST): with check_mongo_calls(4): url = course_home_url(self.course) self.client.get(url) diff --git a/scripts/thresholds.sh b/scripts/thresholds.sh index 4a96f5c01b..31d9d2fb00 100755 --- a/scripts/thresholds.sh +++ b/scripts/thresholds.sh @@ -2,6 +2,6 @@ set -e export LOWER_PYLINT_THRESHOLD=1000 -export UPPER_PYLINT_THRESHOLD=1993 +export UPPER_PYLINT_THRESHOLD=1985 export ESLINT_THRESHOLD=5530 export STYLELINT_THRESHOLD=880