Login

jstree in change_list for mptt models

Author:
pawnhearts
Posted:
February 25, 2010
Language:
Python
Version:
1.1
Tags:
admin mptt jstree
Score:
0 (after 0 ratings)

to get code with all media files and setup.py: hg clone http://code.tabed.org/mptt_admin/ screenshot

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
`# 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');

});`

More like this

  1. django-mptt enabled replacement for SelectBox.js by anentropic 5 years, 6 months ago
  2. MPTTModelAdmin by anentropic 5 years, 7 months ago
  3. django-mptt enabled FilteredSelectMultiple m2m widget by anentropic 5 years, 6 months ago
  4. Convert multiple select for m2m to multiple checkboxes in django admin form by abidibo 2 years, 1 month ago
  5. Update All Apps to Latest Revision by dedaluz 6 years, 10 months ago

Comments

wernight (on June 13, 2010):

Seems not fully working anymore, or may some files are not right. Would be great to have more details about the required files etc.

#

trojkat (on February 9, 2012):

Thx for good snippet!

In django-mptt 0.5.x you need to replace lines:

self.Meta.model.tree

with:

self.Meta.model._tree_manager

#

Please login first before commenting.