Django JQuery Autocomplete for Model Selection

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# fields.py


# -*- coding: utf-8 -*-
from django import forms
from django.utils.safestring import mark_safe
from django.utils.encoding import force_unicode 
from django.core.urlresolvers import reverse
from django.forms.util import ErrorList, ValidationError
CLIENT_CODE = """
<input type="text" name="%s_text" id="%s_text"/>
<input type="hidden" name="%s" id="%s" value="" />
<script type="text/javascript">
     $(function(){
	function formatItem(row) {
		return row[1] ;
	}
	function formatResult(row) {
                return row[1];
	}
	$("#%s_text").autocomplete('%s', {
                mustMatch: true,
		formatItem: formatItem,
		formatResult: formatResult
	});
	$("#%s_text").result(function(event, data, formatted) {
              $("#%s").val(data[0]);                         

	});

     });
</script>
"""

class ModelAutoCompleteWidget(forms.widgets.TextInput):
    """ widget autocomplete for text fields
    """
    html_id = ''
    def __init__(self, 
                 lookup_url=None, 
                 *args, **kw):
        super(forms.widgets.TextInput, self).__init__(*args, **kw)
        # url for Datasource
        self.lookup_url = lookup_url
       

    def render(self, name, value, attrs=None):
        if value == None:
            value = ''
        html_id = attrs.get('id', name)
        self.html_id = html_id

        lookup_url = self.lookup_url
        detail_url = reverse('ajax_platos_detail')
        return mark_safe(CLIENT_CODE % (name, html_id, name, html_id, html_id,
                                       lookup_url, html_id, html_id, detail_url))


    def value_from_datadict(self, data, files, name):
        """
        Given a dictionary of data and this widget's name, returns the value
        of this widget. Returns None if it's not provided.
        """

        return data.get(name, None)



        
class ModelAutoCompleteField(forms.fields.CharField):
    """
    Autocomplete form field for Model Model
    """
    model = None
    url = None


    def __init__(self, model,  lookup_url, *args, **kwargs):
        self.model, self.url = model, lookup_url
        super(ModelAutoCompleteField, self).__init__(
            widget = ModelAutoCompleteWidget(lookup_url=self.url),
            max_length=255,
            *args, **kwargs)

    def clean(self, value):

        try: 
            obj = self.model.objects.get(pk=value)
        except self.model.DoesNotExist:
            raise ValidationError(u'Invalid item selected')            
        return obj     





# urls.py
from django.conf.urls.defaults import *

urlpatterns = patterns('mymodel.views',
    # ajax
   url(r'^ajax/list/$', 'ajax_mymodel_list',
        name='ajax_mymodel_list'),                           

)


# views.py
from django.http import HttpResponse
from django.template import RequestContext
from mymodel.models import MyModel

def ajax_mymodel_list(request):
    """ returns data displayed at autocomplete list - 
    this function is accessed by AJAX calls
    """
    limit = 10
    query = request.GET.get('q', None)
    # it is up to you how query looks
    if query:
        qargs = [django.db.models.Q(name__istartswith=query)]
        
    instances = MyModel.objects.filter(django.db.models.Q(*qargs))[:limit]

    results = ""
    for item in instances:
        results += "%s|%s \n" %(item.pk,item.name)

    return HttpResponse(results)


# forms.py
from django import forms
from django.core.urlresolvers import reverse
from mymodel.models import MyModel
from utils.fields import ModelAutoCompleteField

class TestForm(forms.Form):
    theField = ModelAutoCompleteField(lookup_url = reverse('ajax_mymodel_list'),
                                   model = MyModel, required=True)

More like this

  1. autocompleter with database query by bbolli 4 years, 10 months ago
  2. YUI Autocomplete by pigletto 4 years, 8 months ago
  3. jquery autocomplete widget by skam 5 years ago
  4. jstree integration to django admin by pawnhearts 2 years, 4 months ago
  5. Select Dropdown Widget with jQueryUI by maguspk 1 year, 10 months ago

Comments

mirobe (on October 3, 2008):

Can I see record name instead of ID (pk) after choosing the right one from the list?

Also, In the line: "super(ModelAutoCompleteField, self).init(args, *kwargs)"

I am getting: "global name 'args' is not defined" on save()

Thanks

#

elpenia (on October 4, 2008):

This field was designed to retrieve the object selected by the user but you can always access to the pk and the text field values using POST['field'] and POST['field_text'] or GET['field'] and GET['field_text'].

Sorry for the error the line init in clean is wrong, I'm correcting this now, please let me now if it worked and send me any feedback.

Thanks for using it.

#

denvist (on December 2, 2008):

Sorry for my english. I'm trying to use this snippet. But always get this error:

Tried people_add in module mysite.forum.views. Error was: 'module' object has no attribute 'people_add'

