Merge pull request #10490 from edx/release
Release 4 November 2015 to master
This commit is contained in:
@@ -1377,7 +1377,7 @@ class CourseEnrollment(models.Model):
|
||||
|
||||
# If it is after the refundable cutoff date they should not be refunded.
|
||||
refund_cutoff_date = self.refund_cutoff_date()
|
||||
if refund_cutoff_date and datetime.now() > refund_cutoff_date:
|
||||
if refund_cutoff_date and datetime.now(UTC) > refund_cutoff_date:
|
||||
return False
|
||||
|
||||
course_mode = CourseMode.mode_for_course(self.course_id, 'verified')
|
||||
@@ -1400,7 +1400,7 @@ class CourseEnrollment(models.Model):
|
||||
self.course_overview.start.replace(tzinfo=None)
|
||||
)
|
||||
|
||||
return refund_window_start_date + EnrollmentRefundConfiguration.current().refund_window
|
||||
return refund_window_start_date.replace(tzinfo=UTC) + EnrollmentRefundConfiguration.current().refund_window
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
|
||||
@@ -113,10 +113,10 @@ class RefundableTest(SharedModuleStoreTestCase):
|
||||
self.assertTrue(self.enrollment.refundable())
|
||||
|
||||
with patch('student.models.CourseEnrollment.refund_cutoff_date') as cutoff_date:
|
||||
cutoff_date.return_value = datetime.now() - timedelta(days=1)
|
||||
cutoff_date.return_value = datetime.now(pytz.UTC) - timedelta(minutes=5)
|
||||
self.assertFalse(self.enrollment.refundable())
|
||||
|
||||
cutoff_date.return_value = datetime.now() + timedelta(days=1)
|
||||
cutoff_date.return_value = datetime.now(pytz.UTC) + timedelta(minutes=5)
|
||||
self.assertTrue(self.enrollment.refundable())
|
||||
|
||||
@ddt.data(
|
||||
@@ -132,7 +132,7 @@ class RefundableTest(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Assert that the later date is used with the configurable refund period in calculating the returned cutoff date.
|
||||
"""
|
||||
now = datetime.now().replace(microsecond=0)
|
||||
now = datetime.now(pytz.UTC).replace(microsecond=0)
|
||||
order_date = now + order_date_delta
|
||||
course_start = now + course_start_delta
|
||||
expected_date = now + expected_date_delta
|
||||
|
||||
@@ -11,6 +11,7 @@ from django.conf import settings
|
||||
|
||||
from student.tests.factories import UserFactory, CourseEnrollmentFactory
|
||||
from student.models import CourseEnrollment
|
||||
from student.helpers import DISABLE_UNENROLL_CERT_STATES
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
|
||||
@@ -38,7 +39,10 @@ class TestStudentDashboardUnenrollments(ModuleStoreTestCase):
|
||||
def mock_cert(self, _user, _course_overview, _course_mode): # pylint: disable=unused-argument
|
||||
""" Return a preset certificate status. """
|
||||
if self.cert_status is not None:
|
||||
return {'status': self.cert_status}
|
||||
return {
|
||||
'status': self.cert_status,
|
||||
'can_unenroll': self.cert_status not in DISABLE_UNENROLL_CERT_STATES
|
||||
}
|
||||
else:
|
||||
return {}
|
||||
|
||||
@@ -85,3 +89,17 @@ class TestStudentDashboardUnenrollments(ModuleStoreTestCase):
|
||||
course_enrollment.assert_called_with(self.user, self.course.id)
|
||||
else:
|
||||
course_enrollment.assert_not_called()
|
||||
|
||||
def test_no_cert_status(self):
|
||||
""" Assert that the dashboard loads when cert_status is None."""
|
||||
with patch('student.views.cert_info', return_value=None):
|
||||
response = self.client.get(reverse('dashboard'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_cant_unenroll_status(self):
|
||||
""" Assert that the dashboard loads when cert_status does not allow for unenrollment"""
|
||||
with patch('certificates.models.certificate_status_for_student', return_value={'status': 'ready'}):
|
||||
response = self.client.get(reverse('dashboard'))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -79,6 +79,7 @@ class CourseEndingTest(TestCase):
|
||||
'show_disabled_download_button': False,
|
||||
'show_download_url': False,
|
||||
'show_survey_button': False,
|
||||
'can_unenroll': True,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -91,7 +92,8 @@ class CourseEndingTest(TestCase):
|
||||
'show_download_url': False,
|
||||
'show_survey_button': False,
|
||||
'mode': None,
|
||||
'linked_in_url': None
|
||||
'linked_in_url': None,
|
||||
'can_unenroll': True,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -106,7 +108,8 @@ class CourseEndingTest(TestCase):
|
||||
'survey_url': survey_url,
|
||||
'grade': '67',
|
||||
'mode': 'honor',
|
||||
'linked_in_url': None
|
||||
'linked_in_url': None,
|
||||
'can_unenroll': False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -121,7 +124,8 @@ class CourseEndingTest(TestCase):
|
||||
'survey_url': survey_url,
|
||||
'grade': '67',
|
||||
'mode': 'verified',
|
||||
'linked_in_url': None
|
||||
'linked_in_url': None,
|
||||
'can_unenroll': False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -143,7 +147,8 @@ class CourseEndingTest(TestCase):
|
||||
'survey_url': survey_url,
|
||||
'grade': '67',
|
||||
'mode': 'honor',
|
||||
'linked_in_url': None
|
||||
'linked_in_url': None,
|
||||
'can_unenroll': False,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -162,7 +167,8 @@ class CourseEndingTest(TestCase):
|
||||
'survey_url': survey_url,
|
||||
'grade': '67',
|
||||
'mode': 'honor',
|
||||
'linked_in_url': None
|
||||
'linked_in_url': None,
|
||||
'can_unenroll': True,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -181,21 +187,22 @@ class CourseEndingTest(TestCase):
|
||||
'show_survey_button': False,
|
||||
'grade': '67',
|
||||
'mode': 'honor',
|
||||
'linked_in_url': None
|
||||
'linked_in_url': None,
|
||||
'can_unenroll': True,
|
||||
}
|
||||
)
|
||||
|
||||
# test when the display is unavailable or notpassing, we get the correct results out
|
||||
course2.certificates_display_behavior = 'early_no_info'
|
||||
cert_status = {'status': 'unavailable'}
|
||||
self.assertIsNone(_cert_info(user, course2, cert_status, course_mode))
|
||||
self.assertEqual(_cert_info(user, course2, cert_status, course_mode), {})
|
||||
|
||||
cert_status = {
|
||||
'status': 'notpassing', 'grade': '67',
|
||||
'download_url': download_url,
|
||||
'mode': 'honor'
|
||||
}
|
||||
self.assertIsNone(_cert_info(user, course2, cert_status, course_mode))
|
||||
self.assertEqual(_cert_info(user, course2, cert_status, course_mode), {})
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@@ -1033,6 +1040,32 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin):
|
||||
else:
|
||||
self.assertIn('xseries-border-btn', response.content)
|
||||
|
||||
@patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False})
|
||||
@ddt.data((-2, -1), (-1, 1), (1, 2))
|
||||
@ddt.unpack
|
||||
def test_start_end_offsets(self, start_days_offset, end_days_offset):
|
||||
"""Test that the xseries upsell messaging displays whether the course
|
||||
has not yet started, is in session, or has already ended.
|
||||
"""
|
||||
self.course_1.start = datetime.now(pytz.UTC) + timedelta(days=start_days_offset)
|
||||
self.course_1.end = datetime.now(pytz.UTC) + timedelta(days=end_days_offset)
|
||||
self.update_course(self.course_1, self.user.id)
|
||||
CourseEnrollment.enroll(self.user, self.course_1.id, mode='verified')
|
||||
|
||||
self.client.login(username="jack", password="test")
|
||||
self.create_config(enabled=True, enable_student_dashboard=True)
|
||||
|
||||
with patch(
|
||||
'student.views.get_course_programs_for_dashboard',
|
||||
return_value=self._create_program_data([(self.course_1.id, 'active')])
|
||||
) as mock_get_programs:
|
||||
response = self.client.get(reverse('dashboard'))
|
||||
# ensure that our course id was included in the API call regardless of start/end dates
|
||||
__, course_ids = mock_get_programs.call_args[0]
|
||||
self.assertEqual(list(course_ids), [self.course_1.id])
|
||||
# count total courses appearing on student dashboard
|
||||
self._assert_responses(response, 1)
|
||||
|
||||
@ddt.data(
|
||||
('unpublished', 'unpublished', 'unpublished', 0),
|
||||
('active', 'unpublished', 'unpublished', 1),
|
||||
|
||||
@@ -202,6 +202,7 @@ def cert_info(user, course_overview, course_mode):
|
||||
'show_survey_button': bool
|
||||
'survey_url': url, only if show_survey_button is True
|
||||
'grade': if status is not 'processing'
|
||||
'can_unenroll': if status allows for unenrollment
|
||||
"""
|
||||
if not course_overview.may_certify():
|
||||
return {}
|
||||
@@ -302,6 +303,7 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa
|
||||
'show_disabled_download_button': False,
|
||||
'show_download_url': False,
|
||||
'show_survey_button': False,
|
||||
'can_unenroll': True
|
||||
}
|
||||
|
||||
if cert_status is None:
|
||||
@@ -310,7 +312,7 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa
|
||||
is_hidden_status = cert_status['status'] in ('unavailable', 'processing', 'generating', 'notpassing')
|
||||
|
||||
if course_overview.certificates_display_behavior == 'early_no_info' and is_hidden_status:
|
||||
return None
|
||||
return {}
|
||||
|
||||
status = template_state.get(cert_status['status'], default_status)
|
||||
|
||||
@@ -319,7 +321,8 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa
|
||||
'show_download_url': status == 'ready',
|
||||
'show_disabled_download_button': status == 'generating',
|
||||
'mode': cert_status.get('mode', None),
|
||||
'linked_in_url': None
|
||||
'linked_in_url': None,
|
||||
'can_unenroll': status not in DISABLE_UNENROLL_CERT_STATES,
|
||||
}
|
||||
|
||||
if (status in ('generating', 'ready', 'notpassing', 'restricted') and
|
||||
@@ -581,7 +584,7 @@ def dashboard(request):
|
||||
# program-related information on the dashboard view.
|
||||
course_programs = {}
|
||||
if is_student_dashboard_programs_enabled():
|
||||
course_programs = _get_course_programs(user, show_courseware_links_for)
|
||||
course_programs = _get_course_programs(user, [enrollment.course_id for enrollment in course_enrollments])
|
||||
|
||||
# Construct a dictionary of course mode information
|
||||
# used to render the course list. We re-use the course modes dict
|
||||
@@ -1030,8 +1033,8 @@ def change_enrollment(request, check_access=True):
|
||||
if not enrollment:
|
||||
return HttpResponseBadRequest(_("You are not enrolled in this course"))
|
||||
|
||||
certicifate_info = cert_info(user, enrollment.course_overview, enrollment.mode)
|
||||
if certicifate_info.get('status') in DISABLE_UNENROLL_CERT_STATES:
|
||||
certificate_info = cert_info(user, enrollment.course_overview, enrollment.mode)
|
||||
if certificate_info.get('status') in DISABLE_UNENROLL_CERT_STATES:
|
||||
return HttpResponseBadRequest(_("Your certificate prevents you from unenrolling from this course"))
|
||||
|
||||
CourseEnrollment.unenroll(user, course_id)
|
||||
|
||||
Reference in New Issue
Block a user