diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 01f6a5e7..a5660596 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -39,6 +39,10 @@ repos:
rev: v0.20.0
hooks:
- id: yamlfmt
+ - repo: https://github.com/sphinx-contrib/sphinx-lint
+ rev: v1.0.2
+ hooks:
+ - id: sphinx-lint
ci:
autoupdate_schedule: weekly
skip:
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 67782599..5273a7b3 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -1,26 +1,34 @@
-Contributing
-============
+##############
+ Contributing
+##############
-Before you start editing the python code, you will need to make sure
-you have binary dependencies installed::
+Before you start editing the python code, you will need to make sure you
+have binary dependencies installed:
- # Debian
- sudo apt install -y gettext graphviz google-chrome-stable
- # macOS
- brew install -y gettext graphviz google-chrome-stable
+.. code::
-You may run the tests via::
+ # Debian
+ sudo apt install -y gettext graphviz google-chrome-stable
+ # macOS
+ brew install -y gettext graphviz google-chrome-stable
- uv run pytest
+You may run the tests via:
-Documentation pull requests welcome. The Sphinx documentation can be compiled via::
+.. code::
- uv run sphinx-build -W -b doctest -b html docs docs/_build/html
+ uv run pytest
-Bug reports welcome, even more so if they include a correct patch. Much
-more so if you start your patch by adding a failing unit test, and correct
-the code until zero unit tests fail.
+Documentation pull requests welcome. The Sphinx documentation can be
+compiled via:
-The list of supported Django and Python version can be found in the CI suite setup.
-Please make sure to verify that none of the linters or tests failed, before you submit
-a patch for review.
+.. code::
+
+ uv run sphinx-build -W -b doctest -b html docs docs/_build/html
+
+Bug reports welcome, even more so if they include a correct patch. Much
+more so if you start your patch by adding a failing unit test, and
+correct the code until zero unit tests fail.
+
+The list of supported Django and Python version can be found in the CI
+suite setup. Please make sure to verify that none of the linters or
+tests failed, before you submit a patch for review.
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 371ff035..00000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,4 +0,0 @@
-include django_select2/static/django_select2/django_select2.js
-include django_select2/static/django_select2/django_select2.css
-prune .github
-exclude .*
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..6161da6c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,24 @@
+
+
+# Django-Select2
+
+[](https://pypi.python.org/pypi/Django-Select2/)
+[](https://codecov.io/gh/codingjoe/django-select2)
+[](https://raw.githubusercontent.com/codingjoe/django-select2/main/LICENSE.txt)
+
+Custom autocomplete fields for [Django](https://www.djangoproject.com/).
+
+## Documentation
+
+Documentation available at .
+
+> [!NOTE]
+> Django's admin comes with builtin support for Select2 via the [autocomplete_fields](https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields) feature.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index 93d87344..00000000
--- a/README.rst
+++ /dev/null
@@ -1,31 +0,0 @@
-|header|
-
-==============
-Django-Select2
-==============
-
-|version| |coverage| |license|
-
-Custom autocomplete fields for `Django`_.
-
-Documentation
--------------
-
-Documentation available at https://django-select2.readthedocs.io/.
-
-.. note::
- Django's admin comes with builtin support for Select2
- via the `autocomplete_fields`_ feature.
-
-
-.. _Django: https://www.djangoproject.com/
-.. _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
- :target: https://codecov.io/gh/codingjoe/django-select2
-.. |license| image:: https://img.shields.io/badge/license-APL2-blue.svg
- :target: https://raw.githubusercontent.com/codingjoe/django-select2/master/LICENSE.txt
diff --git a/SECURITY.md b/SECURITY.md
deleted file mode 100644
index b0208c5f..00000000
--- a/SECURITY.md
+++ /dev/null
@@ -1,7 +0,0 @@
-# Security
-
-## Security contact information
-
-To report a security vulnerability, please use the
-[Tidelift security contact](https://tidelift.com/security).
-Tidelift will coordinate the fix and disclosure.
diff --git a/docs/conf.py b/docs/conf.py
index 1dc16c21..9d783b4d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -24,6 +24,7 @@
version = ".".join(release.split(".")[:2])
master_doc = "index" # default in Sphinx v2
+html_theme = "furo"
extensions = [
diff --git a/docs/django_select2.rst b/docs/django_select2.rst
index 512739b1..1cb43e39 100644
--- a/docs/django_select2.rst
+++ b/docs/django_select2.rst
@@ -1,124 +1,140 @@
-API Documentation
-=================
+###################
+ API Documentation
+###################
-Configuration
--------------
+***************
+ Configuration
+***************
.. automodule:: django_select2.conf
- :members:
- :undoc-members:
- :show-inheritance:
+ :members:
+ :undoc-members:
+ :show-inheritance:
-Widgets
--------
+*********
+ Widgets
+*********
.. automodule:: django_select2.forms
- :members:
- :undoc-members:
- :show-inheritance:
+ :members:
+ :undoc-members:
+ :show-inheritance:
-URLs
-----
+******
+ URLs
+******
.. automodule:: django_select2.urls
- :members:
- :undoc-members:
- :show-inheritance:
+ :members:
+ :undoc-members:
+ :show-inheritance:
-Views
------
+*******
+ Views
+*******
.. automodule:: django_select2.views
- :members:
- :undoc-members:
- :show-inheritance:
+ :members:
+ :undoc-members:
+ :show-inheritance:
-Cache
------
+*******
+ Cache
+*******
.. automodule:: django_select2.cache
- :members:
- :undoc-members:
- :show-inheritance:
+ :members:
+ :undoc-members:
+ :show-inheritance:
+************
+ JavaScript
+************
-JavaScript
-----------
+DjangoSelect2 handles the initialization of select2 fields
+automatically. Just include ``{{ form.media.js }}`` in your template
+before the closing ``body`` tag. That's it!
-DjangoSelect2 handles the initialization of select2 fields automatically. Just include
-``{{ form.media.js }}`` in your template before the closing ``body`` tag. That's it!
+If you insert forms after page load or if you want to handle the
+initialization yourself, DjangoSelect2 provides a jQuery plugin,
+replacing and enhancing the Select2 plugin. It will handle both normal
+and heavy fields. Simply call ``djangoSelect2(options)`` on your select
+fields.:
-If you insert forms after page load or if you want to handle the initialization
-yourself, DjangoSelect2 provides a jQuery plugin, replacing and enhancing the Select2
-plugin. It will handle both normal and heavy fields. Simply call
-``djangoSelect2(options)`` on your select fields.::
+.. code::
- $('.django-select2').djangoSelect2();
+ $('.django-select2').djangoSelect2();
Please replace all your ``.select2`` invocations with the here provided
``.djangoSelect2``.
+*********************
+ Configuring Select2
+*********************
-Configuring Select2
--------------------
-
-Select2 options can be configured either directly from Javascript or from within Django
-using widget attributes. `(List of options in the Select2 docs) `_.
+Select2 options can be configured either directly from Javascript or
+from within Django using widget attributes. `(List of options in the
+Select2 docs) `_.
To pass options in javascript
-.. code-block:: javascript
-
- $('.django-select2').djangoSelect2({
- minimumInputLength: 0,
- placeholder: 'Select an option',
- });
-
-From Django, you can use ``data-`` attributes using the same names in camel-case and
-passing them to your widget. Select2 will then pick these up. For example when
-initialising a widget in a form, you could do:
-
-.. code-block:: python
-
- class MyForm(forms.Form):
- my_field = forms.ModelMultipleChoiceField(
- widget=ModelSelect2MultipleWidget(
- model=MyModel
- search_fields=['another_field']
- attrs={
- "data-minimum-input-length": 0,
- "data-placeholder": "Select an option",
- "data-close-on-select": "false",
- }
- )
- )
-
-(If you do not want to initialize the widget, you could add the attributes by overriding
-a widget method and adding them in a super call, e.g. `get_context() `_)
-
-
-Security & Authentication
--------------------------
+.. code:: javascript
+
+ $('.django-select2').djangoSelect2({
+ minimumInputLength: 0,
+ placeholder: 'Select an option',
+ });
+
+From Django, you can use ``data-`` attributes using the same names in
+camel-case and passing them to your widget. Select2 will then pick these
+up. For example when initialising a widget in a form, you could do:
+
+.. code:: python
+
+ class MyForm(forms.Form):
+ my_field = forms.ModelMultipleChoiceField(
+ widget=ModelSelect2MultipleWidget(
+ model=MyModel
+ search_fields=['another_field']
+ attrs={
+ "data-minimum-input-length": 0,
+ "data-placeholder": "Select an option",
+ "data-close-on-select": "false",
+ }
+ )
+ )
+
+(If you do not want to initialize the widget, you could add the
+attributes by overriding a widget method and adding them in a super
+call, e.g. `get_context()
+`_)
+
+***************************
+ Security & Authentication
+***************************
Security is important. Therefore make sure to read and understand what
the security measures in place and their limitations.
-Set up a separate cache. If you have a public form that uses a model widget
-make sure to setup a separate cache database for Select2. An attacker
-could constantly reload your site and fill up the select2 cache.
-Having a separate cache allows you to limit the effect to select2 only.
+Set up a separate cache. If you have a public form that uses a model
+widget make sure to setup a separate cache database for Select2. An
+attacker could constantly reload your site and fill up the select2
+cache. Having a separate cache allows you to limit the effect to select2
+only.
You might want to add a secure select2 JSON endpoint for data you don't
-want to be accessible to the general public. Doing so is easy::
+want to be accessible to the general public. Doing so is easy:
+
+.. code::
- class UserSelect2View(LoginRequiredMixin, AutoResponseView):
- pass
+ class UserSelect2View(LoginRequiredMixin, AutoResponseView):
+ pass
- class UserSelect2WidgetMixin(object):
- def __init__(self, *args, **kwargs):
- kwargs['data_view'] = 'user-select2-view'
- super(UserSelect2WidgetMixin, self).__init__(*args, **kwargs)
+ class UserSelect2WidgetMixin(object):
+ def __init__(self, *args, **kwargs):
+ kwargs['data_view'] = 'user-select2-view'
+ super(UserSelect2WidgetMixin, self).__init__(*args, **kwargs)
- class MySecretWidget(UserSelect2WidgetMixin, ModelSelect2Widget):
- model = MySecretModel
- search_fields = ['title__icontains']
+ class MySecretWidget(UserSelect2WidgetMixin, ModelSelect2Widget):
+ model = MySecretModel
+ search_fields = ['title__icontains']
diff --git a/docs/extra.rst b/docs/extra.rst
index 6555189c..aab12d8b 100644
--- a/docs/extra.rst
+++ b/docs/extra.rst
@@ -1,37 +1,41 @@
-Extra
-=====
+#######
+ Extra
+#######
-Chained select2
----------------
+*****************
+ Chained select2
+*****************
-Suppose you have an address form where a user should choose a Country and a City.
-When the user selects a country we want to show only cities belonging to that country.
-So the one selector depends on another one.
+Suppose you have an address form where a user should choose a Country
+and a City. When the user selects a country we want to show only cities
+belonging to that country. So the one selector depends on another one.
.. note::
- Does not work with the 'light' version (django_select2.forms.Select2Widget).
+
+ Does not work with the 'light' version
+ (django_select2.forms.Select2Widget).
Models
-``````
+======
Here are our two models:
-.. code-block:: python
-
- class Country(models.Model):
- name = models.CharField(max_length=255)
+.. code:: python
+ class Country(models.Model):
+ name = models.CharField(max_length=255)
- class City(models.Model):
- name = models.CharField(max_length=255)
- country = models.ForeignKey('Country', related_name="cities")
+ class City(models.Model):
+ name = models.CharField(max_length=255)
+ country = models.ForeignKey("Country", related_name="cities")
Customizing a Form
-``````````````````
+==================
-Lets link two widgets via a *dependent_fields* dictionary. The key represents the name of
-the field in the form. The value represents the name of the field in the model (used in `queryset`).
+Lets link two widgets via a *dependent_fields* dictionary. The key
+represents the name of the field in the form. The value represents the
+name of the field in the model (used in `queryset`).
.. code-block:: python
:emphasize-lines: 17
@@ -57,12 +61,13 @@ the field in the form. The value represents the name of the field in the model (
)
)
+************************
+ Interdependent select2
+************************
-Interdependent select2
-----------------------
-
-Also you may want not to restrict the user to which field should be selected first.
-Instead you want to suggest to the user options for any select2 depending of his selection in another one.
+Also you may want not to restrict the user to which field should be
+selected first. Instead you want to suggest to the user options for any
+select2 depending of his selection in another one.
Customize the form in a manner:
@@ -89,19 +94,23 @@ Customize the form in a manner:
)
)
-Take attention to country's dependent_fields. The value of 'city' is 'cities' because of
-related name used in a filter condition `cities` which differs from widget field name `city`.
+Take attention to country's dependent_fields. The value of 'city' is
+'cities' because of related name used in a filter condition `cities`
+which differs from widget field name `city`.
.. caution::
- Be aware of using interdependent select2 in parent-child relation.
- When a child is selected, you are restricted to change parent (only one value is available).
- Probably you should let the user reset the child first to release parent select2.
+ Be aware of using interdependent select2 in parent-child relation.
+ When a child is selected, you are restricted to change parent (only
+ one value is available). Probably you should let the user reset the
+ child first to release parent select2.
-Multi-dependent select2
------------------------
+*************************
+ Multi-dependent select2
+*************************
-Furthermore you may want to filter options on two or more select2 selections (some code is dropped for clarity):
+Furthermore you may want to filter options on two or more select2
+selections (some code is dropped for clarity):
.. code-block:: python
:emphasize-lines: 14
diff --git a/docs/index.rst b/docs/index.rst
index 0b2772ea..a6ef3d7f 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,219 +1,239 @@
-.. include:: ../README.rst
+################
+ Django Select2
+################
-Installation
-------------
+.. image:: https://github.com/codingjoe/django-select2/raw/main/images/logo-light.svg
+ :align: center
+ :alt: Django Select2: Custom autocomplete fields for Django
+ :class: only-light
-Install ``django-select2``::
+.. image:: https://github.com/codingjoe/django-select2/raw/main/images/logo-dark.svg
+ :align: center
+ :alt: Django Select2: Custom autocomplete fields for Django
+ :class: only-dark
- python3 -m pip install django-select2
+**************
+ Installation
+**************
-Add ``django_select2`` to your ``INSTALLED_APPS`` in your project settings.
-Since version 8, please ensure that Django's admin app is enabled too:
+Install ``django-select2``:
-.. code-block:: python
+.. code::
- INSTALLED_APPS = [
- # other django apps…
- 'django.contrib.admin',
- # other 3rd party apps…
- 'django_select2',
- ]
+ python3 -m pip install django-select2
-Add ``django_select`` to your URL root configuration:
+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:: python
-.. 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:
- from django.urls import include, path
+.. code:: python
- urlpatterns = [
- # other patterns…
- path("select2/", include("django_select2.urls")),
- # other patterns…
- ]
+ from django.urls import include, path
+ urlpatterns = [
+ # other patterns…
+ path("select2/", include("django_select2.urls")),
+ # other patterns…
+ ]
The :ref:`Model` -widgets require a **persistent** cache backend across
-all application servers. This is because the widget needs to store
-meta data to be able to fetch the results based on the user input.
+all application servers. This is because the widget needs to store meta
+data to be able to fetch the results based on the user input.
**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
-you scale up into multiple servers.
+across a single node. For projects with a single application server 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
-works for multi-server setups:
+Below is an example setup using Redis, which is a solution that works
+for multi-server setups:
-Make sure you have a Redis server up and running::
+Make sure you have a Redis server up and running:
- # Debian
- sudo apt-get install redis-server
+.. code::
- # macOS
- brew install redis
+ # Debian
+ sudo apt-get install redis-server
- # install Redis python client
- python3 -m pip install django-redis
+ # macOS
+ brew install redis
+
+ # install Redis python client
+ python3 -m pip install django-redis
Next, add the cache configuration to your ``settings.py`` as follows:
-.. code-block:: python
+.. code:: python
- CACHES = {
- # … default cache config and others
- "select2": {
- "BACKEND": "django_redis.cache.RedisCache",
- "LOCATION": "redis://127.0.0.1:6379/2",
- "OPTIONS": {
- "CLIENT_CLASS": "django_redis.client.DefaultClient",
- }
- }
- }
+ CACHES = {
+ # … default cache config and others
+ "select2": {
+ "BACKEND": "django_redis.cache.RedisCache",
+ "LOCATION": "redis://127.0.0.1:6379/2",
+ "OPTIONS": {
+ "CLIENT_CLASS": "django_redis.client.DefaultClient",
+ },
+ }
+ }
- # Tell select2 which cache configuration to use:
- SELECT2_CACHE_BACKEND = "select2"
+ # Tell select2 which cache configuration to use:
+ SELECT2_CACHE_BACKEND = "select2"
.. note::
- A custom timeout for your cache backend, will serve as an indirect session limit.
- Auto select fields will stop working after, once the cache has expired.
- It's recommended to use a dedicated cache database with an adequate
- cache replacement policy such as LRU, FILO, etc.
-
-External Dependencies
----------------------
+ A custom timeout for your cache backend, will serve as an indirect
+ session limit. Auto select fields will stop working after, once the
+ cache has expired. It's recommended to use a dedicated cache database
+ with an adequate cache replacement policy such as LRU, FILO, etc.
-- jQuery is not included in the package since it is
- expected that in most scenarios this would already be available.
+***********************
+ External Dependencies
+***********************
+- jQuery is not included in the package since it is expected that in
+ most scenarios this would already be available.
-Quick Start
------------
+*************
+ Quick Start
+*************
Here is a quick example to get you started:
-First make sure you followed the installation instructions above.
-Once everything is setup, let's start with a simple example.
+First make sure you followed the installation instructions above. Once
+everything is setup, let's start with a simple example.
We have the following model:
-.. code-block:: python
+.. code:: python
- # models.py
- from django.conf import settings
- from django.db import models
+ # models.py
+ from django.conf import settings
+ from django.db import models
- class Book(models.Model):
- author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
- co_authors = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='co_authored_by')
-
+ class Book(models.Model):
+ author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
+ co_authors = models.ManyToManyField(
+ settings.AUTH_USER_MODEL, related_name="co_authored_by"
+ )
Next, we create a model form with custom Select2 widgets.
-.. code-block:: python
+.. code:: python
- # forms.py
- from django import forms
- from django_select2 import forms as s2forms
+ # forms.py
+ from django import forms
+ from django_select2 import forms as s2forms
- from . import models
+ from . import models
- class AuthorWidget(s2forms.ModelSelect2Widget):
- search_fields = [
- "username__icontains",
- "email__icontains",
- ]
+ class AuthorWidget(s2forms.ModelSelect2Widget):
+ search_fields = [
+ "username__icontains",
+ "email__icontains",
+ ]
- class CoAuthorsWidget(s2forms.ModelSelect2MultipleWidget):
- search_fields = [
- "username__icontains",
- "email__icontains",
- ]
+ class CoAuthorsWidget(s2forms.ModelSelect2MultipleWidget):
+ search_fields = [
+ "username__icontains",
+ "email__icontains",
+ ]
- class BookForm(forms.ModelForm):
- class Meta:
- model = models.Book
- fields = "__all__"
- widgets = {
- "author": AuthorWidget,
- "co_authors": CoAuthorsWidget,
- }
+ class BookForm(forms.ModelForm):
+ class Meta:
+ model = models.Book
+ fields = "__all__"
+ widgets = {
+ "author": AuthorWidget,
+ "co_authors": CoAuthorsWidget,
+ }
A simple class based view will do, to render your form:
-.. code-block:: python
+.. code:: python
- # views.py
- from django.views import generic
+ # views.py
+ from django.views import generic
- from . import forms, models
+ from . import forms, models
- class BookCreateView(generic.CreateView):
- model = models.Book
- form_class = forms.BookForm
- success_url = "/"
+ class BookCreateView(generic.CreateView):
+ model = models.Book
+ form_class = forms.BookForm
+ success_url = "/"
Make sure to add the view to your ``urls.py``:
-.. code-block:: python
-
- # urls.py
- from django.urls import include, path
-
- from . import views
-
- urlpatterns = [
- # … other patterns
- path("select2/", include("django_select2.urls")),
- # … other patterns
- path("book/create", views.BookCreateView.as_view(), name="book-create"),
- ]
-
-
-Finally, we need a little template, ``myapp/templates/myapp/book_form.html``
-
-.. code-block:: HTML
-
-
-
-
- Create Book
- {{ form.media.css }}
-
-
-
-
Create a new Book
-
-
- {{ form.media.js }}
-
-
+.. code:: python
+
+ # urls.py
+ from django.urls import include, path
+
+ from . import views
+
+ urlpatterns = [
+ # … other patterns
+ path("select2/", include("django_select2.urls")),
+ # … other patterns
+ path("book/create", views.BookCreateView.as_view(), name="book-create"),
+ ]
+
+Finally, we need a little template,
+``myapp/templates/myapp/book_form.html``
+
+.. code:: HTML
+
+
+
+
+ Create Book
+ {{ form.media.css }}
+
+
+
+