parent
dc6e87e7ba
commit
5840622794
|
@ -1,6 +1,3 @@
|
||||||
|
|
||||||
testapp/tt.py
|
|
||||||
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
Django_Select2.egg-info
|
Django_Select2.egg-info
|
||||||
|
@ -19,3 +16,7 @@ docs/_build
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
.cache/
|
.cache/
|
||||||
|
.tox/
|
||||||
|
geckodriver.log
|
||||||
|
ghostdriver.log
|
||||||
|
.coverage
|
||||||
|
|
19
.travis.yml
19
.travis.yml
|
@ -25,16 +25,27 @@ env:
|
||||||
- DISPLAY=:99.0
|
- DISPLAY=:99.0
|
||||||
- GECKO_DRIVER_VERSION=v0.14.0
|
- GECKO_DRIVER_VERSION=v0.14.0
|
||||||
matrix:
|
matrix:
|
||||||
|
- DJANGO=18
|
||||||
|
- DJANGO=110
|
||||||
|
- DJANGO=111
|
||||||
|
- DJANGO=master
|
||||||
- TOXENV=qa
|
- TOXENV=qa
|
||||||
- TOXENV=docs
|
- TOXENV=docs
|
||||||
- DJANGO=18
|
|
||||||
- DJANGO=19
|
|
||||||
- DJANGO=110
|
|
||||||
- DJANGO=master
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env: DJANGO=master
|
- env: DJANGO=master
|
||||||
|
exclude:
|
||||||
|
- env: DJANGO=master
|
||||||
|
python: "2.7"
|
||||||
|
- env: TOXENV=qa
|
||||||
|
python: "2.7"
|
||||||
|
- env: TOXENV=qa
|
||||||
|
python: "3.5"
|
||||||
|
- env: TOXENV=docs
|
||||||
|
python: "2.7"
|
||||||
|
- env: TOXENV=docs
|
||||||
|
python: "3.5"
|
||||||
install:
|
install:
|
||||||
- pip install --upgrade pip tox
|
- pip install --upgrade pip tox
|
||||||
- pip install -U coveralls
|
- pip install -U coveralls
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
Changelog Summary
|
Changelog Summary
|
||||||
=================
|
=================
|
||||||
|
|
||||||
|
### v5.9.0
|
||||||
|
* Add support for Django 1.11 LTS
|
||||||
|
* Drop support for Django 1.9
|
||||||
|
|
||||||
### v5.8.10
|
### v5.8.10
|
||||||
* Fixes tests for Django 1.10+
|
* Fixes tests for Django 1.10+
|
||||||
* retain order of choices [299](https://github.com/applegrew/django-select2/pull/299)
|
* retain order of choices [299](https://github.com/applegrew/django-select2/pull/299)
|
||||||
|
|
|
@ -9,4 +9,4 @@ The app includes Select2 driven Django Widgets and Form Fields.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "5.8.10"
|
__version__ = "5.9.0"
|
||||||
|
|
|
@ -54,7 +54,6 @@ from pickle import PicklingError
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.forms.models import ModelChoiceIterator
|
from django.forms.models import ModelChoiceIterator
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
@ -63,6 +62,11 @@ from django.utils.six.moves.cPickle import PicklingError as cPicklingError
|
||||||
from .cache import cache
|
from .cache import cache
|
||||||
from .conf import settings
|
from .conf import settings
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.urls import reverse
|
||||||
|
except ImportError:
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
class Select2Mixin(object):
|
class Select2Mixin(object):
|
||||||
"""
|
"""
|
||||||
|
@ -73,9 +77,9 @@ class Select2Mixin(object):
|
||||||
form media.
|
form media.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
def build_attrs(self, *args, **kwargs):
|
||||||
"""Add select2 data attributes."""
|
"""Add select2 data attributes."""
|
||||||
attrs = super(Select2Mixin, self).build_attrs(extra_attrs=extra_attrs, **kwargs)
|
attrs = super(Select2Mixin, self).build_attrs(*args, **kwargs)
|
||||||
if self.is_required:
|
if self.is_required:
|
||||||
attrs.setdefault('data-allow-clear', 'false')
|
attrs.setdefault('data-allow-clear', 'false')
|
||||||
else:
|
else:
|
||||||
|
@ -89,9 +93,15 @@ class Select2Mixin(object):
|
||||||
attrs['class'] = 'django-select2'
|
attrs['class'] = 'django-select2'
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
def optgroups(self, name, value, attrs=None):
|
||||||
|
"""Add empty option for clearable selects."""
|
||||||
|
if not self.is_required and not self.allow_multiple_selected:
|
||||||
|
self.choices = list(chain([('', '')], self.choices))
|
||||||
|
return super(Select2Mixin, self).optgroups(name, value, attrs=attrs)
|
||||||
|
|
||||||
def render_options(self, *args, **kwargs):
|
def render_options(self, *args, **kwargs):
|
||||||
"""Render options including an empty one, if the field is not required."""
|
"""Render options including an empty one, if the field is not required."""
|
||||||
output = '<option></option>' if not self.is_required and not self.allow_multiple_selected else ''
|
output = '<option value=""></option>' if not self.is_required and not self.allow_multiple_selected else ''
|
||||||
output += super(Select2Mixin, self).render_options(*args, **kwargs)
|
output += super(Select2Mixin, self).render_options(*args, **kwargs)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
@ -113,12 +123,12 @@ class Select2Mixin(object):
|
||||||
class Select2TagMixin(object):
|
class Select2TagMixin(object):
|
||||||
"""Mixin to add select2 tag functionality."""
|
"""Mixin to add select2 tag functionality."""
|
||||||
|
|
||||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
def build_attrs(self, *args, **kwargs):
|
||||||
"""Add select2's tag attributes."""
|
"""Add select2's tag attributes."""
|
||||||
self.attrs.setdefault('data-minimum-input-length', 1)
|
self.attrs.setdefault('data-minimum-input-length', 1)
|
||||||
self.attrs.setdefault('data-tags', 'true')
|
self.attrs.setdefault('data-tags', 'true')
|
||||||
self.attrs.setdefault('data-token-separators', '[",", " "]')
|
self.attrs.setdefault('data-token-separators', '[",", " "]')
|
||||||
return super(Select2TagMixin, self).build_attrs(extra_attrs, **kwargs)
|
return super(Select2TagMixin, self).build_attrs(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Select2Widget(Select2Mixin, forms.Select):
|
class Select2Widget(Select2Mixin, forms.Select):
|
||||||
|
@ -175,7 +185,7 @@ class Select2TagWidget(Select2TagMixin, Select2Mixin, forms.SelectMultiple):
|
||||||
class HeavySelect2Mixin(object):
|
class HeavySelect2Mixin(object):
|
||||||
"""Mixin that adds select2's AJAX options and registers itself on Django's cache."""
|
"""Mixin that adds select2's AJAX options and registers itself on Django's cache."""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, attrs=None, choices=(), **kwargs):
|
||||||
"""
|
"""
|
||||||
Return HeavySelect2Mixin.
|
Return HeavySelect2Mixin.
|
||||||
|
|
||||||
|
@ -184,12 +194,17 @@ class HeavySelect2Mixin(object):
|
||||||
data_url (str): URL
|
data_url (str): URL
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
self.choices = choices
|
||||||
|
if attrs is not None:
|
||||||
|
self.attrs = attrs.copy()
|
||||||
|
else:
|
||||||
|
self.attrs = {}
|
||||||
|
|
||||||
self.data_view = kwargs.pop('data_view', None)
|
self.data_view = kwargs.pop('data_view', None)
|
||||||
self.data_url = kwargs.pop('data_url', None)
|
self.data_url = kwargs.pop('data_url', None)
|
||||||
if not (self.data_view or self.data_url):
|
if not (self.data_view or self.data_url):
|
||||||
raise ValueError('You must ether specify "data_view" or "data_url".')
|
raise ValueError('You must ether specify "data_view" or "data_url".')
|
||||||
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
|
self.userGetValTextFuncName = kwargs.pop('userGetValTextFuncName', 'null')
|
||||||
super(HeavySelect2Mixin, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
"""Return URL from instance or by reversing :attr:`.data_view`."""
|
"""Return URL from instance or by reversing :attr:`.data_view`."""
|
||||||
|
@ -197,9 +212,9 @@ class HeavySelect2Mixin(object):
|
||||||
return self.data_url
|
return self.data_url
|
||||||
return reverse(self.data_view)
|
return reverse(self.data_view)
|
||||||
|
|
||||||
def build_attrs(self, extra_attrs=None, **kwargs):
|
def build_attrs(self, *args, **kwargs):
|
||||||
"""Set select2's AJAX attributes."""
|
"""Set select2's AJAX attributes."""
|
||||||
attrs = super(HeavySelect2Mixin, self).build_attrs(extra_attrs=extra_attrs, **kwargs)
|
attrs = super(HeavySelect2Mixin, self).build_attrs(*args, **kwargs)
|
||||||
|
|
||||||
# encrypt instance Id
|
# encrypt instance Id
|
||||||
self.widget_id = signing.dumps(id(self))
|
self.widget_id = signing.dumps(id(self))
|
||||||
|
@ -247,7 +262,7 @@ class HeavySelect2Mixin(object):
|
||||||
choices = chain(self.choices, choices)
|
choices = chain(self.choices, choices)
|
||||||
else:
|
else:
|
||||||
choices = self.choices
|
choices = self.choices
|
||||||
output = ['<option></option>' if not self.is_required and not self.allow_multiple_selected else '']
|
output = ['<option value=""></option>' if not self.is_required and not self.allow_multiple_selected else '']
|
||||||
selected_choices = {force_text(v) for v in selected_choices}
|
selected_choices = {force_text(v) for v in selected_choices}
|
||||||
choices = [(k, v) for k, v in choices if force_text(k) in selected_choices]
|
choices = [(k, v) for k, v in choices if force_text(k) in selected_choices]
|
||||||
for option_value, option_label in choices:
|
for option_value, option_label in choices:
|
||||||
|
@ -401,6 +416,36 @@ class ModelSelect2Mixin(object):
|
||||||
return self.search_fields
|
return self.search_fields
|
||||||
raise NotImplementedError('%s, must implement "search_fields".' % self.__class__.__name__)
|
raise NotImplementedError('%s, must implement "search_fields".' % self.__class__.__name__)
|
||||||
|
|
||||||
|
def optgroups(self, name, value, attrs=None):
|
||||||
|
"""Return only selected options and set QuerySet from `ModelChoicesIterator`."""
|
||||||
|
default = (None, [], 0)
|
||||||
|
groups = [default]
|
||||||
|
has_selected = False
|
||||||
|
selected_choices = {force_text(v) for v in value}
|
||||||
|
if not self.is_required and not self.allow_multiple_selected:
|
||||||
|
default[1].append(self.create_option(name, '', '', False, 0))
|
||||||
|
if not isinstance(self.choices, ModelChoiceIterator):
|
||||||
|
return super(ModelSelect2Mixin, self).optgroups(name, value, attrs=attrs)
|
||||||
|
selected_choices = {
|
||||||
|
c for c in selected_choices
|
||||||
|
if c not in self.choices.field.empty_values
|
||||||
|
}
|
||||||
|
choices = (
|
||||||
|
(obj.pk, self.label_from_instance(obj))
|
||||||
|
for obj in self.choices.queryset.filter(pk__in=selected_choices)
|
||||||
|
)
|
||||||
|
for option_value, option_label in choices:
|
||||||
|
selected = (
|
||||||
|
force_text(option_value) in value and
|
||||||
|
(has_selected is False or self.allow_multiple_selected)
|
||||||
|
)
|
||||||
|
if selected is True and has_selected is False:
|
||||||
|
has_selected = True
|
||||||
|
index = len(default[1])
|
||||||
|
subgroup = default[1]
|
||||||
|
subgroup.append(self.create_option(name, option_value, option_label, selected_choices, index))
|
||||||
|
return groups
|
||||||
|
|
||||||
def render_options(self, *args):
|
def render_options(self, *args):
|
||||||
"""Render only selected options and set QuerySet from :class:`ModelChoiceIterator`."""
|
"""Render only selected options and set QuerySet from :class:`ModelChoiceIterator`."""
|
||||||
try:
|
try:
|
||||||
|
@ -411,7 +456,7 @@ class ModelSelect2Mixin(object):
|
||||||
else:
|
else:
|
||||||
choices = self.choices
|
choices = self.choices
|
||||||
selected_choices = {force_text(v) for v in selected_choices}
|
selected_choices = {force_text(v) for v in selected_choices}
|
||||||
output = ['<option></option>' if not self.is_required and not self.allow_multiple_selected else '']
|
output = ['<option value=""></option>' if not self.is_required and not self.allow_multiple_selected else '']
|
||||||
if isinstance(self.choices, ModelChoiceIterator):
|
if isinstance(self.choices, ModelChoiceIterator):
|
||||||
if self.queryset is None:
|
if self.queryset is None:
|
||||||
self.queryset = self.choices.queryset
|
self.queryset = self.choices.queryset
|
||||||
|
|
|
@ -15,3 +15,4 @@ functionalities
|
||||||
plugin
|
plugin
|
||||||
multi
|
multi
|
||||||
Indices
|
Indices
|
||||||
|
clearable
|
||||||
|
|
3
setup.py
3
setup.py
|
@ -44,7 +44,8 @@ setup(
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
"Framework :: Django",
|
"Framework :: Django",
|
||||||
"Framework :: Django :: 1.8",
|
"Framework :: Django :: 1.8",
|
||||||
"Framework :: Django :: 1.9",
|
"Framework :: Django :: 1.10",
|
||||||
|
"Framework :: Django :: 1.11",
|
||||||
],
|
],
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'django-appconf>=0.6.0',
|
'django-appconf>=0.6.0',
|
||||||
|
|
|
@ -6,7 +6,6 @@ import json
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.six import text_type
|
from django.utils.six import text_type
|
||||||
|
@ -26,6 +25,11 @@ from tests.testapp.forms import (
|
||||||
)
|
)
|
||||||
from tests.testapp.models import Genre
|
from tests.testapp.models import Genre
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.urls import reverse
|
||||||
|
except ImportError:
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
class TestSelect2Mixin(object):
|
class TestSelect2Mixin(object):
|
||||||
url = reverse('select2_widget')
|
url = reverse('select2_widget')
|
||||||
|
@ -48,7 +52,7 @@ class TestSelect2Mixin(object):
|
||||||
assert required_field.required is True
|
assert required_field.required is True
|
||||||
assert 'data-allow-clear="true"' not in required_field.widget.render('artist', None)
|
assert 'data-allow-clear="true"' not in required_field.widget.render('artist', None)
|
||||||
assert 'data-allow-clear="false"' in required_field.widget.render('artist', None)
|
assert 'data-allow-clear="false"' in required_field.widget.render('artist', None)
|
||||||
assert '<option></option>' not in required_field.widget.render('artist', None)
|
assert '<option value=""></option>' not in required_field.widget.render('artist', None)
|
||||||
|
|
||||||
not_required_field = self.form.fields['primary_genre']
|
not_required_field = self.form.fields['primary_genre']
|
||||||
assert not_required_field.required is False
|
assert not_required_field.required is False
|
||||||
|
@ -56,7 +60,7 @@ class TestSelect2Mixin(object):
|
||||||
assert 'data-allow-clear="false"' not in not_required_field.widget.render('primary_genre',
|
assert 'data-allow-clear="false"' not in not_required_field.widget.render('primary_genre',
|
||||||
None)
|
None)
|
||||||
assert 'data-placeholder' in not_required_field.widget.render('primary_genre', None)
|
assert 'data-placeholder' in not_required_field.widget.render('primary_genre', None)
|
||||||
assert '<option></option>' in not_required_field.widget.render('primary_genre', None)
|
assert '<option value=""></option>' in not_required_field.widget.render('primary_genre', None)
|
||||||
|
|
||||||
def test_no_js_error(self, db, live_server, driver):
|
def test_no_js_error(self, db, live_server, driver):
|
||||||
driver.get(live_server + self.url)
|
driver.get(live_server + self.url)
|
||||||
|
@ -91,12 +95,12 @@ class TestSelect2Mixin(object):
|
||||||
# https://select2.github.io/options.html#allowClear
|
# https://select2.github.io/options.html#allowClear
|
||||||
single_select = self.form.fields['primary_genre']
|
single_select = self.form.fields['primary_genre']
|
||||||
assert single_select.required is False
|
assert single_select.required is False
|
||||||
assert '<option></option>' in single_select.widget.render('primary_genre', None)
|
assert '<option value=""></option>' in single_select.widget.render('primary_genre', None)
|
||||||
|
|
||||||
multiple_select = self.multiple_form.fields['featured_artists']
|
multiple_select = self.multiple_form.fields['featured_artists']
|
||||||
assert multiple_select.required is False
|
assert multiple_select.required is False
|
||||||
assert multiple_select.widget.allow_multiple_selected
|
assert multiple_select.widget.allow_multiple_selected
|
||||||
assert '<option></option>' not in multiple_select.widget.render('featured_artists', None)
|
assert '<option value=""></option>' not in multiple_select.widget.render('featured_artists', None)
|
||||||
|
|
||||||
|
|
||||||
class TestSelect2MixinSettings(object):
|
class TestSelect2MixinSettings(object):
|
||||||
|
@ -139,7 +143,9 @@ class TestHeavySelect2Mixin(TestSelect2Mixin):
|
||||||
not_required_field = self.form.fields['primary_genre']
|
not_required_field = self.form.fields['primary_genre']
|
||||||
assert not_required_field.required is False
|
assert not_required_field.required is False
|
||||||
assert '<option value="1" selected="selected">One</option>' in \
|
assert '<option value="1" selected="selected">One</option>' in \
|
||||||
not_required_field.widget.render('primary_genre', 1), \
|
not_required_field.widget.render('primary_genre', 1) or \
|
||||||
|
'<option value="1" selected>One</option>' in \
|
||||||
|
not_required_field.widget.render('primary_genre', 1), \
|
||||||
not_required_field.widget.render('primary_genre', 1)
|
not_required_field.widget.render('primary_genre', 1)
|
||||||
|
|
||||||
def test_many_selected_option(self, db, genres):
|
def test_many_selected_option(self, db, genres):
|
||||||
|
@ -147,10 +153,12 @@ class TestHeavySelect2Mixin(TestSelect2Mixin):
|
||||||
field.widget.choices = NUMBER_CHOICES
|
field.widget.choices = NUMBER_CHOICES
|
||||||
widget_output = field.widget.render('genres', [1, 2])
|
widget_output = field.widget.render('genres', [1, 2])
|
||||||
selected_option = '<option value="{pk}" selected="selected">{value}</option>'.format(pk=1, value='One')
|
selected_option = '<option value="{pk}" selected="selected">{value}</option>'.format(pk=1, value='One')
|
||||||
|
selected_option_a = '<option value="{pk}" selected>{value}</option>'.format(pk=1, value='One')
|
||||||
selected_option2 = '<option value="{pk}" selected="selected">{value}</option>'.format(pk=2, value='Two')
|
selected_option2 = '<option value="{pk}" selected="selected">{value}</option>'.format(pk=2, value='Two')
|
||||||
|
selected_option2a = '<option value="{pk}" selected>{value}</option>'.format(pk=2, value='Two')
|
||||||
|
|
||||||
assert selected_option in widget_output, widget_output
|
assert selected_option in widget_output or selected_option_a in widget_output, widget_output
|
||||||
assert selected_option2 in widget_output
|
assert selected_option2 in widget_output or selected_option2a in widget_output
|
||||||
|
|
||||||
def test_multiple_widgets(self, db, live_server, driver):
|
def test_multiple_widgets(self, db, live_server, driver):
|
||||||
driver.get(live_server + self.url)
|
driver.get(live_server + self.url)
|
||||||
|
@ -160,11 +168,11 @@ class TestHeavySelect2Mixin(TestSelect2Mixin):
|
||||||
elem1, elem2 = driver.find_elements_by_css_selector('.select2-selection')
|
elem1, elem2 = driver.find_elements_by_css_selector('.select2-selection')
|
||||||
elem1.click()
|
elem1.click()
|
||||||
|
|
||||||
result1 = WebDriverWait(driver, 10).until(
|
result1 = WebDriverWait(driver, 60).until(
|
||||||
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li:first-child'))
|
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li:first-child'))
|
||||||
).text
|
).text
|
||||||
elem2.click()
|
elem2.click()
|
||||||
result2 = WebDriverWait(driver, 10).until(
|
result2 = WebDriverWait(driver, 60).until(
|
||||||
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li:first-child'))
|
expected_conditions.presence_of_element_located((By.CSS_SELECTOR, '.select2-results li:first-child'))
|
||||||
).text
|
).text
|
||||||
|
|
||||||
|
@ -204,7 +212,7 @@ class TestModelSelect2Mixin(TestHeavySelect2Mixin):
|
||||||
genre.save()
|
genre.save()
|
||||||
|
|
||||||
form = self.form.__class__(initial={'primary_genre': genre.pk})
|
form = self.form.__class__(initial={'primary_genre': genre.pk})
|
||||||
assert genre.title not in form.as_p()
|
assert genre.title not in form.as_p(), form.as_p()
|
||||||
assert genre.title.upper() in form.as_p()
|
assert genre.title.upper() in form.as_p()
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
|
@ -220,10 +228,12 @@ class TestModelSelect2Mixin(TestHeavySelect2Mixin):
|
||||||
'primary_genre', genre.pk)
|
'primary_genre', genre.pk)
|
||||||
selected_option = '<option value="{pk}" selected="selected">{value}</option>'.format(
|
selected_option = '<option value="{pk}" selected="selected">{value}</option>'.format(
|
||||||
pk=genre.pk, value=force_text(genre))
|
pk=genre.pk, value=force_text(genre))
|
||||||
|
selected_option_a = '<option value="{pk}" selected>{value}</option>'.format(
|
||||||
|
pk=genre.pk, value=force_text(genre))
|
||||||
unselected_option = '<option value="{pk}">{value}</option>'.format(
|
unselected_option = '<option value="{pk}">{value}</option>'.format(
|
||||||
pk=genre2.pk, value=force_text(genre2))
|
pk=genre2.pk, value=force_text(genre2))
|
||||||
|
|
||||||
assert selected_option in widget_output, widget_output
|
assert selected_option in widget_output or selected_option_a in widget_output, widget_output
|
||||||
assert unselected_option not in widget_output
|
assert unselected_option not in widget_output
|
||||||
|
|
||||||
def test_selected_option_label_from_instance(self, db, genres):
|
def test_selected_option_label_from_instance(self, db, genres):
|
||||||
|
@ -234,14 +244,15 @@ class TestModelSelect2Mixin(TestHeavySelect2Mixin):
|
||||||
field = self.form.fields['primary_genre']
|
field = self.form.fields['primary_genre']
|
||||||
widget_output = field.widget.render('primary_genre', genre.pk)
|
widget_output = field.widget.render('primary_genre', genre.pk)
|
||||||
|
|
||||||
def get_selected_option(genre):
|
def get_selected_options(genre):
|
||||||
return '<option value="{pk}" selected="selected">{value}</option>'.format(
|
return '<option value="{pk}" selected="selected">{value}</option>'.format(
|
||||||
|
pk=genre.pk, value=force_text(genre)), '<option value="{pk}" selected>{value}</option>'.format(
|
||||||
pk=genre.pk, value=force_text(genre))
|
pk=genre.pk, value=force_text(genre))
|
||||||
|
|
||||||
assert get_selected_option(genre) not in widget_output
|
assert all(o not in widget_output for o in get_selected_options(genre))
|
||||||
genre.title = genre.title.upper()
|
genre.title = genre.title.upper()
|
||||||
|
|
||||||
assert get_selected_option(genre) in widget_output
|
assert any(o in widget_output for o in get_selected_options(genre))
|
||||||
|
|
||||||
def test_get_queryset(self):
|
def test_get_queryset(self):
|
||||||
widget = ModelSelect2Widget()
|
widget = ModelSelect2Widget()
|
||||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import absolute_import, unicode_literals
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from django.core import signing
|
from django.core import signing
|
||||||
from django.core.urlresolvers import reverse
|
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
|
|
||||||
from django_select2.cache import cache
|
from django_select2.cache import cache
|
||||||
|
@ -14,6 +13,11 @@ from tests.testapp.forms import (
|
||||||
)
|
)
|
||||||
from tests.testapp.models import Genre
|
from tests.testapp.models import Genre
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.urls import reverse
|
||||||
|
except ImportError:
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
|
||||||
class TestAutoResponseView(object):
|
class TestAutoResponseView(object):
|
||||||
def test_get(self, client, artists):
|
def test_get(self, client, artists):
|
||||||
|
|
|
@ -25,9 +25,10 @@ class Artist(models.Model):
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class Album(models.Model):
|
class Album(models.Model):
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
artist = models.ForeignKey(Artist)
|
artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
|
||||||
featured_artists = models.ManyToManyField(Artist, blank=True, related_name='featured_album_set')
|
featured_artists = models.ManyToManyField(Artist, blank=True, related_name='featured_album_set')
|
||||||
primary_genre = models.ForeignKey(Genre, blank=True, null=True, related_name='primary_album_set')
|
primary_genre = models.ForeignKey(Genre, on_delete=models.CASCADE, blank=True, null=True,
|
||||||
|
related_name='primary_album_set')
|
||||||
genres = models.ManyToManyField(Genre)
|
genres = models.ManyToManyField(Genre)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
|
|
4
tox.ini
4
tox.ini
|
@ -1,5 +1,5 @@
|
||||||
[tox]
|
[tox]
|
||||||
envlist = py{27,35,36}-dj{18,19,110,master},qa,docs
|
envlist = py{27,35,36}-dj{18,110,111,master},qa,docs
|
||||||
[testenv]
|
[testenv]
|
||||||
setenv=
|
setenv=
|
||||||
DISPLAY=:99.0
|
DISPLAY=:99.0
|
||||||
|
@ -7,8 +7,8 @@ setenv=
|
||||||
deps=
|
deps=
|
||||||
-rrequirements-dev.txt
|
-rrequirements-dev.txt
|
||||||
dj18: https://github.com/django/django/archive/stable/1.8.x.tar.gz#egg=django
|
dj18: https://github.com/django/django/archive/stable/1.8.x.tar.gz#egg=django
|
||||||
dj19: https://github.com/django/django/archive/stable/1.9.x.tar.gz#egg=django
|
|
||||||
dj110: https://github.com/django/django/archive/stable/1.10.x.tar.gz#egg=django
|
dj110: https://github.com/django/django/archive/stable/1.10.x.tar.gz#egg=django
|
||||||
|
dj111: https://github.com/django/django/archive/stable/1.11.x.tar.gz#egg=django
|
||||||
djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django
|
djmaster: https://github.com/django/django/archive/master.tar.gz#egg=django
|
||||||
commands=
|
commands=
|
||||||
coverage run --source=django_select2 -m 'pytest' \
|
coverage run --source=django_select2 -m 'pytest' \
|
||||||
|
|
Loading…
Reference in New Issue