Hi,
I found a nice snippet which seems to be a pretty nice and generic way to get autocomplete to your widgets using jquery autocomplete: link
http://www.djangosnippets.org/snippets/233/
Unfortunately I did not found a complete example implementing this snippet and it seems that I do not understand it in detail to be able to implement it by myself :D
Therefore I'm searching some help to implement it. Some questions would be:
_1: When I assign the JQueryAutoComplete widget to a form field. How do I tell the field that it should use the values from a view, or how do i tell the field to use the values from a list.
Should work somehow like this:
class ProjectForm(forms.ModelForm):
auto_test = forms.CharField(max_length=10, widget=JQueryAutoComplete())
class Meta:
model = Project
exclude = ('created_by',)
class Project(models.Model):
name = models.CharField(max_length=100)
project_owner_externally = models.ForeignKey(Contact, null=True, related_name='project_owner_externally')
_2: In the description of the snippet the author wrote: "to be implemented: - store the pk value into an hidden field - " - How can I implement a hidden field in my form?
_3: Is there a way to ship the widget with the needed jquery source? Or do I have to set the jquery links in each page using this widget?
Edit: Part of the solution:
Ok I made it some steps further to the solution.
I had to make some small changes to the snippet:
from django import forms
from django.forms.widgets import flatatt
from django.forms.util import smart_unicode
from django.utils.html import escape
from django.utils.simplejson import JSONEncoder
from django.utils.safestring import mark_safe
class JQueryAutoComplete(forms.TextInput):
def __init__(self, source, options={}, attrs={}):
"""source can be a list containing the autocomplete values or a
string containing the url used for the XHR request.
For available options see the autocomplete sample page::
http://jquery.bassistance.de/autocomplete/"""
self.options = None
self.attrs = {'autocomplete': 'off'}
self.source = source
if len(options) > 0:
self.options = JSONEncoder().encode(options)
self.attrs.update(attrs)
def render_js(self, field_id):
if isinstance(self.source, list):
source = JSONEncoder().encode(self.source)
elif isinstance(self.source, str):
source = "'%s'" % escape(self.source)
else:
raise ValueError('source type is not valid')
options = ''
if self.options:
options += ',%s' % self.options
return u'$(\'#%s\').autocomplete(%s%s);' % (field_id, source, options)
def render(self, name, value=None, attrs=None):
final_attrs = self.build_attrs(attrs, name=name)
if value:
final_attrs['value'] = escape(smart_unicode(value))
if not self.attrs.has_key('id'):
final_attrs['id'] = 'id_%s' % name
# I added here the mark_safe in order to prevent escaping:
return mark_safe(u'''<input type="text" %(attrs)s/>
<script type="text/javascript"><!--//
%(js)s//--></script>
''' % {
'attrs' : flatatt(final_attrs),
'js' : self.render_js(final_attrs['id']),
})
With this snipped placed into a widget you can assign the widget to a form field like this:
class ProjectForm(forms.ModelForm):
project_owner_externally = forms.CharField(max_length=25, widget=JQueryAutoComplete('/pm/contact_autocomplete'))
class Meta:
model = Project
exclude = ('created_by',)
class Media:
js = (
settings.MEDIA_URL + "js/jquery.autocomplete.js",
)
css = {
'screen': (settings.MEDIA_URL + "css/jquery.autocomplete.css",),
}
Note the url reference in the widget instantiation. widget=JQueryAutoComplete('/pm/contact_autocomplete') . This url has to be defined in your urls config. In my case this url points to a view which returns the filtered contacts like this:
from django.http import HttpResponse, HttpResponseBadRequest
from django.views.decorators.cache import cache_page
from crm.models import Contact
def contact_autocomplete(request):
print "holy jquery s..."
def iter_results(results):
if results:
for r in results:
yield '%s|%s\n' % (r.first_name, r.id)
if not request.GET.get('q'):
return HttpResponse(mimetype='text/plain')
q = request.GET.get('q')
limit = request.GET.get('limit', 15)
try:
limit = int(limit)
except ValueError:
return HttpResponseBadRequest()
contacts = Contact.objects.filter(first_name__startswith=q)[:limit]
print contacts
return HttpResponse(iter_results(contacts), mimetype='text/plain')
And tada.. the form does show now all the contacts found in your input field.
This works nice if you want to fill in a char field with some live-search values.
The question for me is now how do I handle this stuff in order to not only put the first_name of the result in the text box, but to create an hidden field which holds the id of the contact. Still an open question for me is how I can handle this stuff for a ForeignKey field, since the project_owner_externally is a ForeignKey field.
project_owner_externally = models.ForeignKey(Contact, null=True, related_name='project_owner_externally')