Files
edx-platform/cms/djangoapps/contentstore/api/views.py
2018-05-08 14:38:33 -04:00

193 lines
6.9 KiB
Python

""" API v0 views. """
import base64
import logging
import os
from path import Path as path
from six import text_type
from django.conf import settings
from django.core.files import File
from opaque_keys.edx.keys import CourseKey
from rest_framework import status
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.generics import GenericAPIView
from rest_framework.response import Response
from user_tasks.models import UserTaskStatus
from student.auth import has_course_author_access
from contentstore.storage import course_import_export_storage
from contentstore.tasks import CourseImportTask, import_olx
from openedx.core.lib.api.view_utils import DeveloperErrorViewMixin, view_auth_classes
log = logging.getLogger(__name__)
@view_auth_classes()
class CourseImportExportViewMixin(DeveloperErrorViewMixin):
"""
Mixin class for course import/export related views.
"""
def perform_authentication(self, request):
"""
Ensures that the user is authenticated (e.g. not an AnonymousUser)
"""
super(CourseImportExportViewMixin, self).perform_authentication(request)
if request.user.is_anonymous:
raise AuthenticationFailed
class CourseImportView(CourseImportExportViewMixin, GenericAPIView):
"""
**Use Case**
* Start an asynchronous task to import a course from a .tar.gz file into
the specified course ID, overwriting the existing course
* Get a status on an asynchronous task import
**Example Requests**
POST /api/courses/v0/import/{course_id}/
GET /api/courses/v0/import/{course_id}/?task_id={task_id}
**POST Parameters**
A POST request must include the following parameters.
* course_id: (required) A string representation of a Course ID,
e.g., course-v1:edX+DemoX+Demo_Course
* course_data: (required) The course .tar.gz file to import
**POST Response Values**
If the import task is started successfully, an HTTP 200 "OK" response is
returned.
The HTTP 200 response has the following values.
* task_id: UUID of the created task, usable for checking status
* filename: string of the uploaded filename
**Example POST Response**
{
"task_id": "4b357bb3-2a1e-441d-9f6c-2210cf76606f"
}
**GET Parameters**
A GET request must include the following parameters.
* task_id: (required) The UUID of the task to check, e.g. "4b357bb3-2a1e-441d-9f6c-2210cf76606f"
* filename: (required) The filename of the uploaded course .tar.gz
**GET Response Values**
If the import task is found successfully by the UUID provided, an HTTP
200 "OK" response is returned.
The HTTP 200 response has the following values.
* state: String description of the state of the task
**Example GET Response**
{
"state": "Succeeded"
}
"""
# TODO: ARCH-91
# This view is excluded from Swagger doc generation because it
# does not specify a serializer class.
exclude_from_schema = True
def post(self, request, course_id):
"""
Kicks off an asynchronous course import and returns an ID to be used to check
the task's status
"""
courselike_key = CourseKey.from_string(course_id)
if not has_course_author_access(request.user, courselike_key):
return self.make_error_response(
status_code=status.HTTP_403_FORBIDDEN,
developer_message='The user requested does not have the required permissions.',
error_code='user_mismatch'
)
try:
if 'course_data' not in request.FILES:
return self.make_error_response(
status_code=status.HTTP_400_BAD_REQUEST,
developer_message='Missing required parameter',
error_code='internal_error',
field_errors={'course_data': '"course_data" parameter is required, and must be a .tar.gz file'}
)
filename = request.FILES['course_data'].name
if not filename.endswith('.tar.gz'):
return self.make_error_response(
status_code=status.HTTP_400_BAD_REQUEST,
developer_message='Parameter in the wrong format',
error_code='internal_error',
field_errors={'course_data': '"course_data" parameter is required, and must be a .tar.gz file'}
)
course_dir = path(settings.GITHUB_REPO_ROOT) / base64.urlsafe_b64encode(repr(courselike_key))
temp_filepath = course_dir / filename
if not course_dir.isdir(): # pylint: disable=no-value-for-parameter
os.mkdir(course_dir)
log.debug('importing course to {0}'.format(temp_filepath))
with open(temp_filepath, "wb+") as temp_file:
for chunk in request.FILES['course_data'].chunks():
temp_file.write(chunk)
log.info("Course import %s: Upload complete", courselike_key)
with open(temp_filepath, 'rb') as local_file:
django_file = File(local_file)
storage_path = course_import_export_storage.save(u'olx_import/' + filename, django_file)
async_result = import_olx.delay(
request.user.id, text_type(courselike_key), storage_path, filename, request.LANGUAGE_CODE)
return Response({
'task_id': async_result.task_id
})
except Exception as e:
return self.make_error_response(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
developer_message=str(e),
error_code='internal_error'
)
def get(self, request, course_id):
"""
Check the status of the specified task
"""
courselike_key = CourseKey.from_string(course_id)
if not has_course_author_access(request.user, courselike_key):
return self.make_error_response(
status_code=status.HTTP_403_FORBIDDEN,
developer_message='The user requested does not have the required permissions.',
error_code='user_mismatch'
)
try:
task_id = request.GET['task_id']
filename = request.GET['filename']
args = {u'course_key_string': course_id, u'archive_name': filename}
name = CourseImportTask.generate_name(args)
task_status = UserTaskStatus.objects.filter(name=name, task_id=task_id).first()
return Response({
'state': task_status.state
})
except Exception as e:
return self.make_error_response(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
developer_message=str(e),
error_code='internal_error'
)