yourlabs / django-autocomplete-light

A fresh approach to autocomplete implementations, specially for Django. Status: v4 alpha, v3 stable, v2 & v1 deprecated.
https://django-autocomplete-light.readthedocs.io
MIT License
1.79k stars 467 forks source link

Multiple django-autocomplete-light dropdowns in same template #1235

Open preethamsolomon opened 3 years ago

preethamsolomon commented 3 years ago

I am building a web application that requires searching for a specific user record by entering one of two attributes: first name OR last name. Eventually there will be two more search attributes, but currently having problems with having two autocomplete-light drop-downs in the same template. The problem is that only the second drop-down is working as expected.

Below are the relevant code sections (with irrelevant code removed). This approach is not "DRY" but my priority is to have a working implementation before optimization/refactoring.

forms.py

class StudentChoiceFieldLN(forms.Form):

    students = forms.ModelChoiceField(
        queryset=Student.objects.all().order_by("last_name"),
        widget=autocomplete.ModelSelect2(url='student-ln-autocomplete'),
    )

    def __init__(self, *args, **kwargs):
        super(StudentChoiceField, self).__init__(*args, **kwargs)
        # without the next line label_from_instance does NOT work
        self.fields['students'].queryset = Student.objects.all().order_by("last_name")
        self.fields['students'].label_from_instance = lambda obj: "%s %s" % (obj.last_name, obj.first_name)

class StudentChoiceFieldFN(forms.Form):

    students = forms.ModelChoiceField(
        queryset=Student.objects.all().order_by("first_name"),
        widget=autocomplete.ModelSelect2(url='student-fn-autocomplete'),
    )

    def __init__(self, *args, **kwargs):
        super(StudentChoiceFieldFN, self).__init__(*args, **kwargs)
        # without the next line label_from_instance does NOT work
        self.fields['students'].queryset = Student.objects.all().order_by("first_name")
        self.fields['students'].label_from_instance = lambda obj: "%s %s" % (obj.last_name, obj.first_name)

views.py

class StudentLNAutoComplete(autocomplete.Select2QuerySetView):
    def get_queryset(self):

        qs = Student.objects.all()

        if self.q:
            qs = qs.filter(last_name__istartswith=self.q)

        return qs

class StudentFNAutoComplete(autocomplete.Select2QuerySetView):
    def get_queryset(self):

        qs = Student.objects.all()

        if self.q:
            qs = qs.filter(first_name__istartswith=self.q)

        return qs

def index(request):
    students_choice_ln = StudentChoiceFieldLN()
    students_choice_fn = StudentChoiceFieldFN()

    context = {
        'students_choice_ln': students_choice_ln,
        'students_choice_fn': students_choice_fn,
        'selected_student': None
    }

    return render(request, 'awards/index.html', context)

urls.py

from awards.views import StudentLNAutoComplete
from awards.views import StudentFNAutoComplete

urlpatterns = [
    ...
    path('student-ln-autocomplete/', StudentLNAutoComplete.as_view(), name='student-ln-autocomplete'),
    path('student-fn-autocomplete/', StudentFNAutoComplete.as_view(), name='student-fn-autocomplete'),
    ...
]

awards/index.html

Note below the position of the {{ students_choice_fn.media }} declaration. Based on recommendations found in other related threads online, I tried changing the location of this declaration to different parts of the template. I think the problem is related to the rendering of the relevant css/javascript for autocomplete-light, somehow causing the first field to not work correctly. Have also tried moving all of the css/javascript links into the head section of base.html, and the problem was not resolved.


{% extends "base.html" %}

{% block content %}

<body>

{{ students_choice_fn.media }}

<div class="container">

<div class="row">
  <div class="col-md-6">

    <form method=POST action="/awards/select">
        {% csrf_token %}

        {{ students_choice_ln }}
        {{ students_choice_fn }}

        <input type="submit" value="select">
    </form>

    <br>
    <h4> {{ selected_student.first_name }} {{ selected_student.last_name }} </h4>

    {% if selected_student %}
        ....
    {% endif %}

  </div>
  <div class="col-md-6">
      ....
  </div>
</div>

</div>
</body>

{% endblock %}

The problem is that only the second autocomplete dropdown is working, and the first dropdown displays as an empty non-interactive dropdown. See the screenshot at the end of the stackoverflow post I created: https://stackoverflow.com/questions/67133511/multiple-autocomplete-light-dropdowns-in-django-template

I appreciate any advice/help! Really need to resolve this problem. Thanks.

preethamsolomon commented 3 years ago

Foudn the problem in the above code:

Since the two forms are in the same form tag, the same id was assigned to them in the rendered html.

<form method=POST action="/awards/select">
    {% csrf_token %}

    {{ students_choice_ln }}
    {{ students_choice_fn }}

    <input type="submit" value="select">
</form>

After following the instructions documented here (https://docs.djangoproject.com/en/dev/ref/forms/api/#prefixes-for-forms), the dropdowns are working as expected. It could be that the matching ids prevented the javascript/css links from working correctly.

rdmolony commented 1 year ago

I was just blocked by this, it helped to ensure that each time I instantiated my form I included the prefix argument!

(I was gluing this & django-htmx to create an editable table, this is such a handy library btw)