From d410f22254a0761f1af583ef4d0d80765279084e Mon Sep 17 00:00:00 2001 From: Kevin Ramirez Date: Thu, 3 Nov 2022 01:56:00 -0600 Subject: [PATCH 1/9] Fix #79 -- Clear values of related fields (#137) Co-authored-by: Kevin Ramirez --- django_select2/static/django_select2/django_select2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django_select2/static/django_select2/django_select2.js b/django_select2/static/django_select2/django_select2.js index 200534ba..f2358df0 100644 --- a/django_select2/static/django_select2/django_select2.js +++ b/django_select2/static/django_select2/django_select2.js @@ -59,7 +59,7 @@ } $element.on('select2:select', function (e) { var name = $(e.currentTarget).attr('name') - $('[data-select2-dependent-fields=' + name + ']').each(function () { + $('[data-select2-dependent-fields~=' + name + ']').each(function () { $(this).val('').trigger('change') }) }) From b568368cb68045db772a3166f1cf80804d096202 Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Thu, 3 Nov 2022 16:28:58 +0100 Subject: [PATCH 2/9] Add header image --- README.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 11f5f469..81cf3e09 100644 --- a/README.rst +++ b/README.rst @@ -1,12 +1,12 @@ +|header| + ============== Django-Select2 ============== |version| |coverage| |license| -This is a `Django`_ integration of `Select2`_. - -The app includes Select2 driven Django Widgets. +Custom autocompelte fields for `Django`_. Documentation ------------- @@ -22,6 +22,7 @@ Documentation available at https://django-select2.readthedocs.io/. .. _Select2: https://select2.org/ .. _autocomplete_fields: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields +.. |header| image:: https://repository-images.githubusercontent.com/266545281/c6db7d26-9f60-454b-845e-395d45c43fa7 .. |version| image:: https://img.shields.io/pypi/v/Django-Select2.svg :target: https://pypi.python.org/pypi/Django-Select2/ .. |coverage| image:: https://codecov.io/gh/codingjoe/django-select2/branch/master/graph/badge.svg From e62b8ea7e86aad3efd8f586f8836c3c28f8678ce Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Sat, 5 Nov 2022 12:04:55 +0100 Subject: [PATCH 3/9] Add selenium test marker to stablize CI suite --- .github/workflows/ci.yml | 4 ++-- tests/conftest.py | 5 +++++ tests/test_forms.py | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff5413ab..ddd59a2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: python-version: ${{ matrix.python-version }} - run: python -m pip install Django~="${{ matrix.django-version }}.0" - run: python -m pip install -e .[test] - - run: python -m pytest + - run: python -m pytest -m "not selenium" - uses: codecov/codecov-action@v3 Selenium: @@ -85,5 +85,5 @@ jobs: python-version: ${{ matrix.python-version }} - run: python -m pip install Django - run: python -m pip install -e .[test,selenium] - - run: python -m pytest + - run: python -m pytest -m selenium - uses: codecov/codecov-action@v3 diff --git a/tests/conftest.py b/tests/conftest.py index b5e37416..3ac31e51 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,11 @@ from selenium.common.exceptions import WebDriverException +def pytest_configure(config): + config.addinivalue_line( + "markers", "selenium: skip if selenium is not installed" + ) + def random_string(n): return "".join( random.choice(string.ascii_uppercase + string.digits) for _ in range(n) diff --git a/tests/test_forms.py b/tests/test_forms.py index 70a89d55..6e81f50b 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -82,12 +82,14 @@ def test_allow_clear(self, db): "primary_genre", None ) + @pytest.mark.selenium def test_no_js_error(self, db, live_server, driver): driver.get(live_server + self.url) with pytest.raises(NoSuchElementException): error = driver.find_element(By.XPATH, "//body[@JSError]") pytest.fail(error.get_attribute("JSError")) + @pytest.mark.selenium def test_selecting(self, db, live_server, driver): driver.get(live_server + self.url) with pytest.raises(NoSuchElementException): @@ -298,6 +300,7 @@ def test_many_selected_option(self, db, genres): ), widget_output assert selected_option2 in widget_output or selected_option2a in widget_output + @pytest.mark.selenium def test_multiple_widgets(self, db, live_server, driver): driver.get(live_server + self.url) with pytest.raises(NoSuchElementException): @@ -641,6 +644,7 @@ class TestHeavySelect2MultipleWidget: bool(os.environ.get("CI", False)), reason="https://bugs.chromium.org/p/chromedriver/issues/detail?id=1772", ) + @pytest.mark.selenium def test_widgets_selected_after_validation_error(self, db, live_server, driver): driver.get(live_server + self.url) WebDriverWait(driver, 3).until( @@ -678,6 +682,7 @@ class TestAddressChainedSelect2Widget: url = reverse("model_chained_select2_widget") form = forms.AddressChainedSelect2WidgetForm() + @pytest.mark.selenium def test_widgets_selected_after_validation_error( self, db, live_server, driver, countries, cities ): @@ -757,6 +762,7 @@ def test_widgets_selected_after_validation_error( assert len(country_names_from_browser) != Country.objects.count() assert country_names_from_browser == country_names_from_db + @pytest.mark.selenium def test_dependent_fields_clear_after_change_parent( self, db, live_server, driver, countries, cities ): From cc834af3934488c1e98f8fc483835f00b5d10e9d Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Sat, 5 Nov 2022 12:08:58 +0100 Subject: [PATCH 4/9] Repleace fussyfox with GH actions --- .fussyfox.yml | 5 ----- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ linter-requirements.txt | 5 +++++ 3 files changed, 27 insertions(+), 5 deletions(-) delete mode 100644 .fussyfox.yml create mode 100644 linter-requirements.txt diff --git a/.fussyfox.yml b/.fussyfox.yml deleted file mode 100644 index 6a17e6bd..00000000 --- a/.fussyfox.yml +++ /dev/null @@ -1,5 +0,0 @@ -- bandit -- black -- flake8 -- isort -- pydocstyle diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ddd59a2e..042d813a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,26 @@ on: jobs: + lint: + runs-on: ubuntu-latest + strategy: + matrix: + lint-command: + - bandit -r . -x ./tests + - black --check --diff . + - flake8 . + - isort --check-only --diff . + - pydocstyle . + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.x" + cache: 'pip' + cache-dependency-path: 'linter-requirements.txt' + - run: python -m pip install -r linter-requirements.txt + - run: ${{ matrix.lint-command }} + dist: runs-on: ubuntu-latest steps: @@ -41,6 +61,7 @@ jobs: PyTest: needs: + - lint - standardjs strategy: matrix: @@ -65,6 +86,7 @@ jobs: Selenium: needs: + - lint - standardjs strategy: matrix: diff --git a/linter-requirements.txt b/linter-requirements.txt new file mode 100644 index 00000000..c3083bcb --- /dev/null +++ b/linter-requirements.txt @@ -0,0 +1,5 @@ +bandit==1.7.4 +black==22.10.0 +flake8==5.0.4 +isort==5.10.1 +pydocstyle[toml]==6.1.1 From 5894b52ea7303fde4fb58c23172f5240d89a2aef Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Sat, 5 Nov 2022 12:10:00 +0100 Subject: [PATCH 5/9] Reformat code using black & isort --- django_select2/conf.py | 2 +- django_select2/views.py | 4 ++-- tests/conftest.py | 5 ++--- tests/test_forms.py | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/django_select2/conf.py b/django_select2/conf.py index a3117014..03ebe7dd 100644 --- a/django_select2/conf.py +++ b/django_select2/conf.py @@ -198,7 +198,7 @@ class Select2Conf(AppConf): ``settings.DJANGO_SELECT2_I18N`` refers to :attr:`.I18N_PATH`. """ - JSON_ENCODER = 'django.core.serializers.json.DjangoJSONEncoder' + JSON_ENCODER = "django.core.serializers.json.DjangoJSONEncoder" """ A :class:`JSONEncoder` used to generate the API response for the model widgets. diff --git a/django_select2/views.py b/django_select2/views.py index 0af69b64..1665dbd5 100644 --- a/django_select2/views.py +++ b/django_select2/views.py @@ -2,8 +2,8 @@ from django.core import signing from django.core.signing import BadSignature from django.http import Http404, JsonResponse -from django.views.generic.list import BaseListView from django.utils.module_loading import import_string +from django.views.generic.list import BaseListView from .cache import cache from .conf import settings @@ -45,7 +45,7 @@ def get(self, request, *args, **kwargs): ], "more": context["page_obj"].has_next(), }, - encoder=import_string(settings.SELECT2_JSON_ENCODER) + encoder=import_string(settings.SELECT2_JSON_ENCODER), ) def get_queryset(self): diff --git a/tests/conftest.py b/tests/conftest.py index 3ac31e51..60bbe399 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,9 +7,8 @@ def pytest_configure(config): - config.addinivalue_line( - "markers", "selenium: skip if selenium is not installed" - ) + config.addinivalue_line("markers", "selenium: skip if selenium is not installed") + def random_string(n): return "".join( diff --git a/tests/test_forms.py b/tests/test_forms.py index 6e81f50b..621a24e1 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -20,8 +20,8 @@ HeavySelect2Widget, ModelSelect2TagWidget, ModelSelect2Widget, - Select2Widget, Select2AdminMixin, + Select2Widget, ) from tests.testapp import forms from tests.testapp.forms import ( From 10d5e8d681fc0bd38b3520e3f51bbd7fb9eeafac Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Sat, 5 Nov 2022 11:50:59 +0100 Subject: [PATCH 6/9] Fix #163 -- Lazily get i18n locale name A widget instance can leak the i18n setting to another request or be stuck on the default language setting. --- django_select2/forms.py | 7 ++++--- tests/test_forms.py | 25 ++++++++++++++----------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/django_select2/forms.py b/django_select2/forms.py index df3982fe..5e2ea5fd 100644 --- a/django_select2/forms.py +++ b/django_select2/forms.py @@ -86,9 +86,10 @@ class Select2Mixin: empty_label = "" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.i18n_name = SELECT2_TRANSLATIONS.get(get_language()) + @property + def i18n_name(self): + """Name of the i18n file for the current language.""" + return SELECT2_TRANSLATIONS.get(get_language()) def build_attrs(self, base_attrs, extra_attrs=None): """Add select2 data attributes.""" diff --git a/tests/test_forms.py b/tests/test_forms.py index 621a24e1..4e148f7a 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -3,7 +3,6 @@ from collections.abc import Iterable import pytest -from django.contrib.admin.widgets import SELECT2_TRANSLATIONS from django.db.models import QuerySet from django.urls import reverse from django.utils import translation @@ -48,11 +47,16 @@ def test_initial_form_class(self): assert "my-class" in widget.render("name", None) assert "django-select2" in widget.render("name", None) - @pytest.mark.parametrize("code,name", SELECT2_TRANSLATIONS.items()) - def test_lang_attr(self, code, name): - translation.activate(code) - widget = self.widget_cls() - assert f'lang="{name}"' in widget.render("name", None) + def test_lang_attr(self): + with translation.override("de"): + widget = Select2Widget() + assert 'lang="de"' in widget.render("name", None) + + # Regression test for #163 + widget = Select2Widget() + assert widget.i18n_name == "en" + with translation.override("de"): + assert widget.i18n_name == "de" def test_allow_clear(self, db): required_field = self.form.fields["artist"] @@ -258,11 +262,10 @@ def test_initial_form_class(self): "name", None ) - @pytest.mark.parametrize("code,name", SELECT2_TRANSLATIONS.items()) - def test_lang_attr(self, code, name): - translation.activate(code) - widget = self.widget_cls(data_view="heavy_data_1") - assert f'lang="{name}"' in widget.render("name", None) + def test_lang_attr(self): + with translation.override("fr"): + widget = self.widget_cls(data_view="heavy_data_1") + assert 'lang="fr"' in widget.render("name", None) def test_selected_option(self, db): not_required_field = self.form.fields["primary_genre"] From 15790a3dd54f66cd03879497f5016052cf4da9b1 Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Sat, 5 Nov 2022 13:38:41 +0100 Subject: [PATCH 7/9] Update packaging to pyproject.toml --- .editorconfig | 2 +- .github/workflows/ci.yml | 4 +- .github/workflows/release.yml | 1 + .gitignore | 2 + LICENSE | 2 +- django_select2/__init__.py | 5 ++ pyproject.toml | 89 +++++++++++++++++++++++++++++++++++ set_version.py | 5 +- setup.cfg | 85 +-------------------------------- setup.py | 5 -- 10 files changed, 105 insertions(+), 95 deletions(-) create mode 100644 pyproject.toml delete mode 100755 setup.py diff --git a/.editorconfig b/.editorconfig index f69b8ec1..d7e0de91 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,7 +10,7 @@ insert_final_newline = true charset = utf-8 end_of_line = lf -[*.{json,yml,yaml,js,jsx}] +[*.{json,yml,yaml,js,jsx,toml}] indent_size = 2 [LICENSE] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 042d813a..bc44000f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,8 +34,8 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.x" - - run: python -m pip install --upgrade pip setuptools wheel twine readme-renderer - - run: python setup.py sdist bdist_wheel + - run: python -m pip install --upgrade pip build wheel twine readme-renderer + - run: python -m build --sdist --wheel - run: python -m twine check dist/* standardjs: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f8e3b587..f6548af4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,6 +28,7 @@ jobs: - uses: actions/setup-python@v4 with: python-version: "3.x" + - run: python -m pip install --upgrade setuptools_scm - run: python set_version.py - name: Upload packages run: npm publish diff --git a/.gitignore b/.gitignore index d1ef58f6..8c43d48c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,5 @@ ghostdriver.log coverage.xml .eggs/ db.sqlite3 + +_version.py diff --git a/LICENSE b/LICENSE index d5d8ec80..3f28e2b7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Johannes Hoppe +Copyright (c) 2022 Johannes Maron Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/django_select2/__init__.py b/django_select2/__init__.py index 575a0213..95783ce7 100644 --- a/django_select2/__init__.py +++ b/django_select2/__init__.py @@ -9,5 +9,10 @@ """ from django import get_version +from . import _version + +__version__ = _version.version +VERSION = _version.version_tuple + if get_version() < "3.2": default_app_config = "django_select2.apps.Select2AppConfig" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..1d4fb2f5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,89 @@ +[build-system] +requires = ["flit_core>=3.2", "flit_scm", "wheel"] +build-backend = "flit_scm:buildapi" + +[project] +name = "django-select2" +authors = [ + { name = "Johannes Maron", email = "johannes@maron.family" }, +] +readme = "README.rst" +license = { file = "LICENSE" } +keywords = ["Django", "select2", "autocomplete", "typeahead"] +dynamic = ["version", "description"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Environment :: Web Environment", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Framework :: Django", + "Framework :: Django :: 3.2", + "Framework :: Django :: 4.0", + "Framework :: Django :: 4.1", + "Topic :: Software Development", +] +requires-python = ">=3.8" +dependencies = [ + "django>=3.2", + "django-appconf>=0.6.0" +] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-cov", + "pytest-django", + "selenium", +] +selenium = [ + "selenium", +] +docs = [ + "sphinx", +] + +[project.urls] +Project-URL = "https://github.com/codingjoe/django-select2" +Changelog = "https://github.com/codingjoe/django-select2/releases" +Source = "https://github.com/codingjoe/django-select2" +Documentation = "https://django-select2.rtfd.io/" +Issue-Tracker = "https://github.com/codingjoe/django-select2/issues" + +[tool.flit.module] +name = "django_select2" + +[tool.setuptools_scm] +write_to = "django_select2/_version.py" + +[tool.pytest.ini_options] +minversion = "6.0" +addopts = "--cov --tb=short -rxs" +testpaths = ["tests"] +DJANGO_SETTINGS_MODULE = "tests.testapp.settings" + +[tool.coverage.run] +source = ["django_select2"] + +[tool.coverage.report] +show_missing = true + +[tool.isort] +atomic = true +line_length = 88 +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +known_first_party = "django_select2, tests" +default_section = "THIRDPARTY" +combine_as_imports = true + +[tool.pydocstyle] +add_ignore = "D1" diff --git a/set_version.py b/set_version.py index 50ffa7bf..fa18ce33 100755 --- a/set_version.py +++ b/set_version.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 """Set the version in NPM's package.json to match the git tag.""" import json -import os + +from setuptools_scm import get_version if __name__ == "__main__": with open("package.json", "r+") as f: data = json.load(f) f.seek(0) - data["version"] = os.environ["GITHUB_REF"].rsplit("/")[-1] + data["version"] = get_version(root=".", relative_to=__file__) json.dump(data, f) f.truncate() diff --git a/setup.cfg b/setup.cfg index f02b95dc..6f60592c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,87 +1,4 @@ -[metadata] -name = django-select2 -author = Johannes Hoppe -author_email = info@johanneshoppe.com -description = Select2 option fields for Django -long_description = file: README.rst -url = https://github.com/codingjoe/django-select2 -license = MIT -license_file = LICENSE -classifier = - Development Status :: 5 - Production/Stable - Environment :: Web Environment - Intended Audience :: Developers - License :: OSI Approved :: Apache Software License - Operating System :: OS Independent - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3 :: Only - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - Framework :: Django - Framework :: Django :: 3.2 - Framework :: Django :: 4.0 - Framework :: Django :: 4.1 - -[options] -include_package_data = True -packages = django_select2 -install_requires = - django>=3.2 - django-appconf>=0.6.0 -setup_requires = - setuptools_scm - -[options.extras_require] -test = - pytest - pytest-cov - pytest-django - selenium -selenium = - selenium -docs = - sphinx - -[bdist_wheel] -universal = 1 - -[bdist_rpm] -requires = - python-django-appconf >= 2.0 - -[aliases] -test = pytest - -[build_sphinx] -source-dir = docs -build-dir = docs/_build - -[tool:pytest] -addopts = - tests - --doctest-glob='*.rst' - --doctest-modules - --cov=django_select2 -DJANGO_SETTINGS_MODULE=tests.testapp.settings - [flake8] max-line-length=88 select = C,E,F,W,B,B950 -ignore = E203, E501, W503 -exclude = venv,.tox,.eggs - -[pydocstyle] -add-ignore = D1 - -[isort] -atomic = true -line_length = 88 -multi_line_output = 3 -include_trailing_comma = True -force_grid_wrap = 0 -use_parentheses = True -known_first_party = django_select2, tests -default_section = THIRDPARTY -combine_as_imports = true +ignore = E203, E501, W503, E731 diff --git a/setup.py b/setup.py deleted file mode 100755 index f4bd8e57..00000000 --- a/setup.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python - -from setuptools import setup - -setup(name="django-select2", use_scm_version=True) From 3781b67ffd4128585bcf99312f22c92e42d3e04c Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Sat, 5 Nov 2022 14:24:59 +0100 Subject: [PATCH 8/9] Fix Select2AdminMixin --- django_select2/forms.py | 5 +++-- tests/test_forms.py | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/django_select2/forms.py b/django_select2/forms.py index 5e2ea5fd..68be213f 100644 --- a/django_select2/forms.py +++ b/django_select2/forms.py @@ -146,14 +146,15 @@ def media(self): class Select2AdminMixin: """Select2 mixin that uses Django's own select template.""" - css_class_name = "admin-autocomplete" theme = "admin-autocomplete" @property def media(self): + css = {**AutocompleteMixin(None, None).media._css} + css["screen"].append("django_select2/django_select2.css") return forms.Media( js=Select2Mixin().media._js, - css=AutocompleteMixin(None, None).media._css, + css=css, ) diff --git a/tests/test_forms.py b/tests/test_forms.py index 4e148f7a..0b7c0af7 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -195,6 +195,7 @@ def test_media(self): "screen": [ "admin/css/vendor/select2/select2.min.css", "admin/css/autocomplete.css", + "django_select2/django_select2.css", ] } From 112846cc76fa137a3946a2e71425deb9a561aa58 Mon Sep 17 00:00:00 2001 From: Johannes Maron Date: Sat, 5 Nov 2022 17:42:11 +0100 Subject: [PATCH 9/9] Change default Select2 version to Django's vendored version --- django_select2/conf.py | 75 +++---------------- django_select2/forms.py | 4 +- .../static/django_select2/django_select2.js | 2 +- django_select2/views.py | 2 +- docs/extra.rst | 8 +- docs/index.rst | 18 ++++- example/example/settings.py | 6 +- .../example/templates/example/book_form.html | 4 +- example/requirements.txt | 2 +- tests/test_forms.py | 39 ++++------ tests/testapp/settings.py | 1 + tests/testapp/templates/form.html | 7 +- 12 files changed, 56 insertions(+), 112 deletions(-) diff --git a/django_select2/conf.py b/django_select2/conf.py index 03ebe7dd..0f34f774 100644 --- a/django_select2/conf.py +++ b/django_select2/conf.py @@ -4,13 +4,12 @@ __all__ = ("settings", "Select2Conf") +from django.contrib.admin.widgets import SELECT2_TRANSLATIONS + class Select2Conf(AppConf): """Settings for Django-Select2.""" - LIB_VERSION = "4.0.12" - """Version of the Select2 library.""" - CACHE_BACKEND = "default" """ Django-Select2 uses Django's cache to sure a consistent state across multiple machines. @@ -56,11 +55,9 @@ class Select2Conf(AppConf): It has set `select2_` as a default value, which you can change if needed. """ - JS = "https://cdnjs.cloudflare.com/ajax/libs/select2/{version}/js/select2.min.js".format( - version=LIB_VERSION - ) + JS = "admin/js/vendor/select2/select2.full.min.js" """ - The URI for the Select2 JS file. By default this points to the Cloudflare CDN. + The URI for the Select2 JS file. By default this points to version shipped with Django. If you want to select the version of the JS library used, or want to serve it from the local 'static' resources, add a line to your settings.py like so:: @@ -76,11 +73,9 @@ class Select2Conf(AppConf): develop without an Internet connection. """ - CSS = "https://cdnjs.cloudflare.com/ajax/libs/select2/{version}/css/select2.min.css".format( - version=LIB_VERSION - ) + CSS = "admin/css/vendor/select2/select2.min.css" """ - The URI for the Select2 CSS file. By default this points to the Cloudflare CDN. + The URI for the Select2 CSS file. By default this points to version shipped with Django. If you want to select the version of the library used, or want to serve it from the local 'static' resources, add a line to your settings.py like so:: @@ -112,13 +107,9 @@ class Select2Conf(AppConf): .. tip:: When using other themes, you may need use select2 css and theme css. """ - I18N_PATH = ( - "https://cdnjs.cloudflare.com/ajax/libs/select2/{version}/js/i18n".format( - version=LIB_VERSION - ) - ) + I18N_PATH = "admin/js/vendor/select2/i18n" """ - The base URI for the Select2 i18n files. By default this points to the Cloudflare CDN. + The base URI for the Select2 i18n files. By default this points to version shipped with Django. If you want to select the version of the I18N library used, or want to serve it from the local 'static' resources, add a line to your settings.py like so:: @@ -129,55 +120,7 @@ class Select2Conf(AppConf): develop without an Internet connection. """ - I18N_AVAILABLE_LANGUAGES = [ - "ar", - "az", - "bg", - "ca", - "cs", - "da", - "de", - "el", - "en", - "es", - "et", - "eu", - "fa", - "fi", - "fr", - "gl", - "he", - "hi", - "hr", - "hu", - "id", - "is", - "it", - "ja", - "km", - "ko", - "lt", - "lv", - "mk", - "ms", - "nb", - "nl", - "pl", - "pt-BR", - "pt", - "ro", - "ru", - "sk", - "sr-Cyrl", - "sr", - "sv", - "th", - "tr", - "uk", - "vi", - "zh-CN", - "zh-TW", - ] + I18N_AVAILABLE_LANGUAGES = list(SELECT2_TRANSLATIONS.values()) """ List of available translations. diff --git a/django_select2/forms.py b/django_select2/forms.py index 68be213f..4809d066 100644 --- a/django_select2/forms.py +++ b/django_select2/forms.py @@ -16,7 +16,7 @@ have to be pre-rendered onto the page and JavaScript would be used to search through them. Said that, they are also one - the most easiest to use. They are a + the easiest to use. They are a drop-in-replacement for Django's default select widgets. @@ -293,7 +293,7 @@ def render(self, *args, **kwargs): return output def _get_cache_key(self): - return "%s%s" % (settings.SELECT2_CACHE_PREFIX, self.uuid) + return f"{settings.SELECT2_CACHE_PREFIX}{self.uuid}" def set_to_cache(self): """ diff --git a/django_select2/static/django_select2/django_select2.js b/django_select2/static/django_select2/django_select2.js index f2358df0..01db20b5 100644 --- a/django_select2/static/django_select2/django_select2.js +++ b/django_select2/static/django_select2/django_select2.js @@ -6,7 +6,7 @@ module.exports = factory(require('jquery')) } else { // Browser globals - factory(jQuery) + factory(jQuery || window.django.jQuery) } }(function ($) { 'use strict' diff --git a/django_select2/views.py b/django_select2/views.py index 1665dbd5..66fdc420 100644 --- a/django_select2/views.py +++ b/django_select2/views.py @@ -92,7 +92,7 @@ def get_widget_or_404(self): except BadSignature: raise Http404('Invalid "field_id".') else: - cache_key = "%s%s" % (settings.SELECT2_CACHE_PREFIX, key) + cache_key = f"{settings.SELECT2_CACHE_PREFIX}{key}" widget_dict = cache.get(cache_key) if widget_dict is None: raise Http404("field_id not found") diff --git a/docs/extra.rst b/docs/extra.rst index 0865d77c..4de25275 100644 --- a/docs/extra.rst +++ b/docs/extra.rst @@ -39,7 +39,7 @@ the field in the form. The value represents the name of the field in the model ( class AddressForm(forms.Form): country = forms.ModelChoiceField( queryset=Country.objects.all(), - label=u"Country", + label="Country", widget=ModelSelect2Widget( model=Country, search_fields=['name__icontains'], @@ -48,7 +48,7 @@ the field in the form. The value represents the name of the field in the model ( city = forms.ModelChoiceField( queryset=City.objects.all(), - label=u"City", + label="City", widget=ModelSelect2Widget( model=City, search_fields=['name__icontains'], @@ -72,7 +72,7 @@ Customize the form in a manner: class AddressForm(forms.Form): country = forms.ModelChoiceField( queryset=Country.objects.all(), - label=u"Country", + label="Country", widget=ModelSelect2Widget( search_fields=['name__icontains'], dependent_fields={'city': 'cities'}, @@ -81,7 +81,7 @@ Customize the form in a manner: city = forms.ModelChoiceField( queryset=City.objects.all(), - label=u"City", + label="City", widget=ModelSelect2Widget( search_fields=['name__icontains'], dependent_fields={'country': 'country'}, diff --git a/docs/index.rst b/docs/index.rst index 6ba1f8f3..35454f9c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,6 +8,16 @@ Install ``django-select2``:: python3 -m pip install django-select2 Add ``django_select2`` to your ``INSTALLED_APPS`` in your project settings. +Since version 8, please ensure that Django's admin app is enabled too: + +.. code-block:: python + + INSTALLED_APPS = [ + # other django apps… + 'django.contrib.admin', + # other 3rd party apps… + 'django_select2', + ] Add ``django_select`` to your URL root configuration: @@ -16,20 +26,20 @@ Add ``django_select`` to your URL root configuration: from django.urls import include, path urlpatterns = [ - # … other patterns + # other patterns… path("select2/", include("django_select2.urls")), - # … other patterns + # other patterns… ] ``django-select2`` requires a cache backend which is **persistent** -across all application servers.. +across all application servers.. **This means that the** :class:`.DummyCache` **backend will not work!** The default cache backend is :class:`.LocMemCache`, which is persistent across a single node. For projects with a single application server -this will work fine, however you will run into issues when +this will work fine, however you will run into issues when you scale up into multiple servers. Below is an example setup using Redis, which is a solution that diff --git a/example/example/settings.py b/example/example/settings.py index 7e629a6e..6eef77a3 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -117,14 +117,12 @@ CACHES = { "default": { - "BACKEND": "django_redis.cache.RedisCache", + "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, }, "select2": { - "BACKEND": "django_redis.cache.RedisCache", + "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": "redis://127.0.0.1:6379/2", - "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"}, }, } diff --git a/example/example/templates/example/book_form.html b/example/example/templates/example/book_form.html index c2ff60bf..daea5304 100644 --- a/example/example/templates/example/book_form.html +++ b/example/example/templates/example/book_form.html @@ -1,4 +1,4 @@ - +{% load static %} Create Book @@ -14,7 +14,7 @@

