Example #1
0
    def publish(self, request, pk=None):
        if not pk:
            raise Http404
        logging.debug("Entering the publish channel endpoint")

        channel = self.get_edit_object()

        if (not channel.main_tree.get_descendants(include_self=True).filter(
                changed=True).exists()):
            raise ValidationError("Cannot publish an unchanged channel")

        channel.main_tree.publishing = True
        channel.main_tree.save()

        version_notes = request.data.get("version_notes")

        task_args = {
            "user_id": request.user.pk,
            "channel_id": channel.id,
            "version_notes": version_notes,
            "language": get_language(),
        }

        create_async_task("export-channel", request.user, **task_args)
        return Response("")
Example #2
0
def publish_channel(request):
    logging.debug("Entering the publish_channel endpoint")
    if request.method != 'POST':
        return HttpResponseBadRequest(
            "Only POST requests are allowed on this endpoint.")

    data = json.loads(request.body)

    try:
        channel_id = data["channel_id"]
        request.user.can_edit(channel_id)

        task_info = {
            'user': request.user,
            'metadata': {
                'affects': {
                    'channels': [channel_id]
                }
            }
        }

        task_args = {
            'user_id': request.user.pk,
            'channel_id': channel_id,
            'version_notes': data.get('version_notes')
        }

        task, task_info = create_async_task('export-channel', task_info,
                                            task_args)
        return HttpResponse(JSONRenderer().render(
            TaskSerializer(task_info).data))
    except KeyError:
        raise ObjectDoesNotExist(
            "Missing attribute from data: {}".format(data))
Example #3
0
    def sync(self, request, pk=None):
        if not pk:
            raise Http404
        logging.debug("Entering the sync channel endpoint")

        channel = self.get_edit_object()

        if (
            not channel.main_tree.get_descendants()
            .filter(
                Q(original_node__isnull=False)
                | Q(
                    original_channel_id__isnull=False,
                    original_source_node_id__isnull=False,
                )
            )
            .exists()
        ):
            raise ValidationError("Cannot sync a channel with no imported content")

        data = request.data

        task_args = {
            "user_id": request.user.pk,
            "channel_id": channel.id,
            "sync_attributes": data.get("attributes"),
            "sync_tags": data.get("tags"),
            "sync_files": data.get("files"),
            "sync_assessment_items": data.get("assessment_items"),
        }

        _, task_info = create_async_task("sync-channel", request.user, **task_args)
        return Response({
            'changes': [self.create_task_event(task_info)]
        })
Example #4
0
    def move(self, pk, target=None, position=None):
        try:
            contentnode = self.get_edit_queryset().get(pk=pk)
        except ContentNode.DoesNotExist:
            error = ValidationError("Specified node does not exist")
            return str(error), None

        try:
            target, position = self.validate_targeting_args(target, position)

            channel_id = target.channel_id

            task_args = {
                "user_id": self.request.user.id,
                "channel_id": channel_id,
                "node_id": contentnode.id,
                "target_id": target.id,
                "position": position,
            }

            task, task_info = create_async_task("move-nodes",
                                                self.request.user, **task_args)

            return (
                None,
                None,
            )
        except ValidationError as e:
            return str(e), None
Example #5
0
    def delete_from_changes(self, changes):
        errors = []
        changes_to_return = []
        queryset = self.get_edit_queryset().order_by()
        for change in changes:
            try:
                instance = queryset.get(
                    **dict(self.values_from_key(change["key"])))

                task_args = {
                    "user_id": self.request.user.id,
                    "channel_id": instance.channel_id,
                    "node_id": instance.id,
                }

                task, task_info = create_async_task("delete-node",
                                                    self.request.user,
                                                    **task_args)
            except ContentNode.DoesNotExist:
                # If the object already doesn't exist, as far as the user is concerned
                # job done!
                pass
            except Exception as e:
                log_sync_exception(e)
                change["errors"] = [str(e)]
                errors.append(change)
        return errors, changes_to_return
Example #6
0
    def test_asynctask_reports_error(self):
        """
        Tests that if a task fails with an error, that the error information is stored in the Task object for later
        retrieval and analysis.
        """
        metadata = {'test': True}
        task_options = {
            'user_id': self.user.pk,
            'task_type': 'asynctask',
            'metadata': metadata
        }
        task, task_info = create_async_task('error-test', task_options)

        task = Task.objects.get(task_id=task.id)
        self.assertEqual(task.status, 'FAILURE')
        self.assertTrue('error' in task.metadata)

        error = task.metadata['error']
        self.assertItemsEqual(list(error.keys()),
                              ['task_args', 'task_kwargs', 'traceback'])
        self.assertEqual(len(error['task_args']), 0)
        self.assertEqual(len(error['task_kwargs']), 0)
        traceback_string = '\n'.join(error['traceback'])
        self.assertTrue("Exception" in traceback_string)
        self.assertTrue(
            "I'm sorry Dave, I'm afraid I can't do that." in traceback_string)
