Anonymous avatar Anonymous committed 779ce60

little improvements
* AutoComplete.register instead of ac.
* Added support for force_selection.
* AutoComplete is now a subclass of widgets.Widget, and I am using an hidden input instead of an hidden select, so I will be able to skip the ModelChoiceField query to get all the initial choices (it may be a big query).

Comments (0)

Files changed (7)

ac_example/admin.py

 from django.contrib import admin
 from django.contrib.auth.models import Message
-from actest.acapp import forms
+from ac_example import forms
 
 class MessageAdmin(admin.ModelAdmin):
     form = forms.InsertMessage

ac_example/forms.py

 from django import forms
-
 from django.contrib.auth.models import User, Message
-from autocomplete.widgets import ModelAutoComplete
+from autocomplete.widgets import AutoComplete
 
 class InsertMessage(forms.ModelForm):
     class Meta:
         model = Message
 
     user = forms.ModelChoiceField(User.objects.all(),
-            widget=ModelAutoComplete('user'))
+            widget=AutoComplete('user'), help_text="you must select a valid choice")
+    username = forms.CharField(widget=AutoComplete('user', force_selection=False))

ac_example/urls.py

 from django.conf.urls.defaults import *
 
 from django.contrib.auth.models import User
-from autocomplete import ac
+from autocomplete.views import autocomplete
 
-info_dict = {
-    'settings': {
-        'user': ac(User.objects.all(), ('username', 'email'), 5),
-    },
-    'query_param': 'query',
-}
+
+autocomplete.register('user', User.objects.all(), ('username', 'email'), 5)
 
 urlpatterns = patterns('',
-    url('^autocomplete/(\w+)/$', 'autocomplete.views.autocomplete', info_dict,
-        name='autocomplete'),
-    url('^example/$', 'actest.acapp.views.example'),
+    url('^autocomplete/(\w+)/$', autocomplete, name='autocomplete'),
+    url('^example/$', 'ac_example.views.example'),
 )

autocomplete/__init__.py

-from django.utils.encoding import smart_unicode
-
-def ac(queryset, fields, limit=None, key='pk', label=lambda obj: smart_unicode(obj), auth=False):
-    return (queryset, fields, limit, key, label, auth)
-autocomplete = ac

autocomplete/media/js/autocomplete.js

 
-function yui_autocomplete(name, ac_url) {
+function yui_autocomplete(name, ac_url, force_selection) {
     YAHOO.util.Event.onDOMReady(function () {
         var datasource = new YAHOO.util.XHRDataSource(ac_url);
         datasource.responseType = YAHOO.util.XHRDataSource.TYPE_JSON;
         }
         datasource.resultTypeList = false;
 
-        var select = document.getElementById("id_"+name);
-        var input = document.getElementById("id_ac_"+name);
-
-        YAHOO.util.Dom.addClass(input.parentNode, "yui-ac");
-
-        select.style.display = "none";
-        input.style.display = "block";
+        var input = document.getElementById("id_"+name);
 
         var container = document.createElement("div");
         YAHOO.util.Dom.insertAfter(container, input);
         var autocomplete = new YAHOO.widget.AutoComplete(input, container, datasource);
         autocomplete.resultTypeList = false;
         autocomplete.queryDelay = .5;
+        autocomplete.forceSelection = force_selection;
 
+        var selected_item = {label: null, id: null};
+        var hidden = document.getElementById("id_hidden_"+name)
         autocomplete.itemSelectEvent.subscribe(function (type, args) {
-            var item = args[2];
-            select.value = item.id;
+            selected_item = args[2];
+            hidden.value = selected_item.id;
+        });
+        form = document.getElementsByTagName("form")[0];
+        YAHOO.util.Event.addListener(form, "submit", function (event, form) {
+            if (selected_item.label != input.value && !force_selection)
+                hidden.value = input.value;
         });
     });
 }

autocomplete/views.py

 from django.http import HttpResponse
 from django.db.models import Q
 from django.utils import simplejson
+from django.utils.encoding import smart_unicode
 
 AUTOCOMPLETE_NOTFOUND = HttpResponse(status=404)
 AUTOCOMPLETE_FORBIDDEN = HttpResponse(status=403)
 
-def autocomplete(request, ac_name, settings={}, query_param='query'):
-    if not settings.get(ac_name):
-        return AUTOCOMPLETE_NOTFOUND
+class AutoComplete(object):
 
