from django.http import HttpResponse from django.urls import reverse_lazy from django.core.files import File as DjangoFile from django.conf import settings from django.db import transaction from django.shortcuts import get_object_or_404 from rest_framework.renderers import JSONRenderer from rest_framework.parsers import JSONParser from rest_framework.response import Response from rest_framework.views import APIView from courses_learning_objects.models import LearningObject from courses.models import Course from courses.permissions import IsAdmin from courses.course_material.models import File as CourseMaterialFile from .serializer import CourseExportSerializer, CourseImportSerializer import os, io, urllib, tarfile class ExportCourseView(APIView): permission_classes = (IsAdmin,) @staticmethod def add_files_to_export(tar_file, short_file_path): tar_file_path = short_file_path.split('/', 2)[-1] full_file_path = settings.MEDIA_ROOT + '/' + tar_file_path if os.path.isfile(full_file_path): tar_file.add(full_file_path, arcname=tar_file_path) def get(self, request, *args, **kwargs): course = get_object_or_404(Course, id=kwargs.get('course_id')) course_serializer = CourseExportSerializer(course) json_file_bytes = JSONRenderer().render(course_serializer.data) tarfile.ENCODING = 'utf-8' tar_info = tarfile.TarInfo(name='course.json') tar_info.size = len(json_file_bytes) response_bytes = io.BytesIO() course_tar_file = tarfile.open(fileobj=response_bytes, mode='w:gz') course_tar_file.addfile(tar_info, io.BytesIO(json_file_bytes)) course_authors = course_serializer.data.get('course_authors') for course_author in course_authors: picture_path = course_author.get('picture') if picture_path: self.add_files_to_export(course_tar_file, picture_path) # Save the course thumbnail course_thumbnail_path = course_serializer.data.get('thumbnail') if course_thumbnail_path: self.add_files_to_export(course_tar_file, course_thumbnail_path) # Save the home thumbnail course_home_thumbnail_path = course_serializer.data.get('home_thumbnail') if course_home_thumbnail_path: self.add_files_to_export(course_tar_file, course_home_thumbnail_path) # Save the course material course_material = course_serializer.data.get('course_material') if course_material: for course_material_file in course_material['files']: course_material_file_path = urllib.parse.unquote(course_material_file['file']) self.add_files_to_export(course_tar_file, course_material_file_path) # If there are any activities of type 'image', its files must be saved here for lesson in course.lessons.all(): for unit in lesson.units.all(): for activity in unit.activities.all(): if activity.type == 'image': # check if the current activity is of 'image' type try: activity_image_file_path = urllib.parse.unquote(activity.image.url) self.add_files_to_export(course_tar_file, activity_image_file_path) except ValueError: # There is no image file associated with the activity yet pass course_tar_file.close() response = HttpResponse(response_bytes.getvalue(), content_type='application/x-compressed-tar') response['Content-Disposition'] = 'attachment; filename={}''.tar.gz'.format(course.slug) return response class ImportCourseView(APIView): renderer_classes = (JSONRenderer,) permission_classes = (IsAdmin,) @transaction.atomic def post(self, request, *args, **kwargs): import_file = tarfile.open(fileobj=request.FILES.get('course-import-file'), mode='r:gz') file_names = import_file.getnames() json_file_name = [s for s in file_names if '.json' in s][0] json_file = import_file.extractfile(json_file_name) course_data = JSONParser().parse(io.BytesIO(json_file.read())) course_slug = course_data.get('slug') try: course = Course.objects.get(slug=course_slug) if course.has_started: return Response({'error': 'course_started'}) elif not request.DATA.get('force'): return Response({'error': 'course_exists'}) except Course.DoesNotExist: course = None course_thumbnail_path = course_data.pop('thumbnail').replace("/media/", "", 1) course_home_thumbnail_path = course_data.pop('home_thumbnail').replace("/media/", "", 1) # Save course professor images course_author_pictures = {} for course_author in course_data.get('course_authors'): author_name = course_author.get('name') picture_path = course_author.pop('picture') if picture_path and author_name: picture_path = picture_path.split('/', 2)[-1] course_author_pictures[author_name] = picture_path # save course material images course_material = course_data.get('course_material') course_material_files = [] if course_material: course_material_files = course_material.pop('files') # If there are any activities of 'image' type, its files must be given to django now for lesson in course_data['lessons']: for unit in lesson['units']: for activity in unit['activities']: if activity['type'] == 'image': try: image_path = activity.pop('image').replace("/media/", "", 1) new_activity = Activity.objects.create( type='image', image=DjangoFile(import_file.extractfile(image_path)) ) activity['id'] = new_activity.id except AttributeError: # This activity image has no file pass if course: # Update the course if the slug match course_serializer = CourseImportSerializer(course, data=course_data) else: course_serializer = CourseImportSerializer(data=course_data) if course_serializer.is_valid(): course_obj = course_serializer.save() # save thumbnail and home thumbnail if course_thumbnail_path and course_thumbnail_path in file_names: course_thumbnail_file = import_file.extractfile(course_thumbnail_path) course_obj.thumbnail = DjangoFile(name=course_thumbnail_path.split('/')[-1] ,file=course_thumbnail_file) if course_home_thumbnail_path and course_home_thumbnail_path in file_names: course_home_thumbnail_file = import_file.extractfile(course_home_thumbnail_path) course_obj.home_thumbnail = DjangoFile(name=course_home_thumbnail_path.split('/')[-1] ,file=course_home_thumbnail_file) # save course material files course_material_files_list = [] for course_material_file in course_material_files: course_material_file_path = course_material_file.get('file').replace("/media/", "", 1) # remove unnecessary "media" path, if any course_material_file_insntace = CourseMaterialFile( file=DjangoFile(name=course_material_file_path.split('/')[-1] ,file=import_file.extractfile(course_material_file_path)) ) course_material_file_insntace.course_material = course_obj.course_material course_material_file_insntace.save() course_material_files_list.append(course_material_file_insntace) course_obj.course_material.files.set(course_material_files_list) course_obj.course_material.text = course_material['text'] course_obj.course_material.save() # If the course has authors, save save their pictures, if any for course_author in course_obj.course_authors.all(): picture_path = course_author_pictures.get(course_author.name).replace("/media/", "", 1) if picture_path and picture_path in file_names: picture_file_obj = import_file.extractfile(picture_path) # course_author.picture = DjangoFile(picture_file_obj) course_author.picture = DjangoFile(name=picture_path.split('/')[-1] ,file=picture_file_obj) course_author.save() # Save all changes in the new imported course course_obj.save() return Response({'new_course_url': reverse_lazy('courses_legacy:administration.edit_course', kwargs={'course_id': course_obj.id}), }) else: return Response({ 'error': 'invalid_file', 'error_detail': course_serializer.errors, })