Fix #544 -- Ensure correct attribute defaults (#547)

dict.setdefault() does not change the default value if called twice.
Therefore, defaults need to passed to the super call instead.
This commit is contained in:
Mario Frasca 2019-06-10 10:09:04 -05:00 committed by Johannes Hoppe
parent 898b2e84dd
commit c15de464d5
4 changed files with 83 additions and 26 deletions

View file

@ -70,16 +70,18 @@ class Select2Mixin:
form media.
"""
def build_attrs(self, *args, **kwargs):
def build_attrs(self, base_attrs, extra_attrs=None):
"""Add select2 data attributes."""
attrs = super(Select2Mixin, self).build_attrs(*args, **kwargs)
default_attrs = {'data-minimum-input-length': 0}
if self.is_required:
attrs.setdefault('data-allow-clear', 'false')
default_attrs['data-allow-clear'] = 'false'
else:
attrs.setdefault('data-allow-clear', 'true')
attrs.setdefault('data-placeholder', '')
default_attrs['data-allow-clear'] = 'true'
default_attrs['data-placeholder'] = ''
default_attrs.update(base_attrs)
attrs = super().build_attrs(default_attrs, extra_attrs=extra_attrs)
attrs.setdefault('data-minimum-input-length', 0)
if 'class' in attrs:
attrs['class'] += ' django-select2'
else:
@ -120,12 +122,15 @@ class Select2Mixin:
class Select2TagMixin:
"""Mixin to add select2 tag functionality."""
def build_attrs(self, *args, **kwargs):
def build_attrs(self, base_attrs, extra_attrs=None):
"""Add select2's tag attributes."""
self.attrs.setdefault('data-minimum-input-length', 1)
self.attrs.setdefault('data-tags', 'true')
self.attrs.setdefault('data-token-separators', '[",", " "]')
return super(Select2TagMixin, self).build_attrs(*args, **kwargs)
default_attrs = {
'data-minimum-input-length': 1,
'data-tags': 'true',
'data-token-separators': '[",", " "]'
}
default_attrs.update(base_attrs)
return super().build_attrs(default_attrs, extra_attrs=extra_attrs)
class Select2Widget(Select2Mixin, forms.Select):
@ -226,20 +231,27 @@ class HeavySelect2Mixin:
return self.data_url
return reverse(self.data_view)
def build_attrs(self, *args, **kwargs):
def build_attrs(self, base_attrs, extra_attrs=None):
"""Set select2's AJAX attributes."""
attrs = super(HeavySelect2Mixin, self).build_attrs(*args, **kwargs)
default_attrs = {
'data-ajax--url': self.get_url(),
'data-ajax--cache': "true",
'data-ajax--type': "GET",
'data-minimum-input-length': 2,
}
if self.dependent_fields:
default_attrs['data-select2-dependent-fields'] = " ".join(self.dependent_fields)
default_attrs.update(base_attrs)
attrs = super().build_attrs(default_attrs, extra_attrs=extra_attrs)
# encrypt instance Id
self.widget_id = signing.dumps(id(self))
attrs['data-field_id'] = self.widget_id
attrs.setdefault('data-ajax--url', self.get_url())
attrs.setdefault('data-ajax--cache', "true")
attrs.setdefault('data-ajax--type', "GET")
attrs.setdefault('data-minimum-input-length', 2)
if self.dependent_fields:
attrs.setdefault('data-select2-dependent-fields', " ".join(self.dependent_fields))
attrs['class'] += ' django-select2-heavy'
return attrs

View file

@ -2,7 +2,6 @@ import random
import string
import pytest
from django.utils.encoding import force_text
from selenium import webdriver
from selenium.common.exceptions import WebDriverException
@ -22,11 +21,11 @@ def random_name(n):
@pytest.yield_fixture(scope='session')
def driver():
chrome_options = webdriver.ChromeOptions()
chrome_options.headless = True
chrome_options.headless = False
try:
b = webdriver.Chrome(options=chrome_options)
except WebDriverException as e:
pytest.skip(force_text(e))
pytest.skip(str(e))
else:
yield b
b.quit()

View file

