158 lines
5.1 KiB
Python
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)
|