django-searchkit 0.1__py3-none-any.whl → 1.1__py3-none-any.whl
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.
- build/lib/build/lib/example/example/__init__.py +0 -0
- build/lib/build/lib/example/example/admin.py +16 -0
- build/lib/build/lib/example/example/asgi.py +16 -0
- build/lib/build/lib/example/example/management/__init__.py +0 -0
- build/lib/build/lib/example/example/management/commands/__init__.py +0 -0
- build/lib/build/lib/example/example/management/commands/createtestdata.py +62 -0
- build/lib/build/lib/example/example/migrations/0001_initial.py +48 -0
- build/lib/build/lib/example/example/migrations/__init__.py +0 -0
- build/lib/build/lib/example/example/models.py +38 -0
- build/lib/build/lib/example/example/settings.py +125 -0
- build/lib/build/lib/example/example/urls.py +23 -0
- build/lib/build/lib/example/example/wsgi.py +16 -0
- build/lib/build/lib/searchkit/__init__.py +0 -0
- build/lib/build/lib/searchkit/__version__.py +16 -0
- build/lib/build/lib/searchkit/admin.py +30 -0
- build/lib/build/lib/searchkit/apps.py +6 -0
- build/lib/build/lib/searchkit/filters.py +27 -0
- build/lib/build/lib/searchkit/forms/__init__.py +5 -0
- build/lib/build/lib/searchkit/forms/fields.py +55 -0
- build/lib/build/lib/searchkit/forms/search.py +62 -0
- build/lib/build/lib/searchkit/forms/searchkit.py +154 -0
- build/lib/build/lib/searchkit/forms/utils.py +178 -0
- build/lib/build/lib/searchkit/migrations/0001_initial.py +30 -0
- build/lib/build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
- build/lib/build/lib/searchkit/migrations/__init__.py +0 -0
- build/lib/build/lib/searchkit/models.py +27 -0
- build/lib/build/lib/searchkit/templatetags/__init__.py +0 -0
- build/lib/build/lib/searchkit/templatetags/searchkit.py +20 -0
- build/lib/build/lib/searchkit/tests.py +402 -0
- build/lib/build/lib/searchkit/urls.py +7 -0
- build/lib/build/lib/searchkit/views.py +23 -0
- build/lib/example/example/__init__.py +0 -0
- build/lib/example/example/admin.py +16 -0
- build/lib/example/example/asgi.py +16 -0
- build/lib/example/example/management/__init__.py +0 -0
- build/lib/example/example/management/commands/__init__.py +0 -0
- build/lib/example/example/management/commands/createtestdata.py +62 -0
- build/lib/example/example/migrations/0001_initial.py +48 -0
- build/lib/example/example/migrations/__init__.py +0 -0
- build/lib/example/example/models.py +38 -0
- build/lib/example/example/settings.py +125 -0
- build/lib/example/example/urls.py +23 -0
- build/lib/example/example/wsgi.py +16 -0
- build/lib/searchkit/__init__.py +0 -0
- build/lib/searchkit/__version__.py +16 -0
- build/lib/searchkit/admin.py +30 -0
- build/lib/searchkit/apps.py +6 -0
- build/lib/searchkit/filters.py +27 -0
- build/lib/searchkit/forms/__init__.py +5 -0
- build/lib/searchkit/forms/fields.py +55 -0
- build/lib/searchkit/forms/search.py +62 -0
- build/lib/searchkit/forms/searchkit.py +154 -0
- build/lib/searchkit/forms/utils.py +178 -0
- build/lib/searchkit/migrations/0001_initial.py +30 -0
- build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
- build/lib/searchkit/migrations/__init__.py +0 -0
- build/lib/searchkit/models.py +27 -0
- build/lib/searchkit/templatetags/__init__.py +0 -0
- build/lib/searchkit/templatetags/searchkit.py +20 -0
- build/lib/searchkit/tests.py +402 -0
- build/lib/searchkit/urls.py +7 -0
- build/lib/searchkit/views.py +23 -0
- {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/METADATA +34 -13
- django_searchkit-1.1.dist-info/RECORD +99 -0
- {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/WHEEL +1 -1
- {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/top_level.txt +1 -0
- example/example/admin.py +1 -1
- searchkit/__version__.py +1 -1
- searchkit/admin.py +4 -4
- searchkit/filters.py +7 -11
- searchkit/forms/__init__.py +5 -2
- searchkit/forms/search.py +36 -19
- searchkit/forms/searchkit.py +61 -99
- searchkit/forms/utils.py +44 -15
- searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
- searchkit/models.py +8 -4
- searchkit/templatetags/searchkit.py +0 -27
- searchkit/tests.py +283 -78
- searchkit/urls.py +1 -2
- searchkit/views.py +11 -18
- django_searchkit-0.1.dist-info/RECORD +0 -36
- {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/licenses/LICENCE +0 -0
- {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/zip-safe +0 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
from django.utils.translation import gettext_lazy as _
|
2
|
+
from django.http import HttpResponse
|
3
|
+
from django.http import Http404, HttpResponseBadRequest
|
4
|
+
from django.apps import apps
|
5
|
+
from django.contrib.contenttypes.models import ContentType
|
6
|
+
from django.views.generic import View
|
7
|
+
from .forms import SearchkitModelForm
|
8
|
+
from .forms import searchkit_formset_factory
|
9
|
+
|
10
|
+
|
11
|
+
# FIXME: Check permissions and authentication.
|
12
|
+
class SearchkitAjaxView(View):
|
13
|
+
"""
|
14
|
+
Reload the formset via ajax.
|
15
|
+
"""
|
16
|
+
def get(self, request, **kwargs):
|
17
|
+
contenttype_form = SearchkitModelForm(data=self.request.GET)
|
18
|
+
if contenttype_form.is_valid():
|
19
|
+
model = contenttype_form.cleaned_data['searchkit_model'].model_class()
|
20
|
+
formset = searchkit_formset_factory(model=model)(data=request.GET)
|
21
|
+
return HttpResponse(formset.render())
|
22
|
+
else:
|
23
|
+
return HttpResponseBadRequest(_('Invalid searchkit-model-form.'))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: django-searchkit
|
3
|
-
Version:
|
3
|
+
Version: 1.1
|
4
4
|
Summary: Finally a real searchkit for django!
|
5
5
|
Home-page: https://github.com/thomst/django-searchkit
|
6
6
|
Author: Thomas Leichtfuß
|
@@ -9,9 +9,6 @@ License: BSD License
|
|
9
9
|
Platform: OS Independent
|
10
10
|
Classifier: Development Status :: 5 - Production/Stable
|
11
11
|
Classifier: Framework :: Django
|
12
|
-
Classifier: Framework :: Django :: 3.0
|
13
|
-
Classifier: Framework :: Django :: 3.1
|
14
|
-
Classifier: Framework :: Django :: 3.2
|
15
12
|
Classifier: Framework :: Django :: 4.0
|
16
13
|
Classifier: Framework :: Django :: 4.1
|
17
14
|
Classifier: Framework :: Django :: 4.2
|
@@ -23,8 +20,6 @@ Classifier: License :: OSI Approved :: BSD License
|
|
23
20
|
Classifier: Operating System :: OS Independent
|
24
21
|
Classifier: Programming Language :: Python
|
25
22
|
Classifier: Programming Language :: Python :: 3
|
26
|
-
Classifier: Programming Language :: Python :: 3.7
|
27
|
-
Classifier: Programming Language :: Python :: 3.8
|
28
23
|
Classifier: Programming Language :: Python :: 3.9
|
29
24
|
Classifier: Programming Language :: Python :: 3.10
|
30
25
|
Classifier: Programming Language :: Python :: 3.11
|
@@ -33,7 +28,9 @@ Classifier: Topic :: Software Development
|
|
33
28
|
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
34
29
|
Description-Content-Type: text/markdown
|
35
30
|
License-File: LICENCE
|
36
|
-
Requires-Dist: Django>=
|
31
|
+
Requires-Dist: Django>=4.0
|
32
|
+
Requires-Dist: django-picklefield>=2.0
|
33
|
+
Requires-Dist: django-modeltree
|
37
34
|
Dynamic: author
|
38
35
|
Dynamic: author-email
|
39
36
|
Dynamic: classifier
|
@@ -50,13 +47,19 @@ Dynamic: summary
|
|
50
47
|
|
51
48
|
[<img src="https://github.com/thomst/django-searchkit/actions/workflows/ci.yml/badge.svg">](https://github.com/thomst/django-searchkit/)
|
52
49
|
[<img src="https://coveralls.io/repos/github/thomst/django-searchkit/badge.svg?branch=main">](https://coveralls.io/github/thomst/django-searchkit?branch=main)
|
53
|
-
[<img src="https://img.shields.io/badge/python-3.
|
54
|
-
[<img src="https://img.shields.io/badge/django-
|
50
|
+
[<img src="https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11-blue">](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11-blue)
|
51
|
+
[<img src="https://img.shields.io/badge/django-4.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2-orange">](https://img.shields.io/badge/django-4.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1%20%7C%205.2-orange)
|
55
52
|
|
56
53
|
|
57
54
|
## Description
|
58
55
|
|
59
|
-
|
56
|
+
Finally there is a real searchkit application for django that integrates best
|
57
|
+
with the django admin backend.
|
58
|
+
|
59
|
+
Build and apply complex searches on model instances right in the backend without
|
60
|
+
any coding. Save and reuse your searches by a handy django admin filter with a
|
61
|
+
single click.
|
62
|
+
|
60
63
|
|
61
64
|
## Setup
|
62
65
|
|
@@ -73,10 +76,28 @@ INSTALLED_APPS = [
|
|
73
76
|
]
|
74
77
|
```
|
75
78
|
|
76
|
-
|
79
|
+
Add the `SearkitFilter` to your `ModelAdmin`:
|
80
|
+
```
|
81
|
+
from django.contrib import admin
|
82
|
+
from searchkit.filters import SearchkitFilter
|
83
|
+
from .models import MyModel
|
84
|
+
|
77
85
|
|
78
|
-
|
86
|
+
@admin.register(MyModel)
|
87
|
+
class MyModelAdmin(admin.ModelAdmin):
|
88
|
+
...
|
89
|
+
list_filter = [
|
90
|
+
SearchkitFilter,
|
91
|
+
...
|
92
|
+
]
|
93
|
+
...
|
94
|
+
```
|
79
95
|
|
80
96
|
## Usage
|
81
97
|
|
82
|
-
|
98
|
+
1. Open the admin changelist of your Model.
|
99
|
+
2. Click the "Add filter" button of the Searchkit filter.
|
100
|
+
3. Give your Filter a name.
|
101
|
+
4. Configure as many filter rules as you want.
|
102
|
+
5. Click "Save and apply".
|
103
|
+
6. Reuse your filter whenever you want using the Searchkit filter section.
|
@@ -0,0 +1,99 @@
|
|
1
|
+
build/lib/build/lib/example/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
build/lib/build/lib/example/example/admin.py,sha256=eTZ5mCA1ToGouqYgIf-6xDl2nvYAz7T9lU1Bnu_gP9c,462
|
3
|
+
build/lib/build/lib/example/example/asgi.py,sha256=mqLnzcCH9X-8UhDo5ZhcM8tF3jtALEGSjsBmphbkLcs,391
|
4
|
+
build/lib/build/lib/example/example/models.py,sha256=fB9X782cX01Yg2uIiPCVgrS8C7DkgG9U-9mxGtX3P7Q,1097
|
5
|
+
build/lib/build/lib/example/example/settings.py,sha256=du9bWbAceSvDkYeNYOc_3KxKL1p1se93TdAueT2RQ_E,3256
|
6
|
+
build/lib/build/lib/example/example/urls.py,sha256=D8LxjiTdJSA87AVh3BzpVo5E2aC30hlp20HshPDrfvU,800
|
7
|
+
build/lib/build/lib/example/example/wsgi.py,sha256=OAXHHeNQoj7FBDc5NMET39i_kIGpFrcdNDdY1thrh58,391
|
8
|
+
build/lib/build/lib/example/example/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
9
|
+
build/lib/build/lib/example/example/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
build/lib/build/lib/example/example/management/commands/createtestdata.py,sha256=IVtyUqak133h9Ua-Ag_GP8mw3dUdiDYAa2tGmLkKluU,2737
|
11
|
+
build/lib/build/lib/example/example/migrations/0001_initial.py,sha256=62tOi17oHGuqqLmSAYWLep6AMkxCJF6Tp13odghS7gI,2014
|
12
|
+
build/lib/build/lib/example/example/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
+
build/lib/build/lib/searchkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
|
+
build/lib/build/lib/searchkit/__version__.py,sha256=YHc4yqTXD8EGEgS4jTIrg3vbI45fTQWhAF19GPsIcZM,661
|
15
|
+
build/lib/build/lib/searchkit/admin.py,sha256=URZgMKnfKlDqOgmQXJHu7mFGrjwkElpunAUbp2_9JcA,1182
|
16
|
+
build/lib/build/lib/searchkit/apps.py,sha256=17m6wPu5N59Dq4MLoWaQd0aNHEzMT4z4emQD632GIMA,150
|
17
|
+
build/lib/build/lib/searchkit/filters.py,sha256=z3L2bWIk-LRWwW_95JwZk14fjtOnsBqXI8LZx3foV-0,1135
|
18
|
+
build/lib/build/lib/searchkit/models.py,sha256=G3kCU_bsLVzcToKgk1Gbh_8B3FCw3KofFubyddaxTUU,985
|
19
|
+
build/lib/build/lib/searchkit/tests.py,sha256=p_YHEVNo9JbpwdIk_8Hz6yda2ei-ALTKRIK7IaKii6g,14946
|
20
|
+
build/lib/build/lib/searchkit/urls.py,sha256=Mhrq4CUOhrh28ZhRocSP2AYnfWFW2c_eJyMR81q5fps,162
|
21
|
+
build/lib/build/lib/searchkit/views.py,sha256=EvCJOsxackQAzk3-uc6V4cmgEqkS-f2ZVBVRmXoMukA,937
|
22
|
+
build/lib/build/lib/searchkit/forms/__init__.py,sha256=FBggDa1owkYrceIU3cqNhqsMB49RJWZRAkA4OfvUMcM,207
|
23
|
+
build/lib/build/lib/searchkit/forms/fields.py,sha256=TpFZdAyWKaMhOoZFTuy5ODNrW6RLy289Lk1fcMSGJ20,1621
|
24
|
+
build/lib/build/lib/searchkit/forms/search.py,sha256=mca67piLDqKUhXzaKIOztk5Gg8V1XL21n27aIffH88A,2330
|
25
|
+
build/lib/build/lib/searchkit/forms/searchkit.py,sha256=pg3vw6dMdtaHja-g1Yh72XJkLp7I48nXht9zxWbGW9Y,6022
|
26
|
+
build/lib/build/lib/searchkit/forms/utils.py,sha256=m8vwB-3AoYuMx0QxlJTwSWbDHBG8MyCxFmsyK6xyai8,5221
|
27
|
+
build/lib/build/lib/searchkit/migrations/0001_initial.py,sha256=cP886X1UMrDTf-KxfHfuoQ4b1T1lhdNJyVNOGMNyWNw,1093
|
28
|
+
build/lib/build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py,sha256=RHXShizf689AdPb2Ca-hONWmwZbB2Fs5DQhXlEPE-f0,389
|
29
|
+
build/lib/build/lib/searchkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
+
build/lib/build/lib/searchkit/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
|
+
build/lib/build/lib/searchkit/templatetags/searchkit.py,sha256=HpEGTGYi_enDh_bgTgWdVbZftNuZqRt4J9N5A0Nc1AM,581
|
32
|
+
build/lib/example/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
33
|
+
build/lib/example/example/admin.py,sha256=eTZ5mCA1ToGouqYgIf-6xDl2nvYAz7T9lU1Bnu_gP9c,462
|
34
|
+
build/lib/example/example/asgi.py,sha256=mqLnzcCH9X-8UhDo5ZhcM8tF3jtALEGSjsBmphbkLcs,391
|
35
|
+
build/lib/example/example/models.py,sha256=fB9X782cX01Yg2uIiPCVgrS8C7DkgG9U-9mxGtX3P7Q,1097
|
36
|
+
build/lib/example/example/settings.py,sha256=du9bWbAceSvDkYeNYOc_3KxKL1p1se93TdAueT2RQ_E,3256
|
37
|
+
build/lib/example/example/urls.py,sha256=D8LxjiTdJSA87AVh3BzpVo5E2aC30hlp20HshPDrfvU,800
|
38
|
+
build/lib/example/example/wsgi.py,sha256=OAXHHeNQoj7FBDc5NMET39i_kIGpFrcdNDdY1thrh58,391
|
39
|
+
build/lib/example/example/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
40
|
+
build/lib/example/example/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
|
+
build/lib/example/example/management/commands/createtestdata.py,sha256=IVtyUqak133h9Ua-Ag_GP8mw3dUdiDYAa2tGmLkKluU,2737
|
42
|
+
build/lib/example/example/migrations/0001_initial.py,sha256=62tOi17oHGuqqLmSAYWLep6AMkxCJF6Tp13odghS7gI,2014
|
43
|
+
build/lib/example/example/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
|
+
build/lib/searchkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
|
+
build/lib/searchkit/__version__.py,sha256=YHc4yqTXD8EGEgS4jTIrg3vbI45fTQWhAF19GPsIcZM,661
|
46
|
+
build/lib/searchkit/admin.py,sha256=URZgMKnfKlDqOgmQXJHu7mFGrjwkElpunAUbp2_9JcA,1182
|
47
|
+
build/lib/searchkit/apps.py,sha256=17m6wPu5N59Dq4MLoWaQd0aNHEzMT4z4emQD632GIMA,150
|
48
|
+
build/lib/searchkit/filters.py,sha256=z3L2bWIk-LRWwW_95JwZk14fjtOnsBqXI8LZx3foV-0,1135
|
49
|
+
build/lib/searchkit/models.py,sha256=G3kCU_bsLVzcToKgk1Gbh_8B3FCw3KofFubyddaxTUU,985
|
50
|
+
build/lib/searchkit/tests.py,sha256=p_YHEVNo9JbpwdIk_8Hz6yda2ei-ALTKRIK7IaKii6g,14946
|
51
|
+
build/lib/searchkit/urls.py,sha256=Mhrq4CUOhrh28ZhRocSP2AYnfWFW2c_eJyMR81q5fps,162
|
52
|
+
build/lib/searchkit/views.py,sha256=EvCJOsxackQAzk3-uc6V4cmgEqkS-f2ZVBVRmXoMukA,937
|
53
|
+
build/lib/searchkit/forms/__init__.py,sha256=FBggDa1owkYrceIU3cqNhqsMB49RJWZRAkA4OfvUMcM,207
|
54
|
+
build/lib/searchkit/forms/fields.py,sha256=TpFZdAyWKaMhOoZFTuy5ODNrW6RLy289Lk1fcMSGJ20,1621
|
55
|
+
build/lib/searchkit/forms/search.py,sha256=mca67piLDqKUhXzaKIOztk5Gg8V1XL21n27aIffH88A,2330
|
56
|
+
build/lib/searchkit/forms/searchkit.py,sha256=pg3vw6dMdtaHja-g1Yh72XJkLp7I48nXht9zxWbGW9Y,6022
|
57
|
+
build/lib/searchkit/forms/utils.py,sha256=m8vwB-3AoYuMx0QxlJTwSWbDHBG8MyCxFmsyK6xyai8,5221
|
58
|
+
build/lib/searchkit/migrations/0001_initial.py,sha256=cP886X1UMrDTf-KxfHfuoQ4b1T1lhdNJyVNOGMNyWNw,1093
|
59
|
+
build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py,sha256=RHXShizf689AdPb2Ca-hONWmwZbB2Fs5DQhXlEPE-f0,389
|
60
|
+
build/lib/searchkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
61
|
+
build/lib/searchkit/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
62
|
+
build/lib/searchkit/templatetags/searchkit.py,sha256=HpEGTGYi_enDh_bgTgWdVbZftNuZqRt4J9N5A0Nc1AM,581
|
63
|
+
django_searchkit-1.1.dist-info/licenses/LICENCE,sha256=oqmlRYPA5GHG2T1yp8fPFGQdOO-NnJPdGITeFiJYWLg,1521
|
64
|
+
example/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
65
|
+
example/example/admin.py,sha256=KDjYyHPEqNdv7Xe8yx0VuWdx-bDn7qKKHM5ZYhFwdU0,445
|
66
|
+
example/example/asgi.py,sha256=mqLnzcCH9X-8UhDo5ZhcM8tF3jtALEGSjsBmphbkLcs,391
|
67
|
+
example/example/models.py,sha256=fB9X782cX01Yg2uIiPCVgrS8C7DkgG9U-9mxGtX3P7Q,1097
|
68
|
+
example/example/settings.py,sha256=du9bWbAceSvDkYeNYOc_3KxKL1p1se93TdAueT2RQ_E,3256
|
69
|
+
example/example/urls.py,sha256=D8LxjiTdJSA87AVh3BzpVo5E2aC30hlp20HshPDrfvU,800
|
70
|
+
example/example/wsgi.py,sha256=OAXHHeNQoj7FBDc5NMET39i_kIGpFrcdNDdY1thrh58,391
|
71
|
+
example/example/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
72
|
+
example/example/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
73
|
+
example/example/management/commands/createtestdata.py,sha256=IVtyUqak133h9Ua-Ag_GP8mw3dUdiDYAa2tGmLkKluU,2737
|
74
|
+
example/example/migrations/0001_initial.py,sha256=62tOi17oHGuqqLmSAYWLep6AMkxCJF6Tp13odghS7gI,2014
|
75
|
+
example/example/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
76
|
+
searchkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
77
|
+
searchkit/__version__.py,sha256=YHc4yqTXD8EGEgS4jTIrg3vbI45fTQWhAF19GPsIcZM,661
|
78
|
+
searchkit/admin.py,sha256=URZgMKnfKlDqOgmQXJHu7mFGrjwkElpunAUbp2_9JcA,1182
|
79
|
+
searchkit/apps.py,sha256=17m6wPu5N59Dq4MLoWaQd0aNHEzMT4z4emQD632GIMA,150
|
80
|
+
searchkit/filters.py,sha256=z3L2bWIk-LRWwW_95JwZk14fjtOnsBqXI8LZx3foV-0,1135
|
81
|
+
searchkit/models.py,sha256=G3kCU_bsLVzcToKgk1Gbh_8B3FCw3KofFubyddaxTUU,985
|
82
|
+
searchkit/tests.py,sha256=p_YHEVNo9JbpwdIk_8Hz6yda2ei-ALTKRIK7IaKii6g,14946
|
83
|
+
searchkit/urls.py,sha256=Mhrq4CUOhrh28ZhRocSP2AYnfWFW2c_eJyMR81q5fps,162
|
84
|
+
searchkit/views.py,sha256=EvCJOsxackQAzk3-uc6V4cmgEqkS-f2ZVBVRmXoMukA,937
|
85
|
+
searchkit/forms/__init__.py,sha256=FBggDa1owkYrceIU3cqNhqsMB49RJWZRAkA4OfvUMcM,207
|
86
|
+
searchkit/forms/fields.py,sha256=TpFZdAyWKaMhOoZFTuy5ODNrW6RLy289Lk1fcMSGJ20,1621
|
87
|
+
searchkit/forms/search.py,sha256=mca67piLDqKUhXzaKIOztk5Gg8V1XL21n27aIffH88A,2330
|
88
|
+
searchkit/forms/searchkit.py,sha256=pg3vw6dMdtaHja-g1Yh72XJkLp7I48nXht9zxWbGW9Y,6022
|
89
|
+
searchkit/forms/utils.py,sha256=m8vwB-3AoYuMx0QxlJTwSWbDHBG8MyCxFmsyK6xyai8,5221
|
90
|
+
searchkit/migrations/0001_initial.py,sha256=cP886X1UMrDTf-KxfHfuoQ4b1T1lhdNJyVNOGMNyWNw,1093
|
91
|
+
searchkit/migrations/0002_rename_searchkitsearch_search.py,sha256=RHXShizf689AdPb2Ca-hONWmwZbB2Fs5DQhXlEPE-f0,389
|
92
|
+
searchkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
93
|
+
searchkit/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
94
|
+
searchkit/templatetags/searchkit.py,sha256=HpEGTGYi_enDh_bgTgWdVbZftNuZqRt4J9N5A0Nc1AM,581
|
95
|
+
django_searchkit-1.1.dist-info/METADATA,sha256=oFRweWx1Rxx8Nshj_rrsoGCbLkAk_JIGoBzFYjQT-ew,3325
|
96
|
+
django_searchkit-1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
97
|
+
django_searchkit-1.1.dist-info/top_level.txt,sha256=-4gF42VIaG-ckn2rb2wFa7LhxIZymHBsYVedNOr_NIY,29
|
98
|
+
django_searchkit-1.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
99
|
+
django_searchkit-1.1.dist-info/RECORD,,
|
example/example/admin.py
CHANGED
@@ -7,7 +7,7 @@ from .models import ModelB
|
|
7
7
|
@admin.register(ModelA)
|
8
8
|
class ModelAAdmin(admin.ModelAdmin):
|
9
9
|
list_display = [f.name for f in ModelA._meta.fields]
|
10
|
-
list_filter = [SearchkitFilter
|
10
|
+
list_filter = [SearchkitFilter]
|
11
11
|
|
12
12
|
|
13
13
|
@admin.register(ModelB)
|
searchkit/__version__.py
CHANGED
searchkit/admin.py
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
from django.contrib import admin
|
2
2
|
from django.http import HttpResponseRedirect
|
3
3
|
from django.urls import reverse
|
4
|
-
from .models import
|
5
|
-
from .forms import
|
4
|
+
from .models import Search
|
5
|
+
from .forms import SearchForm
|
6
6
|
from .filters import SearchkitFilter
|
7
7
|
|
8
8
|
|
9
|
-
@admin.register(
|
9
|
+
@admin.register(Search)
|
10
10
|
class SearchkitSearchAdmin(admin.ModelAdmin):
|
11
|
-
form =
|
11
|
+
form = SearchForm
|
12
12
|
list_display = ('name', 'contenttype', 'created_date')
|
13
13
|
|
14
14
|
def get_url_for_applied_search(self, obj):
|
searchkit/filters.py
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
from django.contrib.admin import SimpleListFilter
|
2
2
|
from django.contrib.contenttypes.models import ContentType
|
3
|
-
from .models import
|
4
|
-
from .forms import SearchkitFormSet
|
5
|
-
from .forms.utils import get_filter_rules
|
3
|
+
from .models import Search
|
6
4
|
|
7
5
|
|
8
6
|
class SearchkitFilter(SimpleListFilter):
|
@@ -11,21 +9,19 @@ class SearchkitFilter(SimpleListFilter):
|
|
11
9
|
template = 'searchkit/searchkit_filter.html'
|
12
10
|
|
13
11
|
def __init__(self, request, params, model, model_admin):
|
14
|
-
# We need the app_label and
|
15
|
-
#
|
16
|
-
self.
|
17
|
-
self.model_name = model._meta.model_name
|
12
|
+
# We need the app_label and model as get parameter for the new search
|
13
|
+
# link.
|
14
|
+
self.searchkit_model = ContentType.objects.get_for_model(model)
|
18
15
|
super().__init__(request, params, model, model_admin)
|
19
16
|
|
20
17
|
def lookups(self, request, model_admin):
|
21
18
|
# Fetch the last three objects from SearchkitSearch and return them as
|
22
19
|
# choices.
|
23
|
-
|
24
|
-
searches = SearchkitSearch.objects.filter(contenttype=ct).order_by('-created_date')[:3]
|
20
|
+
searches = Search.objects.filter(contenttype=self.searchkit_model).order_by('-created_date')[:3]
|
25
21
|
return [(str(obj.id), obj.name) for obj in searches]
|
26
22
|
|
27
23
|
def queryset(self, request, queryset):
|
28
24
|
# Filter the queryset based on the selected SearchkitSearch object
|
29
25
|
if self.value():
|
30
|
-
search =
|
31
|
-
return
|
26
|
+
search = Search.objects.get(id=int(self.value()))
|
27
|
+
return search.as_queryset()
|
searchkit/forms/__init__.py
CHANGED
@@ -1,2 +1,5 @@
|
|
1
|
-
from .search import
|
2
|
-
from .searchkit import
|
1
|
+
from .search import SearchForm
|
2
|
+
from .searchkit import SearchkitModelForm
|
3
|
+
from .searchkit import BaseSearchkitFormSet
|
4
|
+
from .searchkit import BaseSearchkitForm
|
5
|
+
from .searchkit import searchkit_formset_factory
|
searchkit/forms/search.py
CHANGED
@@ -1,25 +1,40 @@
|
|
1
1
|
from django import forms
|
2
2
|
from django.utils.functional import cached_property
|
3
|
-
from
|
4
|
-
from
|
3
|
+
from django.contrib.contenttypes.models import ContentType
|
4
|
+
from ..models import Search
|
5
|
+
from .searchkit import SearchkitModelForm
|
6
|
+
from .searchkit import searchkit_formset_factory
|
7
|
+
from .utils import MediaMixin
|
5
8
|
|
6
9
|
|
7
|
-
class
|
10
|
+
class SearchForm(MediaMixin, forms.ModelForm):
|
8
11
|
"""
|
9
12
|
Represents a SearchkitSearch model. Using a SearchkitFormSet for the data
|
10
13
|
json field.
|
11
14
|
"""
|
12
15
|
class Meta:
|
13
|
-
model =
|
16
|
+
model = Search
|
14
17
|
fields = ['name']
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
@cached_property
|
20
|
+
def searchkit_model(self):
|
21
|
+
if self.instance.pk:
|
22
|
+
return self.instance.contenttype.model_class()
|
23
|
+
elif self.searchkit_model_form.is_valid():
|
24
|
+
return self.searchkit_model_form.cleaned_data['searchkit_model'].model_class()
|
25
|
+
elif 'searchkit_model' in self.searchkit_model_form.initial:
|
26
|
+
value = self.searchkit_model_form.initial['searchkit_model']
|
27
|
+
try:
|
28
|
+
return self.searchkit_model_form.fields['searchkit_model'].clean(value).model_class()
|
29
|
+
except forms.ValidationError:
|
30
|
+
return None
|
18
31
|
|
19
|
-
@
|
20
|
-
def
|
21
|
-
|
22
|
-
|
32
|
+
@cached_property
|
33
|
+
def searchkit_model_form(self):
|
34
|
+
kwargs = dict(data=self.data or None, initial=self.initial or None)
|
35
|
+
if self.instance.pk:
|
36
|
+
kwargs['initial'] = dict(searchkit_model=self.instance.contenttype)
|
37
|
+
return SearchkitModelForm(**kwargs)
|
23
38
|
|
24
39
|
@cached_property
|
25
40
|
def formset(self):
|
@@ -27,19 +42,21 @@ class SearchkitSearchForm(forms.ModelForm):
|
|
27
42
|
A searchkit formset for the model.
|
28
43
|
"""
|
29
44
|
kwargs = dict()
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
kwargs
|
34
|
-
|
35
|
-
|
45
|
+
if self.searchkit_model and self.data:
|
46
|
+
kwargs = dict(data=self.data)
|
47
|
+
elif self.searchkit_model and self.instance.pk:
|
48
|
+
kwargs = dict(initial=self.instance.data)
|
49
|
+
|
50
|
+
extra = 0 if self.instance.pk else 1
|
51
|
+
formset = searchkit_formset_factory(model=self.searchkit_model, extra=extra)
|
52
|
+
return formset(**kwargs)
|
36
53
|
|
37
54
|
def is_valid(self):
|
38
|
-
return self.formset.is_valid() and super().is_valid()
|
55
|
+
return self.formset.is_valid() and self.searchkit_model_form.is_valid and super().is_valid()
|
39
56
|
|
40
57
|
def clean(self):
|
41
|
-
if self.
|
42
|
-
self.instance.contenttype = self.
|
58
|
+
if self.searchkit_model_form.is_valid():
|
59
|
+
self.instance.contenttype = self.searchkit_model_form.cleaned_data['searchkit_model']
|
43
60
|
if self.formset.is_valid():
|
44
61
|
self.instance.data = self.formset.cleaned_data
|
45
62
|
return super().clean()
|
searchkit/forms/searchkit.py
CHANGED
@@ -1,17 +1,29 @@
|
|
1
|
-
from collections import OrderedDict
|
2
1
|
from django import forms
|
3
2
|
from django.utils.translation import gettext_lazy as _
|
4
3
|
from django.utils.functional import cached_property
|
5
|
-
from
|
6
|
-
from .utils import
|
7
|
-
from .utils import
|
4
|
+
from .utils import CssClassMixin, FIELD_PLAN, OPERATOR_DESCRIPTION
|
5
|
+
from .utils import SUPPORTED_FIELDS
|
6
|
+
from .utils import ModelTree
|
7
|
+
from .utils import MediaMixin
|
8
|
+
from .utils import get_searchable_models
|
8
9
|
|
9
10
|
|
10
|
-
|
11
|
-
|
11
|
+
class SearchkitModelForm(forms.Form):
|
12
|
+
"""
|
13
|
+
Form to select a content type.
|
14
|
+
"""
|
15
|
+
searchkit_model = forms.ModelChoiceField(
|
16
|
+
queryset=get_searchable_models(),
|
17
|
+
label=_('Model'),
|
18
|
+
empty_label=_('Select a Model'),
|
19
|
+
widget=forms.Select(attrs={
|
20
|
+
"class": CssClassMixin.reload_on_change_css_class,
|
21
|
+
"data-total-forms": 1,
|
22
|
+
}),
|
23
|
+
)
|
12
24
|
|
13
25
|
|
14
|
-
class
|
26
|
+
class BaseSearchkitForm(MediaMixin, CssClassMixin, forms.Form):
|
15
27
|
"""
|
16
28
|
Searchkit form representing a model field lookup based on the field name,
|
17
29
|
the operator and one or two values.
|
@@ -24,9 +36,11 @@ class SearchkitForm(CSS_CLASSES, forms.Form):
|
|
24
36
|
|
25
37
|
See the FIELD_PLAN variable for the logic of building the form.
|
26
38
|
"""
|
27
|
-
|
39
|
+
model = None # Set by the formset factory.
|
40
|
+
|
41
|
+
def __init__(self, *args, **kwargs):
|
28
42
|
super().__init__(*args, **kwargs)
|
29
|
-
self.
|
43
|
+
self.model_tree = ModelTree(self.model)
|
30
44
|
self.model_field = None
|
31
45
|
self.field_plan = None
|
32
46
|
self.operator = None
|
@@ -56,7 +70,7 @@ class SearchkitForm(CSS_CLASSES, forms.Form):
|
|
56
70
|
# Do we have a valid value?
|
57
71
|
return self.fields[field_name].clean(self.unprefixed_data[field_name])
|
58
72
|
except forms.ValidationError:
|
59
|
-
|
73
|
+
return self.fields[field_name].choices[0][0]
|
60
74
|
else:
|
61
75
|
# At last simply return the first option which will be the selected
|
62
76
|
# one.
|
@@ -64,40 +78,44 @@ class SearchkitForm(CSS_CLASSES, forms.Form):
|
|
64
78
|
|
65
79
|
def _get_model_field(self, lookup):
|
66
80
|
path = lookup.split('__')
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
return
|
73
|
-
|
74
|
-
def _get_model_field_choices(self
|
81
|
+
field_name = path[-1]
|
82
|
+
if path[:-1]:
|
83
|
+
model = self.model_tree.get('__'.join(path[:-1])).model
|
84
|
+
else:
|
85
|
+
model = self.model
|
86
|
+
return model._meta.get_field(field_name)
|
87
|
+
|
88
|
+
def _get_model_field_choices(self):
|
75
89
|
choices = list()
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
90
|
+
label_path = list()
|
91
|
+
for node in self.model_tree.iterate():
|
92
|
+
label_path.append
|
93
|
+
for model_field in node.model._meta.fields:
|
94
|
+
if not any(isinstance(model_field, f) for f in SUPPORTED_FIELDS):
|
95
|
+
continue
|
96
|
+
if node.is_root:
|
97
|
+
lookup = model_field.name
|
98
|
+
label = f'`{model_field.verbose_name}`'
|
99
|
+
else:
|
100
|
+
lookup = f'{node.field_path}__{model_field.name}'
|
101
|
+
get_field_name = lambda f: getattr(f, 'verbose_name', f.name)
|
102
|
+
label_path = [f'`{get_field_name(n.field)}` => <{n.model._meta.verbose_name}>' for n in node.path[1:]]
|
103
|
+
label = ".".join(label_path + [f'`{model_field.verbose_name}`'])
|
80
104
|
choices.append((lookup, label))
|
81
|
-
if len(fields) < RELATION_DEPTH:
|
82
|
-
for model_field in model._meta.fields:
|
83
|
-
if any(isinstance(model_field, f) for f in SUPPORTED_RELATIONS):
|
84
|
-
related_model = model_field.remote_field.model
|
85
|
-
fields = [*fields, model_field]
|
86
|
-
choices += self._get_model_field_choices(related_model, fields)
|
87
105
|
return choices
|
88
106
|
|
89
107
|
def _add_field_name_field(self):
|
90
108
|
initial = self.initial.get('field')
|
91
|
-
choices = self._get_model_field_choices(
|
109
|
+
choices = self._get_model_field_choices()
|
92
110
|
field = forms.ChoiceField(label=_('Model field'), choices=choices, initial=initial)
|
93
|
-
field.widget.attrs.update({"class":
|
111
|
+
field.widget.attrs.update({"class": self.reload_on_change_css_class})
|
94
112
|
self.fields['field'] = field
|
95
113
|
|
96
114
|
def _add_operator_field(self):
|
97
115
|
initial = self.initial.get('operator')
|
98
116
|
choices = [(o, OPERATOR_DESCRIPTION[o]) for o in self.field_plan.keys()]
|
99
117
|
field = forms.ChoiceField(label=_('Operator'), choices=choices, initial=initial)
|
100
|
-
field.widget.attrs.update({"class":
|
118
|
+
field.widget.attrs.update({"class": self.reload_on_change_css_class})
|
101
119
|
self.fields['operator'] = field
|
102
120
|
|
103
121
|
def _add_value_field(self):
|
@@ -110,83 +128,27 @@ class SearchkitForm(CSS_CLASSES, forms.Form):
|
|
110
128
|
self.fields['value'] = field
|
111
129
|
|
112
130
|
|
113
|
-
class
|
114
|
-
"""
|
115
|
-
Form to select a content type.
|
116
|
-
"""
|
117
|
-
contenttype = forms.ModelChoiceField(
|
118
|
-
queryset=ContentType.objects.all(),
|
119
|
-
label=_('Model'),
|
120
|
-
empty_label=_('Select a Model'),
|
121
|
-
widget=forms.Select(attrs={"class": CSS_CLASSES.reload_on_change_css_class}),
|
122
|
-
)
|
123
|
-
|
124
|
-
class Media:
|
125
|
-
js = [
|
126
|
-
'admin/js/vendor/jquery/jquery.min.js',
|
127
|
-
'admin/js/jquery.init.js',
|
128
|
-
"searchkit/searchkit.js"
|
129
|
-
]
|
130
|
-
|
131
|
-
|
132
|
-
class BaseSearchkitFormset(CSS_CLASSES, forms.BaseFormSet):
|
131
|
+
class BaseSearchkitFormSet(CssClassMixin, forms.BaseFormSet):
|
133
132
|
"""
|
134
133
|
Formset holding all searchkit forms.
|
135
134
|
"""
|
136
135
|
template_name = "searchkit/searchkit.html"
|
137
136
|
template_name_div = "searchkit/searchkit.html"
|
138
|
-
|
139
|
-
form = SearchkitForm
|
140
|
-
contenttype_form_class = ContentTypeForm
|
141
|
-
|
142
|
-
def __init__(self, *args, **kwargs):
|
143
|
-
self.contenttype_form = self.get_conttenttype_form(kwargs)
|
144
|
-
self.model = self.get_model(kwargs)
|
145
|
-
super().__init__(*args, **kwargs)
|
146
|
-
if self.initial:
|
147
|
-
self.extra = 0
|
148
|
-
|
149
|
-
def get_conttenttype_form(self, kwargs):
|
150
|
-
ct_kwargs = dict()
|
151
|
-
ct_kwargs['data'] = kwargs.get('data')
|
152
|
-
ct_kwargs['prefix'] = kwargs.get('prefix')
|
153
|
-
if model := kwargs.pop('model', None):
|
154
|
-
ct_kwargs['initial'] = dict(contenttype=ContentType.objects.get_for_model(model))
|
155
|
-
return self.contenttype_form_class(**ct_kwargs)
|
156
|
-
|
157
|
-
def get_model(self, kwargs):
|
158
|
-
if self.contenttype_form.initial:
|
159
|
-
return self.contenttype_form.initial['contenttype'].model_class()
|
160
|
-
elif self.contenttype_form.is_valid():
|
161
|
-
return self.contenttype_form.cleaned_data['contenttype'].model_class()
|
162
|
-
|
163
|
-
def get_form_kwargs(self, index):
|
164
|
-
kwargs = self.form_kwargs.copy()
|
165
|
-
kwargs['model'] = self.model
|
166
|
-
return kwargs
|
137
|
+
model = None # Set by the formset factory.
|
167
138
|
|
168
139
|
def add_prefix(self, index):
|
169
|
-
|
170
|
-
return "%s-%s-%s" % (self.prefix, self.model._meta.model_name, index)
|
140
|
+
return "%s-%s-%s-%s" % (self.prefix, self.model._meta.app_label, self.model._meta.model_name, index)
|
171
141
|
|
172
142
|
@classmethod
|
173
|
-
def get_default_prefix(
|
174
|
-
return
|
175
|
-
|
176
|
-
@cached_property
|
177
|
-
def forms(self):
|
178
|
-
# We won't render any forms if we got no model.
|
179
|
-
return super().forms if self.model else []
|
180
|
-
|
181
|
-
@property
|
182
|
-
def media(self):
|
183
|
-
return self.contenttype_form.media
|
184
|
-
|
185
|
-
def is_valid(self):
|
186
|
-
return self.contenttype_form.is_valid() and self.forms and super().is_valid()
|
143
|
+
def get_default_prefix(self):
|
144
|
+
return "searchkit"
|
187
145
|
|
188
146
|
|
189
|
-
|
190
|
-
|
191
|
-
|
147
|
+
def searchkit_formset_factory(model, **kwargs):
|
148
|
+
form = type('SearchkitForm', (BaseSearchkitForm,), dict(model=model))
|
149
|
+
formset = type('SearchkitFormSet', (BaseSearchkitFormSet,), dict(model=model))
|
150
|
+
return forms.formset_factory(
|
151
|
+
form=form,
|
152
|
+
formset=formset,
|
153
|
+
**kwargs
|
192
154
|
)
|