diff --git a/apis_core/generic/forms/__init__.py b/apis_core/generic/forms/__init__.py index 2a08accbd..42ce34dc6 100644 --- a/apis_core/generic/forms/__init__.py +++ b/apis_core/generic/forms/__init__.py @@ -7,6 +7,9 @@ from apis_core.generic.abc import GenericModel from apis_core.generic.forms.fields import ModelImportChoiceField +from django_tomselect.forms import TomSelectModelChoiceField +from django_tomselect.widgets import TomSelectModelWidget, TomSelectIterablesWidget +from django_tomselect.app_settings import TomSelectConfig class GenericImportForm(forms.Form): @@ -70,8 +73,8 @@ def __init__(self, *args, **kwargs): # override the fields pointing to other models, # to make them use the autocomplete widgets override_fieldtypes = { - "ModelMultipleChoiceField": autocomplete.ModelSelect2Multiple, - "ModelChoiceField": autocomplete.ModelSelect2, + "ModelMultipleChoiceField": TomSelectIterablesWidget, + "ModelChoiceField": TomSelectModelChoiceField, "ModelImportChoiceField": autocomplete.ModelSelect2, } for field in self.fields: @@ -81,9 +84,10 @@ def __init__(self, *args, **kwargs): self.fields[field]._queryset.model ) if issubclass(ct.model_class(), GenericModel): - url = reverse("apis_core:generic:autocomplete", args=[ct]) + url = reverse("apis_core:generic:tautocomplete", args=[ct]) + tsc = TomSelectConfig(url=url) self.fields[field].widget = override_fieldtypes[clsname]( - url, attrs={"data-html": True} + config = tsc ) self.fields[field].widget.choices = self.fields[field].choices diff --git a/apis_core/generic/urls.py b/apis_core/generic/urls.py index f7946a428..f918e2c20 100644 --- a/apis_core/generic/urls.py +++ b/apis_core/generic/urls.py @@ -68,6 +68,7 @@ def to_url(self, value): {"external_only": True}, name="autocompleteexternalonly", ), + path("tomselect-autocomplete", views.AutocompleteView.as_view(), name="tautocomplete"), ] ), ), diff --git a/apis_core/generic/views.py b/apis_core/generic/views.py index c0ca5d3db..5d76b39a9 100644 --- a/apis_core/generic/views.py +++ b/apis_core/generic/views.py @@ -1,6 +1,7 @@ from collections import namedtuple from copy import copy +from django_tomselect.autocompletes import AutocompleteModelView from dal import autocomplete from django import forms, http from django.conf import settings @@ -60,10 +61,10 @@ class GenericModelMixin: """ def setup(self, *args, **kwargs): - super().setup(*args, **kwargs) if contenttype := kwargs.get("contenttype"): self.model = contenttype.model_class() self.queryset = self.model.objects.all() + super().setup(*args, **kwargs) def get_template_names(self): template_names = [] @@ -507,3 +508,7 @@ def form_valid(self, form): def get_success_url(self): return self.object.get_absolute_url() + + +class AutocompleteView(GenericModelMixin, AutocompleteModelView): + permission_action_required = "view" diff --git a/poetry.lock b/poetry.lock index 8094ddddf..295a09162 100644 --- a/poetry.lock +++ b/poetry.lock @@ -419,6 +419,21 @@ files = [ [package.dependencies] Django = ">=4.2" +[[package]] +name = "django-htmx" +version = "1.21.0" +description = "Extensions for using Django with htmx." +optional = false +python-versions = ">=3.9" +files = [ + {file = "django_htmx-1.21.0-py3-none-any.whl", hash = "sha256:64bc31463017a80552b767bc216ee5700248fa72e7ccd2963495e69afbdb6abe"}, + {file = "django_htmx-1.21.0.tar.gz", hash = "sha256:6ed3b42effd5980f22e68f36cd14ee4311bff3b6cb8435a89e27f45995691572"}, +] + +[package.dependencies] +asgiref = ">=3.6" +django = ">=4.2" + [[package]] name = "django-model-utils" version = "5.0.0" @@ -464,6 +479,22 @@ Django = ">=3.2" [package.extras] tablib = ["tablib"] +[[package]] +name = "django-tomselect" +version = "2024.12.2" +description = "Django autocomplete widgets and views using Tom Select" +optional = false +python-versions = "<4.0,>=3.10" +files = [ + {file = "django_tomselect-2024.12.2-py3-none-any.whl", hash = "sha256:ca0546f1ca870c40f00ebf68adb4ec6a0a8f611592af274b6a5a2fc05e7aac32"}, + {file = "django_tomselect-2024.12.2.tar.gz", hash = "sha256:8610ef0d85066937996e3826ac955379740db6fe371dc688c00f64117d904d1f"}, +] + +[package.dependencies] +django = ">=4.2.15" +django-htmx = ">=1.21.0" +pytest-asyncio = ">=0.25.0" + [[package]] name = "djangorestframework" version = "3.15.2" @@ -612,6 +643,17 @@ files = [ {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, ] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "jinja2" version = "3.1.3" @@ -860,6 +902,21 @@ files = [ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, ] +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "pydot" version = "3.0.3" @@ -908,6 +965,44 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pytest" +version = "8.3.4" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, + {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.25.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pytest_asyncio-0.25.0-py3-none-any.whl", hash = "sha256:db5432d18eac6b7e28b46dcd9b69921b55c3b1086e85febfe04e70b18d9e81b3"}, + {file = "pytest_asyncio-0.25.0.tar.gz", hash = "sha256:8c0610303c9e0442a5db8604505fc0f545456ba1528824842b37b4a626cbf609"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1574,4 +1669,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "081e0b4cc03a6d6ef135867fab3a0d03b45522b9ab7afec533e73554809cb521" +content-hash = "387f8cfba567eda0444facd1e20c451fab6400693cc544c295c931a6cb3fbbcd" diff --git a/pyproject.toml b/pyproject.toml index 47eec7995..05a03b810 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ apis-override-select2js = ">=0.1,<0.3" crispy-bootstrap4 = ">=2023.1,<2025.0" django-simple-history = ">=3.6" pydot = "^3.0.2" +django-tomselect = "^2024.12.2" [tool.poetry.group.docs] optional = true diff --git a/sample_project/settings.py b/sample_project/settings.py index d6a03d7bb..b4a97f254 100644 --- a/sample_project/settings.py +++ b/sample_project/settings.py @@ -60,6 +60,7 @@ "apis_core.generic", "apis_core.core", "apis_core.documentation", + "django_tomselect", ] MIDDLEWARE = [ @@ -72,6 +73,7 @@ "django.middleware.clickjacking.XFrameOptionsMiddleware", "crum.CurrentRequestUserMiddleware", "simple_history.middleware.HistoryRequestMiddleware", + "django_tomselect.middleware.TomSelectMiddleware", ] # ROOT_URLCONF = "apis_core.urls" @@ -88,6 +90,7 @@ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", + "django_tomselect.context_processors.tomselect", ], }, },