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.
Files changed (83) hide show
  1. build/lib/build/lib/example/example/__init__.py +0 -0
  2. build/lib/build/lib/example/example/admin.py +16 -0
  3. build/lib/build/lib/example/example/asgi.py +16 -0
  4. build/lib/build/lib/example/example/management/__init__.py +0 -0
  5. build/lib/build/lib/example/example/management/commands/__init__.py +0 -0
  6. build/lib/build/lib/example/example/management/commands/createtestdata.py +62 -0
  7. build/lib/build/lib/example/example/migrations/0001_initial.py +48 -0
  8. build/lib/build/lib/example/example/migrations/__init__.py +0 -0
  9. build/lib/build/lib/example/example/models.py +38 -0
  10. build/lib/build/lib/example/example/settings.py +125 -0
  11. build/lib/build/lib/example/example/urls.py +23 -0
  12. build/lib/build/lib/example/example/wsgi.py +16 -0
  13. build/lib/build/lib/searchkit/__init__.py +0 -0
  14. build/lib/build/lib/searchkit/__version__.py +16 -0
  15. build/lib/build/lib/searchkit/admin.py +30 -0
  16. build/lib/build/lib/searchkit/apps.py +6 -0
  17. build/lib/build/lib/searchkit/filters.py +27 -0
  18. build/lib/build/lib/searchkit/forms/__init__.py +5 -0
  19. build/lib/build/lib/searchkit/forms/fields.py +55 -0
  20. build/lib/build/lib/searchkit/forms/search.py +62 -0
  21. build/lib/build/lib/searchkit/forms/searchkit.py +154 -0
  22. build/lib/build/lib/searchkit/forms/utils.py +178 -0
  23. build/lib/build/lib/searchkit/migrations/0001_initial.py +30 -0
  24. build/lib/build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
  25. build/lib/build/lib/searchkit/migrations/__init__.py +0 -0
  26. build/lib/build/lib/searchkit/models.py +27 -0
  27. build/lib/build/lib/searchkit/templatetags/__init__.py +0 -0
  28. build/lib/build/lib/searchkit/templatetags/searchkit.py +20 -0
  29. build/lib/build/lib/searchkit/tests.py +402 -0
  30. build/lib/build/lib/searchkit/urls.py +7 -0
  31. build/lib/build/lib/searchkit/views.py +23 -0
  32. build/lib/example/example/__init__.py +0 -0
  33. build/lib/example/example/admin.py +16 -0
  34. build/lib/example/example/asgi.py +16 -0
  35. build/lib/example/example/management/__init__.py +0 -0
  36. build/lib/example/example/management/commands/__init__.py +0 -0
  37. build/lib/example/example/management/commands/createtestdata.py +62 -0
  38. build/lib/example/example/migrations/0001_initial.py +48 -0
  39. build/lib/example/example/migrations/__init__.py +0 -0
  40. build/lib/example/example/models.py +38 -0
  41. build/lib/example/example/settings.py +125 -0
  42. build/lib/example/example/urls.py +23 -0
  43. build/lib/example/example/wsgi.py +16 -0
  44. build/lib/searchkit/__init__.py +0 -0
  45. build/lib/searchkit/__version__.py +16 -0
  46. build/lib/searchkit/admin.py +30 -0
  47. build/lib/searchkit/apps.py +6 -0
  48. build/lib/searchkit/filters.py +27 -0
  49. build/lib/searchkit/forms/__init__.py +5 -0
  50. build/lib/searchkit/forms/fields.py +55 -0
  51. build/lib/searchkit/forms/search.py +62 -0
  52. build/lib/searchkit/forms/searchkit.py +154 -0
  53. build/lib/searchkit/forms/utils.py +178 -0
  54. build/lib/searchkit/migrations/0001_initial.py +30 -0
  55. build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
  56. build/lib/searchkit/migrations/__init__.py +0 -0
  57. build/lib/searchkit/models.py +27 -0
  58. build/lib/searchkit/templatetags/__init__.py +0 -0
  59. build/lib/searchkit/templatetags/searchkit.py +20 -0
  60. build/lib/searchkit/tests.py +402 -0
  61. build/lib/searchkit/urls.py +7 -0
  62. build/lib/searchkit/views.py +23 -0
  63. {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/METADATA +34 -13
  64. django_searchkit-1.1.dist-info/RECORD +99 -0
  65. {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/WHEEL +1 -1
  66. {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/top_level.txt +1 -0
  67. example/example/admin.py +1 -1
  68. searchkit/__version__.py +1 -1
  69. searchkit/admin.py +4 -4
  70. searchkit/filters.py +7 -11
  71. searchkit/forms/__init__.py +5 -2
  72. searchkit/forms/search.py +36 -19
  73. searchkit/forms/searchkit.py +61 -99
  74. searchkit/forms/utils.py +44 -15
  75. searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
  76. searchkit/models.py +8 -4
  77. searchkit/templatetags/searchkit.py +0 -27
  78. searchkit/tests.py +283 -78
  79. searchkit/urls.py +1 -2
  80. searchkit/views.py +11 -18
  81. django_searchkit-0.1.dist-info/RECORD +0 -36
  82. {django_searchkit-0.1.dist-info → django_searchkit-1.1.dist-info}/licenses/LICENCE +0 -0
  83. {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: 0.1
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>=3.0
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.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue">](https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20%7C%203.11-blue)
54
- [<img src="https://img.shields.io/badge/django-3.1%20%7C%203.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1-orange">](https://img.shields.io/badge/django-3.1%20%7C%203.2%20%7C%204.0%20%7C%204.1%20%7C%204.2%20%7C%205.0%20%7C%205.1-orange)
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
- TODO
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
- ## Getting started
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
- TODO
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
- TODO
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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,3 +1,4 @@
1
+ build
1
2
  dist
2
3
  example
3
4
  searchkit
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, 'chars_choices']
10
+ list_filter = [SearchkitFilter]
11
11
 
12
12
 
13
13
  @admin.register(ModelB)
searchkit/__version__.py CHANGED
@@ -13,4 +13,4 @@ Version 0.x should be considered a development version with an unstable API,
13
13
  and backwards compatibility is not guaranteed for minor versions.
14
14
  """
15
15
 
16
- __version__ = "0.1"
16
+ __version__ = "1.1"
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 SearchkitSearch
5
- from .forms import SearchkitSearchForm
4
+ from .models import Search
5
+ from .forms import SearchForm
6
6
  from .filters import SearchkitFilter
7
7
 
8
8
 
9
- @admin.register(SearchkitSearch)
9
+ @admin.register(Search)
10
10
  class SearchkitSearchAdmin(admin.ModelAdmin):
11
- form = SearchkitSearchForm
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 SearchkitSearch
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 model_name for the reverse url lookup in the
15
- # template.
16
- self.app_label = model._meta.app_label
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
- ct = ContentType.objects.get_for_model(model_admin.model)
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 = SearchkitSearch.objects.get(id=int(self.value()))
31
- return queryset.filter(**search.get_filter_rules())
26
+ search = Search.objects.get(id=int(self.value()))
27
+ return search.as_queryset()
@@ -1,2 +1,5 @@
1
- from .search import SearchkitSearchForm
2
- from .searchkit import SearchkitFormSet
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 ..models import SearchkitSearch
4
- from .searchkit import SearchkitFormSet
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 SearchkitSearchForm(forms.ModelForm):
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 = SearchkitSearch
16
+ model = Search
14
17
  fields = ['name']
15
18
 
16
- def __init__(self, *args, **kwargs):
17
- super().__init__(*args, **kwargs)
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
- @property
20
- def media(self):
21
- # TODO: Check if child classes inherit those media files.
22
- return self.formset.media
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
- kwargs['data'] = self.data or None
31
- kwargs['prefix'] = self.prefix
32
- if self.instance.pk:
33
- kwargs['model'] = self.instance.contenttype.model_class()
34
- kwargs['initial'] = self.instance.data
35
- return SearchkitFormSet(**kwargs)
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.formset.contenttype_form.is_valid():
42
- self.instance.contenttype = self.formset.contenttype_form.cleaned_data['contenttype']
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()
@@ -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 django.contrib.contenttypes.models import ContentType
6
- from .utils import CSS_CLASSES, FIELD_PLAN, OPERATOR_DESCRIPTION
7
- from .utils import SUPPORTED_FIELDS, SUPPORTED_RELATIONS
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
- # FIXME: Make this a setting
11
- RELATION_DEPTH = 3
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 SearchkitForm(CSS_CLASSES, forms.Form):
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
- def __init__(self, model, *args, **kwargs):
39
+ model = None # Set by the formset factory.
40
+
41
+ def __init__(self, *args, **kwargs):
28
42
  super().__init__(*args, **kwargs)
29
- self.model = model
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
- pass
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
- model = self.model
68
- for index, field_name in enumerate(path, 1):
69
- field = model._meta.get_field(field_name)
70
- if index < len(path):
71
- model = field.remote_field.model
72
- return field
73
-
74
- def _get_model_field_choices(self, model, fields=[]):
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
- for model_field in model._meta.fields:
77
- if any(isinstance(model_field, f) for f in SUPPORTED_FIELDS):
78
- lookup = '__'.join([f.name for f in [*fields, model_field]])
79
- label = ' -> '.join(f.verbose_name for f in [*fields, model_field])
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(self.model)
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": CSS_CLASSES.reload_on_change_css_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": CSS_CLASSES.reload_on_change_css_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 ContentTypeForm(CSS_CLASSES, forms.Form):
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
- default_prefix = 'searchkit'
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
- if self.model:
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(cls):
174
- return cls.default_prefix
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
- SearchkitFormSet = forms.formset_factory(
190
- form=SearchkitForm,
191
- formset=BaseSearchkitFormset,
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
  )