how to create a autocomplete input field in a form using Django
Asked Answered
C

3

13

I am pretty new to django and its ways. I am trying to create an autocomplete field for a form. My code is as below

forms.py

from django import forms

class LeaveForm(forms.Form):
    leave_list = (
        ('Casual Leave', 'Casual Leave'),
        ('Sick Leave', 'Sick Leave')
    )
    from_email = forms.EmailField(required=True, widget=forms.TextInput(attrs={'style': 'width: 400px'}))
    start_date = end_date = forms.CharField(widget=forms.TextInput(attrs={'type': 'date', 'style': 'width: 175px'}))
    leave_type = forms.ChoiceField(choices=leave_list, widget=forms.Select(attrs={'style': 'width: 400px'}))
    comments = forms.CharField(required=True, widget=forms.Textarea(attrs={'style': 'width: 400px; height: 247px'}))

    def clean_from_email(self):
        data = self.cleaned_data['from_email']
        if "@testdomain.com" not in data:
            raise forms.ValidationError("Must be @testdomain.com")
        return data

What I want to achieve is that when an user types words into the "From Email" field the list of emails I have stored in an external DB should appear in the autocomplete list option.

models.py

from django.db import models


class ListOfUsers(models.Model):
    emp_number = models.CharField(db_column='Emp_Number', primary_key=True, max_length=50, unique=True)  # Field name made lowercase.
    name = models.CharField(db_column='Name', max_length=40)  # Field name made lowercase.
    supervisor = models.CharField(db_column='Supervisor', max_length=40)  # Field name made lowercase.
    email = models.CharField(db_column='Email', max_length=50, blank=False, null=False, unique=True)  # Field name made lowercase.


    class Meta:
        managed = False
        db_table = 'List of users'

Any idea how this can be done ?

Update :

I started messing around with django-autocomplete-light and now able to get a reply from the autocomplete url. It looks like this

{"results": [{"id": "[email protected]", "text": "[email protected]"}, {"id": "[email protected]", "text": "[email protected]"}, {"id": "[email protected]", "text": "[email protected]"}]}

views.py

class EmailAutocomplete(autocomplete.Select2ListView):
    def get_list(self):
        qs = ListOfUsers.objects.using('legacy')

        if self.q:
            qs = qs.filter(email__icontains=self.q).values_list('email', flat=True)

        return qs

I still do not know how to get this data to appear in the field "from_email"

Cottager answered 16/5, 2018 at 22:24 Comment(2)
It requires client side (i.e. Javascript) coding and Ajax. If you are not experienced with those, there are several Django packages for implementing autocomplete fields. A Google search might help.Sun
Any idea which package is easy to learn from. Autocomolete-light made no sense for meCottager
C
28

I finally got the autocomplete search working using the instructions found here

https://github.com/xcash/bootstrap-autocomplete
https://bootstrap-autocomplete.readthedocs.io/en/latest/

It is very simple to use and does not need to install any app in settings.py and it works for both bootstrap 3 and bootstrap 4

There are lot of other packages available but this was simple and easy for my need.

I am going to explain the code I used

page.html

{% block script %}
    <script src="https://cdn.rawgit.com/xcash/bootstrap-autocomplete/3de7ad37/dist/latest/bootstrap-autocomplete.js"></script>
    <script>
        $('.basicAutoComplete').autoComplete(
            {minLength: 1}
        );
        $('.dropdown-menu').css({'top': 'auto', 'left': 'auto'})

    </script>
{% endblock %}
.
.
.
.
.
{% if field.name == "from_email" %}
   {% render_field field class="basicAutoComplete form-control" %}
{% else %}
   {% render_field field class="form-control" %}
{% endif %}

autoComplete is the function called to perform the action and minLength specifies the minimum length of the text before performing the fetch action I added an extra CSS to fix the autocomplete dropdown otherwise it was weird.

The Jinja render kept overwriting the class definition from views so I added an if check for it.

urls.py

from . import views

urlpatterns = [
    .
    .
    .
    path('email_autocomplete/', views.email_autocomplete, name='email_autocomplete')
]

Added this line to urls.py

forms.py

class LeaveForm(forms.Form):
    from_email = forms.EmailField(required=True, widget=forms.TextInput(
        attrs={
            'style': 'width: 400px',
            'class': 'basicAutoComplete',
            'data-url': "/domain/email_autocomplete/"
        }))

The above is the code for the input field in forms.py. data-url points to where the JSON result would be generated.

views.py

from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
from .models import model
        
def email_autocomplete(request):
    if request.GET.get('q'):
        q = request.GET['q']
        data = model.objects.using('legacy').filter(email__startswith=q).values_list('email',flat=True)
        json = list(data)
        return JsonResponse(json, safe=False)
    else:
        HttpResponse("No cookies")
            

This was the most confusing part for me. The GET request is easy to understand but it took a while to convert the data from model.objects into a JSON formatted object. The trick was to use

values_list('columnName',flat=True)

when filtering the data from the database, then converting to a list using list(data) and finally use JsonResponse to convert it to JSON.

Hope this will help any one who wants a simple autocomplete

Cottager answered 30/5, 2018 at 21:3 Comment(8)
I appreciate your sharing of this solution. I am trying to follow your example and am trying to mesh this with my class based view CreateView. Is there a way to do this? If so do i still need a separate url for the JsonResponce? I woudl be happy to share my code or if you could explain the views a little more I would appreciate it. I am piping in a separate list of expected text (not connected to my model instances) so I am less interested in creating the json object and more interested in how the view looks in general. Keeping in mind i am coming from the generic class based view. Many thanksWhiten
This is amazing. I was completely lost with django auto-complete-light.Perfection
@TravisN.Miller sorry have not visited here in a while. Do you still need help.Cottager
Great post, way simpler and more robust than django-autocomplete-light, which I could not get to work in a modal...Recourse
In fact Django proposes this functionality in the admin.ModelAdmin class, with the property 'autocomplete_fields'. Why isn't this also available in custom forms?Simp
This is great! Any suggestion on how to handle the use-case where the autocomplete returns and empty set so you allow the user to create a new object on the fly?Headrick
@Headrick I have not worked on Django for a while. If any one wants to work on this feel free.Cottager
For your filter, you can also use email__icontains=qCosmopolitan
S
2

You can consider a different way to get the autocompletion for the input element using the standard HTML tag <datalist>.

Consider the example below. It does not use Javascript and it does not need anything to do in the server code.

<input list="browsers" id="browser_id" name="browser" placeholder="Starting typing the name of the browser" size="50"/>
<datalist id="browsers">
  <option>Chrome</option>
  <option>Firefox</option>
  <option>Internet Explorer</option>
  <option>Opera</option>
  <option>Safari</option>
  <option>Microsoft Edge</option>
</datalist>

The documentation about the tag is here - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist

Seta answered 27/2, 2022 at 8:56 Comment(3)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Glede
I think your solution works only if the list is static. If the data in the list of options changes or is a based on a list in an DB it may not be safe to load the data into the option list.Cottager
Disagree. You can re-populate the list in “datalist” via Ajax as well when the user changes the input field. My point was to use the autosuggestion UI functionality of the browser, not from the bootstrap or any other JavaScript based methods.Seta
O
1

A very little addition. The answer of Abilash is great and very clear. But if it doesn't work for you, may be you should wrapping you basicAutoComplete activation into

$(document).ready(function(){
};

The reason and some explanation could be found here : Bootstrap Auto-complete Error: Cannot read property " " of undefined

Occlude answered 6/8, 2021 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.