Compare commits

...
14 Commits
Author SHA1 Message Date
Johannes Hoppe e1d42bfcad Update Select2 library to version 4.0.5 2018-07-24 13:05:11 +02:00
Johannes Hoppe 1954bf57fa Add SELECT2_LIB_VERSION setting
The new setting allows to change to default library version to
a specific value without overriding all URLs individually.
2018-07-24 13:05:11 +02:00
Asger Hautop Drewsen 08ca1c1c36 Add note about Select2 support in Django 2.0 admin (#488)
Resolve #485
2018-07-01 10:37:30 +02:00
Bill Yeh aef27f48df Add option to exclude the Select2 library from form media (#484) 2018-06-30 14:58:47 +02:00
Johannes Hoppe f31beec0c4 Add support for custom to_field names for foreign keys (#486)
Django's ForeignKey supports custom to_fields. The to field is the
primary key by default, but can be modified. The to field is also
used by forms to reduce database lookups.

This patch add support for custom to_field names on both model or
form layer.
2018-06-30 14:56:32 +02:00
dzhuang 496cc7c502 Fallback i18n js files for zh-hans/zh-hant. (#468)
* Fallback i18n js files for zh-hans/zh-hant.

* Use Django admin's built-in SELECT2_TRANSLATIONS to determine the lang of i18n js files.
2018-06-14 11:49:47 +02:00
Jan Pieter Waagmeester 17af750d88 Link to stable Django version docs not 1.8 (#480) 2018-05-31 09:30:22 +02:00
Jan Pieter Waagmeester 67bf060991 Link to github releases from readme+changelog. (#481) 2018-05-31 09:29:18 +02:00
Johannes Hoppe d9c899e8f7 Resolve pickle security note 2018-05-07 18:52:17 +02:00
Johannes Hoppe e4729feb9a Add bandit config 2018-05-07 18:52:17 +02:00
Jan Pieter Waagmeester 2ea6ef8edb Fix typo in docstring ChoicesField -> ChoiceField (#472)
It's `django.forms.ChoiceField`, not `django.forms.ChoicesField`.

Used correctly here: 
https://github.com/applegrew/django-select2/blob/a9aa31146f44017848451178123268e38c9def38/django_select2/forms.py#L143
2018-05-06 13:02:38 +02:00
Sergey Fedoseev a9aa31146f Fixed display of ModelSelect2Mixin.filter_queryset() doc. (#470) 2018-03-24 14:25:42 +01:00
Inti Ocean c2331b272a Add some quickstart docs (#467)
Add some quickstart docs to make it easy for someone to pick up the library and get going
straight away.
2018-03-19 12:09:54 +01:00
Johannes Hoppe 5d98048c9f Fix #464 -- Freeze Sphinx to 1.6.5
Ref sphinx/shpinx#4520
2018-03-07 22:36:43 +01:00
10 changed files with 143 additions and 36 deletions
+2
View File
@@ -0,0 +1,2 @@
[bandit]
exclude: tests
+2
View File
@@ -1,6 +1,8 @@
Changelog Summary
=================
Changes for django-select2 v6.0.1 and later are logged on the [github releases page](https://github.com/applegrew/django-select2/releases).
### v6.0.0
* Add Support for Django 2.0
+7 -1
View File
@@ -7,6 +7,10 @@ This is a `Django`_ integration of `Select2`_.
The app includes Select2 driven Django Widgets.
.. note::
Django's admin comes with builtin support for Select2
since version 2.0 via the `autocomplete_fields`_ feature.
Installation
------------
@@ -46,12 +50,14 @@ good example.
Changelog
---------
See `CHANGELOG.md`_
See `Github releases`_
.. _Django: https://www.djangoproject.com/
.. _Select2: http://ivaynberg.github.com/select2/
.. _autocomplete_fields: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields
.. _CHANGELOG.md: CHANGELOG.md
.. _Github releases: https://github.com/applegrew/django-select2/releases
.. |version| image:: https://img.shields.io/pypi/v/Django-Select2.svg
:target: https://pypi.python.org/pypi/Django-Select2/
+16 -3
View File
@@ -8,6 +8,9 @@ __all__ = ('settings', 'Select2Conf')
class Select2Conf(AppConf):
"""Settings for Django-Select2."""
LIB_VERSION = '4.0.5'
"""Version of the Select2 library."""
CACHE_BACKEND = 'default'
"""
Django-Select2 uses Django's cache to sure a consistent state across multiple machines.
@@ -53,7 +56,7 @@ class Select2Conf(AppConf):
It has set `select2_` as a default value, which you can change if needed.
"""
JS = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js'
JS = f'//cdnjs.cloudflare.com/ajax/libs/select2/{LIB_VERSION}/js/select2.min.js'
"""
The URI for the Select2 JS file. By default this points to the Cloudflare CDN.
@@ -62,11 +65,16 @@ class Select2Conf(AppConf):
SELECT2_JS = 'assets/js/select2.min.js'
If you provide your own JS and would not like Django-Select2 to load any, change
this setting to a blank string like so::
SELECT2_JS = ''
.. tip:: Change this setting to a local asset in your development environment to
develop without an Internet connection.
"""
CSS = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css'
CSS = f'//cdnjs.cloudflare.com/ajax/libs/select2/{LIB_VERSION}/css/select2.min.css'
"""
The URI for the Select2 CSS file. By default this points to the Cloudflare CDN.
@@ -75,11 +83,16 @@ class Select2Conf(AppConf):
SELECT2_CSS = 'assets/css/select2.css'
If you provide your own CSS and would not like Django-Select2 to load any, change
this setting to a blank string like so::
SELECT2_CSS = ''
.. tip:: Change this setting to a local asset in your development environment to
develop without an Internet connection.
"""
I18N_PATH = '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n'
I18N_PATH = f'//cdnjs.cloudflare.com/ajax/libs/select2/{LIB_VERSION}/js/i18n'
"""
The base URI for the Select2 i18n files. By default this points to the Cloudflare CDN.
+32 -17
View File
@@ -47,7 +47,7 @@ Light widgets are normally named, i.e. there is no
"""
from functools import reduce
from itertools import chain
from pickle import PicklingError
from pickle import PicklingError # nosec
from django import forms
from django.core import signing
@@ -96,17 +96,31 @@ class Select2Mixin(object):
Construct Media as a dynamic property.
.. Note:: For more information visit
https://docs.djangoproject.com/en/1.8/topics/forms/media/#media-as-a-dynamic-property
https://docs.djangoproject.com/en/stable/topics/forms/media/#media-as-a-dynamic-property
"""
lang = get_language()
i18n_name = None
select2_js = (settings.SELECT2_JS,) if settings.SELECT2_JS else ()
select2_css = (settings.SELECT2_CSS,) if settings.SELECT2_CSS else ()
try:
# get_language() will always return a lower case language code, where some files are named upper case.
i = [x.lower() for x in settings.SELECT2_I18N_AVAILABLE_LANGUAGES].index(get_language())
i18n_file = ('%s/%s.js' % (settings.SELECT2_I18N_PATH, settings.SELECT2_I18N_AVAILABLE_LANGUAGES[i]), )
except ValueError:
i18n_file = ()
from django.contrib.admin.widgets import SELECT2_TRANSLATIONS
i18n_name = SELECT2_TRANSLATIONS.get(lang)
if i18n_name not in settings.SELECT2_I18N_AVAILABLE_LANGUAGES:
i18n_name = None
except ImportError:
# TODO: select2 widget feature needs to be backported into Django 1.11
try:
i = [x.lower() for x in settings.SELECT2_I18N_AVAILABLE_LANGUAGES].index(lang)
i18n_name = settings.SELECT2_I18N_AVAILABLE_LANGUAGES[i]
except ValueError:
pass
i18n_file = ('%s/%s.js' % (settings.SELECT2_I18N_PATH, i18n_name),) if i18n_name else ()
return forms.Media(
js=(settings.SELECT2_JS,) + i18n_file + ('django_select2/django_select2.js',),
css={'screen': (settings.SELECT2_CSS,)}
js=select2_js + i18n_file + ('django_select2/django_select2.js',),
css={'screen': select2_css}
)
media = property(_get_media)
@@ -277,7 +291,7 @@ class HeavySelect2Widget(HeavySelect2Mixin, Select2Widget):
or::
class MyForm(forms.Form):
my_field = forms.ChoicesField(
my_field = forms.ChoiceField(
widget=HeavySelect2Widget(
data_url='/url/to/json/response'
)
@@ -370,8 +384,8 @@ class ModelSelect2Mixin(object):
term (str): Search term
queryset (django.db.models.query.QuerySet): QuerySet to select choices from.
**dependent_fields: Dependent fields and their values. If you want to inherit
from ModelSelect2Mixin and later call to this method, be sure to pop
from kwargs everything if it is not a dependent field.
from ModelSelect2Mixin and later call to this method, be sure to pop
everything from keyword arguments that is not a dependent field.
Returns:
QuerySet: Filtered QuerySet
@@ -433,11 +447,12 @@ class ModelSelect2Mixin(object):
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:
field_name = self.choices.field.to_field_name or 'pk'
query = Q(**{'%s__in' % field_name: selected_choices})
for obj in self.choices.queryset.filter(query):
option_value = self.choices.choice(obj)[0]
option_label = self.label_from_instance(obj)
selected = (
str(option_value) in value and
(has_selected is False or self.allow_multiple_selected)
+28
View File
@@ -23,6 +23,34 @@ Installation
url(r'^select2/', include('django_select2.urls')),
Quick Start
-----------
Here is a quick example to get you started:
0. Follow the installation instructions above.
1. Add a select2 widget to the form. For example if you wanted Select2 with multi-select you would use
``Select2MultipleWidget``
Replacing::
class MyForm(forms.Form):
things = ModelMultipleChoiceField(queryset=Thing.objects.all())
with::
class MyForm(forms.Form):
things = ModelMultipleChoiceField(queryset=Thing.objects.all(), widget=Select2MultipleWidget)
2. Add the CSS to the ``head`` of your Django template::
{{ form.media.css }}
3. Add the JavaScript to the end of the ``body`` of your Django template::
{{ form.media.js }}
4. Done - enjoy the wonders of Select2!
External Dependencies
---------------------
+1 -1
View File
@@ -8,7 +8,7 @@ pydocstyle
pytest
pytest-django
selenium
sphinx
sphinx==1.6.5
sphinxcontrib-spelling
pyenchant
django-redis
+41 -13
View File
@@ -14,6 +14,7 @@ from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from django_select2.cache import cache
from django_select2.conf import settings
from django_select2.forms import (
HeavySelect2MultipleWidget, HeavySelect2Widget, ModelSelect2TagWidget,
ModelSelect2Widget, Select2Widget
@@ -22,7 +23,7 @@ from tests.testapp import forms
from tests.testapp.forms import (
NUMBER_CHOICES, HeavySelect2MultipleWidgetForm, TitleModelSelect2Widget
)
from tests.testapp.models import City, Country, Genre
from tests.testapp.models import Artist, City, Country, Genre, Groupie
try:
from django.urls import reverse
@@ -104,35 +105,44 @@ class TestSelect2Mixin(object):
def test_i18n(self):
translation.activate('de')
assert tuple(Select2Widget().media._js) == (
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js',
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/de.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/de.js',
'django_select2/django_select2.js'
)
translation.activate('en')
assert tuple(Select2Widget().media._js) == (
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js',
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/en.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/en.js',
'django_select2/django_select2.js'
)
translation.activate('00')
assert tuple(Select2Widget().media._js) == (
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js',
'django_select2/django_select2.js'
)
translation.activate('sr-cyrl')
assert tuple(Select2Widget().media._js) == (
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js',
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/sr-Cyrl.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/sr-Cyrl.js',
'django_select2/django_select2.js'
)
translation.activate('zh-cn')
pytest.importorskip("django", minversion="2.0.4")
translation.activate('zh-hans')
assert tuple(Select2Widget().media._js) == (
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js',
'//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/i18n/zh-CN.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/zh-CN.js',
'django_select2/django_select2.js'
)
translation.activate('zh-hant')
assert tuple(Select2Widget().media._js) == (
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js',
f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/zh-TW.js',
'django_select2/django_select2.js'
)
@@ -141,8 +151,8 @@ class TestSelect2MixinSettings(object):
def test_default_media(self):
sut = Select2Widget()
result = sut.media.render()
assert '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/js/select2.min.js' in result
assert '//cdnjs.cloudflare.com/ajax/libs/select2/4.0.3/css/select2.min.css' in result
assert f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js' in result
assert f'//cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/css/select2.min.css' in result
assert 'django_select2/django_select2.js' in result
def test_js_setting(self, settings):
@@ -152,12 +162,24 @@ class TestSelect2MixinSettings(object):
assert 'alternate.js' in result
assert 'django_select2/django_select2.js' in result
def test_empty_js_setting(self, settings):
settings.SELECT2_JS = ''
sut = Select2Widget()
result = sut.media.render()
assert 'django_select2/django_select2.js' in result
def test_css_setting(self, settings):
settings.SELECT2_CSS = 'alternate.css'
sut = Select2Widget()
result = sut.media.render()
assert 'alternate.css' in result
def test_empty_css_setting(self, settings):
settings.SELECT2_CSS = ''
sut = Select2Widget()
result = sut.media.render()
assert '.css' not in result
class TestHeavySelect2Mixin(TestSelect2Mixin):
url = reverse('heavy_select2_widget')
@@ -353,6 +375,12 @@ class TestModelSelect2Mixin(TestHeavySelect2Mixin):
widget = ModelSelect2Widget(queryset=Genre.objects.all(), search_fields=['title__icontains'])
assert isinstance(widget.get_url(), text_type)
def test_custom_to_field_name(self):
the_best_band_in_the_world = Artist.objects.create(title='Take That')
groupie = Groupie.objects.create(obsession=the_best_band_in_the_world)
form = forms.GroupieForm(instance=groupie)
assert '<option value="Take That" selected>TAKE THAT</option>' in form.as_p()
class TestHeavySelect2TagWidget(TestHeavySelect2Mixin):
+9
View File
@@ -195,3 +195,12 @@ class AddressChainedSelect2WidgetForm(forms.Form):
max_results=500,
)
)
class GroupieForm(forms.ModelForm):
class Meta:
model = models.Groupie
fields = '__all__'
widgets = {
'obsession': ArtistCustomTitleWidget
}
+5 -1
View File
@@ -12,7 +12,7 @@ class Genre(models.Model):
class Artist(models.Model):
title = models.CharField(max_length=50)
title = models.CharField(max_length=50, unique=True)
genres = models.ManyToManyField(Genre)
class Meta:
@@ -56,3 +56,7 @@ class City(models.Model):
def __str__(self):
return self.name
class Groupie(models.Model):
obsession = models.ForeignKey(Artist, to_field='title', on_delete=models.CASCADE)