Example #7
0
    def test_duplicate_nodes_task(self):
        ids = []
        node_ids = []
        for i in range(3, 6):
            node_id = "0000000000000000000000000000000" + str(i)
            node_ids.append(node_id)
            node = ContentNode.objects.get(node_id=node_id)
            ids.append(node.pk)

        parent_node = ContentNode.objects.get(
            node_id="00000000000000000000000000000002")

        tasks = []

        for source_id in ids:

            task_args = {
                "user_id": self.user.pk,
                "channel_id": self.channel.pk,
                "source_id": source_id,
                "target_id": parent_node.pk,
                "pk": uuid.uuid4().hex,
            }
            task, task_info = create_async_task("duplicate-nodes",
                                                self.user,
                                                apply_async=False,
                                                **task_args)
            tasks.append((task_args, task_info))

        for task_args, task_info in tasks:
            # progress is retrieved dynamically upon calls to get the task info, so
            # use an API call rather than checking the db directly for progress.
            url = reverse("task-detail", kwargs={"task_id": task_info.task_id})
            response = self.get(url)
            assert (response.data["status"] == states.SUCCESS
                    ), "Task failed, exception: {}".format(
                        response.data["metadata"]["error"]["traceback"])
            self.assertEqual(response.data["status"], states.SUCCESS)
            self.assertEqual(response.data["task_type"], "duplicate-nodes")
            result = response.data["metadata"]["result"]
            node_id = ContentNode.objects.get(pk=task_args["pk"]).node_id
            self.assertEqual(
                result["changes"][0],
                generate_update_event(task_args["pk"], CONTENTNODE, {
                    COPYING_FLAG: False,
                    "node_id": node_id
                }),
            )

        parent_node.refresh_from_db()
        children = parent_node.get_children()

        for child in children:
            # make sure the copies are in the results
            if child.original_source_node_id and child.source_node_id:
                assert child.original_source_node_id in node_ids
                assert child.source_node_id in node_ids
Example #8
0
    def test_duplicate_nodes_task(self):
        metadata = {'test': True}
        task_options = {'user_id': self.user.pk, 'metadata': metadata}

        ids = []
        node_ids = []
        for i in range(3, 6):
            node_id = '0000000000000000000000000000000' + str(i)
            node_ids.append(node_id)
            node = ContentNode.objects.get(node_id=node_id)
            ids.append(node.pk)

        parent_node = ContentNode.objects.get(
            node_id='00000000000000000000000000000002')

        task_args = {
            'user_id': self.user.pk,
            'channel_id': self.channel.pk,
            'node_ids': ids,
            'target_parent': parent_node.pk
        }
        task, task_info = create_async_task('duplicate-nodes', task_options,
                                            task_args)

        # progress is retrieved dynamically upon calls to get the task info, so
        # use an API call rather than checking the db directly for progress.
        url = '{}/{}'.format(self.task_url, task_info.id)
        response = self.get(url)
        assert response.data[
            'status'] == 'SUCCESS', "Task failed, exception: {}".format(
                response.data['metadata']['error']['traceback'])
        self.assertEqual(response.data['status'], 'SUCCESS')
        self.assertEqual(response.data['task_type'], 'duplicate-nodes')
        self.assertEqual(response.data['metadata']['progress'], 100)
        result = response.data['metadata']['result']
        self.assertTrue(isinstance(result, list))

        parent_node.refresh_from_db()
        children = parent_node.get_children()

        child_ids = []
        for child in children:
            child_ids.append(child.source_node_id)

        # make sure the changes were actually made to the DB
        for node_id in node_ids:
            assert node_id in child_ids

        # make sure the copies are in the results
        for item in result:
            assert item['original_source_node_id'] in node_ids
