From da8103d2eaa10c6c7c6bd0018f6eb1ad760e522e Mon Sep 17 00:00:00 2001 From: Will Daly Date: Wed, 15 Apr 2015 13:15:23 -0400 Subject: [PATCH] Expose course start/end date in the enrollment API --- common/djangoapps/enrollment/api.py | 36 ++++++++++------ common/djangoapps/enrollment/serializers.py | 2 + .../djangoapps/enrollment/tests/test_views.py | 41 +++++++++++++++++++ common/djangoapps/enrollment/views.py | 29 +++++++++---- 4 files changed, 88 insertions(+), 20 deletions(-) diff --git a/common/djangoapps/enrollment/api.py b/common/djangoapps/enrollment/api.py index 24d058b9cc..02055ccea5 100644 --- a/common/djangoapps/enrollment/api.py +++ b/common/djangoapps/enrollment/api.py @@ -36,7 +36,10 @@ def get_enrollments(user_id): "user": "Bob", "course": { "course_id": "edX/DemoX/2014T2", - "enrollment_end": 2014-12-20T20:18:00Z, + "enrollment_end": "2014-12-20T20:18:00Z", + "enrollment_start": "2014-10-15T20:18:00Z", + "course_start": "2015-02-03T00:00:00Z", + "course_end": "2015-05-06T00:00:00Z", "course_modes": [ { "slug": "honor", @@ -49,7 +52,6 @@ def get_enrollments(user_id): "sku": null } ], - "enrollment_start": 2014-10-15T20:18:00Z, "invite_only": False } }, @@ -60,7 +62,10 @@ def get_enrollments(user_id): "user": "Bob", "course": { "course_id": "edX/edX-Insider/2014T2", - "enrollment_end": 2014-12-20T20:18:00Z, + "enrollment_end": "2014-12-20T20:18:00Z", + "enrollment_start": "2014-10-15T20:18:00Z", + "course_start": "2015-02-03T00:00:00Z", + "course_end": "2015-05-06T00:00:00Z", "course_modes": [ { "slug": "honor", @@ -73,7 +78,6 @@ def get_enrollments(user_id): "sku": null } ], - "enrollment_start": 2014-10-15T20:18:00Z, "invite_only": True } } @@ -104,7 +108,10 @@ def get_enrollment(user_id, course_id): "user": "Bob", "course": { "course_id": "edX/DemoX/2014T2", - "enrollment_end": 2014-12-20T20:18:00Z, + "enrollment_end": "2014-12-20T20:18:00Z", + "enrollment_start": "2014-10-15T20:18:00Z", + "course_start": "2015-02-03T00:00:00Z", + "course_end": "2015-05-06T00:00:00Z", "course_modes": [ { "slug": "honor", @@ -117,7 +124,6 @@ def get_enrollment(user_id, course_id): "sku": null } ], - "enrollment_start": 2014-10-15T20:18:00Z, "invite_only": False } } @@ -151,7 +157,10 @@ def add_enrollment(user_id, course_id, mode='honor', is_active=True): "user": "Bob", "course": { "course_id": "edX/DemoX/2014T2", - "enrollment_end": 2014-12-20T20:18:00Z, + "enrollment_end": "2014-12-20T20:18:00Z", + "enrollment_start": "2014-10-15T20:18:00Z", + "course_start": "2015-02-03T00:00:00Z", + "course_end": "2015-05-06T00:00:00Z", "course_modes": [ { "slug": "honor", @@ -164,7 +173,6 @@ def add_enrollment(user_id, course_id, mode='honor', is_active=True): "sku": null } ], - "enrollment_start": 2014-10-15T20:18:00Z, "invite_only": False } } @@ -196,7 +204,10 @@ def update_enrollment(user_id, course_id, mode=None, is_active=None): "user": "Bob", "course": { "course_id": "edX/DemoX/2014T2", - "enrollment_end": 2014-12-20T20:18:00Z, + "enrollment_end": "2014-12-20T20:18:00Z", + "enrollment_start": "2014-10-15T20:18:00Z", + "course_start": "2015-02-03T00:00:00Z", + "course_end": "2015-05-06T00:00:00Z", "course_modes": [ { "slug": "honor", @@ -209,7 +220,6 @@ def update_enrollment(user_id, course_id, mode=None, is_active=None): "sku": null } ], - "enrollment_start": 2014-10-15T20:18:00Z, "invite_only": False } } @@ -239,7 +249,10 @@ def get_course_enrollment_details(course_id): >>> get_course_enrollment_details("edX/DemoX/2014T2") { "course_id": "edX/DemoX/2014T2", - "enrollment_end": 2014-12-20T20:18:00Z, + "enrollment_end": "2014-12-20T20:18:00Z", + "enrollment_start": "2014-10-15T20:18:00Z", + "course_start": "2015-02-03T00:00:00Z", + "course_end": "2015-05-06T00:00:00Z", "course_modes": [ { "slug": "honor", @@ -252,7 +265,6 @@ def get_course_enrollment_details(course_id): "sku": null } ], - "enrollment_start": 2014-10-15T20:18:00Z, "invite_only": False } diff --git a/common/djangoapps/enrollment/serializers.py b/common/djangoapps/enrollment/serializers.py index e93e4208d9..4f9d78bf63 100644 --- a/common/djangoapps/enrollment/serializers.py +++ b/common/djangoapps/enrollment/serializers.py @@ -46,6 +46,8 @@ class CourseField(serializers.RelatedField): "course_id": course_id, "enrollment_start": course.enrollment_start, "enrollment_end": course.enrollment_end, + "course_start": course.start, + "course_end": course.end, "invite_only": course.invitation_only, "course_modes": course_modes, } diff --git a/common/djangoapps/enrollment/tests/test_views.py b/common/djangoapps/enrollment/tests/test_views.py index a3e961a69f..c0adf71655 100644 --- a/common/djangoapps/enrollment/tests/test_views.py +++ b/common/djangoapps/enrollment/tests/test_views.py @@ -4,6 +4,7 @@ Tests for user enrollment. import ddt import json import unittest +import datetime from mock import patch from django.test import Client @@ -253,6 +254,46 @@ class EnrollmentTest(ModuleStoreTestCase, APITestCase): self.assertEqual(mode['sku'], '123') self.assertEqual(mode['name'], CourseMode.HONOR) + @ddt.data( + # NOTE: Studio requires a start date, but this is not + # enforced at the data layer, so we need to handle the case + # in which no dates are specified. + (None, None, None, None), + (datetime.datetime(2015, 1, 2, 3, 4, 5), None, "2015-01-02T03:04:05Z", None), + (None, datetime.datetime(2015, 1, 2, 3, 4, 5), None, "2015-01-02T03:04:05Z"), + (datetime.datetime(2014, 6, 7, 8, 9, 10), datetime.datetime(2015, 1, 2, 3, 4, 5), "2014-06-07T08:09:10Z", "2015-01-02T03:04:05Z"), + ) + @ddt.unpack + def test_get_course_details_course_dates(self, start_datetime, end_datetime, expected_start, expected_end): + course = CourseFactory.create(start=start_datetime, end=end_datetime) + self._create_enrollment(course_id=unicode(course.id)) + + # Check course details + url = reverse('courseenrollmentdetails', kwargs={"course_id": unicode(course.id)}) + resp = self.client.get(url) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + + data = json.loads(resp.content) + self.assertEqual(data['course_start'], expected_start) + self.assertEqual(data['course_end'], expected_end) + + # Check enrollment course details + url = reverse('courseenrollment', kwargs={"course_id": unicode(course.id)}) + resp = self.client.get(url) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + + data = json.loads(resp.content) + self.assertEqual(data['course_details']['course_start'], expected_start) + self.assertEqual(data['course_details']['course_end'], expected_end) + + # Check enrollment list course details + resp = self.client.get(reverse('courseenrollments')) + self.assertEqual(resp.status_code, status.HTTP_200_OK) + + data = json.loads(resp.content) + self.assertEqual(data[0]['course_details']['course_start'], expected_start) + self.assertEqual(data[0]['course_details']['course_end'], expected_end) + def test_with_invalid_course_id(self): self._create_enrollment(course_id='entirely/fake/course', expected_status=status.HTTP_400_BAD_REQUEST) diff --git a/common/djangoapps/enrollment/views.py b/common/djangoapps/enrollment/views.py index bdd867dd4d..0dbb11db90 100644 --- a/common/djangoapps/enrollment/views.py +++ b/common/djangoapps/enrollment/views.py @@ -81,7 +81,13 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn): * course_id: The unique identifier for the course. - * enrollment_end: The date and time after which users cannot enroll for the course. + * enrollment_start: The date and time that users can begin enrolling in the course. If null, enrollment opens immediately when the course is created. + + * enrollment_end: The date and time after which users cannot enroll for the course. If null, the enrollment period never ends. + + * course_start: The date and time at which the course opens. If null, the course opens immediately when created. + + * course_end: The date and time at which the course closes. If null, the course never ends. * course_modes: An array of data about the enrollment modes supported for the course. Each enrollment mode collection includes: @@ -93,8 +99,6 @@ class EnrollmentView(APIView, ApiKeyPermissionMixIn): * expiration_datetime: The date and time after which users cannot enroll in the course in this mode. * description: A description of this mode. - * enrollment_start: The date and time that users can begin enrolling in the course. - * invite_only: Whether students must be invited to enroll in the course; true or false. * user: The ID of the user. @@ -164,7 +168,13 @@ class EnrollmentCourseDetailView(APIView): * course_id: The unique identifier of the course. - * enrollment_end: The date and time after which users cannot enroll for the course. + * enrollment_start: The date and time that users can begin enrolling in the course. If null, enrollment opens immediately when the course is created. + + * enrollment_end: The date and time after which users cannot enroll for the course. If null, the enrollment period never ends. + + * course_start: The date and time at which the course opens. If null, the course opens immediately when created. + + * course_end: The date and time at which the course closes. If null, the course never ends. * course_modes: An array of data about the enrollment modes supported for the course. Each enrollment mode collection includes: @@ -176,8 +186,6 @@ class EnrollmentCourseDetailView(APIView): * expiration_datetime: The date and time after which users cannot enroll in the course in this mode. * description: A description of this mode. - * enrollment_start: The date and time that users can begin enrolling in the course. - * invite_only: Whether students must be invited to enroll in the course; true or false. """ @@ -264,7 +272,13 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): * course_id: The unique identifier for the course. - * enrollment_end: The date and time after which users cannot enroll for the course. + * enrollment_start: The date and time that users can begin enrolling in the course. If null, enrollment opens immediately when the course is created. + + * enrollment_end: The date and time after which users cannot enroll for the course. If null, the enrollment period never ends. + + * course_start: The date and time at which the course opens. If null, the course opens immediately when created. + + * course_end: The date and time at which the course closes. If null, the course never ends. * course_modes: An array of data about the enrollment modes supported for the course. Each enrollment mode collection includes: @@ -276,7 +290,6 @@ class EnrollmentListView(APIView, ApiKeyPermissionMixIn): * expiration_datetime: The date and time after which users cannot enroll in the course in this mode. * description: A description of this mode. - * enrollment_start: The date and time that users can begin enrolling in the course. * invite_only: Whether students must be invited to enroll in the course; true or false.