예제 #1
0
    def update(self, request, **kwargs):
        # logging to help debug error around course url slugs incrementing
        log.info('The raw course run data coming from publisher is {}.'.format(
            request.data))

        # Update one, or more, fields for a course run.
        course_run = self.get_object()
        course_run = ensure_draft_world(course_run)  # always work on drafts
        partial = kwargs.pop('partial', False)
        # Sending draft=False triggers the review process for unpublished courses
        draft = request.data.pop(
            'draft', True)  # Don't let draft parameter trickle down
        prices = request.data.pop('prices', {})

        serializer = self.get_serializer(course_run,
                                         data=request.data,
                                         partial=partial)
        serializer.is_valid(raise_exception=True)

        # Handle staff update on course run in review with valid status transition
        if (request.user.is_staff and course_run.in_review
                and 'status' in request.data and request.data['status']
                in CourseRunStatus.INTERNAL_STATUS_TRANSITIONS):
            return self.handle_internal_review(request, serializer)

        # Handle regular non-internal update
        request.data.pop('status',
                         None)  # Status management is handled in the model
        serializer.validated_data.pop(
            'status', None)  # Status management is handled in the model
        # Disallow patch or put if the course run is in review.
        if course_run.in_review:
            return Response(_('Course run is in review. Editing disabled.'),
                            status=status.HTTP_403_FORBIDDEN)
        # Disallow internal review fields when course run is not in review
        for key in request.data.keys():
            if key in CourseRun.INTERNAL_REVIEW_FIELDS:
                return Response(_('Invalid parameter'),
                                status=status.HTTP_400_BAD_REQUEST)

        changed_fields = reviewable_data_has_changed(
            course_run, serializer.validated_data.items(),
            CourseRun.STATUS_CHANGE_EXEMPT_FIELDS)
        response = self._update_course_run(course_run, draft,
                                           bool(changed_fields), serializer,
                                           request, prices)

        self.update_course_run_image_in_studio(course_run)

        return response
예제 #2
0
    def update_course(self, data, partial=False):  # pylint: disable=too-many-statements
        """ Updates an existing course from incoming data. """
        changed = False
        # Sending draft=False means the course data is live and updates should be pushed out immediately
        draft = data.pop('draft', True)
        image_data = data.pop('image', None)
        video_data = data.pop('video', None)
        url_slug = data.pop('url_slug', '')

        # Get and validate object serializer
        course = self.get_object()
        course = ensure_draft_world(course)  # always work on drafts
        serializer = self.get_serializer(course, data=data, partial=partial)
        serializer.is_valid(raise_exception=True)

        # TEMPORARY - log incoming request (subject and prices) for all course updates, see Jira DISCO-1593
        self.log_request_subjects_and_prices(data, course)

        # First, update course entitlements
        if data.get('type') or data.get('prices'):
            entitlements = []
            prices = data.get('prices', {})
            course_type = CourseType.objects.get(
                uuid=data.get('type')) if data.get('type') else course.type
            entitlement_types = course_type.entitlement_types.all()
            for entitlement_type in entitlement_types:
                price = prices.get(entitlement_type.slug)
                if price is None:
                    continue
                entitlement, did_change = self.update_entitlement(
                    course, entitlement_type, price, partial=partial)
                entitlements.append(entitlement)
                changed = changed or did_change
            # Deleting entitlements here since they would be orphaned otherwise.
            # One example of how this situation can happen is if a course team is switching between
            # "Verified and Audit" and "Audit Only" before actually publishing their course run.
            course.entitlements.exclude(mode__in=entitlement_types).delete()
            course.entitlements.set(entitlements)

        # Save video if a new video source is provided
        if (video_data and video_data.get('src') and
            (not course.video or video_data.get('src') != course.video.src)):
            video, __ = Video.objects.get_or_create(src=video_data['src'])
            course.video = video

        # Save image and convert to the correct format
        if image_data and isinstance(
                image_data, str) and image_data.startswith('data:image'):
            # base64 encoded image - decode
            file_format, imgstr = image_data.split(
                ';base64,')  # format ~= data:image/X;base64,/xxxyyyzzz/
            ext = file_format.split('/')[-1]  # guess file extension
            image_data = ContentFile(
                base64.b64decode(imgstr),
                name='tmp.{extension}'.format(extension=ext))
            course.image.save(image_data.name, image_data)

        if data.get('collaborators'):
            collaborators_uuids = data.get('collaborators')
            collaborators = Collaborator.objects.filter(
                uuid__in=collaborators_uuids)
            course.collaborators.add(*collaborators)

        # If price didnt change, check the other fields on the course
        # (besides image and video, they are popped off above)
        changed = changed or reviewable_data_has_changed(
            course, serializer.validated_data.items())

        if url_slug:
            validators.validate_slug(url_slug)
            all_course_historical_slugs_excluding_present = CourseUrlSlug.objects.filter(
                url_slug=url_slug,
                partner=course.partner).exclude(course__uuid=course.uuid)
            if all_course_historical_slugs_excluding_present.exists():
                raise Exception(
                    _('Course edit was unsuccessful. The course URL slug ‘[{url_slug}]’ is already in use. '
                      'Please update this field and try again.').format(
                          url_slug=url_slug))

        # Then the course itself
        course = serializer.save()
        if url_slug:
            course.set_active_url_slug(url_slug)

        if not draft:
            for course_run in course.active_course_runs:
                if course_run.status == CourseRunStatus.Published:
                    # This will also update the course
                    course_run.update_or_create_official_version()

        # Revert any Reviewed course runs back to Unpublished
        if changed:
            for course_run in course.course_runs.filter(
                    status=CourseRunStatus.Reviewed):
                course_run.status = CourseRunStatus.Unpublished
                course_run.save()
                course_run.official_version.status = CourseRunStatus.Unpublished
                course_run.official_version.save()

        # hack to get the correctly-updated url slug into the response
        return_dict = {'url_slug': course.active_url_slug}
        return_dict.update(serializer.data)
        return Response(return_dict)