Example #9
0
def generate_node_diff(request, updated_id, original_id):
    try:
        # Get queryset to test permissions
        nodes = ContentNode.objects.filter(
            Q(pk=updated_id) | Q(pk=original_id))
        request.user.can_view_nodes(nodes)

    except PermissionDenied:
        return Response('Diff is not available',
                        status=status.HTTP_403_FORBIDDEN)

    # See if there's already a staging task in progress
    is_generating = Task.objects.filter(
        task_type="get-node-diff",
        metadata__args__updated_id=updated_id,
        metadata__args__original_id=original_id,
    ).exclude(Q(status='FAILURE') | Q(status='SUCCESS')).exists()

    if not is_generating:
        create_async_task("get-node-diff",
                          request.user,
                          updated_id=updated_id,
                          original_id=original_id)
    return Response('Diff is being generated')
Example #10
0
def move_nodes(request):
    logging.debug("Entering the move_nodes endpoint")

    data = request.data

    try:
        nodes = data["nodes"]
        target_parent = ContentNode.objects.get(pk=data["target_parent"])
        channel_id = data["channel_id"]
        min_order = data.get("min_order") or 0
        max_order = data.get("max_order") or min_order + len(nodes)

        channel = target_parent.get_channel()
        try:
            request.user.can_edit(channel and channel.pk)
            request.user.can_edit_nodes(
                ContentNode.objects.filter(id__in=list(n["id"]
                                                       for n in nodes)))
        except PermissionDenied:
            return HttpResponseNotFound("Resources not found")

        task_info = {
            'user': request.user,
            'metadata': {
                'affects': {
                    'channels': [channel_id],
                    'nodes': nodes,
                }
            }
        }

        task_args = {
            'user_id': request.user.pk,
            'channel_id': channel_id,
            'node_ids': nodes,
            'target_parent': data["target_parent"],
            'min_order': min_order,
            'max_order': max_order
        }

        task, task_info = create_async_task('move-nodes', task_info, task_args)
        return HttpResponse(JSONRenderer().render(
            TaskSerializer(task_info).data))

    except KeyError:
        raise ObjectDoesNotExist(
            "Missing attribute from data: {}".format(data))
Example #11
0
    def copy(self,
             pk,
             from_key=None,
             target=None,
             position=None,
             mods=None,
             excluded_descendants=None,
             **kwargs):
        try:
            target, position = self.validate_targeting_args(target, position)
        except ValidationError as e:
            return str(e), None

        try:
            source = self.get_queryset().get(pk=from_key)
        except ContentNode.DoesNotExist:
            error = ValidationError("Copy source node does not exist")
            return str(error), [generate_delete_event(pk, CONTENTNODE)]

        # Affected channel for the copy is the target's channel
        channel_id = target.channel_id

        if ContentNode.objects.filter(pk=pk).exists():
            error = ValidationError("Copy pk already exists")
            return str(error), None

        task_args = {
            "user_id": self.request.user.id,
            "channel_id": channel_id,
            "source_id": source.id,
            "target_id": target.id,
            "pk": pk,
            "mods": mods,
            "excluded_descendants": excluded_descendants,
            "position": position,
        }

        task, task_info = create_async_task("duplicate-nodes",
                                            self.request.user, **task_args)

        return (
            None,
            [
                generate_update_event(pk, CONTENTNODE,
                                      {TASK_ID: task_info.task_id})
            ],
        )
Example #12
0
 def test_asynctask_reports_success(self):
     """
     Tests that when an async task is created and completed, the Task object has a status of 'SUCCESS' and
     contains the return value of the task.
     """
     metadata = {'test': True}
     task_options = {'user_id': self.user.pk, 'metadata': metadata}
     task, task_info = create_async_task('test', task_options)
     self.assertTrue(Task.objects.filter(metadata__test=True).count() == 1)
     self.assertEqual(task_info.user, self.user)
     self.assertEqual(task_info.task_type, 'test')
     self.assertEqual(task_info.is_progress_tracking, False)
     result = task.get()
     self.assertEqual(result, 42)
     self.assertEqual(
         Task.objects.get(task_id=task.id).metadata['result'], 42)
     self.assertEqual(Task.objects.get(task_id=task.id).status, 'SUCCESS')
Example #13
0
    def test_asynctask_reports_success(self):
        """
        Tests that when an async task is created and completed, the Task object has a status of 'SUCCESS' and
        contains the return value of the task.
        """
        task, task_info = create_async_task("test",
                                            self.user,
                                            apply_async=False)
        self.assertEqual(task_info.user, self.user)
        self.assertEqual(task_info.task_type, "test")

        result = task.get()
        self.assertEqual(result, 42)
        self.assertEqual(task.status, states.SUCCESS)
        self.assertEqual(
            Task.objects.get(task_id=task.id).metadata["result"], 42)
        self.assertEqual(
            Task.objects.get(task_id=task.id).status, states.SUCCESS)
