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"
Common Fields These built-in widgets represent HTML elements. CharField() is a Django form field that takes in a text input. It has the default widget TextInput, the equivalent of rendering the HTML code <input type="text" ...> . This field works well for collecting one line inputs such as name or address.
The is_valid() method is used to perform validation for each field of the form, it is defined in Django Form class. It returns True if data is valid and place all data into a cleaned_data attribute.
cleaned_data returns a dictionary of validated form input fields and their values, where string primary keys are returned as objects. form. data returns a dictionary of un-validated form input fields and their values in string format (i.e. not objects).
Basically to extract data from a form field of a form, all you have to do is use the form. is_valid() function along with the form. cleaned_data. get() function, passing in the name of the form field into this function as a parameter.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With