views.py 9.21 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
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

Bruno Martin's avatar
Bruno Martin committed
18
from courses.course_material.models import File as CourseMaterialFile
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

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']:
Glaucia S. Santos's avatar
Glaucia S. Santos committed
74
                course_material_file_path = urllib.parse.unquote(course_material_file['file'])
75 76 77 78 79 80 81 82
                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:
Glaucia S. Santos's avatar
Glaucia S. Santos committed
83
                                activity_image_file_path = urllib.parse.unquote(activity.image.url)
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
                                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:
Bruno Martin's avatar
Bruno Martin committed
138
            course_material_files = course_material.pop('files')
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177

        # 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
Bruno Martin's avatar
Bruno Martin committed
178 179 180 181 182 183
                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)
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208

            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,
            })