Files
edx-platform/openedx/core/djangoapps/ccxcon/api.py
2017-05-30 16:04:54 -04:00

158 lines
5.1 KiB
Python

"""
Module containing API functions for the CCXCon
"""
import logging
import urlparse
from django.core.exceptions import ValidationError
from django.core.validators import URLValidator
from django.http import Http404
from oauthlib.oauth2 import BackendApplicationClient
from requests_oauthlib import OAuth2Session
from rest_framework.status import HTTP_200_OK, HTTP_201_CREATED
from lms.djangoapps.courseware.courses import get_course_by_id
from openedx.core.djangoapps.models.course_details import CourseDetails
from student.models import anonymous_id_for_user
from student.roles import CourseInstructorRole
from .models import CCXCon
log = logging.getLogger(__name__)
CCXCON_COURSEXS_URL = '/api/v1/coursexs/'
CCXCON_TOKEN_URL = '/o/token/'
CCXCON_REQUEST_TIMEOUT = 30
class CCXConnServerError(Exception):
"""
Custom exception to be raised in case there is any
issue with the request to the server
"""
def is_valid_url(url):
"""
Helper function used to check if a string is a valid url.
Args:
url (str): the url string to be validated
Returns:
bool: whether the url is valid or not
"""
validate = URLValidator()
try:
validate(url)
return True
except ValidationError:
return False
def get_oauth_client(server_token_url, client_id, client_secret):
"""
Function that creates an oauth client and fetches a token.
It intentionally doesn't handle errors.
Args:
server_token_url (str): server URL where to get an authentication token
client_id (str): oauth client ID
client_secret (str): oauth client secret
Returns:
OAuth2Session: an instance of OAuth2Session with a token
"""
if not is_valid_url(server_token_url):
return
client = BackendApplicationClient(client_id=client_id)
oauth_ccxcon = OAuth2Session(client=client)
oauth_ccxcon.fetch_token(
token_url=server_token_url,
client_id=client_id,
client_secret=client_secret,
timeout=CCXCON_REQUEST_TIMEOUT
)
return oauth_ccxcon
def course_info_to_ccxcon(course_key):
"""
Function that gathers informations about the course and
makes a post request to a CCXCon with the data.
Args:
course_key (CourseLocator): the master course key
"""
try:
course = get_course_by_id(course_key)
except Http404:
log.error('Master Course with key "%s" not found', unicode(course_key))
return
if not course.enable_ccx:
log.debug('ccx not enabled for course key "%s"', unicode(course_key))
return
if not course.ccx_connector:
log.debug('ccx connector not defined for course key "%s"', unicode(course_key))
return
if not is_valid_url(course.ccx_connector):
log.error(
'ccx connector URL "%s" for course key "%s" is not a valid URL.',
course.ccx_connector, unicode(course_key)
)
return
# get the oauth credential for this URL
try:
ccxcon = CCXCon.objects.get(url=course.ccx_connector)
except CCXCon.DoesNotExist:
log.error('ccx connector Oauth credentials not configured for URL "%s".', course.ccx_connector)
return
# get an oauth client with a valid token
oauth_ccxcon = get_oauth_client(
server_token_url=urlparse.urljoin(course.ccx_connector, CCXCON_TOKEN_URL),
client_id=ccxcon.oauth_client_id,
client_secret=ccxcon.oauth_client_secret
)
# get the entire list of instructors
course_instructors = CourseInstructorRole(course.id).users_with_role()
# get anonymous ids for each of them
course_instructors_ids = [anonymous_id_for_user(user, course_key) for user in course_instructors]
# extract the course details
course_details = CourseDetails.fetch(course_key)
payload = {
'course_id': unicode(course_key),
'title': course.display_name,
'author_name': None,
'overview': course_details.overview,
'description': course_details.short_description,
'image_url': course_details.course_image_asset_path,
'instructors': course_instructors_ids
}
headers = {'content-type': 'application/json'}
# make the POST request
add_course_url = urlparse.urljoin(course.ccx_connector, CCXCON_COURSEXS_URL)
resp = oauth_ccxcon.post(
url=add_course_url,
json=payload,
headers=headers,
timeout=CCXCON_REQUEST_TIMEOUT
)
if resp.status_code >= 500:
raise CCXConnServerError('Server returned error Status: %s, Content: %s', resp.status_code, resp.content)
if resp.status_code >= 400:
log.error("Error creating course on ccxcon. Status: %s, Content: %s", resp.status_code, resp.content)
# this API performs a POST request both for POST and PATCH, but the POST returns 201 and the PATCH returns 200
elif resp.status_code != HTTP_200_OK and resp.status_code != HTTP_201_CREATED:
log.error('Server returned unexpected status code %s', resp.status_code)
else:
log.debug('Request successful. Status: %s, Content: %s', resp.status_code, resp.content)