ModelChoiceField with choice groups for recursive relationships

 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
#-*- coding: utf-8 -*-

from django import forms
from django.utils.html import conditional_escape, mark_safe
from django.utils.encoding import smart_text


class NestedModelChoiceField(forms.ModelChoiceField):
    """A ModelChoiceField that groups parents and childrens"""
    def __init__(self, related_name, parent_field, label_field, *args, **kwargs):
        """
        @related_name: related_name or "FOO_set"
        @parent_field: ForeignKey('self') field, use 'name_id' to save some queries
        @label_field: field for obj representation

        ie: 
        class MyModel(models.Model):
            parent = models.ForeignKey('self', null=True, blank=True)
            title = models.CharField()
        
        field = NestedModelChoiceField(queryset=MyModel.objects.all(),
                                       related_name='mymodel_set',
                                       parent_field='parent_id',
                                       label_field='title')
        """
        super(NestedModelChoiceField, self).__init__(*args, **kwargs)
        self.related_name = related_name
        self.parent_field = parent_field
        self.label_field = label_field
        self._populate_choices()

    def _populate_choices(self):
        # This is *hackish* but simpler than subclassing ModelChoiceIterator
        choices = []
        kwargs = {self.parent_field: None, }
        queryset = self.queryset.filter(**kwargs)\
            .prefetch_related(self.related_name)

        for parent in queryset:
            choices.append((self.prepare_value(parent), self.label_from_instance(parent)))
            choices.extend([(self.prepare_value(children), self.label_from_instance(children))
                            for children in getattr(parent, self.related_name).all()])

        self.choices = choices

    def label_from_instance(self, obj):
        level_indicator = ""
        if getattr(obj, self.parent_field):
            level_indicator = "--- "

        return mark_safe(level_indicator + conditional_escape(smart_text(getattr(obj, self.label_field))))

More like this

  1. Javascript Chain Select Widget by ogo 5 years, 10 months ago
  2. CustomChoiceField, Selectable label field version of ModelChoiceField by mauro 6 years, 1 month ago
  3. Recurse template tag for Django by Zarin 6 years, 2 months ago
  4. SuperChoices by willhardy 5 years, 5 months ago
  5. A Lazy ModelChoiceField implementation by alecdotico 9 months, 1 week ago

Comments

(Forgotten your password?)