`# coding: utf-8
# vim: ai ts=4 sts=4 et sw=4
#from django.utils.translation import ugettext_lazy as _
from django.contrib import admin
from django.conf.urls.defaults import *
from django.http import HttpResponse, HttpResponseBadRequest
from django.shortcuts import  get_object_or_404
from django.conf import settings
from django.utils.encoding import force_unicode
from django.utils import simplejson
from django import template
from copy import deepcopy

class MpttAdmin(admin.ModelAdmin):
    """ Base class for mptt model admins, usage:
        class ExampleAdmin(MpttAdmin):
            tree_title_field = 'name'
            tree_display = ('name','slug','created|date') # you can use filters here
            prepopulated_fields = {"slug": ("name",)}
            class Meta:
                model = Example
    """
    def __init__(self,*args,**kargs):
        super(MpttAdmin,self).__init__(*args,**kargs)
        if not hasattr(self,'tree_display'):
            self.tree_display = ()
        if self.tree_display and not hasattr(self,'tree_title_field'):
            self.tree_title_field = self.tree_display[0]
        if not hasattr(self,'tree_title_field'):
            title_field = ''#self.tree_display[0]
        else:
            title_field = '.'+self.tree_title_field
        extra_fields = '&nbsp;'.join('<span title="%s">{{ node.%s }}</span>' % (field,field) for field in self.tree_display if not hasattr(self,'tree_title_field') or field!=self.tree_title_field)
        model = '%s.%s' % (self.Meta.model._meta.app_label, self.Meta.model._meta.object_name)
        self._tree_tpl = template.Template("""{% load mptt_tags %}{% full_tree_for_model """+model+""" as nodes %}{% for node,structure in nodes|tree_info %}{% if structure.new_level %}{% if node.is_child_node %}<ul>{% endif %}<li id="n{{node.pk}}">{% else %}</li><li id="n{{node.pk}}">{% endif %}<ins> </ins><a href="{{node.pk}}/">{{ node"""+title_field+""" }}</a>"""+extra_fields+"""{% for level in structure.closed_levels %}</li>{% if node.is_child_node %}</ul>{% endif %}{% endfor %}{% endfor %}""")
        self._changelist_tpl = template.Template("""{% extends "admin/change_list.html" %}
        {% load mptt_tags %}        
        {% block extrahead %}
        <script>var permissions={{permissions|safe}};</script>
        <script src="{{ MEDIA_URL }}js/jstree_admin.js"></script>
        {% endblock %}
        {% block search %}{% endblock %}{% block date_hierarchy %}{% endblock %}
        {% block result_list %}{% endblock %}{% block pagination %}{% endblock %}
        {% block filters %}<div id="tree"><ul>{{tree}}</ul></div>{% endblock %}""")

        #self.move_node = permission_required('%s.change_%s' % (self.model._meta.app_label,self.model._meta.object_name))(self.move_node)
        #self.rename = permission_required('%s.change_%s' % (self.model._meta.app_label,self.model._meta.object_name))(self.rename)
        #self.remove = permission_required('%s.delete_%s' % (self.model._meta.app_label,self.model._meta.object_name))(self.remove)

    def changelist_view(self, request, extra_context=None):
        model = '%s.%s' % (self.Meta.model._meta.app_label, self.Meta.model._meta.object_name)
        opts = self.model._meta
        app_label = opts.app_label

        media = deepcopy(self.media)
        media.add_js(['js/lib/jquery-1.3.2.min.js',
            'js/lib/jquery.tree.min.js',
            'js/lib/plugins/jquery.tree.contextmenu.js'])

        module_name = force_unicode(opts.verbose_name_plural)

        permissions = simplejson.dumps({
            'renameable' : self.has_change_permission(request, None) and hasattr(self,'tree_title_field'),
		    'deletable'	: self.has_delete_permission(request, None),
		    'creatable'	: self.has_add_permission(request),
		    'draggable'	: self.has_change_permission(request, None),
        })

        context = {
            'module_name': module_name,
            'title': module_name,
            'is_popup': False,
            'cl': {'opts':{'verbose_name_plural': module_name}},
            'media': media,
            'has_add_permission': self.has_add_permission(request),
            'root_path': self.admin_site.root_path,
            'app_label': app_label,
            'tree':self._tree_tpl.render(template.Context()),
            'permissions': permissions,
            'MEDIA_URL': settings.MEDIA_URL,
        }
        context.update(extra_context or {})
        context_instance = template.RequestContext(request, current_app=self.admin_site.name)
        context_instance.update(context)
        return HttpResponse(self._changelist_tpl.render(context_instance))

    def get_urls(self):
        urls = super(MpttAdmin, self).get_urls()

        my_urls = patterns('',
            (r'^tree/$', self.get_tree),
            (r'^move_node/$', self.move_node),
            (r'^rename/$', self.rename),
            (r'^remove/$', self.remove),
        )
        return my_urls + urls

    def get_tree(self,request):
        return HttpResponse(self._tree_tpl.render(template.Context()))

    def move_node(self,request):
        if not self.has_change_permission(request, None):
            raise PermissionDenied
        node = get_object_or_404(self.Meta.model,pk=request.POST.get('node'))
        target = get_object_or_404(self.Meta.model,pk=request.POST.get('target'))
        position = request.POST.get('position')
        if position not in ('left','right','last-child','first-child'):
            return HttpResponseBadRequest('bad position')
        self.Meta.model.tree.move_node(node,target,position)
        return self.get_tree(request)

    def rename(self,request):
        if not self.has_change_permission(request, None):
            raise PermissionDenied
        node = get_object_or_404(self.Meta.model,pk=request.POST.get('node'))
        setattr(node,self.tree_title_field, request.POST.get('name'))
        print self.tree_title_field, request.POST.get('name')
        node.save()
        return self.get_tree(request)

    def remove(self,request):
        if not self.has_delete_permission(request, None):
            raise PermissionDenied
        node = get_object_or_404(self.Meta.model,pk=request.POST.get('node'))
        node.delete()
        return self.get_tree(request)`

