Skip to content
Snippets Groups Projects
Unverified Commit 181cb701 authored by Michael Terry's avatar Michael Terry Committed by GitHub
Browse files

Merge pull request #25670 from edx/mikix/as-user-timezone

AA-459: Respect user's timezone in access-expiration message
parents 00ad3683 e8d9a254
No related branches found
No related tags found
No related merge requests found
......@@ -6,7 +6,6 @@ Convenience methods for working with datetime objects
import re
from datetime import datetime, timedelta
import six
from django.utils.translation import pgettext, ugettext
from pytz import UnknownTimeZoneError, timezone, utc
......@@ -59,7 +58,7 @@ def get_time_display(dtime, format_string=None, coerce_tz=None):
if dtime is None or format_string is None:
return get_default_time_display(dtime)
try:
return six.text_type(strftime_localized(dtime, format_string))
return strftime_localized(dtime, format_string)
except ValueError:
return get_default_time_display(dtime)
......
......@@ -416,24 +416,24 @@ def get_expiration_banner_text(user, course, language='en'):
Get text for banner that messages user course expiration date
for different tests that depend on it.
"""
expiration_date = now() + timedelta(weeks=4)
upgrade_link = verified_upgrade_deadline_link(user=user, course=course)
enrollment = CourseEnrollment.get_enrollment(user, course.id)
expiration_date = enrollment.created + timedelta(weeks=4)
upgrade_deadline = enrollment.upgrade_deadline
if upgrade_deadline is None or now() < upgrade_deadline:
upgrade_deadline = enrollment.course_upgrade_deadline
date_string = u'<span class="localized-datetime" data-format="shortDate" \
date_string = u'<span class="localized-datetime" data-format="shortDate" data-timezone="None" \
data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>'
formatted_expiration_date = date_string.format(
language=language,
formatted_date=expiration_date.strftime("%Y-%m-%d"),
formatted_date=expiration_date.isoformat(),
formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR)
)
if upgrade_deadline:
formatted_upgrade_deadline = date_string.format(
language=language,
formatted_date=upgrade_deadline.strftime("%Y-%m-%d"),
formatted_date=upgrade_deadline.isoformat(),
formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR)
)
......
......@@ -4,10 +4,7 @@ Contains code related to computing content gating course duration limits
and course access based on these limits.
"""
from datetime import timedelta
import six
import crum
from django.utils import timezone
from django.utils.translation import get_language
from django.utils.translation import ugettext as _
......@@ -15,17 +12,17 @@ from edx_django_utils.cache import RequestCache
from web_fragments.fragment import Fragment
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.date_utils import strftime_localized
from lms.djangoapps.courseware.access_response import AccessError
from lms.djangoapps.courseware.access_utils import ACCESS_GRANTED
from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs
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
from openedx.core.djangoapps.course_date_signals.utils import get_expected_duration
from openedx.core.djangolib.markup import HTML
from openedx.features.course_duration_limits.models import CourseDurationLimitConfig
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.date_utils import strftime_localized
EXPIRATION_DATE_FORMAT_STR = u'%b %-d, %Y'
......@@ -118,7 +115,7 @@ def check_course_expired(user, course):
def get_date_string():
# Creating this method to allow unit testing an issue where this string was missing the unicode prefix
return u'<span class="localized-datetime" data-format="shortDate" \
return u'<span class="localized-datetime" data-format="shortDate" data-timezone="{user_timezone}" \
data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>'
......@@ -130,7 +127,11 @@ def generate_course_expired_message(user, course):
if not expiration_date:
return
if is_masquerading_as_specific_student(user, course.id) and timezone.now() > expiration_date:
user_timezone_locale = user_timezone_locale_prefs(crum.get_current_request())
user_timezone = user_timezone_locale['user_timezone']
now = timezone.now()
if is_masquerading_as_specific_student(user, course.id) and now > expiration_date:
upgrade_message = _('This learner does not have access to this course. '
u'Their access expired on {expiration_date}.')
return HTML(upgrade_message).format(
......@@ -142,10 +143,8 @@ def generate_course_expired_message(user, course):
return
upgrade_deadline = enrollment.upgrade_deadline
now = timezone.now()
course_upgrade_deadline = enrollment.course_upgrade_deadline
if (not upgrade_deadline) or (upgrade_deadline < now):
upgrade_deadline = course_upgrade_deadline
upgrade_deadline = enrollment.course_upgrade_deadline
expiration_message = _(u'{strong_open}Audit Access Expires {expiration_date}{strong_close}'
u'{line_break}You lose all access to this course, including your progress, on '
......@@ -164,13 +163,15 @@ def generate_course_expired_message(user, course):
date_string = get_date_string()
formatted_expiration_date = date_string.format(
language=language,
formatted_date=expiration_date.strftime("%Y-%m-%d"),
user_timezone=user_timezone,
formatted_date=expiration_date.isoformat(),
formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR)
)
if using_upgrade_messaging:
formatted_upgrade_deadline = date_string.format(
language=language,
formatted_date=upgrade_deadline.strftime("%Y-%m-%d"),
user_timezone=user_timezone,
formatted_date=upgrade_deadline.isoformat(),
formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR)
)
......@@ -254,7 +255,7 @@ def course_expiration_wrapper(user, block, view, frag, context): # pylint: disa
# Course content must be escaped to render correctly due to the way the
# way the XBlock rendering works. Transforming the safe markup to unicode
# escapes correctly.
course_expiration_fragment.content = six.text_type(course_expiration_fragment.content)
course_expiration_fragment.content = str(course_expiration_fragment.content)
course_expiration_fragment.add_content(frag.content)
course_expiration_fragment.add_fragment_resources(frag)
......
......@@ -5,13 +5,17 @@ import itertools
from datetime import datetime, timedelta
import ddt
from crum import set_current_request
from django.test import RequestFactory
from django.utils import timezone
from pytz import UTC
from common.djangoapps.course_modes.models import CourseMode
from common.djangoapps.course_modes.tests.factories import CourseModeFactory
from common.djangoapps.student.tests.factories import UserFactory
from lms.djangoapps.courseware.models import DynamicUpgradeDeadlineConfiguration
from openedx.core.djangoapps.schedules.tests.factories import ScheduleFactory
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.core.djangolib.testing.utils import CacheIsolationTestCase
from openedx.features.course_duration_limits.access import (
generate_course_expired_message,
......@@ -32,6 +36,13 @@ class TestAccess(CacheIsolationTestCase):
CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1, tzinfo=UTC))
DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
def assertDateInMessage(self, date, message):
# First, check that the formatted version is in there
self.assertIn(strftime_localized(date, '%b %-d, %Y'), message)
# But also that the machine-readable version is in there
self.assertIn('data-datetime="%s"' % date.isoformat(), message)
@ddt.data(
*itertools.product(
itertools.product([None, -2, -1, 1, 2], repeat=2),
......@@ -42,6 +53,13 @@ class TestAccess(CacheIsolationTestCase):
now = timezone.now()
schedule_offset, course_offset = offsets
# Set a timezone and request, to test that the message looks at the user's setting
request = RequestFactory().get('/')
request.user = UserFactory()
set_current_request(request)
self.addCleanup(set_current_request, None)
set_user_preference(request.user, 'time_zone', 'Asia/Tokyo')
if schedule_offset is not None:
schedule_upgrade_deadline = now + timedelta(days=schedule_offset)
else:
......@@ -52,9 +70,6 @@ class TestAccess(CacheIsolationTestCase):
else:
course_upgrade_deadline = None
def format_date(date):
return strftime_localized(date, u'%b %-d, %Y')
enrollment = CourseEnrollmentFactory.create(
course__start=datetime(2018, 1, 1, tzinfo=UTC),
course__self_paced=True,
......@@ -78,16 +93,17 @@ class TestAccess(CacheIsolationTestCase):
message = generate_course_expired_message(enrollment.user, enrollment.course)
self.assertIn(format_date(duration_limit_upgrade_deadline), message)
self.assertDateInMessage(duration_limit_upgrade_deadline, message)
self.assertIn('data-timezone="Asia/Tokyo"', message)
soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline
upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline
has_upgrade_deadline = course_upgrade_deadline is not None
if upgradeable and soft_upgradeable:
self.assertIn(format_date(schedule_upgrade_deadline), message)
self.assertDateInMessage(schedule_upgrade_deadline, message)
elif upgradeable and has_upgrade_deadline:
self.assertIn(format_date(course_upgrade_deadline), message)
self.assertDateInMessage(course_upgrade_deadline, message)
else:
self.assertNotIn("Upgrade by", message)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment