""" 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" } """ 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' )