@ -217,12 +217,17 @@ class TestHeavySelect2Mixin(TestSelect2Mixin):
driver.find_element_by_css_selector('.select2-results')
elem1, elem2 = driver.find_elements_by_css_selector('.select2-selection')
elem1.click()
elem1.click()
search1 = driver.find_element_by_css_selector('.select2-search__field')
search1.send_keys('fo')
result1 = WebDriverWait(driver, 60).until(
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li:first-child'))
).text
elem2.click()
search2 = driver.find_element_by_css_selector('.select2-search__field')
search2.send_keys('fo')
result2 = WebDriverWait(driver, 60).until(
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li:first-child'))
).text
@ -315,6 +320,37 @@ class TestModelSelect2Mixin(TestHeavySelect2Mixin):
widget.queryset = Genre.objects.all()
assert isinstance(widget.get_queryset(), QuerySet)
def test_tag_attrs_Select2Widget(self):
widget = Select2Widget()
output = widget.render('name', 'value')
assert 'data-minimum-input-length="0"' in output
def test_custom_tag_attrs_Select2Widget(self):
widget = Select2Widget(attrs={'data-minimum-input-length': '3'})
output = widget.render('name', 'value')
assert 'data-minimum-input-length="3"' in output
def test_tag_attrs_ModelSelect2Widget(self):
widget = ModelSelect2Widget(queryset=Genre.objects.all(), search_fields=['title__icontains'])
output = widget.render('name', 'value')
assert 'data-minimum-input-length="2"' in output
def test_tag_attrs_ModelSelect2TagWidget(self):
widget = ModelSelect2TagWidget(queryset=Genre.objects.all(), search_fields=['title__icontains'])
output = widget.render('name', 'value')
assert 'data-minimum-input-length="2"' in output
def test_tag_attrs_HeavySelect2Widget(self):
widget = HeavySelect2Widget(data_url='/foo/bar/')
output = widget.render('name', 'value')
assert 'data-minimum-input-length="2"' in output
def test_custom_tag_attrs_ModelSelect2Widget(self):
widget = ModelSelect2Widget(
queryset=Genre.objects.all(), search_fields=['title__icontains'], attrs={'data-minimum-input-length': '3'})
output = widget.render('name', 'value')
assert 'data-minimum-input-length="3"' in output
def test_get_search_fields(self):
widget = ModelSelect2Widget()
with pytest.raises(NotImplementedError):
@ -382,7 +418,7 @@ class TestHeavySelect2TagWidget(TestHeavySelect2Mixin):
def test_tag_attrs(self):
widget = ModelSelect2TagWidget(queryset=Genre.objects.all(), search_fields=['title__icontains'])
output = widget.render('name', 'value')
assert 'data-minimum-input-length="1"' in output
assert 'data-minimum-input-length="2"' in output
assert 'data-tags="true"' in output
assert 'data-token-separators' in output

View file

@ -149,11 +149,19 @@ class HeavySelect2WidgetForm(forms.Form):
class HeavySelect2MultipleWidgetForm(forms.Form):
title = forms.CharField(max_length=50)
genres = forms.MultipleChoiceField(
widget=HeavySelect2MultipleWidget(data_view='heavy_data_1', choices=NUMBER_CHOICES),
widget=HeavySelect2MultipleWidget(
data_view='heavy_data_1',
choices=NUMBER_CHOICES,
attrs={'data-minimum-input-length': 0},
),
choices=NUMBER_CHOICES
)
featured_artists = forms.MultipleChoiceField(
widget=HeavySelect2MultipleWidget(data_view='heavy_data_2', choices=NUMBER_CHOICES),
widget=HeavySelect2MultipleWidget(
data_view='heavy_data_2',
choices=NUMBER_CHOICES,
attrs={'data-minimum-input-length': 0},
),
choices=NUMBER_CHOICES,
required=False
)
@ -182,6 +190,7 @@ class AddressChainedSelect2WidgetForm(forms.Form):
search_fields=['name__icontains'],
max_results=500,
dependent_fields={'city': 'cities'},
attrs={'data-minimum-input-length': 0},
)
)
@ -193,6 +202,7 @@ class AddressChainedSelect2WidgetForm(forms.Form):
search_fields=['name__icontains'],
dependent_fields={'country': 'country'},
max_results=500,
attrs={'data-minimum-input-length': 0},
)
)