Ian Struble avatar Ian Struble committed a035b2f

Updated by_id lookups to properly support id other than 'id'.

Made the client-side select handler ignore bogus data. This is not the most desirable behavior but it will works. Also updated the server side of the lookups so the select handler should no longer get the bogus data in the first place.

Example field configuration that was failing:

autocomplete_fields = dict(
char_notrequired = dict(
queryset = User.objects.all(),
fields = ('^email', '^username'),
id = 'email',
value = 'email',
),
)

Thanks to Klemens Mantzos for the testing and bug report.

Comments (0)

Files changed (6)

django/contrib/admin/media/js/admin/autocomplete.js

         this.element.autocomplete({
             appendTo: this.element.parent(),
             select: function( event, ui ) {
-                var item = ui.item.data ? ui.item.data( "item.autocomplete" ) : ui.item;
+                if (!ui.item) { return; /* unexpected result */ }
+                var item = ui.item['data'] && ui.item.data( "item.autocomplete" ) || ui.item;
                 self.lastSelected = item
                 if (self.options.is_djangoautocomplete === true) {
                     if ( self.options.multiple ) {

django/contrib/admin/options.py

     models.FileField:       {'widget': widgets.AdminFileWidget},
 }
 
-AUTOCOMPLETE_FIELDS_DEFAULTS = dict(
-    limit = 5,
-    value = lambda o: unicode(o),
-    label = lambda o: unicode(o),
-    show_search = True,
-)
+AUTOCOMPLETE_FIELDS_DEFAULTS = {
+    'limit': 5,
+    'value': lambda o: unicode(o),
+    'label': lambda o: unicode(o),
+    'show_search': True,
+}
 
 csrf_protect_m = method_decorator(csrf_protect)
 
         search_fields = settings['fields']
         if request.GET.get('by_id', None) is not None:
             # lookup only via an exact match on id
-            search_fields = ('=id',)
+            search_fields = ('=%s' % settings['id'],)
 
         def construct_search(field_name):
             # use different lookup methods depending on the notation
             or_queries = [models.Q(**{construct_search(
                 smart_str(field_name)): bit})
                     for field_name in search_fields]
-
             queryset = queryset.filter(reduce(operator.or_, or_queries))
         
         data = []
         for o in queryset[:settings['limit']]:
-            data.append(dict(
-                id = getattr(o, settings['id']),
-                value = settings['value'](o),
-                label = settings['label'](o),
-            ))
+            data.append({
+                'id': getattr(o, settings['id']),
+                'value': settings['value'](o),
+                'label': settings['label'](o),
+            })
         
         return HttpResponse(simplejson.dumps(data))
     

docs/ref/contrib/admin/index.txt

     class AuthorAdmin(admin.ModelAdmin):
         date_hierarchy = 'pub_date'
 
+.. versionadded:: 1.3
+
 .. attribute:: ModelAdmin.autocomplete_fields
 
 By default, Django's admin uses a select-box interface (<select>) for fields 
         A tuple of field names used to search for objects associated with 
         ``field_name``. This key is required.
 
+        Example::
+
+            'fields': ('name', '^user__email',),
+
     * ``label``
         A formatting string or subroutine that controls how each choice 
         is displayed in the list of autocomplete choices.
 
         Example::
 
+            'label': 'name'
             'label': '%(name)s [%(gender)s]'
             'label': lambda o: o.name.lower()
 

tests/regressiontests/admin_widgets/models.py

     name = models.CharField(max_length=100)
     birthdate = models.DateTimeField(blank=True, null=True)
     gender = models.CharField(max_length=1, blank=True, choices=[('M','Male'), ('F', 'Female')])
+    user = models.ForeignKey(User, blank=True, null=True)
 
     def __unicode__(self):
         return self.name

tests/regressiontests/admin_widgets/tests.py

         )
         
 
-    def test_autocomplete_lookup(self):
+    def test_lookup(self):
         band = self.bands[1]
         self.client.login(username="super", password="secret")
         response = self.client.get('%s/admin_widgets/album/autocomplete/band/?term=johnny' % self.admin_root )
                            'value': band.name,
                            'label': band.name}])
         
+
+    def test_different_id(self):
+        self.client.login(username="super", password="secret")
+        user = models.User.objects.get(username='testser')
+        user.member_set.create(
+            name='Man Named Sue',
+            user=user,
+        )
+        expected = [{'id': user.email, 
+                     'value': user.username,
+                     'label': user.username}]
+        lookup_url = "%s/admin_widgets/member/autocomplete/user/?term=%%s" % self.admin_root
+        lookup_by_id_url = "%s&by_id=1" % lookup_url
+
+        response = self.client.get(lookup_url % user.username[:2])
+        self.assertEqual(simplejson.loads(response.content),
+                         expected)
+        response = self.client.get(lookup_url % user.email[:2])
+        self.assertEqual(simplejson.loads(response.content),
+                         expected)
+        response = self.client.get(lookup_url % user.first_name[:2])
+        self.assertEqual(simplejson.loads(response.content),
+                         expected)
+        response = self.client.get(lookup_by_id_url % user.email)
+        self.assertEqual(simplejson.loads(response.content),
+                         expected)
+        

tests/regressiontests/admin_widgets/widgetadmin.py

         'band': { 'fields': ('name',) }
             }
 
+class MemberAdmin(admin.ModelAdmin):
+    autocomplete_fields = {
+        'user': { 
+            'fields': ('^email', '^username', '^first_name', ),
+            'id': 'email',  # weird id for test_different_id
+            }
+        }
+
+
 site = WidgetAdmin(name='widget-admin')
 
 site.register(models.User)
 site.register(models.Car, CarAdmin)
 site.register(models.CarTire, CarTireAdmin)
 site.register(models.Event, EventAdmin)
+site.register(models.Member, MemberAdmin)
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.