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 06839a93c3..1453198574 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..b0483ee3a3 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,66 @@
}
// 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;
+ font-weight: $font-bold;
+ color: theme-color("dark");
}
+
+ .description {
+ margin-bottom: 0;
+ display: inline-block;
+ }
+
+ .heading, .description {
+ font-size: 0.9rem;
+ }
+
+ .date-summary-link {
+ margin-top: 8px;
+ 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 1b5ed1323b..6dfd476157 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:
-
- ${course_date.link_text}
-
- % endif
-
+
+
+
+ % 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