def _cut_plugin(self, request, plugin, target_language,  target_placeholder):
        if not self.has_move_plugin_permission(request, plugin, target_placeholder):
            message = force_str(_("You have no permission to cut this plugin"))
            raise PermissionDenied(message)

        plugin_data = {
            'language': target_language,
            'placeholder': target_placeholder,
        }

        source_language = plugin.language
        source_placeholder = plugin.placeholder
        source_tree_order = source_placeholder.get_plugin_tree_order(
            language=source_language,
            parent_id=plugin.parent_id,
        )

        action_token = self._send_pre_placeholder_operation(
            request,
            operation=operations.CUT_PLUGIN,
            plugin=plugin,
            clipboard=target_placeholder,
            clipboard_language=target_language,
            source_language=source_language,
            source_placeholder=source_placeholder,
            source_parent_id=plugin.parent_id,
            source_order=source_tree_order,
        )

        # Empty the clipboard
        target_placeholder.clear()

        target = CMSPlugin.get_last_root_node()
        updated_plugin = plugin.update(refresh=True, parent=None, **plugin_data)
        updated_plugin = updated_plugin.move(target, pos='right')

        # Update all children to match the parent's
        # language and placeholder (clipboard)
        updated_plugin.get_descendants().update(**plugin_data)

        # Avoid query by removing the plugin being moved
        # from the source order
        new_source_order = list(source_tree_order)
        new_source_order.remove(updated_plugin.pk)

        source_placeholder.mark_as_dirty(target_language, clear_cache=False)

        self._send_post_placeholder_operation(
            request,
            operation=operations.CUT_PLUGIN,
            token=action_token,
            plugin=updated_plugin.get_bound_plugin(),
            clipboard=target_placeholder,
            clipboard_language=target_language,
            source_language=source_language,
            source_placeholder=source_placeholder,
            source_parent_id=plugin.parent_id,
            source_order=new_source_order,
        )
        return updated_plugin