Example #14
0
    def test_asynctask_reports_error(self):
        """
        Tests that if a task fails with an error, that the error information is stored in the Task object for later
        retrieval and analysis.
        """
        celery_task, task_info = create_async_task("error-test",
                                                   self.user,
                                                   apply_async=False)

        task_info.refresh_from_db()
        self.assertEqual(task_info.status, states.FAILURE)
        self.assertTrue("error" in task_info.metadata)

        error = task_info.metadata["error"]
        self.assertEqual(list(error.keys()), ["message", "traceback"])
        traceback_string = "\n".join(error["traceback"])
        self.assertTrue("Exception" in traceback_string)
        self.assertTrue(
            "I'm sorry Dave, I'm afraid I can't do that." in traceback_string)
 def test_asynctask_reports_success(self):
     """
     Tests that when an async task is created and completed, the Task object has a status of 'SUCCESS' and
     contains the return value of the task.
     """
     metadata = {'test': True}
     task_options = {
         'user_id': self.user.pk,
         'task_type': 'asynctask',
         'metadata': metadata
     }
     task, task_info = create_async_task('test', task_options)
     self.assertTrue(Task.objects.filter(metadata__test=True).count()==1)
     self.assertEqual(task_info.user, self.user)
     self.assertEqual(task_info.task_type, 'test')
     self.assertEqual(task_info.is_progress_tracking, False)
     result = task.get()
     self.assertEqual(Task.objects.get(task_id=task.id).metadata['result'], 42)
     self.assertEqual(Task.objects.get(task_id=task.id).status, 'SUCCESS')
Example #16
0
def duplicate_node_inline(request):
    logging.debug("Entering the dupllicate_node_inline endpoint")

    if request.method != 'POST':
        return HttpResponseBadRequest(
            "Only POST requests are allowed on this endpoint.")

    data = request.data

    try:
        node_id = data["node_id"]
        channel_id = data["channel_id"]
        target_parent = ContentNode.objects.get(pk=data["target_parent"])
        channel = target_parent.get_channel()
        try:
            request.user.can_edit(channel and channel.pk)
        except PermissionDenied:
            return HttpResponseNotFound("No channel matching: {}".format(
                channel and channel.pk))

        task_info = {
            'user': request.user,
            'metadata': {
                'affects': {
                    'channels': [channel_id],
                    'nodes': [node_id],
                }
            }
        }

        task_args = {
            'user_id': request.user.pk,
            'channel_id': channel_id,
            'target_parent': target_parent.pk,
            'node_id': node_id,
        }

        task, task_info = create_async_task('duplicate-node-inline', task_info,
                                            task_args)
        return Response(TaskSerializer(task_info).data)
    except KeyError:
        raise ObjectDoesNotExist(
            "Missing attribute from data: {}".format(data))
Example #17
0
def duplicate_nodes(request):
    logging.debug("Entering the copy_node endpoint")

    data = request.data

    try:
        node_ids = data["node_ids"]
        sort_order = data.get("sort_order") or 1
        channel_id = data["channel_id"]
        target_parent = ContentNode.objects.get(pk=data["target_parent"])
        channel = target_parent.get_channel()
        try:
            request.user.can_edit(channel and channel.pk)
        except PermissionDenied:
            return HttpResponseNotFound("No channel matching: {}".format(
                channel and channel.pk))

        task_info = {
            'user': request.user,
            'metadata': {
                'affects': {
                    'channels': [channel_id],
                    'nodes': node_ids,
                }
            }
        }

        task_args = {
            'user_id': request.user.pk,
            'channel_id': channel_id,
            'target_parent': target_parent.pk,
            'node_ids': node_ids,
            'sort_order': sort_order
        }

        task, task_info = create_async_task('duplicate-nodes', task_info,
                                            task_args)
        return HttpResponse(JSONRenderer().render(
            TaskSerializer(task_info).data))
    except KeyError:
        raise ObjectDoesNotExist(
            "Missing attribute from data: {}".format(data))
