Fixed #28871 -- Fixed initialization of autocomplete widgets in "Add another" inlines.

Also allowed autocomplete widgets to work on AdminSites with a name other
than 'admin'.
This commit is contained in:
Tim Graham 2017-12-01 14:00:00 -05:00
parent 095c1aaa89
commit 81057645f6
5 changed files with 38 additions and 14 deletions

View File

@ -220,7 +220,7 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
db = kwargs.get('using') db = kwargs.get('using')
if db_field.name in self.get_autocomplete_fields(request): if db_field.name in self.get_autocomplete_fields(request):
kwargs['widget'] = AutocompleteSelect(db_field.remote_field, using=db) kwargs['widget'] = AutocompleteSelect(db_field.remote_field, self.admin_site, using=db)
elif db_field.name in self.raw_id_fields: elif db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.remote_field, self.admin_site, using=db) kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.remote_field, self.admin_site, using=db)
elif db_field.name in self.radio_fields: elif db_field.name in self.radio_fields:
@ -248,7 +248,7 @@ class BaseModelAdmin(metaclass=forms.MediaDefiningClass):
autocomplete_fields = self.get_autocomplete_fields(request) autocomplete_fields = self.get_autocomplete_fields(request)
if db_field.name in autocomplete_fields: if db_field.name in autocomplete_fields:
kwargs['widget'] = AutocompleteSelectMultiple(db_field.remote_field, using=db) kwargs['widget'] = AutocompleteSelectMultiple(db_field.remote_field, self.admin_site, using=db)
elif db_field.name in self.raw_id_fields: elif db_field.name in self.raw_id_fields:
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field, self.admin_site, using=db) kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.remote_field, self.admin_site, using=db)
elif db_field.name in list(self.filter_vertical) + list(self.filter_horizontal): elif db_field.name in list(self.filter_vertical) + list(self.filter_horizontal):

View File

@ -24,15 +24,14 @@
}; };
$(function() { $(function() {
$('.admin-autocomplete').djangoAdminSelect2(); // Initialize all autocomplete widgets except the one in the template
// form used when a new formset is added.
$('.admin-autocomplete').not('[name*=__prefix__]').djangoAdminSelect2();
}); });
$(document).on('formset:added', (function() { $(document).on('formset:added', (function() {
return function(event, $newFormset) { return function(event, $newFormset) {
var $widget = $newFormset.find('.admin-autocomplete'); return $newFormset.find('.admin-autocomplete').djangoAdminSelect2();
// Exclude already initialized Select2 inputs.
$widget = $widget.not('.select2-hidden-accessible');
return init($widget);
}; };
})(this)); })(this));
}(django.jQuery)); }(django.jQuery));

View File

@ -400,10 +400,11 @@ class AutocompleteMixin:
Renders the necessary data attributes for select2 and adds the static form Renders the necessary data attributes for select2 and adds the static form
media. media.
""" """
url_name = 'admin:%s_%s_autocomplete' url_name = '%s:%s_%s_autocomplete'
def __init__(self, rel, attrs=None, choices=(), using=None): def __init__(self, rel, admin_site, attrs=None, choices=(), using=None):
self.rel = rel self.rel = rel
self.admin_site = admin_site
self.db = using self.db = using
self.choices = choices self.choices = choices
if attrs is not None: if attrs is not None:
@ -413,7 +414,7 @@ class AutocompleteMixin:
def get_url(self): def get_url(self):
model = self.rel.model model = self.rel.model
return reverse(self.url_name % (model._meta.app_label, model._meta.model_name)) return reverse(self.url_name % (self.admin_site.name, model._meta.app_label, model._meta.model_name))
def build_attrs(self, base_attrs, extra_attrs=None): def build_attrs(self, base_attrs, extra_attrs=None):
""" """

View File

