`# 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 = ' '.join('{{ node.%s }}' % (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 %}{% endif %}{% endfor %}{% endfor %}""") self._changelist_tpl = template.Template("""{% extends "admin/change_list.html" %} {% load mptt_tags %} {% block extrahead %} {% endblock %} {% block search %}{% endblock %}{% block date_hierarchy %}{% endblock %} {% block result_list %}{% endblock %}{% block pagination %}{% endblock %} {% block filters %}
{% 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'); });`