Example #18
0
def sync_nodes(request):
    logging.debug("Entering the sync_nodes endpoint")

    data = request.data

    try:
        nodes = data["nodes"]
        channel_id = data['channel_id']
        try:
            request.user.can_edit(channel_id)
            request.user.can_edit_nodes(
                ContentNode.objects.filter(id__in=list(n["id"]
                                                       for n in nodes)))
        except PermissionDenied:
            return HttpResponseNotFound("Resources not found")

        task_info = {
            'user': request.user,
            'metadata': {
                'affects': {
                    'channels': [channel_id],
                    'nodes': nodes,
                }
            }
        }

        task_args = {
            'user_id': request.user.pk,
            'channel_id': channel_id,
            'node_ids': nodes,
            'sync_attributes': True,
            'sync_tags': True,
            'sync_files': True,
            'sync_assessment_items': True,
        }

        task, task_info = create_async_task('sync-nodes', task_info, task_args)
        return HttpResponse(JSONRenderer().render(
            TaskSerializer(task_info).data))
    except KeyError:
        raise ObjectDoesNotExist(
            "Missing attribute from data: {}".format(data))
Example #19
0
    def test_asynctask_reports_progress(self):
        """
        Test that we can retrieve task progress via the Task API.
        """
        metadata = {'test': True}
        task_options = {'user_id': self.user.pk, 'metadata': metadata}
        task, task_info = create_async_task('progress-test', task_options)
        self.assertTrue(Task.objects.filter(metadata__test=True).count() == 1)
        result = task.get()
        self.assertEqual(result, 42)
        self.assertEqual(Task.objects.get(task_id=task.id).status, 'SUCCESS')

        # progress is retrieved dynamically upon calls to get the task info, so
        # use an API call rather than checking the db directly for progress.
        url = '{}/{}'.format(self.task_url, task_info.id)
        response = self.get(url)
        self.assertEqual(response.data['status'], 'SUCCESS')
        self.assertEqual(response.data['task_type'], 'progress-test')
        self.assertEqual(response.data['metadata']['progress'], 100)
        self.assertEqual(response.data['metadata']['result'], 42)
Example #20
0
    def test_asynctask_filters_by_channel(self):
        """
        Test that we can filter tasks by channel ID.
        """

        self.channel.editors.add(self.user)
        self.channel.save()
        metadata = {'affects': {'channels': [self.channel.id]}}
        task_options = {
            'user_id': self.user.pk,
            'metadata': metadata
        }
        task, task_info = create_async_task('progress-test', task_options)
        self.assertTrue(Task.objects.filter(metadata__affects__channels__contains=[self.channel.id]).count() == 1)
        result = task.get()
        self.assertEqual(result, 42)
        self.assertEqual(Task.objects.get(task_id=task.id).status, 'SUCCESS')

        # since tasks run sync in tests, we can't test it in an actual running state
        # so simulate the running state in the task object.
        db_task = Task.objects.get(task_id=task.id)
        db_task.status = 'STARTED'
        db_task.save()
        url = '{}?channel_id={}'.format(self.task_url, self.channel.id)
        response = self.get(url)
        self.assertEqual(len(response.data), 1)
        self.assertEqual(response.data[0]['status'], 'STARTED')
        self.assertEqual(response.data[0]['task_type'], 'progress-test')
        self.assertEqual(response.data[0]['metadata']['progress'], 100)
        self.assertEqual(response.data[0]['metadata']['result'], 42)

        # once the task is completed, it should be removed from the list of channel tasks.
        db_task.status = 'SUCCESS'
        db_task.save()
        response = self.get(url)
        self.assertEqual(len(response.data), 0)

        url = '{}?channel_id={}'.format(self.task_url, task_info.id, "nope")
        response = self.get(url)
        self.assertEqual(len(response.data), 0)