@ -17,6 +17,7 @@ PAGINATOR_SIZE = AutocompleteJsonView.paginate_by
class AuthorAdmin(admin.ModelAdmin): class AuthorAdmin(admin.ModelAdmin):
ordering = ['id']
search_fields = ['id'] search_fields = ['id']
@ -229,3 +230,23 @@ class SeleniumTests(AdminSeleniumTestCase):
search.send_keys(Keys.RETURN) search.send_keys(Keys.RETURN)
select = Select(self.selenium.find_element_by_id('id_related_questions')) select = Select(self.selenium.find_element_by_id('id_related_questions'))
self.assertEqual(len(select.all_selected_options), 2) self.assertEqual(len(select.all_selected_options), 2)
def test_inline_add_another_widgets(self):
def assertNoResults(row):
elem = row.find_element_by_css_selector('.select2-selection')
elem.click() # Open the autocomplete dropdown.
results = self.selenium.find_element_by_css_selector('.select2-results')
self.assertTrue(results.is_displayed())
option = self.selenium.find_element_by_css_selector('.select2-results__option')
self.assertEqual(option.text, 'No results found')
# Autocomplete works in rows present when the page loads.
self.selenium.get(self.live_server_url + reverse('autocomplete_admin:admin_views_book_add'))
rows = self.selenium.find_elements_by_css_selector('.dynamic-authorship_set')
self.assertEqual(len(rows), 3)
assertNoResults(rows[0])
# Autocomplete works in rows added using the "Add another" button.
self.selenium.find_element_by_link_text('Add another Authorship').click()
rows = self.selenium.find_elements_by_css_selector('.dynamic-authorship_set')
self.assertEqual(len(rows), 4)
assertNoResults(rows[-1])

View File

@ -1,4 +1,5 @@
from django import forms from django import forms
from django.contrib import admin
from django.contrib.admin.widgets import AutocompleteSelect from django.contrib.admin.widgets import AutocompleteSelect
from django.forms import ModelChoiceField from django.forms import ModelChoiceField
from django.test import TestCase, override_settings from django.test import TestCase, override_settings
@ -14,10 +15,12 @@ class AlbumForm(forms.ModelForm):
widgets = { widgets = {
'band': AutocompleteSelect( 'band': AutocompleteSelect(
Album._meta.get_field('band').remote_field, Album._meta.get_field('band').remote_field,
admin.site,
attrs={'class': 'my-class'}, attrs={'class': 'my-class'},
), ),
'featuring': AutocompleteSelect( 'featuring': AutocompleteSelect(
Album._meta.get_field('featuring').remote_field, Album._meta.get_field('featuring').remote_field,
admin.site,
) )
} }
@ -25,7 +28,7 @@ class AlbumForm(forms.ModelForm):
class NotRequiredBandForm(forms.Form): class NotRequiredBandForm(forms.Form):
band = ModelChoiceField( band = ModelChoiceField(
queryset=Album.objects.all(), queryset=Album.objects.all(),
widget=AutocompleteSelect(Album._meta.get_field('band').remote_field), widget=AutocompleteSelect(Album._meta.get_field('band').remote_field, admin.site),
required=False, required=False,
) )
@ -33,7 +36,7 @@ class NotRequiredBandForm(forms.Form):
class RequiredBandForm(forms.Form): class RequiredBandForm(forms.Form):
band = ModelChoiceField( band = ModelChoiceField(
queryset=Album.objects.all(), queryset=Album.objects.all(),
widget=AutocompleteSelect(Album._meta.get_field('band').remote_field), widget=AutocompleteSelect(Album._meta.get_field('band').remote_field, admin.site),
required=True, required=True,
) )
@ -68,7 +71,7 @@ class AutocompleteMixinTests(TestCase):
def test_get_url(self): def test_get_url(self):
rel = Album._meta.get_field('band').remote_field rel = Album._meta.get_field('band').remote_field
w = AutocompleteSelect(rel) w = AutocompleteSelect(rel, admin.site)
url = w.get_url() url = w.get_url()
self.assertEqual(url, '/admin_widgets/band/autocomplete/') self.assertEqual(url, '/admin_widgets/band/autocomplete/')
@ -130,4 +133,4 @@ class AutocompleteMixinTests(TestCase):
else: else:
expected_files = base_files expected_files = base_files
with translation.override(lang): with translation.override(lang):
self.assertEqual(AutocompleteSelect(rel).media._js, expected_files) self.assertEqual(AutocompleteSelect(rel, admin.site).media._js, expected_files)