jstree_admin.js:

`$(document).ready(function() {
    if (!$('#tree').length)
        return false;

    $(function() {
        window.jtree = $("#tree").tree({
            plugins: {
                contextmenu: {
                    items: {
                        remove: true,
                        create: {
                            label: "Create",
                            icon: "create",
                            visible: function(node, treeobj) {
                                if (node.length != 1)
                                    return 0;
                                return treeobj.check("creatable", node);
                            },
                            action: function(node, treeobj) {
                                location.href = 'add/?parent=' + node.attr('id').replace('n','');
                                },
                            separator_after: true
                        },
                        rename: {
                            label: "Rename",
                            icon: "rename",
                            visible: function(node, treeobj) {
                                if (node.length != 1)
                                    return false;
                                return treeobj.check("renameable", node);
                            },
                            action: function(node, treeobj) {
                                treeobj.rename(node);
                            }
                        },
                        edit: {
                            label: "Change",
                            icon: "rename",
                            visible: function(node, treeobj) {
                                if (node.length != 1)
                                    return false;
                                return true;
                            },
                            action: function(node, treeobj) {
                                location.href = $(node).attr('id').replace('n','') + '/';
                            }
                        },
                        remove: {
                            label: "Remove",
                            icon: "remove",
                            visible: function(node, treeobj) {
                                if (node.length != 1)
                                    return false;
                                return treeobj.check("deletable", node);
                            },
                            action: function(node, treeobj) {

                                treeobj.remove(node);
                                
                            }
                        }

                    }
                }

            },

            callback: {
                beforemove: function(node, ref_node, TYPE, treeobj) {
                    if(treeobj._moving){
                        treeobj._moving=false;
                        return true;
                    }else treeobj._moving=true;
                    var position={'inside':'last-child','before':'left','after':'right'}[TYPE];
                    treeobj.settings.data.opts.url = 'move_node/';
                    treeobj.settings.data.opts.method='POST';
                    treeobj._params={node:node.id.replace('n',''),target:ref_node.id.replace('n',''),position:position};
                    treeobj.refresh();
                    return false;
                },
                ondblclk: function(node, treeobj) {
                    location.href = $(node).attr('id').replace('n','');
                },
                onload: function(treeobj) {
                    treeobj.open_all();
                },
                onrename: function(node, treeobj, RB) {
                    //treeobj.settings.data._node=node;
                    var new_name=$(node).children('a:first').text();//.replace(/^\s\s*/, '');
                    treeobj._params={node:node.id.replace('n',''),name:new_name};
                    treeobj.settings.data.opts.url = 'rename/';
                    treeobj.settings.data.opts.method = 'POST';
                    treeobj.refresh();
                    return false;
                },
                beforedelete: function(node, treeobj) {
                    treeobj._params={node:node.id.replace('n','')};
                    treeobj.settings.data.opts.url = 'remove/';
                    treeobj.settings.data.opts.method = 'POST';
                    treeobj.refresh();
                    return false;
                },
                onsearch : function (n,t) {
		    t.container.find('.search').removeClass('search');
		    n.addClass('search');
		},
                ondata: function(data, treeobj) {
                    if(typeof(treeobj._params)!='undefined'){
                    treeobj._params={};
                    treeobj.settings.data.opts.url='tree/';
                    treeobj.settings.data.opts.method='POST';}
                    return data;
                },
                beforedata: function(node, treeobj) {
                    return typeof(treeobj._params)!='undefined' && treeobj._params || {};
                },
                onchange : function (node) {
                    document.location.href = $(node).children("a:eq(0)").attr("href");
                }

            },
            data: {
                type: "html",async: false
            },
            "types": {
                "default": permissions
            }
        });
    });
    $('#changelist').removeClass('module');

});`