Exemple #2
0
    def move_plugin(self, request):
        """
        POST request with following parameters:
        -plugin_id
        -placeholder_id
        -plugin_language (optional)
        -plugin_parent (optional)
        -plugin_order (array, optional)
        """
        plugin = CMSPlugin.objects.get(pk=int(request.POST['plugin_id']))
        placeholder = Placeholder.objects.get(pk=request.POST['placeholder_id'])
        parent_id = request.POST.get('plugin_parent', None)
        language = request.POST.get('plugin_language', None)
        source_placeholder = plugin.placeholder
        if not parent_id:
            parent_id = None
        else:
            parent_id = int(parent_id)
        if not language and plugin.language:
            language = plugin.language
        order = request.POST.getlist("plugin_order[]")
        if not self.has_move_plugin_permission(request, plugin, placeholder):
            return HttpResponseForbidden(force_text(_("You have no permission to move this plugin")))
        if not placeholder == source_placeholder:
            try:
                template = self.get_placeholder_template(request, placeholder)
                has_reached_plugin_limit(placeholder, plugin.plugin_type, plugin.language, template=template)
            except PluginLimitReached as er:
                return HttpResponseBadRequest(er)
        if parent_id:
            if plugin.parent_id != parent_id:
                parent = CMSPlugin.objects.get(pk=parent_id)
                if parent.placeholder_id != placeholder.pk:
                    return HttpResponseBadRequest(force_text('parent must be in the same placeholder'))
                if parent.language != language:
                    return HttpResponseBadRequest(force_text('parent must be in the same language as plugin_language'))
                plugin.parent_id = parent.pk
                plugin.save()
                plugin = plugin.move(parent, pos='last-child')
        else:
            sibling = CMSPlugin.get_last_root_node()
            plugin.parent_id = None
            plugin.save()
            plugin = plugin.move(sibling, pos='right')
        for child in [plugin] + list(plugin.get_descendants()):
            child.placeholder = placeholder
            child.language = language
            child.save()
        plugins = reorder_plugins(placeholder, parent_id, language, order)
        if not plugins:
            return HttpResponseBadRequest('order parameter did not have all plugins of the same level in it')

        self.post_move_plugin(request, source_placeholder, placeholder, plugin)
        json_response = {'reload': requires_reload(PLUGIN_MOVE_ACTION, [plugin])}
        return HttpResponse(json.dumps(json_response), content_type='application/json')
    def _move_plugin(self, request, plugin, target_language,
                     target_placeholder, tree_order, target_parent=None):
        if not self.has_move_plugin_permission(request, plugin, target_placeholder):
            message = force_text(_("You have no permission to move this plugin"))
            raise PermissionDenied(message)

        plugin_data = {
            'language': target_language,
            'placeholder': target_placeholder,
        }

        source_language = plugin.language
        source_placeholder = plugin.placeholder
        source_tree_order = source_placeholder.get_plugin_tree_order(
            language=source_language,
            parent_id=plugin.parent_id,
        )

        if target_parent:
            target_parent_id = target_parent.pk
        else:
            target_parent_id = None

        if target_placeholder != source_placeholder:
            target_tree_order = target_placeholder.get_plugin_tree_order(
                language=target_language,
                parent_id=target_parent_id,
            )
        else:
            target_tree_order = source_tree_order

        action_token = self._send_pre_placeholder_operation(
            request,
            operation=operations.MOVE_PLUGIN,
            plugin=plugin,
            source_language=source_language,
            source_placeholder=source_placeholder,
            source_parent_id=plugin.parent_id,
            source_order=source_tree_order,
            target_language=target_language,
            target_placeholder=target_placeholder,
            target_parent_id=target_parent_id,
            target_order=target_tree_order,
        )

        if target_parent and plugin.parent != target_parent:
            # Plugin is being moved to another tree (under another parent)
            updated_plugin = plugin.update(refresh=True, parent=target_parent, **plugin_data)
            updated_plugin = updated_plugin.move(target_parent, pos='last-child')
        elif target_parent:
            # Plugin is being moved within the same tree (different position, same parent)
            updated_plugin = plugin.update(refresh=True, **plugin_data)
        else:
            # Plugin is being moved to the root (no parent)
            target = CMSPlugin.get_last_root_node()
            updated_plugin = plugin.update(refresh=True, parent=None, **plugin_data)
            updated_plugin = updated_plugin.move(target, pos='right')

        # Update all children to match the parent's
        # language and placeholder
        updated_plugin.get_descendants().update(**plugin_data)

        # Avoid query by removing the plugin being moved
        # from the source order
        new_source_order = list(source_tree_order)
        new_source_order.remove(updated_plugin.pk)

        # Reorder all plugins in the target placeholder according to the
        # passed order
        new_target_order = [int(pk) for pk in tree_order]
        reorder_plugins(
            target_placeholder,
            parent_id=target_parent_id,
            language=target_language,
            order=new_target_order,
        )

        # Refresh plugin to get new tree and position values
        updated_plugin.refresh_from_db()

        self._send_post_placeholder_operation(
            request,
            operation=operations.MOVE_PLUGIN,
            plugin=updated_plugin.get_bound_plugin(),
            token=action_token,
            source_language=source_language,
            source_placeholder=source_placeholder,
            source_parent_id=plugin.parent_id,
            source_order=new_source_order,
            target_language=target_language,
            target_placeholder=target_placeholder,
            target_parent_id=target_parent_id,
            target_order=new_target_order,
        )
        return updated_plugin
    def move_plugin(self, request):
        """
        Performs a move or a "paste" operation (when «move_a_copy» is set)

        POST request with following parameters:
        - plugin_id
        - placeholder_id
        - plugin_language (optional)
        - plugin_parent (optional)
        - plugin_order (array, optional)
        - move_a_copy (Boolean, optional) (anything supplied here except a case-
                                        insensitive "false" is True)
        NOTE: If move_a_copy is set, the plugin_order should contain an item
              '__COPY__' with the desired destination of the copied plugin.
        """
        # plugin_id and placeholder_id are required, so, if nothing is supplied,
        # an ValueError exception will be raised by get_int().
        try:
            plugin_id = get_int(request.POST.get('plugin_id'))
        except TypeError:
            raise RuntimeError("'plugin_id' is a required parameter.")

        plugin = (CMSPlugin.objects.select_related('placeholder').get(
            pk=plugin_id))

        try:
            placeholder_id = get_int(request.POST.get('placeholder_id'))
        except TypeError:
            raise RuntimeError("'placeholder_id' is a required parameter.")
        except ValueError:
            raise RuntimeError("'placeholder_id' must be an integer string.")
        placeholder = Placeholder.objects.get(pk=placeholder_id)
        # The rest are optional
        parent_id = get_int(request.POST.get('plugin_parent', ""), None)
        language = request.POST.get('plugin_language', None)
        move_a_copy = request.POST.get('move_a_copy', False)
        move_a_copy = (move_a_copy and move_a_copy != "0"
                       and move_a_copy.lower() != "false")

        source_language = plugin.language
        source_placeholder = plugin.placeholder

        if not language and plugin.language:
            language = plugin.language
        order = request.POST.getlist("plugin_order[]")

        if placeholder != source_placeholder:
            try:
                template = self.get_placeholder_template(request, placeholder)
                has_reached_plugin_limit(placeholder,
                                         plugin.plugin_type,
                                         plugin.language,
                                         template=template)
            except PluginLimitReached as er:
                return HttpResponseBadRequest(er)

        if move_a_copy:  # "paste"
            if plugin.plugin_type == "PlaceholderPlugin":
                parent_id = None
                inst = plugin.get_plugin_instance()[0]
                plugins = inst.placeholder_ref.get_plugins()
            else:
                plugins = [plugin] + list(plugin.get_descendants())

            if not self.has_copy_from_clipboard_permission(
                    request, placeholder, plugins):
                return HttpResponseForbidden(
                    force_text(
                        _("You have no permission to paste this plugin")))

            new_plugins = copy_plugins.copy_plugins_to(
                plugins,
                placeholder,
                language,
                parent_plugin_id=parent_id,
            )

            top_plugins = []
            top_parent = new_plugins[0][0].parent_id
            for new_plugin, old_plugin in new_plugins:
                if new_plugin.parent_id == top_parent:
                    # NOTE: There is no need to save() the plugins here.
                    new_plugin.position = old_plugin.position
                    top_plugins.append(new_plugin)

            # Creates a list of string PKs of the top-level plugins ordered by
            # their position.
            top_plugins_pks = [
                str(p.pk)
                for p in sorted(top_plugins, key=lambda x: x.position)
            ]

            if parent_id:
                parent = CMSPlugin.objects.get(pk=parent_id)

                for plugin in top_plugins:
                    plugin.parent = parent
                    plugin.placeholder = placeholder
                    plugin.language = language
                    plugin.save()

            # If an ordering was supplied, we should replace the item that has
            # been copied with the new copy
            if order:
                if '__COPY__' in order:
                    copy_idx = order.index('__COPY__')
                    del order[copy_idx]
                    order[copy_idx:0] = top_plugins_pks
                else:
                    order.extend(top_plugins_pks)

            # Set the plugin variable to point to the newly created plugin.
            plugin = new_plugins[0][0]
        else:
            # Regular move
            if not self.has_move_plugin_permission(request, plugin,
                                                   placeholder):
                return HttpResponseForbidden(
                    force_text(
                        _("You have no permission to move this plugin")))

            plugin_data = {
                'language': language,
                'placeholder': placeholder,
            }

            if parent_id:
                if plugin.parent_id != parent_id:
                    parent = CMSPlugin.objects.get(pk=parent_id)
                    if parent.placeholder_id != placeholder.pk:
                        return HttpResponseBadRequest(
                            force_text(
                                _('parent must be in the same placeholder')))
                    if parent.language != language:
                        return HttpResponseBadRequest(
                            force_text(
                                _('parent must be in the same language as '
                                  'plugin_language')))
                    plugin = plugin.update(refresh=True,
                                           parent=parent,
                                           **plugin_data)
                    plugin = plugin.move(parent, pos='last-child')
                else:
                    plugin = plugin.update(refresh=True, **plugin_data)
            else:
                target = CMSPlugin.get_last_root_node()
                plugin = plugin.update(refresh=True,
                                       parent=None,
                                       **plugin_data)
                plugin = plugin.move(target, pos='right')

            # Update all children to match the parent's
            # language and placeholder
            plugin.get_descendants().update(**plugin_data)

        if order:
            # order should be a list of plugin primary keys
            # it's important that the plugins being referenced
            # are all part of the same tree.
            order = [int(pk) for pk in order]
            plugins_in_tree = CMSPlugin.objects.filter(
                parent=parent_id,
                placeholder=placeholder,
                language=language,
                pk__in=order,
            )

            if len(order) != plugins_in_tree.count():
                # Seems like order does not match the tree on the db
                message = _(
                    'order parameter references plugins in different trees')
                return HttpResponseBadRequest(force_text(message))

        # Mark the target placeholder as dirty
        placeholder.mark_as_dirty(language)

        if placeholder != source_placeholder:
            # Plugin is being moved or copied into a separate placeholder
            # Mark source placeholder as dirty
            source_placeholder.mark_as_dirty(source_language)

        reorder_plugins(placeholder, parent_id, language, order)

        # When this is executed we are in the admin class of the source placeholder
        # It can be a page or a model with a placeholder field.
        # Because of this we need to get the admin class instance of the
        # target placeholder and call post_move_plugin() on it.
        # By doing this we make sure that both the source and target are
        # informed of the operation.
        target_placeholder_admin = self._get_attached_admin(placeholder)

        if move_a_copy:  # "paste"
            plugins = list(plugin.get_tree())
            self.post_copy_plugins(request, source_placeholder, placeholder,
                                   plugins)

            if (target_placeholder_admin
                    and target_placeholder_admin.model != self.model):
                target_placeholder_admin.post_copy_plugins(
                    request,
                    source_placeholder=source_placeholder,
                    target_placeholder=placeholder,
                    plugins=plugins,
                )
        else:
            self.post_move_plugin(request, source_placeholder, placeholder,
                                  plugin)

            if (target_placeholder_admin
                    and target_placeholder_admin.model != self.model):
                target_placeholder_admin.post_move_plugin(
                    request,
                    source_placeholder=source_placeholder,
                    target_placeholder=placeholder,
                    plugin=plugin,
                )

        try:
            language = request.toolbar.toolbar_language
        except AttributeError:
            language = get_language_from_request(request)

        with force_language(language):
            plugin_urls = plugin.get_action_urls()

        json_response = {
            'urls': plugin_urls,
            'reload': move_a_copy
            or requires_reload(PLUGIN_MOVE_ACTION, [plugin])
        }
        return HttpResponse(json.dumps(json_response),
                            content_type='application/json')
    def move_plugin(self, request):
        """
        Performs a move or a "paste" operation (when «move_a_copy» is set)

        POST request with following parameters:
        - plugin_id
        - placeholder_id
        - plugin_language (optional)
        - plugin_parent (optional)
        - plugin_order (array, optional)
        - move_a_copy (Boolean, optional) (anything supplied here except a case-
                                        insensitive "false" is True)
        NOTE: If move_a_copy is set, the plugin_order should contain an item
              '__COPY__' with the desired destination of the copied plugin.
        """
        # plugin_id and placeholder_id are required, so, if nothing is supplied,
        # an ValueError exception will be raised by get_int().
        try:
            plugin_id = get_int(request.POST.get('plugin_id'))
        except TypeError:
            raise RuntimeError("'plugin_id' is a required parameter.")
        plugin = CMSPlugin.objects.get(pk=plugin_id)
        try:
            placeholder_id = get_int(request.POST.get('placeholder_id'))
        except TypeError:
            raise RuntimeError("'placeholder_id' is a required parameter.")
        except ValueError:
            raise RuntimeError("'placeholder_id' must be an integer string.")
        placeholder = Placeholder.objects.get(pk=placeholder_id)
        # The rest are optional
        parent_id = get_int(request.POST.get('plugin_parent', ""), None)
        language = request.POST.get('plugin_language', None)
        move_a_copy = request.POST.get('move_a_copy', False)
        move_a_copy = (move_a_copy and move_a_copy != "0" and
                       move_a_copy.lower() != "false")

        source_placeholder = plugin.placeholder
        if not language and plugin.language:
            language = plugin.language
        order = request.POST.getlist("plugin_order[]")

        if not self.has_move_plugin_permission(request, plugin, placeholder):
            return HttpResponseForbidden(
                force_text(_("You have no permission to move this plugin")))
        if placeholder != source_placeholder:
            try:
                template = self.get_placeholder_template(request, placeholder)
                has_reached_plugin_limit(placeholder, plugin.plugin_type,
                                         plugin.language, template=template)
            except PluginLimitReached as er:
                return HttpResponseBadRequest(er)

        if move_a_copy:  # "paste"
            if plugin.plugin_type == "PlaceholderPlugin":
                parent_id = None
                inst = plugin.get_plugin_instance()[0]
                plugins = inst.placeholder_ref.get_plugins()
            else:
                plugins = [plugin] + list(plugin.get_descendants())

            new_plugins = copy_plugins.copy_plugins_to(
                plugins,
                placeholder,
                language,
                parent_plugin_id=parent_id,
            )

            top_plugins = []
            top_parent = new_plugins[0][0].parent_id
            for new_plugin, old_plugin in new_plugins:
                if new_plugin.parent_id == top_parent:
                    # NOTE: There is no need to save() the plugins here.
                    new_plugin.position = old_plugin.position
                    top_plugins.append(new_plugin)

            # Creates a list of string PKs of the top-level plugins ordered by
            # their position.
            top_plugins_pks = [str(p.pk) for p in sorted(
                top_plugins, key=lambda x: x.position)]

            if parent_id:
                parent = CMSPlugin.objects.get(pk=parent_id)

                for plugin in top_plugins:
                    plugin.parent = parent
                    plugin.placeholder = placeholder
                    plugin.language = language
                    plugin.save()

            # If an ordering was supplied, we should replace the item that has
            # been copied with the new copy
            if order:
                if '__COPY__' in order:
                    copy_idx = order.index('__COPY__')
                    del order[copy_idx]
                    order[copy_idx:0] = top_plugins_pks
                else:
                    order.extend(top_plugins_pks)

            # Set the plugin variable to point to the newly created plugin.
            plugin = new_plugins[0][0]
        else:
            # Regular move
            if parent_id:
                if plugin.parent_id != parent_id:
                    parent = CMSPlugin.objects.get(pk=parent_id)
                    if parent.placeholder_id != placeholder.pk:
                        return HttpResponseBadRequest(force_text(
                            _('parent must be in the same placeholder')))
                    if parent.language != language:
                        return HttpResponseBadRequest(force_text(
                            _('parent must be in the same language as '
                              'plugin_language')))
                    plugin.parent_id = parent.pk
                    plugin.language = language
                    plugin.save()
                    plugin = plugin.move(parent, pos='last-child')
            else:
                sibling = CMSPlugin.get_last_root_node()
                plugin.parent = plugin.parent_id = None
                plugin.placeholder = placeholder
                plugin.save()
                plugin = plugin.move(sibling, pos='right')

            plugins = [plugin] + list(plugin.get_descendants())

            # Don't neglect the children
            for child in plugins:
                child.placeholder = placeholder
                child.language = language
                child.save()

        reorder_plugins(placeholder, parent_id, language, order)

        # When this is executed we are in the admin class of the source placeholder
        # It can be a page or a model with a placeholder field.
        # Because of this we need to get the admin class instance of the
        # target placeholder and call post_move_plugin() on it.
        # By doing this we make sure that both the source and target are
        # informed of the operation.
        target_placeholder_admin = self._get_attached_admin(placeholder)

        if move_a_copy:  # "paste"
            self.post_copy_plugins(request, source_placeholder, placeholder, plugins)

            if (target_placeholder_admin and
                    target_placeholder_admin.model != self.model):
                target_placeholder_admin.post_copy_plugins(
                    request,
                    source_placeholder=source_placeholder,
                    target_placeholder=placeholder,
                    plugins=plugins,
                )
        else:
            self.post_move_plugin(request, source_placeholder, placeholder, plugin)

            if (target_placeholder_admin and
                    target_placeholder_admin.model != self.model):
                target_placeholder_admin.post_move_plugin(
                    request,
                    source_placeholder=source_placeholder,
                    target_placeholder=placeholder,
                    plugin=plugin,
                )

        try:
            language = request.toolbar.toolbar_language
        except AttributeError:
            language = get_language_from_request(request)

        with force_language(language):
            plugin_urls = plugin.get_action_urls()

        json_response = {
            'urls': plugin_urls,
            'reload': move_a_copy or requires_reload(
                PLUGIN_MOVE_ACTION, [plugin])
        }
        return HttpResponse(
            json.dumps(json_response), content_type='application/json')
