django-tomselect 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- django-tomselect-0.4.0/LICENSE +21 -0
- django-tomselect-0.4.0/PKG-INFO +353 -0
- django-tomselect-0.4.0/README.md +341 -0
- django-tomselect-0.4.0/pyproject.toml +67 -0
- django-tomselect-0.4.0/setup.cfg +4 -0
- django-tomselect-0.4.0/src/django_tomselect/__init__.py +4 -0
- django-tomselect-0.4.0/src/django_tomselect/static/django_tomselect/css/django-tomselect.css +18 -0
- django-tomselect-0.4.0/src/django_tomselect/static/django_tomselect/js/django-tomselect.js +4439 -0
- django-tomselect-0.4.0/src/django_tomselect/static/vendor/tom-select/css/tom-select.bootstrap5.css +615 -0
- django-tomselect-0.4.0/src/django_tomselect/static/vendor/tom-select/css/tom-select.bootstrap5.css.map +1 -0
- django-tomselect-0.4.0/src/django_tomselect/views.py +119 -0
- django-tomselect-0.4.0/src/django_tomselect/widgets.py +166 -0
- django-tomselect-0.4.0/src/django_tomselect.egg-info/PKG-INFO +353 -0
- django-tomselect-0.4.0/src/django_tomselect.egg-info/SOURCES.txt +18 -0
- django-tomselect-0.4.0/src/django_tomselect.egg-info/dependency_links.txt +1 -0
- django-tomselect-0.4.0/src/django_tomselect.egg-info/requires.txt +1 -0
- django-tomselect-0.4.0/src/django_tomselect.egg-info/top_level.txt +1 -0
- django-tomselect-0.4.0/tests/test_e2e.py +459 -0
- django-tomselect-0.4.0/tests/test_views.py +387 -0
- django-tomselect-0.4.0/tests/test_widgets.py +146 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Actionb, Jack Linke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: django-tomselect
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Django autocomplete widgets and views using Tom Select
|
|
5
|
+
Author-email: Jack Linke <jacklinke@gmail.com>, Philip Becker <yummytea1@gmail.com>
|
|
6
|
+
Project-URL: Source, https://github.com/jacklinke/django-tomselect
|
|
7
|
+
Classifier: Framework :: Django
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
|
|
13
|
+
# Tom Select for Django
|
|
14
|
+
|
|
15
|
+
Django autocomplete widgets and views using [Tom Select](https://tom-select.js.org/).
|
|
16
|
+
|
|
17
|
+
This package provides a Django autocomplete widget and view that can be used
|
|
18
|
+
together to provide a user interface for selecting a model instance from a
|
|
19
|
+
database table.
|
|
20
|
+
|
|
21
|
+
The package is adapted from the fantastic work of
|
|
22
|
+
[Philip Becker](https://pypi.org/user/actionb/) in
|
|
23
|
+
[mizdb-tomselect](https://www.pypi.org/project/mizdb-tomselect/), with the goal
|
|
24
|
+
of a more generalized solution for Django autocompletion.
|
|
25
|
+
|
|
26
|
+
<!-- TOC -->
|
|
27
|
+
* [Tom Select for Django](#tom-select-for-django)
|
|
28
|
+
* [Installation](#installation)
|
|
29
|
+
* [Usage](#usage)
|
|
30
|
+
* [Widgets](#widgets)
|
|
31
|
+
* [TomSelectWidget](#tomselectwidget)
|
|
32
|
+
* [TomSelectTabularWidget](#tomselecttabularwidget)
|
|
33
|
+
* [Adding more columns](#adding-more-columns-)
|
|
34
|
+
* [Function & Features](#function--features)
|
|
35
|
+
* [Searching](#searching)
|
|
36
|
+
* [Option creation](#option-creation)
|
|
37
|
+
* [AJAX request](#ajax-request)
|
|
38
|
+
* [List View link](#list-view-link)
|
|
39
|
+
* [Chained Dropdown Filtering](#chained-dropdown-filtering)
|
|
40
|
+
* [Development & Demo](#development--demo)
|
|
41
|
+
<!-- TOC -->
|
|
42
|
+
|
|
43
|
+
----
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
Install:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install -U django-tomselect
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
Add to installed apps:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
INSTALLED_APPS = [
|
|
59
|
+
# ...
|
|
60
|
+
"django_tomselect"
|
|
61
|
+
]
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Configure an endpoint for autocomplete requests:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
# urls.py
|
|
68
|
+
from django.urls import path
|
|
69
|
+
|
|
70
|
+
from django_tomselect.views import AutocompleteView
|
|
71
|
+
|
|
72
|
+
urlpatterns = [
|
|
73
|
+
# ...
|
|
74
|
+
path("autocomplete/", AutocompleteView.as_view(), name="my_autocomplete_view")
|
|
75
|
+
]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use the widgets in a form.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from django import forms
|
|
82
|
+
|
|
83
|
+
from django_tomselect.widgets import TomSelectWidget, TomSelectTabularWidget
|
|
84
|
+
from .models import City, Person
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class MyForm(forms.Form):
|
|
88
|
+
city = forms.ModelChoiceField(
|
|
89
|
+
City.objects.all(),
|
|
90
|
+
widget=TomSelectWidget(City, url="my_autocomplete_view"),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
# Display results in a table, with additional columns for fields
|
|
94
|
+
# "first_name" and "last_name":
|
|
95
|
+
person = forms.ModelChoiceField(
|
|
96
|
+
Person.objects.all(),
|
|
97
|
+
widget=TomSelectTabularWidget(
|
|
98
|
+
Person,
|
|
99
|
+
url="my_autocomplete_view",
|
|
100
|
+
search_lookups=[
|
|
101
|
+
"full_name__icontains",
|
|
102
|
+
],
|
|
103
|
+
# for extra columns pass a mapping of {"model_field": "Column Header Label"}
|
|
104
|
+
extra_columns={"first_name": "First Name", "last_name": "Last Name"},
|
|
105
|
+
# The column header label for the labelField column
|
|
106
|
+
label_field_label="Full Name",
|
|
107
|
+
),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
NOTE: Make sure to include [bootstrap](https://getbootstrap.com/docs/5.2/getting-started/download/) somewhere. For example in the template:
|
|
113
|
+
|
|
114
|
+
```html
|
|
115
|
+
<!DOCTYPE html>
|
|
116
|
+
<html lang="en">
|
|
117
|
+
<head>
|
|
118
|
+
<meta charset="UTF-8">
|
|
119
|
+
<title>Django Tom Select Demo</title>
|
|
120
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
|
|
121
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
|
|
122
|
+
{{ form.media }}
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
<div class="container">
|
|
126
|
+
<form>
|
|
127
|
+
{% csrf_token %}
|
|
128
|
+
{{ form.as_div }}
|
|
129
|
+
<button type="submit" class="btn btn-success">Save</button>
|
|
130
|
+
</form>
|
|
131
|
+
</div>
|
|
132
|
+
</body>
|
|
133
|
+
</html>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
----
|
|
137
|
+
|
|
138
|
+
## Widgets
|
|
139
|
+
|
|
140
|
+
The widgets pass attributes necessary to make autocomplete requests to the
|
|
141
|
+
HTML element via the dataset property. The Tom Select element is then initialized
|
|
142
|
+
from the attributes in the dataset property.
|
|
143
|
+
|
|
144
|
+
### TomSelectWidget
|
|
145
|
+
|
|
146
|
+
Base autocomplete widget. The arguments of TomSelectWidget are:
|
|
147
|
+
|
|
148
|
+
| Argument | Default value | Description |
|
|
149
|
+
|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------|
|
|
150
|
+
| model | **required** | the model class that provides the choices |
|
|
151
|
+
| url | `"autocomplete"` | URL pattern name of the autocomplete view |
|
|
152
|
+
| value_field | `f"{model._meta.pk.name}"` | model field that provides the value of an option |
|
|
153
|
+
| label_field | `getattr(model, "name_field", "name")` | model field that provides the label of an option |
|
|
154
|
+
| search_lookups | <code>[<br/> f"{self.value_field}__icontains",<br/> f"{self.label_field}__icontains",<br/>]<code> | the list of lookups to use when filtering the results |
|
|
155
|
+
| create_field | | model field to create new objects with ([see below](#ajax-request)) |
|
|
156
|
+
| multiple | False | if True, allow selecting multiple options |
|
|
157
|
+
| listview_url | | URL name of the list view for this model ([see below](#list-view-link)) |
|
|
158
|
+
| add_url | | URL name of the add view for this model([see below](#option-creation)) |
|
|
159
|
+
| filter_by | | a 2-tuple defining an additional filter ([see below](#filter-against-values-of-another-field)) |
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
### TomSelectTabularWidget
|
|
163
|
+
|
|
164
|
+
This widget displays the results in tabular form. A table header will be added
|
|
165
|
+
to the dropdown. By default, the table contains two columns: one column for the choice
|
|
166
|
+
value (commonly the "ID" of the option) and one column for the choice label (the
|
|
167
|
+
human-readable part of the choice).
|
|
168
|
+
|
|
169
|
+

|
|
170
|
+
|
|
171
|
+
TomSelectTabularWidget has the following additional arguments:
|
|
172
|
+
|
|
173
|
+
| Argument | Default value | Description |
|
|
174
|
+
|-------------------|---------------------------------|----------------------------------------------|
|
|
175
|
+
| extra_columns | | a mapping for additional columns |
|
|
176
|
+
| value_field_label | `f"{value_field.title()}"` | table header for the value column |
|
|
177
|
+
| label_field_label | `f"{model._meta.verbose_name}"` | table header for the label column |
|
|
178
|
+
| label_field_label | `f"{model._meta.verbose_name}"` | table header for the label column |
|
|
179
|
+
| show_value_field | `False` | show the value field column (typically `id`) |
|
|
180
|
+
|
|
181
|
+
#### Adding more columns
|
|
182
|
+
|
|
183
|
+
To add more columns, pass a dictionary mapping field names to column labels as
|
|
184
|
+
`extra_columns` to the widget's arguments.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from django import forms
|
|
188
|
+
from django_tomselect.widgets import TomSelectTabularWidget
|
|
189
|
+
from .models import Person
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class MyForm(forms.Form):
|
|
193
|
+
person = forms.ModelChoiceField(
|
|
194
|
+
Person.objects.all(),
|
|
195
|
+
widget=TomSelectTabularWidget(
|
|
196
|
+
Person,
|
|
197
|
+
url="my_autocomplete_view",
|
|
198
|
+
# for extra columns pass a mapping of {"model_field": "Column Header Label"}
|
|
199
|
+
extra_columns={"first_name": "First Name", "last_name": "Last Name"},
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The column label is the header label for a given column in the table.
|
|
206
|
+
|
|
207
|
+
The attribute name tells Tom Select what value to look up on a result for the column.
|
|
208
|
+
|
|
209
|
+
**Important**: that means that the result visible to Tom Select must have an attribute
|
|
210
|
+
or property with that name or the column will remain empty.
|
|
211
|
+
The results for Tom Select are created by the view calling `values()` on the
|
|
212
|
+
result queryset, so you must make sure that the attribute name is available
|
|
213
|
+
on the view's root queryset as either a model field or as an annotation.
|
|
214
|
+
|
|
215
|
+
----
|
|
216
|
+
|
|
217
|
+
## Function & Features
|
|
218
|
+
|
|
219
|
+
### Searching
|
|
220
|
+
|
|
221
|
+
The AutocompleteView filters the result queryset against the `search_lookups`
|
|
222
|
+
passed to the widget. The default value for the lookup is `name__icontains`.
|
|
223
|
+
Overwrite the `AutocompleteView.search` method to modify the search process.
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
from django_tomselect.views import AutocompleteView
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class MyAutocompleteView(AutocompleteView):
|
|
230
|
+
def search(self, queryset, q):
|
|
231
|
+
# Filter using your own queryset method:
|
|
232
|
+
return queryset.search(q)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Option creation
|
|
236
|
+
|
|
237
|
+
To enable option creation in the dropdown, pass the URL pattern name of the
|
|
238
|
+
add page of the given model to the widget. This will add an 'Add' button to the
|
|
239
|
+
bottom of the dropdown.
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
# urls.py
|
|
243
|
+
from django.urls import path
|
|
244
|
+
from django_tomselect.views import AutocompleteView
|
|
245
|
+
from django_tomselect.widgets import TomSelectWidget
|
|
246
|
+
from .models import City
|
|
247
|
+
from .views import CityAddView
|
|
248
|
+
|
|
249
|
+
urlpatterns = [
|
|
250
|
+
# ...
|
|
251
|
+
path("autocomplete/", AutocompleteView.as_view(), name="my_autocomplete_view"),
|
|
252
|
+
path("city/add/", CityAddView.as_view(), name="city_add"),
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
# forms.py
|
|
256
|
+
widget = TomSelectWidget(City, url="my_autocomplete_view", add_url="city_add")
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Clicking on that button sends the user to the add page of the model.
|
|
260
|
+
|
|
261
|
+
#### AJAX request
|
|
262
|
+
|
|
263
|
+
If `create_field` was also passed to the widget, clicking on the button will
|
|
264
|
+
create a new object using an AJAX POST request to the autocomplete URL. The
|
|
265
|
+
autocomplete view will use the search term that the user put in on the
|
|
266
|
+
`create_field` to create the object:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
class AutocompleteView:
|
|
270
|
+
|
|
271
|
+
def create_object(self, data):
|
|
272
|
+
"""Create a new object with the given data."""
|
|
273
|
+
return self.model.objects.create(**{self.create_field: data[self.create_field]})
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Override the view's `create_object` method to change the creation process.
|
|
277
|
+
|
|
278
|
+
### List View link
|
|
279
|
+
|
|
280
|
+
The dropdown will include a link to the list view of the given model if you
|
|
281
|
+
pass in the URL pattern name of the list view.
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
# urls.py
|
|
285
|
+
from django.urls import path
|
|
286
|
+
from django_tomselect.views import AutocompleteView
|
|
287
|
+
from django_tomselect.widgets import TomSelectWidget
|
|
288
|
+
from .models import City
|
|
289
|
+
from .views import CityListView
|
|
290
|
+
|
|
291
|
+
urlpatterns = [
|
|
292
|
+
# ...
|
|
293
|
+
path("autocomplete/", AutocompleteView.as_view(), name="my_autocomplete_view"),
|
|
294
|
+
path("city/list/", CityListView.as_view(), name="city_listview"),
|
|
295
|
+
]
|
|
296
|
+
|
|
297
|
+
# forms.py
|
|
298
|
+
widget = TomSelectWidget(City, url="my_autocomplete_view", listview_url="city_listview")
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Chained Dropdown Filtering
|
|
302
|
+
|
|
303
|
+
Use the `filter_by` argument to restrict the available options of one
|
|
304
|
+
TomSelectWidget to the value selected in another form field. The parameter must
|
|
305
|
+
be a 2-tuple: `(name_of_the_other_form_field, django_field_lookup)`
|
|
306
|
+
|
|
307
|
+
```python
|
|
308
|
+
# models.py
|
|
309
|
+
from django import forms
|
|
310
|
+
from django.db import models
|
|
311
|
+
from django_tomselect.widgets import TomSelectWidget
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class Person(models.Model):
|
|
315
|
+
name = models.CharField(max_length=50)
|
|
316
|
+
city = models.ForeignKey("City", on_delete=models.SET_NULL, blank=True, null=True)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class City(models.Model):
|
|
320
|
+
name = models.CharField(max_length=50)
|
|
321
|
+
is_capitol = models.BooleanField(default=False)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# forms.py
|
|
325
|
+
class PersonsFromCapitolsForm(forms.Form):
|
|
326
|
+
capitol = forms.ModelChoiceField(queryset=City.objects.filter(is_capitol=True))
|
|
327
|
+
person = forms.ModelChoiceField(
|
|
328
|
+
queryset=Person.objects.all(),
|
|
329
|
+
widget=TomSelectWidget(Person, filter_by=("capitol", "city_id")),
|
|
330
|
+
)
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
This will result in the Person result queryset to be filtered against
|
|
334
|
+
`city_id` for the currently selected `capitol` formfield value.
|
|
335
|
+
NOTE: When using `filter_by`, the declaring element now **requires** that the
|
|
336
|
+
other field provides a value, since its choices are dependent on the other
|
|
337
|
+
field. If the other field does not have a value, the search will not return any
|
|
338
|
+
results.
|
|
339
|
+
|
|
340
|
+
----
|
|
341
|
+
|
|
342
|
+
## Development & Demo
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
python3 -m venv venv
|
|
346
|
+
source venv/bin/activate
|
|
347
|
+
make init
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
Then see the demo for a preview: `python demo/manage.py runserver`
|
|
351
|
+
|
|
352
|
+
Run tests with `make test` or `make tox`. To install required browsers for playwright: `playwright install`.
|
|
353
|
+
See the makefile for other commands.
|