-    qs, fields, limit, key, label, auth = settings[ac_name]
-    if auth and not request.user.is_authenticated():
-        return AUTOCOMPLETE_FORBIDDEN
-    query = request.GET.get(query_param, '')
-    
-    filter = Q()
-    for field in fields:
-        if not '__' in field:
-            field = '%s__startswith' % field
-        filter |= Q(**{field: query})
-    
-    qs = qs.filter(filter)[:limit]
-    
-    if isinstance(label, basestring):
-        if key == 'pk':
-            key = qs.model._meta.pk.attname
-        result = list(qs.values_list(key, label))
-    else:
-        result = []
-        for obj in qs:
-            result.append((getattr(obj, key), label(obj)))
-    return HttpResponse(simplejson.dumps(result),
-            mimetype='application/json')
+    def __init__(self):
+        self.settings = dict()
+
+    def __call__(self, request, ac_name, query_param='query'):
+        if not self.settings.get(ac_name):
+            return AUTOCOMPLETE_NOTFOUND
+
+        qs, fields, limit, key, label, auth = self.settings[ac_name]
+        if auth and not request.user.is_authenticated():
+            return AUTOCOMPLETE_FORBIDDEN
+        query = request.GET.get(query_param, '')
+        
+        filter = Q()
+        for field in fields:
+            if not '__' in field:
+                field = '%s__startswith' % field
+            filter |= Q(**{field: query})
+        
+        qs = qs.filter(filter)[:limit]
+        
+        if isinstance(label, basestring):
+            if key == 'pk':
+                key = qs.model._meta.pk.attname
+            result = list(qs.values_list(key, label))
+        else:
+            result = []
+            for obj in qs:
+                result.append((getattr(obj, key), label(obj)))
+        return HttpResponse(simplejson.dumps(result),
+                mimetype='application/json')
+
+    def register(self, id, queryset, fields, limit=None, key='pk', label=lambda obj: smart_unicode(obj), auth=False):
+        self.settings[id] = (queryset, fields, limit, key, label, auth)
+
+autocomplete = AutoComplete()

autocomplete/widgets.py

-from django import forms
+from django.forms import widgets
 from django.utils.safestring import mark_safe
 from django.core.urlresolvers import reverse
 
 
 # FIXME not ready for admin edit page, need ac_id_%(name)s prefill (2 line of js).
 
-class ModelAutoComplete(forms.Select):
+AC_TEMPLATE = u'''
+<div>
+  <input type="hidden" name="%(name)s" id="id_hidden_%(name)s" />
+  <input type="text" id="id_%(name)s" />
+  <script type="text/javascript">autocomplete("%(name)s", "%(url)s", %(force_selection)s);</script>
+</div>
+'''
+
+class AutoComplete(widgets.Widget):
+
+    AC_TEMPLATE = AC_TEMPLATE
 
     class Media:
         css = {'all':
               '&2.6.0/build/autocomplete/autocomplete-min.js',
               'js/autocomplete.js')
 
-    def __init__(self, ac_name, attrs=None, view_name='autocomplete'):
-        super(ModelAutoComplete, self).__init__(attrs, ())
+    def __init__(self, ac_name, force_selection=True, view_name='autocomplete', attrs=None):
+        super(AutoComplete, self).__init__(attrs)
         self.ac_name = ac_name
         self.view_name = view_name
+        self.force_selection = force_selection
     
     def render(self, name, value, attrs=None, choices=()):
-        output = [super(ModelAutoComplete, self).render(name, value, attrs, choices)]
-        output.append(u'<input type="text" id="id_ac_%s" style="display:none" />\n' % (name))
-        output.append(u'<script type="text/javascript">autocomplete("%s", "%s");</script>\n' %
-            (name, reverse(self.view_name, args=[self.ac_name])))
-        return mark_safe(u''.join(output))
+        #input = super(AutoComplete, self).render(name, value, attrs)
+        url = reverse(self.view_name, args=[self.ac_name])
+        force_selection = str(self.force_selection).lower()
+        return mark_safe(self.AC_TEMPLATE % locals())
 
+"""
+# TODO:
+class AdvancedChoiceField(forms.CharField):
+    
+    def __init__(self, ac_name=None, *args, **kwargs):
+        if not kwargs.pop('widget'):
+            kwargs['widget'] = AutoComplete(ac_name)
+        super(AdvancedChoiceField, self).__init__(*args, **kwargs)
+"""
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.