Search code examples
pythondjangodjango-autocomplete-light

How can i filter autocomplete queryset by form.instance attributes?


I am trying to filter M2M queryset with autocomplete-light support. I can get the filter working with the built-in ModelForm. Here is simplified version of code that works flawlessly without autocomplete:

models.py:

class FEmodel(models.Model):
    name = models.Charfield(max_length=200)

class Workspace(models.Model):
    name = models.Charfield(max_length=200)
    fem = models.ForeignKey(FEmodel)

class Element(models.Model):
    EID = models.PositiveIntegerField()
    fem = models.ForeignKey(FEmodel)

class Panel(models.Model):
    workspace = models.ForeignKey(Workspace)
    elements = models.ManyToManyField(Element)

forms.py:

class PanelForm(forms.ModelForm):
    def __init__(self,*args,**kwargs):
        super(PanelForm,self).__init__(*args,**kwargs)
        ws = self.instance.workspace
        self.fields['elements'].queryset = Element.objects.filter(fem=ws.fem)
    class Meta:
        model = Panel
        fields = ('__all__')

views.py:

@login_required(login_url='/login/')
def panel_edit(request, pk, id=None):
    workspace = get_object_or_404(Workspace, pk=pk)
    if id:
        panel = get_object_or_404(Panel, pk=id)
    else:
        panel = Panel(workspace = workspace)
    if request.method == 'POST':
        form = PanelForm(request.POST, instance=panel)    
        if form.is_valid():
            panel = form.save(commit=True)        
            return panels(request, pk)
        else:
            print form.errors
    else:
        form = PanelForm(instance=panel)

    return render(request, 'structures/Panel/panel_edit.html', {'form': form, 'panel': panel, 'workspace': workspace})

urls.py:

...
url(r'^workspace/(?P<pk>[0-9]+)/panel/new/$', views.panel_edit, name='panel_edit'),
...

panel_edit.html:

...
<form method="POST" class="form-horizontal">
    {% csrf_token %}
    {% bootstrap_form form %}
    {% buttons %}
        <button type="submit"> Save</button>
    {% endbuttons %}
</form>
....

And here is autocomplete version which i could not get working:

autocomplete_light_registry.py

class ElementAutocomplete(acl.AutocompleteModelBase):
    search_fields = ['EID']
acl.register(Element, ElementAutocomplete)

forms.py:

import autocomplete_light.shortcuts as acl

class PanelForm(acl.ModelForm):
    def __init__(self,*args,**kwargs):
        super(PanelForm,self).__init__(*args,**kwargs)
        ws = self.instance.workspace
        self.fields['elements'].queryset = Element.objects.filter(fem=ws.fem)
    class Meta:
        model = Panel
        fields = ('__all__')

This version throws no errors but does not provide Element choices filtered by form.instance.ws.fem attribute. Instead it gives all Element objects.

What am i doing wrong?

edit 1:

  • in forms.py super(Panel,self) was corrected as super(PanelForm,self)
  • indent typos were corrected

edit 2: required portions of url, view and template added

edit 3: According to @jpic's answer here is the solution:

added to panel_edit.html:

{% block bootstrap3_extra_head %}
{{ block.super }}

<script type="text/javascript">
    $( document ).ready(function() {
        elements_autocomplete = $('input[name=elements-autocomplete]').yourlabsAutocomplete()
        elements_autocomplete.data['ws_pk'] = {{ form.instance.workspace.pk }}
    });

</script>

{% endblock %}

autocomplete_light_registry.py:

import autocomplete_light as acl

class ElementAutocomplete(acl.AutocompleteModelBase):
    search_fields = ['EID']
    model = Element

    def choices_for_request(self):
        ws = Workspace.objects.get(pk=self.request.GET.get('ws_pk', None))
        self.choices = self.choices.filter(fem=ws.fem)
        return super(ElementAutocomplete, self).choices_for_request()

acl.register(ElementAutocomplete)

forms.py:

class PanelForm(acl.ModelForm):

    class Meta:
        model = Panel
        fields = ('__all__')

Solution

  • The autocomplete JS object needs the pk value to pass it on to the view which calls the Python autocomplete object, then you can filter on the instance pk in choices_for_request() method of the python autocomplete object.

    One way to get the js autocomplete object is to get it from the input element itself with the jQuery plugin, ie.:

    elements_autocomplete = $('input[name=elements-autocomplete]').yourlabsAutocomplete()
    

    Ensure that this is called after jquery-autocomplete-light JS is loaded.

    Then, add the fk to its data:

    elements_autocomplete.data['panel_pk'] = {{ form.instance.pk }}
    

    In choices_for_request(), you probably figured it out by now:

    def choices_for_request(self):
        choices = super(ElementAutocomplete, self).choices_for_request()
    
        panel_pk = request.GET.get('panel_pk', None)
        if panel_pk and panel_pk.isdigit():
            choices = choices.filter(panel__pk=panel_pk)
    
        return choices
    

    Actually, that's very easy to test, in your browser, open the JS console and run: $('input[name=elements-autocomplete]').yourlabsAutocomplete().data['foo'] = 'bar' and you'll see that subsequent requests made by the autocomplete script will add &foo=bar to the URL it its, making it available to choices_for_request through self.request !