Create a new Book

{{ form.as_p }} - + {{ form.media.js }} diff --git a/example/requirements.txt b/example/requirements.txt index 53500ba2..b93550d1 100644 --- a/example/requirements.txt +++ b/example/requirements.txt @@ -1,2 +1,2 @@ -e .. -django-redis +redis diff --git a/tests/test_forms.py b/tests/test_forms.py index 0b7c0af7..a3a0d8b5 100644 --- a/tests/test_forms.py +++ b/tests/test_forms.py @@ -13,7 +13,6 @@ 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, @@ -135,28 +134,28 @@ def test_empty_option(self, db): def test_i18n(self): translation.activate("de") assert tuple(Select2Widget().media._js) == ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js", - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/de.js", + "admin/js/vendor/select2/select2.full.min.js", + "admin/js/vendor/select2/i18n/de.js", "django_select2/django_select2.js", ) translation.activate("en") assert tuple(Select2Widget().media._js) == ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js", - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/en.js", + "admin/js/vendor/select2/select2.full.min.js", + "admin/js/vendor/select2/i18n/en.js", "django_select2/django_select2.js", ) translation.activate("00") assert tuple(Select2Widget().media._js) == ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js", + "admin/js/vendor/select2/select2.full.min.js", "django_select2/django_select2.js", ) - translation.activate("sr-cyrl") + translation.activate("sr-Cyrl") assert tuple(Select2Widget().media._js) == ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js", - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/sr-Cyrl.js", + "admin/js/vendor/select2/select2.full.min.js", + "admin/js/vendor/select2/i18n/sr-Cyrl.js", "django_select2/django_select2.js", ) @@ -164,15 +163,15 @@ def test_i18n(self): translation.activate("zh-hans") assert tuple(Select2Widget().media._js) == ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js", - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/zh-CN.js", + "admin/js/vendor/select2/select2.full.min.js", + "admin/js/vendor/select2/i18n/zh-CN.js", "django_select2/django_select2.js", ) translation.activate("zh-hant") assert tuple(Select2Widget().media._js) == ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js", - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/zh-TW.js", + "admin/js/vendor/select2/select2.full.min.js", + "admin/js/vendor/select2/i18n/zh-TW.js", "django_select2/django_select2.js", ) @@ -186,8 +185,8 @@ class TestSelect2AdminMixin: def test_media(self): translation.activate("en") assert tuple(Select2AdminMixin().media._js) == ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js", - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/i18n/en.js", + "admin/js/vendor/select2/select2.full.min.js", + "admin/js/vendor/select2/i18n/en.js", "django_select2/django_select2.js", ) @@ -204,14 +203,8 @@ class TestSelect2MixinSettings: def test_default_media(self): sut = Select2Widget() result = sut.media.render() - assert ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/js/select2.min.js" - in result - ) - assert ( - f"https://cdnjs.cloudflare.com/ajax/libs/select2/{settings.SELECT2_LIB_VERSION}/css/select2.min.css" - in result - ) + assert "admin/js/vendor/select2/select2.full.min.js" in result + assert "admin/css/vendor/select2/select2.min.css" in result assert "django_select2/django_select2.js" in result def test_js_setting(self, settings): diff --git a/tests/testapp/settings.py b/tests/testapp/settings.py index 15b29b59..f6673aa9 100644 --- a/tests/testapp/settings.py +++ b/tests/testapp/settings.py @@ -12,6 +12,7 @@ "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.staticfiles", + "django.contrib.admin", "django_select2", "tests.testapp", ) diff --git a/tests/testapp/templates/form.html b/tests/testapp/templates/form.html index 7499bc8e..bd817eb2 100644 --- a/tests/testapp/templates/form.html +++ b/tests/testapp/templates/form.html @@ -1,6 +1,5 @@ -{% load static %} - - +{% load static %} + {{ form.media.css }}