diff --git a/.gitignore b/.gitignore
index 2c9d9656..875d21f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,3 +23,4 @@ ghostdriver.log
coverage.xml
.eggs/
+db.sqlite3
diff --git a/README.rst b/README.rst
index bbd72b68..3cb880d5 100644
--- a/README.rst
+++ b/README.rst
@@ -1,68 +1,29 @@
+==============
Django-Select2
==============
-|version| |ci| |coverage| |license|
+|version| |coverage| |license|
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
-------------
-
-1. Install ``django_select2``
-
-.. code:: python
-
- pip install django_select2
-
-2. Add ``django_select2`` to your ``INSTALLED_APPS`` in your project
- settings.
-
-3. Add ``django_select`` to your urlconf if you use any "Auto" fields.
-
-.. code:: python
-
- url(r'^select2/', include('django_select2.urls')),
-
Documentation
-------------
Documentation available at https://django-select2.readthedocs.io/.
-External Dependencies
----------------------
-
-- jQuery version 2 This is not included in the package since it is
- expected that in most scenarios this would already be available.
-
-Example Application
--------------------
-
-Please see ``tests/testapp`` application. This application is used to
-manually test the functionalities of this package. This also serves as a
-good example.
-
-Changelog
----------
-
-See `Github releases`_
+.. 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
-.. _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/
-.. |ci| image:: https://travis-ci.org/applegrew/django-select2.svg?branch=master
- :target: https://travis-ci.org/applegrew/django-select2
.. |coverage| image:: https://codecov.io/gh/applegrew/django-select2/branch/master/graph/badge.svg
:target: https://codecov.io/gh/applegrew/django-select2
.. |license| image:: https://img.shields.io/badge/license-APL2-blue.svg
diff --git a/docs/conf.py b/docs/conf.py
index 52d38669..b339972f 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,8 +1,11 @@
import os
+import pathlib
import sys
from pkg_resources import get_distribution
+BASE_DIR = pathlib.Path(__file__).resolve(strict=True).parent.parent
+
# This is needed since django_select2 requires django model modules
# and those modules assume that django settings is configured and
# have proper DB settings.
@@ -12,13 +15,13 @@
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.insert(0, os.path.abspath("../tests.testapp"))
-sys.path.insert(0, os.path.abspath(".."))
+sys.path.insert(0, str(BASE_DIR / "tests" / "testapp"))
+sys.path.insert(0, str(BASE_DIR))
project = "Django-Select2"
author = "Johannes Hoppe"
-copyright = "2017, Johannes Hoppe"
+copyright = "2017-2020, Johannes Hoppe"
release = get_distribution("django_select2").version
version = ".".join(release.split(".")[:2])
diff --git a/docs/get_started.rst b/docs/get_started.rst
deleted file mode 100644
index 3af8b26d..00000000
--- a/docs/get_started.rst
+++ /dev/null
@@ -1,78 +0,0 @@
-===========
-Get Started
-===========
-
-Overview
---------
-
-.. automodule:: django_select2
- :members:
-
-Assumptions
------------
-
-* You have a running Django up and running.
-* You have form fully working without Django-Select2.
-
-Installation
-------------
-
-1. Install ``django_select2``::
-
- pip install django_select2
-
-2. Add ``django_select2`` to your ``INSTALLED_APPS`` in your project settings.
-
-3. Add ``django_select`` to your ``urlconf``::
-
- path('select2/', include('django_select2.urls')),
-
- You can safely skip this one if you do not use any
- :class:`ModelWidgets <.django_select2.forms.ModelSelect2Mixin>`
-
-Quick Start
------------
-
-Here is a quick example to get you started:
-
-0. Follow the installation instructions above.
-
-1. Replace native Django forms widgets with one of the several ``django_select2.form`` widgets.
- Start by importing them into your ``forms.py``, right next to Django own ones::
-
- from django import forms
- from django_select2 import forms as s2forms
-
- Then let's assume you have a model with a choice, a :class:`.ForeignKey`, and a
- :class:`.ManyToManyField`, you would add this information to your Form Meta
- class::
-
- widgets = {
- 'category': s2forms.Select2Widget,
- 'author': s2forms.ModelSelect2Widget(model=auth.get_user_model(),
- search_fields=['first_name__istartswith', 'last_name__icontains']),
- 'attending': s2forms.ModelSelect2MultipleWidget …
- }
-
-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
----------------------
-
-* jQuery (version >=2)
- jQuery is not included in the package since it is expected
- that in most scenarios jQuery is already loaded.
-
-Example Application
--------------------
-Please see ``tests/testapp`` application.
-This application is used to manually test the functionalities of this package.
-This also serves as a good example.
diff --git a/docs/index.rst b/docs/index.rst
index dedf2e97..460460af 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,7 +1,189 @@
-.. Django-Select2 documentation master file, created by
- sphinx-quickstart on Sat Aug 25 10:23:46 2012.
- You can adapt this file completely to your liking, but it should at least
- contain the root `toctree` directive.
+.. include:: ../README.rst
+
+Installation
+------------
+
+Install ``django-select2``
+
+.. code-block:: python
+
+ python3 -m pip install django-select2
+
+Add ``django_select2`` to your ``INSTALLED_APPS`` in your project settings.
+
+Add ``django_select`` to your URL root configuration:
+
+.. code-block:: python
+
+ from django.urls import include, path
+
+ urlpatterns = [
+ # … other patterns
+ path("select2/", include("django_select2.urls")),
+ # … other patterns
+ ]
+
+Finally make sure you have a persistent cache backend setup (NOT
+:class:`.DummyCache` or :class:`.LocMemCache`), we will use Redis in this
+example. Make sure you have a Redis server up and running::
+
+ # Debian
+ sudo apt-get install redis-server
+
+ # 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
+
+ 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"
+
+
+External Dependencies
+---------------------
+
+- jQuery is not included in the package since it is
+ expected that in most scenarios this would already be available.
+
+
+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.
+
+We have the following model:
+
+.. code-block:: python
+
+ # 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')
+
+
+Next, we create a model form with custom Select2 widgets.
+
+.. code-block:: python
+
+ # forms.py
+ from django import forms
+ from django_select2 import forms as s2forms
+
+ from . import models
+
+
+ class AuthorWidget(s2forms.ModelSelect2Widget):
+ 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,
+ }
+
+A simple class based view will do, to render your form:
+
+.. code-block:: python
+
+ # views.py
+ from django.views import generic
+
+ from . import forms, models
+
+
+ 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 }}
+
+
+
+Done - enjoy the wonders of Select2!
+
+
+Changelog
+---------
+
+See `Github releases`_.
+
+.. _Github releases: https://github.com/applegrew/django-select2/releases
All Contents
============
@@ -12,7 +194,6 @@ Contents:
:maxdepth: 2
:glob:
- get_started
django_select2
extra
CONTRIBUTING
diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
deleted file mode 100644
index fedb12ec..00000000
--- a/docs/spelling_wordlist.txt
+++ /dev/null
@@ -1,18 +0,0 @@
-jQuery
-Django
-mixin
-backend
-redis
-memcached
-AJAX
-Cloudflare
-lookup
-QuerySet
-pre
-py
-lookups
-functionalities
-plugin
-multi
-Indices
-clearable
diff --git a/example/README.md b/example/README.md
new file mode 100644
index 00000000..9da2c11e
--- /dev/null
+++ b/example/README.md
@@ -0,0 +1,24 @@
+# Sample App
+
+Before you start, make sure you have Redis installed, since
+we need if for caching purposes.
+
+```
+# Debian
+sudo apt-get install redis-server -y
+# macOS
+brew install redis
+```
+
+Now, to run the sample app, please execute:
+
+```
+git clone https://github.com/applegrew/django-select2.git
+cd django-select2/example
+python3 -m pip install -r requirements.txt
+python3 manage.py migrate
+python3 manage.py createsuperuser
+# follow the instructions to create a superuser
+python3 manage.py runserver
+# follow the instructions and open your browser
+```
diff --git a/example/example/__init__.py b/example/example/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/example/example/asgi.py b/example/example/asgi.py
new file mode 100644
index 00000000..0042f21c
--- /dev/null
+++ b/example/example/asgi.py
@@ -0,0 +1,16 @@
+"""
+ASGI config for example project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/dev/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
+
+application = get_asgi_application()
diff --git a/example/example/forms.py b/example/example/forms.py
new file mode 100644
index 00000000..a6152cda
--- /dev/null
+++ b/example/example/forms.py
@@ -0,0 +1,23 @@
+from django import forms
+
+from django_select2 import forms as s2forms
+
+from . import models
+
+
+class AuthorWidget(s2forms.ModelSelect2Widget):
+ search_fields = ["username__istartswith", "email__icontains"]
+
+
+class CoAuthorsWidget(s2forms.ModelSelect2MultipleWidget):
+ search_fields = ["username__istartswith", "email__icontains"]
+
+
+class BookForm(forms.ModelForm):
+ class Meta:
+ model = models.Book
+ fields = "__all__"
+ widgets = {
+ "author": AuthorWidget,
+ "co_authors": CoAuthorsWidget,
+ }
diff --git a/example/example/migrations/0001_initial.py b/example/example/migrations/0001_initial.py
new file mode 100644
index 00000000..db45b1bb
--- /dev/null
+++ b/example/example/migrations/0001_initial.py
@@ -0,0 +1,44 @@
+# Generated by Django 3.1a1 on 2020-05-23 17:06
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="Book",
+ fields=[
+ (
+ "id",
+ models.AutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "author",
+ models.ForeignKey(
+ on_delete=django.db.models.deletion.CASCADE,
+ to=settings.AUTH_USER_MODEL,
+ ),
+ ),
+ (
+ "co_authors",
+ models.ManyToManyField(
+ related_name="co_authored_by", to=settings.AUTH_USER_MODEL
+ ),
+ ),
+ ],
+ ),
+ ]
diff --git a/example/example/migrations/__init__.py b/example/example/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/example/example/models.py b/example/example/models.py
new file mode 100644
index 00000000..7d76374b
--- /dev/null
+++ b/example/example/models.py
@@ -0,0 +1,9 @@
+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"
+ )
diff --git a/example/example/settings.py b/example/example/settings.py
new file mode 100644
index 00000000..83067236
--- /dev/null
+++ b/example/example/settings.py
@@ -0,0 +1,131 @@
+"""
+Django settings for example project.
+
+Generated by 'django-admin startproject' using Django 3.1a1.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/dev/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/dev/ref/settings/
+"""
+
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = "kstexlapcf3lucx@47mmxsu9-9eixia+6n97aw)4$qo&!laxad"
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "django.contrib.staticfiles",
+ "django_select2",
+ "example",
+]
+
+MIDDLEWARE = [
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
+]
+
+ROOT_URLCONF = "example.urls"
+
+TEMPLATES = [
+ {
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [BASE_DIR / "templates"],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = "example.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/dev/ref/settings/#databases
+
+DATABASES = {
+ "default": {
+ "ENGINE": "django.db.backends.sqlite3",
+ "NAME": BASE_DIR / "db.sqlite3",
+ }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/dev/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+ },
+ {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
+ {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
+ {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/dev/topics/i18n/
+
+LANGUAGE_CODE = "en-us"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/dev/howto/static-files/
+
+STATIC_URL = "/static/"
+
+CACHES = {
+ "default": {
+ "BACKEND": "django_redis.cache.RedisCache",
+ "LOCATION": "redis://127.0.0.1:6379/1",
+ "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
+ },
+ "select2": {
+ "BACKEND": "django_redis.cache.RedisCache",
+ "LOCATION": "redis://127.0.0.1:6379/2",
+ "OPTIONS": {"CLIENT_CLASS": "django_redis.client.DefaultClient"},
+ },
+}
+
+SELECT2_CACHE_BACKEND = "select2"
diff --git a/example/example/templates/example/book_form.html b/example/example/templates/example/book_form.html
new file mode 100644
index 00000000..c2ff60bf
--- /dev/null
+++ b/example/example/templates/example/book_form.html
@@ -0,0 +1,20 @@
+
+
+
+ Create Book
+ {{ form.media.css }}
+
+
+
+ Create a new Book
+
+
+ {{ form.media.js }}
+
+
diff --git a/example/example/urls.py b/example/example/urls.py
new file mode 100644
index 00000000..ea4c18c3
--- /dev/null
+++ b/example/example/urls.py
@@ -0,0 +1,10 @@
+from django.contrib import admin
+from django.urls import include, path
+
+from . import views
+
+urlpatterns = [
+ path("", views.BookCreateView.as_view(), name="book-create"),
+ path("select2/", include("django_select2.urls")),
+ path("admin/", admin.site.urls),
+]
diff --git a/example/example/views.py b/example/example/views.py
new file mode 100644
index 00000000..4bd09428
--- /dev/null
+++ b/example/example/views.py
@@ -0,0 +1,9 @@
+from django.views import generic
+
+from . import forms, models
+
+
+class BookCreateView(generic.CreateView):
+ model = models.Book
+ form_class = forms.BookForm
+ success_url = "/"
diff --git a/example/example/wsgi.py b/example/example/wsgi.py
new file mode 100644
index 00000000..9f37c021
--- /dev/null
+++ b/example/example/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for example project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
+
+application = get_wsgi_application()
diff --git a/example/manage.py b/example/manage.py
new file mode 100755
index 00000000..c6b69b9c
--- /dev/null
+++ b/example/manage.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+ """Run administrative tasks."""
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings")
+ try:
+ from django.core.management import execute_from_command_line
+ except ImportError as exc:
+ raise ImportError(
+ "Couldn't import Django. Are you sure it's installed and "
+ "available on your PYTHONPATH environment variable? Did you "
+ "forget to activate a virtual environment?"
+ ) from exc
+ execute_from_command_line(sys.argv)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/example/requirements.txt b/example/requirements.txt
new file mode 100644
index 00000000..53500ba2
--- /dev/null
+++ b/example/requirements.txt
@@ -0,0 +1,2 @@
+-e ..
+django-redis
diff --git a/setup.cfg b/setup.cfg
index 95a47ada..ea4b66de 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -61,6 +61,7 @@ build-dir = docs/_build
[tool:pytest]
addopts =
+ tests
--doctest-glob='*.rst'
--doctest-modules
--cov=django_select2