urls.py: from django.conf.urls.defaults import * urlpatterns = patterns('', url(r'^people_list/$', mysite.forum.views.people_list', name='people_list'), url(r'^people_add/$', 'mysite.forum.views.people_add', name='people_add'), )

forum/models.py: from django.db import models

class People(models.Model): name = models.CharField(max_length=100)

forum/forms.py: from django import forms from django.core.urlresolvers import reverse from forum.models import People from forum.fields import ModelAutoCompleteField

class TestForm(forms.Form): theField = ModelAutoCompleteField(lookup_url = reverse('mysite.forum.views.people_list'), model = People, required=True)

forum/views.py: from django.http import HttpResponse from django.template import RequestContext from django.shortcuts import render_to_response from django.db.models import Q from forum.models import People from forum.forms import TestForm

def people_list(request): """ returns data displayed at autocomplete list - this function is accessed by AJAX calls """ limit = 10 query = request.GET.get('q', None) # it is up to you how query looks qargs = '' if query: qargs = [Q(name__istartswith=query)]

instances = People.objects.filter(Q(*qargs))[:limit]

results = ""
for item in instances:
    results += "%s|%s \n" %(item.pk,item.name)

return HttpResponse(results)

def people_add(request): form = TestForm() return render_to_response('people_add.html', {'form': form})

fields.py was saved as mysite/forum/fields.py

P.S.:

If forum/forms.py looks like this: class TestForm(forms.Form): theField = forms.CharField(max_length=50)

All work perfectly.

#

elpenia (on March 18, 2009):

Try defining the field in the init method

#

Romain Hardouin (on October 28, 2009):

gonna try this

#

erosb (on June 22, 2010):

For some reason I can't get this to work.

When I enter the code verbatim (with some small changes for the models I'm using), I get an ImproperlyConfigured error - no patterns found, which I presume is due to the reverses.

When I change to a lazy reverse:

artist = ModelAutoCompleteField(lookup_url = reverse_lazy('artist_list'), model = Artist, required=True)

    detail_url = reverse_lazy('ajax_platos_detail')

I eliminate the error but now nothing happens at all. When I go to the url that should supposedly be called:

http://127.0.0.1:8000/ajax/list/?q=a

I get the correct response output, But when I type it into my ModelAutoCompleteField I get nothing. I've verified with {% url artist_list %} that the reverse_lazy is working, but for some reason nothing happens on my actual template.

Here is some pertinent code:

------urls.py from django.utils.functional import lazy from django.http import HttpResponse

reverse_lazy = lazy(reverse, unicode) url(r'^ajax/list/$', 'showbook.booker.views.artist_list', name='artist_list'),

-------forms.py from django.core.urlresolvers import reverse from showbook.booker.fields import ModelAutoCompleteField from django.utils.functional import lazy

reverse_lazy = lazy(reverse, unicode) artist = ModelAutoCompleteField(lookup_url = reverse_lazy('artist_list'), model = Artist, required=True)

Please, any help would be very much appreciated. I've been working on autocomplete for three days and haven't been able to get any solution to work and this one seems the closest so far.

Thank you!

#

pedromagnus (on November 10, 2010):

I have a similar trouble to make this work as erosb. The view works fine, but the response isn't correctly displayed in the browser (maybe a .css missed that the autocomplete script needs to display it properly). I have a question about ModelAutoCompleteWidget.render(): What the ´detail_url´ supose to be? (specifically reverse('ajax_platos_detail') where has to point to??). Anyway, below that line, in the RETURN, the string CLIENT_CODE has space for 8 variables, and 9 are given. Is there any missing javascript function in CLIENT_CODE that requires that extra value (detail_url)? Regards. Pedro

#

pedromagnus (on November 11, 2010):

Never mind. I switch to ajax-select (http://code.google.com/p/django-ajax-selects/). It has the selects for ForeignKey I was looking for.

Both projects could merge in one (both have pros & cons), a good (and complete) result could born there.

Regards! Pedro

#

jonativ (on November 12, 2011):

My application is using two forms:

The first one requesting from the user to provide a (partial) string of a geographic location, for example: "ictori"

The second form is providing a selection of all the places with that string, including Victoria in Australia, Canada etc.

The application is on GAE - Python

The code looks like this:

form action="/onarrive" method="post"

input type="text" name="description" maxlength="20" value="{{ user_input }}"

input type="submit" value=" Find "

/form

form action="/selectlocation" name="selectlocation" method="post"

select name="selected" size="3"

{% for location_name in locations_list_ %} option value = "{{ forloop.counter0 }}" {{location_name}} /option

{% endfor %} /select

input type="submit" value=" OK "

/form

Two questions:

  1. How to replace the first form with autocomplete so the user doesnot have to press the " Find " botton in order to refresh the whole page?

  2. How to configure the app engine?

Thanks for any help

#

(Forgotten your password?)