Merge pull request #8509 from edx/will/deprecate-course-about-v0
Deprecate course details API v0
This commit is contained in:
@@ -332,10 +332,6 @@ VIDEO_UPLOAD_PIPELINE = ENV_TOKENS.get('VIDEO_UPLOAD_PIPELINE', VIDEO_UPLOAD_PIP
|
||||
PARSE_KEYS = AUTH_TOKENS.get("PARSE_KEYS", {})
|
||||
|
||||
|
||||
#date format the api will be formatting the datetime values
|
||||
API_DATE_FORMAT = '%Y-%m-%d'
|
||||
API_DATE_FORMAT = ENV_TOKENS.get('API_DATE_FORMAT', API_DATE_FORMAT)
|
||||
|
||||
# Video Caching. Pairing country codes with CDN URLs.
|
||||
# Example: {'CN': 'http://api.xuetangx.com/edx/video?s3_url='}
|
||||
VIDEO_CDN_URL = ENV_TOKENS.get('VIDEO_CDN_URL', {})
|
||||
|
||||
@@ -951,8 +951,6 @@ ADVANCED_PROBLEM_TYPES = [
|
||||
}
|
||||
]
|
||||
|
||||
#date format the api will be formatting the datetime values
|
||||
API_DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
# Files and Uploads type filter values
|
||||
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
"""
|
||||
The Python API layer of the Course About API. Essentially the middle tier of the project, responsible for all
|
||||
business logic that is not directly tied to the data itself.
|
||||
|
||||
Data access is managed through the configured data module, or defaults to the project's data.py module.
|
||||
|
||||
This API is exposed via the RESTful layer (views.py) but may be used directly in-process.
|
||||
|
||||
"""
|
||||
import logging
|
||||
from django.conf import settings
|
||||
from django.utils import importlib
|
||||
from django.core.cache import cache
|
||||
from course_about import errors
|
||||
|
||||
DEFAULT_DATA_API = 'course_about.data'
|
||||
|
||||
COURSE_ABOUT_API_CACHE_PREFIX = 'course_about_api_'
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_course_about_details(course_id):
|
||||
"""Get course about details for the given course ID.
|
||||
|
||||
Given a Course ID, retrieve all the metadata necessary to fully describe the Course.
|
||||
First its checks the default cache for given course id if its exists then returns
|
||||
the course otherwise it get the course from module store and set the cache.
|
||||
By default cache expiry set to 5 minutes.
|
||||
|
||||
Args:
|
||||
course_id (str): The String representation of a Course ID. Used to look up the requested
|
||||
course.
|
||||
|
||||
Returns:
|
||||
A JSON serializable dictionary of metadata describing the course.
|
||||
|
||||
Example:
|
||||
>>> get_course_about_details('edX/Demo/2014T2')
|
||||
{
|
||||
"advertised_start": "FALL",
|
||||
"announcement": "YYYY-MM-DD",
|
||||
"course_id": "edx/DemoCourse",
|
||||
"course_number": "DEMO101",
|
||||
"start": "YYYY-MM-DD",
|
||||
"end": "YYYY-MM-DD",
|
||||
"effort": "HH:MM",
|
||||
"display_name": "Demo Course",
|
||||
"is_new": true,
|
||||
"media": {
|
||||
"course_image": "/some/image/location.png"
|
||||
},
|
||||
}
|
||||
"""
|
||||
cache_key = "{}_{}".format(course_id, COURSE_ABOUT_API_CACHE_PREFIX)
|
||||
cache_course_info = cache.get(cache_key)
|
||||
|
||||
if cache_course_info:
|
||||
return cache_course_info
|
||||
|
||||
course_info = _data_api().get_course_about_details(course_id)
|
||||
time_out = getattr(settings, 'COURSE_INFO_API_CACHE_TIME_OUT', 300)
|
||||
cache.set(cache_key, course_info, time_out)
|
||||
|
||||
return course_info
|
||||
|
||||
|
||||
def _data_api():
|
||||
"""Returns a Data API.
|
||||
This relies on Django settings to find the appropriate data API.
|
||||
|
||||
We retrieve the settings in-line here (rather than using the
|
||||
top-level constant), so that @override_settings will work
|
||||
in the test suite.
|
||||
"""
|
||||
api_path = getattr(settings, "COURSE_ABOUT_DATA_API", DEFAULT_DATA_API)
|
||||
try:
|
||||
return importlib.import_module(api_path)
|
||||
except (ImportError, ValueError):
|
||||
log.exception(u"Could not load module at '{path}'".format(path=api_path))
|
||||
raise errors.CourseAboutApiLoadError(api_path)
|
||||
@@ -1,76 +0,0 @@
|
||||
"""Data Aggregation Layer for the Course About API.
|
||||
This is responsible for combining data from the following resources:
|
||||
* CourseDescriptor
|
||||
* CourseAboutDescriptor
|
||||
"""
|
||||
import logging
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from course_about.serializers import serialize_content
|
||||
from course_about.errors import CourseNotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.exceptions import ItemNotFoundError
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
ABOUT_ATTRIBUTES = [
|
||||
'effort',
|
||||
'overview',
|
||||
'title',
|
||||
'university',
|
||||
'number',
|
||||
'short_description',
|
||||
'description',
|
||||
'key_dates',
|
||||
'video',
|
||||
'course_staff_short',
|
||||
'course_staff_extended',
|
||||
'requirements',
|
||||
'syllabus',
|
||||
'textbook',
|
||||
'faq',
|
||||
'more_info',
|
||||
'ocw_links',
|
||||
]
|
||||
|
||||
|
||||
def get_course_about_details(course_id): # pylint: disable=unused-argument
|
||||
"""
|
||||
Return course information for a given course id.
|
||||
Args:
|
||||
course_id(str) : The course id to retrieve course information for.
|
||||
|
||||
Returns:
|
||||
Serializable dictionary of the Course About Information.
|
||||
|
||||
Raises:
|
||||
CourseNotFoundError
|
||||
"""
|
||||
try:
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
course_descriptor = modulestore().get_course(course_key)
|
||||
if course_descriptor is None:
|
||||
raise CourseNotFoundError("course not found")
|
||||
except InvalidKeyError as err:
|
||||
raise CourseNotFoundError(err.message)
|
||||
|
||||
about_descriptor = {
|
||||
attribute: _fetch_course_detail(course_key, attribute)
|
||||
for attribute in ABOUT_ATTRIBUTES
|
||||
}
|
||||
|
||||
course_info = serialize_content(course_descriptor=course_descriptor, about_descriptor=about_descriptor)
|
||||
return course_info
|
||||
|
||||
|
||||
def _fetch_course_detail(course_key, attribute):
|
||||
"""
|
||||
Fetch the course about attribute for the given course's attribute from persistence and return its value.
|
||||
"""
|
||||
usage_key = course_key.make_usage_key('about', attribute)
|
||||
try:
|
||||
value = modulestore().get_item(usage_key).data
|
||||
except ItemNotFoundError:
|
||||
value = None
|
||||
return value
|
||||
@@ -1,23 +0,0 @@
|
||||
"""
|
||||
Contains all the errors associated with the Course About API.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class CourseAboutError(Exception):
|
||||
"""Generic Course About Error"""
|
||||
|
||||
def __init__(self, msg, data=None):
|
||||
super(CourseAboutError, self).__init__(msg)
|
||||
# Corresponding information to help resolve the error.
|
||||
self.data = data
|
||||
|
||||
|
||||
class CourseAboutApiLoadError(CourseAboutError):
|
||||
"""The data API could not be loaded. """
|
||||
pass
|
||||
|
||||
|
||||
class CourseNotFoundError(CourseAboutError):
|
||||
"""The Course Not Found. """
|
||||
pass
|
||||
@@ -1,6 +0,0 @@
|
||||
"""
|
||||
A models.py is required to make this an app (until we move to Django 1.7)
|
||||
The Course About API is responsible for aggregating descriptive course information into a single response.
|
||||
This should eventually hold some initial Marketing Meta Data objects that are platform-specific.
|
||||
|
||||
"""
|
||||
@@ -1,67 +0,0 @@
|
||||
"""
|
||||
Serializers for all Course Descriptor and Course About Descriptor related return objects.
|
||||
|
||||
"""
|
||||
from xmodule.contentstore.content import StaticContent
|
||||
from django.conf import settings
|
||||
|
||||
DATE_FORMAT = getattr(settings, 'API_DATE_FORMAT', '%Y-%m-%d')
|
||||
|
||||
|
||||
def serialize_content(course_descriptor, about_descriptor):
|
||||
"""
|
||||
Returns a serialized representation of the course_descriptor and about_descriptor
|
||||
Args:
|
||||
course_descriptor(CourseDescriptor) : course descriptor object
|
||||
about_descriptor(dict) : Dictionary of CourseAboutDescriptor objects
|
||||
return:
|
||||
serialize data for course information.
|
||||
"""
|
||||
data = {
|
||||
'media': {},
|
||||
'display_name': getattr(course_descriptor, 'display_name', None),
|
||||
'course_number': course_descriptor.location.course,
|
||||
'course_id': None,
|
||||
'advertised_start': getattr(course_descriptor, 'advertised_start', None),
|
||||
'is_new': getattr(course_descriptor, 'is_new', None),
|
||||
'start': _formatted_datetime(course_descriptor, 'start'),
|
||||
'end': _formatted_datetime(course_descriptor, 'end'),
|
||||
'announcement': None,
|
||||
}
|
||||
data.update(about_descriptor)
|
||||
|
||||
content_id = unicode(course_descriptor.id)
|
||||
data["course_id"] = unicode(content_id)
|
||||
if getattr(course_descriptor, 'course_image', False):
|
||||
data['media']['course_image'] = course_image_url(course_descriptor)
|
||||
|
||||
announcement = getattr(course_descriptor, 'announcement', None)
|
||||
data["announcement"] = announcement.strftime(DATE_FORMAT) if announcement else None
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def course_image_url(course):
|
||||
"""
|
||||
Return url of course image.
|
||||
Args:
|
||||
course(CourseDescriptor) : The course id to retrieve course image url.
|
||||
Returns:
|
||||
Absolute url of course image.
|
||||
"""
|
||||
loc = StaticContent.compute_location(course.id, course.course_image)
|
||||
url = StaticContent.serialize_asset_key_with_slash(loc)
|
||||
return url
|
||||
|
||||
|
||||
def _formatted_datetime(course_descriptor, date_type):
|
||||
"""
|
||||
Return formatted date.
|
||||
Args:
|
||||
course_descriptor(CourseDescriptor) : The CourseDescriptor Object.
|
||||
date_type (str) : Either start or end.
|
||||
Returns:
|
||||
formatted date or None .
|
||||
"""
|
||||
course_date_ = getattr(course_descriptor, date_type, None)
|
||||
return course_date_.strftime(DATE_FORMAT) if course_date_ else None
|
||||
@@ -1,54 +0,0 @@
|
||||
"""
|
||||
Tests the logical Python API layer of the Course About API.
|
||||
"""
|
||||
|
||||
import ddt
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from django.conf import settings
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, CourseAboutFactory
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class CourseInfoTest(ModuleStoreTestCase, APITestCase):
|
||||
"""
|
||||
Test course information.
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "edx"
|
||||
|
||||
def setUp(self):
|
||||
""" Create a course"""
|
||||
super(CourseInfoTest, self).setUp()
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
|
||||
def test_get_course_details_from_cache(self):
|
||||
kwargs = dict()
|
||||
kwargs["course_id"] = self.course.id
|
||||
kwargs["course_runtime"] = self.course.runtime
|
||||
kwargs["user_id"] = self.user.id
|
||||
CourseAboutFactory.create(**kwargs)
|
||||
resp = self.client.get(
|
||||
reverse('courseabout', kwargs={"course_id": unicode(self.course.id)})
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
resp_data = json.loads(resp.content)
|
||||
self.assertIsNotNone(resp_data)
|
||||
|
||||
resp = self.client.get(
|
||||
reverse('courseabout', kwargs={"course_id": unicode(self.course.id)})
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
resp_data = json.loads(resp.content)
|
||||
self.assertIsNotNone(resp_data)
|
||||
@@ -1,66 +0,0 @@
|
||||
"""
|
||||
Tests specific to the Data Aggregation Layer of the Course About API.
|
||||
|
||||
"""
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from nose.tools import raises
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from student.tests.factories import UserFactory
|
||||
from course_about import data
|
||||
from course_about.errors import CourseNotFoundError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class CourseAboutDataTest(ModuleStoreTestCase):
|
||||
"""
|
||||
Test course enrollment data aggregation.
|
||||
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "edx"
|
||||
|
||||
def setUp(self):
|
||||
"""Create a course and user, then log in. """
|
||||
super(CourseAboutDataTest, self).setUp()
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
|
||||
def test_get_course_about_details(self):
|
||||
course_info = data.get_course_about_details(unicode(self.course.id))
|
||||
self.assertIsNotNone(course_info)
|
||||
|
||||
def test_get_course_about_valid_date(self):
|
||||
module_store = modulestore()
|
||||
self.course.start = datetime.now()
|
||||
self.course.end = datetime.now()
|
||||
self.course.announcement = datetime.now()
|
||||
module_store.update_item(self.course, self.user.id)
|
||||
course_info = data.get_course_about_details(unicode(self.course.id))
|
||||
self.assertIsNotNone(course_info["start"])
|
||||
self.assertIsNotNone(course_info["end"])
|
||||
self.assertIsNotNone(course_info["announcement"])
|
||||
|
||||
def test_get_course_about_none_date(self):
|
||||
module_store = modulestore()
|
||||
self.course.start = None
|
||||
self.course.end = None
|
||||
self.course.announcement = None
|
||||
module_store.update_item(self.course, self.user.id)
|
||||
course_info = data.get_course_about_details(unicode(self.course.id))
|
||||
self.assertIsNone(course_info["start"])
|
||||
self.assertIsNone(course_info["end"])
|
||||
self.assertIsNone(course_info["announcement"])
|
||||
|
||||
@raises(CourseNotFoundError)
|
||||
def test_non_existent_course(self):
|
||||
data.get_course_about_details("this/is/bananas")
|
||||
|
||||
@raises(CourseNotFoundError)
|
||||
def test_invalid_key(self):
|
||||
data.get_course_about_details("invalid:key:k")
|
||||
@@ -1,149 +0,0 @@
|
||||
"""
|
||||
Tests for user enrollment.
|
||||
"""
|
||||
import ddt
|
||||
import json
|
||||
import unittest
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from django.conf import settings
|
||||
from datetime import datetime
|
||||
from mock import patch
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, CourseAboutFactory
|
||||
from student.tests.factories import UserFactory
|
||||
from course_about.serializers import course_image_url
|
||||
from course_about import api
|
||||
from course_about.errors import CourseNotFoundError, CourseAboutError
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class CourseInfoTest(ModuleStoreTestCase, APITestCase):
|
||||
"""
|
||||
Test course information.
|
||||
"""
|
||||
USERNAME = "Bob"
|
||||
EMAIL = "bob@example.com"
|
||||
PASSWORD = "edx"
|
||||
|
||||
def setUp(self):
|
||||
""" Create a course"""
|
||||
super(CourseInfoTest, self).setUp()
|
||||
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD)
|
||||
self.client.login(username=self.USERNAME, password=self.PASSWORD)
|
||||
|
||||
def test_user_not_authenticated(self):
|
||||
# Log out, so we're no longer authenticated
|
||||
self.client.logout()
|
||||
resp_data, status_code = self._get_course_about(self.course.id)
|
||||
self.assertEqual(status_code, status.HTTP_200_OK)
|
||||
self.assertIsNotNone(resp_data)
|
||||
|
||||
def test_with_valid_course_id(self):
|
||||
_resp_data, status_code = self._get_course_about(self.course.id)
|
||||
self.assertEqual(status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_with_invalid_course_id(self):
|
||||
resp = self.client.get(
|
||||
reverse('courseabout', kwargs={"course_id": 'not/a/validkey'})
|
||||
)
|
||||
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_get_course_details_all_attributes(self):
|
||||
kwargs = dict()
|
||||
kwargs["course_id"] = self.course.id
|
||||
kwargs["course_runtime"] = self.course.runtime
|
||||
CourseAboutFactory.create(**kwargs)
|
||||
|
||||
resp_data, status_code = self._get_course_about(self.course.id)
|
||||
|
||||
all_attributes = ['display_name', 'start', 'end', 'announcement', 'advertised_start', 'is_new', 'course_number',
|
||||
'course_id',
|
||||
'effort', 'media', 'course_image']
|
||||
for attr in all_attributes:
|
||||
self.assertIn(attr, str(resp_data))
|
||||
self.assertEqual(status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_get_course_about_valid_date(self):
|
||||
module_store = modulestore()
|
||||
self.course.start = datetime.now()
|
||||
self.course.end = datetime.now()
|
||||
self.course.announcement = datetime.now()
|
||||
module_store.update_item(self.course, self.user.id)
|
||||
|
||||
resp_data, _status_code = self._get_course_about(self.course.id)
|
||||
|
||||
self.assertIsNotNone(resp_data["start"])
|
||||
self.assertIsNotNone(resp_data["end"])
|
||||
self.assertIsNotNone(resp_data["announcement"])
|
||||
|
||||
def test_get_course_about_none_date(self):
|
||||
module_store = modulestore()
|
||||
self.course.start = None
|
||||
self.course.end = None
|
||||
self.course.announcement = None
|
||||
module_store.update_item(self.course, self.user.id)
|
||||
|
||||
resp_data, _status_code = self._get_course_about(self.course.id)
|
||||
self.assertIsNone(resp_data["start"])
|
||||
self.assertIsNone(resp_data["end"])
|
||||
self.assertIsNone(resp_data["announcement"])
|
||||
|
||||
def test_get_course_details(self):
|
||||
kwargs = dict()
|
||||
kwargs["course_id"] = self.course.id
|
||||
kwargs["course_runtime"] = self.course.runtime
|
||||
kwargs["user_id"] = self.user.id
|
||||
CourseAboutFactory.create(**kwargs)
|
||||
|
||||
resp_data, status_code = self._get_course_about(self.course.id)
|
||||
self.assertEqual(status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(unicode(self.course.id), resp_data['course_id'])
|
||||
self.assertIn('Run', resp_data['display_name'])
|
||||
|
||||
url = course_image_url(self.course)
|
||||
self.assertEquals(url, resp_data['media']['course_image'])
|
||||
|
||||
@patch.object(api, "get_course_about_details")
|
||||
def test_get_enrollment_course_not_found_error(self, mock_get_course_about_details):
|
||||
mock_get_course_about_details.side_effect = CourseNotFoundError("Something bad happened.")
|
||||
_resp_data, status_code = self._get_course_about(self.course.id)
|
||||
self.assertEqual(status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
@patch.object(api, "get_course_about_details")
|
||||
def test_get_enrollment_invalid_key_error(self, mock_get_course_about_details):
|
||||
mock_get_course_about_details.side_effect = CourseNotFoundError('a/a/a', "Something bad happened.")
|
||||
resp_data, status_code = self._get_course_about(self.course.id)
|
||||
self.assertEqual(status_code, status.HTTP_404_NOT_FOUND)
|
||||
self.assertIn('An error occurred', resp_data["message"])
|
||||
|
||||
@patch.object(api, "get_course_about_details")
|
||||
def test_get_enrollment_internal_error(self, mock_get_course_about_details):
|
||||
mock_get_course_about_details.side_effect = CourseAboutError('error')
|
||||
resp_data, status_code = self._get_course_about(self.course.id)
|
||||
self.assertEqual(status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
self.assertIn('An error occurred', resp_data["message"])
|
||||
|
||||
@override_settings(COURSE_ABOUT_DATA_API='foo')
|
||||
def test_data_api_config_error(self):
|
||||
# Retrive the invalid course
|
||||
resp_data, status_code = self._get_course_about(self.course.id)
|
||||
self.assertEqual(status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
self.assertIn('An error occurred', resp_data["message"])
|
||||
|
||||
def _get_course_about(self, course_id):
|
||||
"""
|
||||
helper function to get retrieve course about information.
|
||||
args course_id (str): course id
|
||||
"""
|
||||
resp = self.client.get(
|
||||
reverse('courseabout', kwargs={"course_id": unicode(course_id)})
|
||||
)
|
||||
return json.loads(resp.content), resp.status_code
|
||||
@@ -1,15 +0,0 @@
|
||||
"""
|
||||
URLs for exposing the RESTful HTTP endpoints for the Course About API.
|
||||
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, url
|
||||
from course_about.views import CourseAboutView
|
||||
|
||||
urlpatterns = patterns(
|
||||
'course_about.views',
|
||||
url(
|
||||
r'^{course_key}'.format(course_key=settings.COURSE_ID_PATTERN),
|
||||
CourseAboutView.as_view(), name="courseabout"
|
||||
),
|
||||
)
|
||||
@@ -1,63 +0,0 @@
|
||||
"""
|
||||
Implementation of the RESTful endpoints for the Course About API.
|
||||
|
||||
"""
|
||||
from rest_framework.throttling import UserRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
from course_about import api
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
from course_about.errors import CourseNotFoundError, CourseAboutError
|
||||
|
||||
|
||||
class CourseAboutThrottle(UserRateThrottle):
|
||||
"""Limit the number of requests users can make to the Course About API."""
|
||||
# TODO Limit based on expected throughput # pylint: disable=fixme
|
||||
rate = '50/second'
|
||||
|
||||
|
||||
class CourseAboutView(APIView):
|
||||
""" RESTful Course About API view.
|
||||
|
||||
Used to retrieve JSON serialized Course About information.
|
||||
|
||||
"""
|
||||
authentication_classes = []
|
||||
permission_classes = []
|
||||
throttle_classes = CourseAboutThrottle,
|
||||
|
||||
def get(self, request, course_id=None): # pylint: disable=unused-argument
|
||||
"""Read course information.
|
||||
|
||||
HTTP Endpoint for course info api.
|
||||
|
||||
Args:
|
||||
Course Id = URI element specifying the course location. Course information will be
|
||||
returned for this particular course.
|
||||
|
||||
Return:
|
||||
A JSON serialized representation of the course information
|
||||
|
||||
"""
|
||||
try:
|
||||
return Response(api.get_course_about_details(course_id))
|
||||
except CourseNotFoundError:
|
||||
return Response(
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
data={
|
||||
"message": (
|
||||
u"An error occurred while retrieving course information"
|
||||
u" for course '{course_id}' no course found"
|
||||
).format(course_id=course_id)
|
||||
}
|
||||
)
|
||||
except CourseAboutError:
|
||||
return Response(
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
data={
|
||||
"message": (
|
||||
u"An error occurred while retrieving course information"
|
||||
u" for course '{course_id}'"
|
||||
).format(course_id=course_id)
|
||||
}
|
||||
)
|
||||
@@ -566,9 +566,6 @@ COURSE_ABOUT_VISIBILITY_PERMISSION = ENV_TOKENS.get(
|
||||
COURSE_ABOUT_VISIBILITY_PERMISSION
|
||||
)
|
||||
|
||||
#date format the api will be formatting the datetime values
|
||||
API_DATE_FORMAT = '%Y-%m-%d'
|
||||
API_DATE_FORMAT = ENV_TOKENS.get('API_DATE_FORMAT', API_DATE_FORMAT)
|
||||
|
||||
# Enrollment API Cache Timeout
|
||||
ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = ENV_TOKENS.get('ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT', 60)
|
||||
|
||||
@@ -2360,8 +2360,6 @@ COURSE_CATALOG_VISIBILITY_PERMISSION = 'see_exists'
|
||||
# visible. We default this to the legacy permission 'see_exists'.
|
||||
COURSE_ABOUT_VISIBILITY_PERMISSION = 'see_exists'
|
||||
|
||||
#date format the api will be formatting the datetime values
|
||||
API_DATE_FORMAT = '%Y-%m-%d'
|
||||
|
||||
# Enrollment API Cache Timeout
|
||||
ENROLLMENT_COURSE_DETAILS_CACHE_TIMEOUT = 60
|
||||
|
||||
@@ -75,9 +75,6 @@ urlpatterns = (
|
||||
# Enrollment API RESTful endpoints
|
||||
url(r'^api/enrollment/v1/', include('enrollment.urls')),
|
||||
|
||||
# CourseInfo API RESTful endpoints
|
||||
url(r'^api/course/details/v0/', include('course_about.urls')),
|
||||
|
||||
# Courseware search endpoints
|
||||
url(r'^search/', include('search.urls')),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user