Ajax auto-filtered Foreign Key Field

 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
class AjaxAutoSelect(forms.Select):
    """
    May not work for all possible widget types for the related field
    (simple html form widgets should be okay, we listen to jQuery 'change' event)
    """
    related_field = ''
    def render(self, name, value, attrs=None, choices=()):
        self._element_id = attrs['id']
        
        # normal widget output from the ancestor
        # create a field with a dummy name, the real value
        # will be retrieved from a hidden field
        parent_output = super(AjaxAutoSelect, self).render("dummy-%s" % name, value, attrs, choices)
        
        # output
        if value is None:
            value = ''
        mapping = {
            "related_field": self.related_field,
            "parent_output": parent_output,
            "name": name,
            "element_id": self._element_id, 
            "value": value,
            'app_label': self.model._meta.app_label,
            'object_name': self.model._meta.object_name,
            'select_related': self.select_related,
        }
        
        # TO DO:
        # we shouldn't SelectBox.empty() if we're editing an existing record that has a value in the related field
        output = u"""
            <input type="hidden" name="%(name)s" id="hidden-%(element_id)s" value="%(value)s" />
            
            %(parent_output)s
            
            <script type="text/javascript" charset="utf-8">
        		$(document).ready(function(){
                    SelectBox.init('%(element_id)s');
                    if (!$('#id_%(related_field)s').val()) {
                        SelectBox.empty('%(element_id)s');
                    }
                    
                    ajax_filtered_fields.bindForeignKeyOptions("%(element_id)s");
                    
                    $('#id_%(related_field)s').change(function(evt){
                        ajax_filtered_fields.getForeignKeyJSON('%(element_id)s', '%(app_label)s', '%(object_name)s', '%(related_field)s__pk='+$(evt.target).val(), '%(select_related)s');
                    });
        		});
        	</script>
            """ % mapping
            
        return mark_safe(output)


class AjaxFilteredForeignKeyField(forms.ModelChoiceField):
    def __init__(self, model, field_name, widget=AjaxAutoSelect, *args, **kwargs):
        queryset = model.objects.none()
        super(AjaxFilteredForeignKeyField, self).__init__(queryset, widget=widget, *args, **kwargs)
        
        self.isFilteredForeignKeyField = True
        self.widget.model = self.model = model
        self.widget.related_field = self.related_field = field_name
        self.widget.select_related = None


class AjaxFilteredFKFieldsForm(forms.ModelForm):
    def __init__(self, initial=None, *args, **kwargs):
        super(AjaxFilteredFKFieldsForm, self).__init__(initial=initial, *args, **kwargs)
        for field_name in self.fields:
            field = self.fields[field_name]
            if getattr(field, 'isFilteredForeignKeyField', False):
                if field.related_field in self.initial:
                    # sometimes it's a list with one member, sometimes it's just a number (awesome!)
                    try:
                        fk_val = self.initial.get(field.related_field, self.fields[field.related_field].initial)[0]
                    except TypeError:
                        fk_val = self.initial.get(field.related_field, self.fields[field.related_field].initial)
                    fargs = {'%s__pk'%field.related_field: fk_val}
                    field.queryset = field.model.objects.filter(**fargs)

More like this

  1. ModelChoiceField with optiongroups by anentropic 4 years, 1 month ago
  2. django-mptt enabled replacement for SelectBox.js by anentropic 4 years, 5 months ago
  3. django-mptt enabled FilteredSelectMultiple m2m widget by anentropic 4 years, 5 months ago
  4. MPTTModelAdmin by anentropic 4 years, 6 months ago
  5. django ajax_view decorator by chizcracker 1 year, 11 months ago

Comments

(Forgotten your password?)