Exemple #6
0
    def move_plugin(self, request):
        """
        Performs a move or a "paste" operation (when «move_a_copy» is set)

        POST request with following parameters:
        - plugin_id
        - placeholder_id
        - plugin_language (optional)
        - plugin_parent (optional)
        - plugin_order (array, optional)
        - move_a_copy (Boolean, optional) (anything supplied here except a case-
                                        insensitive "false" is True)
        NOTE: If move_a_copy is set, the plugin_order should contain an item
              '__COPY__' with the desired destination of the copied plugin.
        """
        # plugin_id and placeholder_id are required, so, if nothing is supplied,
        # an ValueError exception will be raised by get_int().
        try:
            plugin_id = get_int(request.POST.get('plugin_id'))
        except TypeError:
            raise RuntimeError("'plugin_id' is a required parameter.")
        plugin = CMSPlugin.objects.get(pk=plugin_id)
        try:
            placeholder_id = get_int(request.POST.get('placeholder_id'))
        except TypeError:
            raise RuntimeError("'placeholder_id' is a required parameter.")
        except ValueError:
            raise RuntimeError("'placeholder_id' must be an integer string.")
        placeholder = Placeholder.objects.get(pk=placeholder_id)
        # The rest are optional
        parent_id = get_int(request.POST.get('plugin_parent', ""), None)
        language = request.POST.get('plugin_language', None)
        move_a_copy = request.POST.get('move_a_copy', False)
        move_a_copy = (move_a_copy and move_a_copy != "0"
                       and move_a_copy.lower() != "false")

        source_placeholder = plugin.placeholder
        if not language and plugin.language:
            language = plugin.language
        order = request.POST.getlist("plugin_order[]")

        if not self.has_move_plugin_permission(request, plugin, placeholder):
            return HttpResponseForbidden(
                force_text(_("You have no permission to move this plugin")))
        if placeholder != source_placeholder:
            try:
                template = self.get_placeholder_template(request, placeholder)
                has_reached_plugin_limit(placeholder,
                                         plugin.plugin_type,
                                         plugin.language,
                                         template=template)
            except PluginLimitReached as er:
                return HttpResponseBadRequest(er)

        if move_a_copy:  # "paste"
            if plugin.plugin_type == "PlaceholderPlugin":
                inst, _plugin = plugin.get_plugin_instance()
                source_plugins = inst.placeholder_ref.get_plugins()
                new_plugins = copy_plugins.copy_plugins_to(
                    source_plugins, placeholder, language)
            else:
                source_plugins = [plugin] + list(plugin.get_descendants())
                new_plugins = copy_plugins.copy_plugins_to(
                    source_plugins, placeholder, language, parent_id)

            top_plugins = []
            top_parent = new_plugins[0][0].parent_id
            for new_plugin, old_plugin in new_plugins:
                if new_plugin.parent_id == top_parent:
                    # NOTE: There is no need to save() the plugins here.
                    new_plugin.position = old_plugin.position
                    top_plugins.append(new_plugin)

            # Creates a list of string PKs of the top-level plugins ordered by
            # their position.
            top_plugins_pks = [
                str(p.pk)
                for p in sorted(top_plugins, key=lambda x: x.position)
            ]

            if parent_id:
                parent = CMSPlugin.objects.get(pk=parent_id)
                for plugin in top_plugins:
                    plugin.parent = parent
                    plugin.placeholder = placeholder
                    plugin.language = language
                    plugin.save()

            # If an ordering was supplied, we should replace the item that has
            # been copied with the new copy
            if order:
                if '__COPY__' in order:
                    copy_idx = order.index('__COPY__')
                    del order[copy_idx]
                    order[copy_idx:0] = top_plugins_pks
                else:
                    order.extend(top_plugins_pks)
        else:
            # Regular move
            if parent_id:
                if plugin.parent_id != parent_id:
                    parent = CMSPlugin.objects.get(pk=parent_id)
                    if parent.placeholder_id != placeholder.pk:
                        return HttpResponseBadRequest(
                            force_text(
                                _('parent must be in the same placeholder')))
                    if parent.language != language:
                        return HttpResponseBadRequest(
                            force_text(
                                _('parent must be in the same language as '
                                  'plugin_language')))
                    plugin.parent_id = parent.pk
                    plugin.language = language
                    plugin.save()
                    plugin = plugin.move(parent, pos='last-child')
            else:
                sibling = CMSPlugin.get_last_root_node()
                plugin.parent = plugin.parent_id = None
                plugin.placeholder = placeholder
                plugin.save()
                plugin = plugin.move(sibling, pos='right')

            # Don't neglect the children
            for child in [plugin] + list(plugin.get_descendants()):
                child.placeholder = placeholder
                child.language = language
                child.save()

        reorder_plugins(placeholder, parent_id, language, order)

        self.post_move_plugin(request, source_placeholder, placeholder, plugin)
        json_response = {
            'reload': move_a_copy
            or requires_reload(PLUGIN_MOVE_ACTION, [plugin])
        }
        return HttpResponse(json.dumps(json_response),
                            content_type='application/json')