Example #21
0
def sync_channel_endpoint(request):
    logging.debug("Entering the sync_nodes endpoint")

    data = request.data

    try:
        channel_id = data['channel_id']

        try:
            request.user.can_edit(channel_id)
        except PermissionDenied:
            return HttpResponseNotFound(
                "No channel matching: {}".format(channel_id))

        task_info = {
            'user': request.user,
            'metadata': {
                'affects': {
                    'channels': [channel_id],
                }
            }
        }

        task_args = {
            'user_id': request.user.pk,
            'channel_id': channel_id,
            'sync_attributes': data.get('attributes'),
            'sync_tags': data.get('tags'),
            'sync_files': data.get('files'),
            'sync_assessment_items': data.get('assessment_items'),
            'sync_sort_order': data.get('sort'),
        }

        task, task_info = create_async_task('sync-channel', task_info,
                                            task_args)
        return HttpResponse(JSONRenderer().render(
            TaskSerializer(task_info).data))
    except KeyError:
        raise ObjectDoesNotExist(
            "Missing attribute from data: {}".format(data))
    def test_asynctask_reports_error(self):
        """
        Tests that if a task fails with an error, that the error information is stored in the Task object for later
        retrieval and analysis.
        """
        metadata = {'test': True}
        task_options = {
            'user_id': self.user.pk,
            'task_type': 'asynctask',
            'metadata': metadata
        }
        task, task_info = create_async_task('error-test', task_options)

        task = Task.objects.get(task_id=task.id)
        self.assertEqual(task.status, 'FAILURE')
        self.assertTrue('error' in task.metadata)

        error = task.metadata['error']
        self.assertItemsEqual(list(error.keys()), ['task_args', 'task_kwargs', 'traceback'])
        self.assertEqual(len(error['task_args']), 0)
        self.assertEqual(len(error['task_kwargs']), 0)
        traceback_string = '\n'.join(error['traceback'])
        self.assertTrue("Exception" in traceback_string)
        self.assertTrue("I'm sorry Dave, I'm afraid I can't do that." in traceback_string)
    def test_asynctask_reports_progress(self):
        """
        Test that we can retrieve task progress via the Task API.
        """
        metadata = {'test': True}
        task_options = {
            'user_id': self.user.pk,
            'task_type': 'asynctask',
            'metadata': metadata
        }
        task, task_info = create_async_task('progress-test', task_options)
        self.assertTrue(Task.objects.filter(metadata__test=True).count()==1)
        result = task.get()
        self.assertEqual(result, 42)
        self.assertEqual(Task.objects.get(task_id=task.id).status, 'SUCCESS')

        # progress is retrieved dynamically upon calls to get the task info, so
        # use an API call rather than checking the db directly for progress.
        url = '{}/{}'.format(self.task_url, task_info.id)
        response = self.get(url)
        self.assertEqual(response.data['status'], 'SUCCESS')
        self.assertEqual(response.data['task_type'], 'progress-test')
        self.assertEqual(response.data['metadata']['progress'], 100)
        self.assertEqual(response.data['metadata']['result'], 42)
Example #24
0
def api_commit_channel(request):
    """
    Commit the channel chef_tree to staging tree to the main tree.
    This view backs the endpoint `/api/internal/finish_channel` called by ricecooker.
    """
    data = json.loads(request.body)
    try:
        channel_id = data['channel_id']

        request.user.can_edit(channel_id)

        obj = Channel.objects.get(pk=channel_id)

        # Need to rebuild MPTT tree pointers since we used `disable_mptt_updates`
        ContentNode.objects.partial_rebuild(obj.chef_tree.tree_id)
        # set original_channel_id and source_channel_id to self since chef tree
        obj.chef_tree.get_descendants(include_self=True).update(original_channel_id=channel_id,
                                                                source_channel_id=channel_id)

        # replace staging_tree with chef_tree
        old_staging = obj.staging_tree
        obj.staging_tree = obj.chef_tree
        obj.chef_tree = None
        obj.save()

        # Prepare change event indicating a new staging_tree is available
        event = generate_update_event(channel_id, CHANNEL, {
            "root_id": obj.main_tree.id,
            "staging_root_id": obj.staging_tree.id,
        })

        # Mark old staging tree for garbage collection
        if old_staging and old_staging != obj.main_tree:
            # IMPORTANT: Do not remove this block, MPTT updating the deleted chefs block could hang the server
            with ContentNode.objects.disable_mptt_updates():
                garbage_node = get_deleted_chefs_root()
                old_staging.parent = garbage_node
                old_staging.title = "Old staging tree for channel {}".format(obj.pk)
                old_staging.save()

        # Send event (new staging tree or new main tree) to all channel editors
        for editor in obj.editors.all():
            add_event_for_user(editor.id, event)

        _, task = create_async_task(
            "get-node-diff",
            request.user,
            updated_id=obj.staging_tree.id,
            original_id=obj.main_tree.id,
        )

        # Send response back to the content integration script
        return Response({
            "success": True,
            "new_channel": obj.pk,
            "diff_task_id": task.pk,
        })
    except (Channel.DoesNotExist, PermissionDenied):
        return HttpResponseNotFound("No channel matching: {}".format(channel_id))
    except KeyError:
        return HttpResponseBadRequest("Required attribute missing from data: {}".format(data))
    except Exception as e:
        handle_server_error(request)
        return HttpResponseServerError(content=str(e), reason=str(e))