Login

django-mptt enabled FilteredSelectMultiple m2m widget

Author:
anentropic
Posted:
November 5, 2009
Language:
Python
Version:
1.1
Tags:
widget mptt
Score:
0 (after 0 ratings)

If you are using django-mptt to manage content (eg heirarchical categories) then it needs a bit of help to make a nice admin interface. For many-to-many fields, Django provides the quite nice FilteredSelectMultiple widget (a two-pane selection list with search box) but it only renders 'flat' lists... if you have a big category tree it's going to be confusing to know what belongs to what. Also, list items are sorted alphabetically in the js, which won't be what you want.

This snippet extends FilteredSelectMultiple to show the tree structure in the list.

You'll also need the js from this snippet: #1780

For usage details see my blog at: http://anentropic.wordpress.com

 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
from itertools import chain
from django import forms
from django.conf import settings
from django.contrib.admin import widgets
from django.utils.encoding import smart_unicode, force_unicode
from django.utils.safestring import mark_safe
from django.utils.html import escape, conditional_escape


class MPTTModelChoiceIterator(forms.models.ModelChoiceIterator):
    def choice(self, obj):
        tree_id = getattr(obj, getattr(self.queryset.model._meta, 'tree_id_atrr', 'tree_id'), 0)
        left = getattr(obj, getattr(self.queryset.model._meta, 'left_atrr', 'lft'), 0)
        return super(MPTTModelChoiceIterator, self).choice(obj) + ((tree_id, left),)


class MPTTModelMultipleChoiceField(forms.ModelMultipleChoiceField):
    def label_from_instance(self, obj):
        level = getattr(obj, getattr(self.queryset.model._meta, 'level_attr', 'level'), 0)
        return u'%s %s' % ('-'*level, smart_unicode(obj))
    
    def _get_choices(self):
        if hasattr(self, '_choices'):
            return self._choices
        return MPTTModelChoiceIterator(self)
    
    choices = property(_get_choices, forms.ChoiceField._set_choices)


class MPTTFilteredSelectMultiple(widgets.FilteredSelectMultiple):
    def __init__(self, verbose_name, is_stacked, attrs=None, choices=()):
        super(MPTTFilteredSelectMultiple, self).__init__(verbose_name, is_stacked, attrs, choices)
    
    def render_options(self, choices, selected_choices):
        """
        this is copy'n'pasted from django.forms.widgets Select(Widget)
        change to the for loop and render_option so they will unpack and use our extra tuple of mptt sort fields
        (if you pass in some default choices for this field, make sure they have the extra tuple too!)
        """
        def render_option(option_value, option_label, sort_fields):
            option_value = force_unicode(option_value)
            selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
            return u'<option value="%s" data-tree-id="%s" data-left-value="%s"%s>%s</option>' % (
                escape(option_value),
                sort_fields[0],
                sort_fields[1],
                selected_html,
                conditional_escape(force_unicode(option_label)),
            )
        # Normalize to strings.
        selected_choices = set([force_unicode(v) for v in selected_choices])
        output = []
        for option_value, option_label, sort_fields in chain(self.choices, choices):
            if isinstance(option_label, (list, tuple)):
                output.append(u'<optgroup label="%s">' % escape(force_unicode(option_value)))
                for option in option_label:
                    output.append(render_option(*option))
                output.append(u'</optgroup>')
            else:
                output.append(render_option(option_value, option_label, sort_fields))
        return u'\n'.join(output)
    
    class Media:
        extend = False
        js = (settings.ADMIN_MEDIA_PREFIX + "js/core.js",
              settings.MEDIA_URL + "js/mptt_m2m_selectbox.js",
              settings.ADMIN_MEDIA_PREFIX + "js/SelectFilter2.js",
              )

More like this

  1. django-mptt enabled replacement for SelectBox.js by anentropic 5 years, 4 months ago
  2. MPTTModelAdmin by anentropic 5 years, 4 months ago
  3. Ajax auto-filtered Foreign Key Field by anentropic 5 years, 3 months ago
  4. Convert multiple select for m2m to multiple checkboxes in django admin form by abidibo 1 year, 10 months ago
  5. Many 2 Many Admin Ordering with Mysql by visik7 1 year, 6 months ago

Comments

Please login first before commenting.