django-searchkit 1.0__py3-none-any.whl → 1.2__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 (97) hide show
  1. build/lib/build/lib/build/lib/example/example/__init__.py +0 -0
  2. build/lib/build/lib/build/lib/example/example/admin.py +16 -0
  3. build/lib/build/lib/build/lib/example/example/asgi.py +16 -0
  4. build/lib/build/lib/build/lib/example/example/management/__init__.py +0 -0
  5. build/lib/build/lib/build/lib/example/example/management/commands/__init__.py +0 -0
  6. build/lib/build/lib/build/lib/example/example/management/commands/createtestdata.py +62 -0
  7. build/lib/build/lib/build/lib/example/example/migrations/0001_initial.py +48 -0
  8. build/lib/build/lib/build/lib/example/example/migrations/__init__.py +0 -0
  9. build/lib/build/lib/build/lib/example/example/models.py +38 -0
  10. build/lib/build/lib/build/lib/example/example/settings.py +125 -0
  11. build/lib/build/lib/build/lib/example/example/urls.py +23 -0
  12. build/lib/build/lib/build/lib/example/example/wsgi.py +16 -0
  13. build/lib/build/lib/build/lib/searchkit/__init__.py +0 -0
  14. build/lib/build/lib/build/lib/searchkit/__version__.py +16 -0
  15. build/lib/build/lib/build/lib/searchkit/admin.py +30 -0
  16. build/lib/build/lib/build/lib/searchkit/apps.py +6 -0
  17. build/lib/build/lib/build/lib/searchkit/filters.py +28 -0
  18. build/lib/build/lib/build/lib/searchkit/forms/__init__.py +5 -0
  19. build/lib/build/lib/build/lib/searchkit/forms/fields.py +55 -0
  20. build/lib/build/lib/build/lib/searchkit/forms/search.py +62 -0
  21. build/lib/build/lib/build/lib/searchkit/forms/searchkit.py +154 -0
  22. build/lib/build/lib/build/lib/searchkit/forms/utils.py +178 -0
  23. build/lib/build/lib/build/lib/searchkit/migrations/0001_initial.py +30 -0
  24. build/lib/build/lib/build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
  25. build/lib/build/lib/build/lib/searchkit/migrations/__init__.py +0 -0
  26. build/lib/build/lib/build/lib/searchkit/models.py +21 -0
  27. build/lib/build/lib/build/lib/searchkit/templatetags/__init__.py +0 -0
  28. build/lib/build/lib/build/lib/searchkit/templatetags/searchkit.py +20 -0
  29. build/lib/build/lib/build/lib/searchkit/tests.py +400 -0
  30. build/lib/build/lib/build/lib/searchkit/urls.py +7 -0
  31. build/lib/build/lib/build/lib/searchkit/views.py +23 -0
  32. build/lib/build/lib/example/example/__init__.py +0 -0
  33. build/lib/build/lib/example/example/admin.py +16 -0
  34. build/lib/build/lib/example/example/asgi.py +16 -0
  35. build/lib/build/lib/example/example/management/__init__.py +0 -0
  36. build/lib/build/lib/example/example/management/commands/__init__.py +0 -0
  37. build/lib/build/lib/example/example/management/commands/createtestdata.py +62 -0
  38. build/lib/build/lib/example/example/migrations/0001_initial.py +48 -0
  39. build/lib/build/lib/example/example/migrations/__init__.py +0 -0
  40. build/lib/build/lib/example/example/models.py +38 -0
  41. build/lib/build/lib/example/example/settings.py +125 -0
  42. build/lib/build/lib/example/example/urls.py +23 -0
  43. build/lib/build/lib/example/example/wsgi.py +16 -0
  44. build/lib/build/lib/searchkit/__init__.py +0 -0
  45. build/lib/build/lib/searchkit/__version__.py +16 -0
  46. build/lib/build/lib/searchkit/admin.py +30 -0
  47. build/lib/build/lib/searchkit/apps.py +6 -0
  48. build/lib/build/lib/searchkit/filters.py +28 -0
  49. build/lib/build/lib/searchkit/forms/__init__.py +5 -0
  50. build/lib/build/lib/searchkit/forms/fields.py +55 -0
  51. build/lib/build/lib/searchkit/forms/search.py +62 -0
  52. build/lib/build/lib/searchkit/forms/searchkit.py +154 -0
  53. build/lib/build/lib/searchkit/forms/utils.py +178 -0
  54. build/lib/build/lib/searchkit/migrations/0001_initial.py +30 -0
  55. build/lib/build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
  56. build/lib/build/lib/searchkit/migrations/__init__.py +0 -0
  57. build/lib/build/lib/searchkit/models.py +21 -0
  58. build/lib/build/lib/searchkit/templatetags/__init__.py +0 -0
  59. build/lib/build/lib/searchkit/templatetags/searchkit.py +20 -0
  60. build/lib/build/lib/searchkit/tests.py +400 -0
  61. build/lib/build/lib/searchkit/urls.py +7 -0
  62. build/lib/build/lib/searchkit/views.py +23 -0
  63. build/lib/example/example/admin.py +1 -1
  64. build/lib/searchkit/__version__.py +1 -1
  65. build/lib/searchkit/admin.py +4 -4
  66. build/lib/searchkit/filters.py +10 -13
  67. build/lib/searchkit/forms/__init__.py +5 -3
  68. build/lib/searchkit/forms/search.py +37 -17
  69. build/lib/searchkit/forms/searchkit.py +60 -95
  70. build/lib/searchkit/forms/utils.py +44 -15
  71. build/lib/searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
  72. build/lib/searchkit/models.py +2 -4
  73. build/lib/searchkit/templatetags/searchkit.py +0 -27
  74. build/lib/searchkit/tests.py +200 -50
  75. build/lib/searchkit/urls.py +1 -2
  76. build/lib/searchkit/views.py +11 -18
  77. {django_searchkit-1.0.dist-info → django_searchkit-1.2.dist-info}/METADATA +9 -24
  78. django_searchkit-1.2.dist-info/RECORD +130 -0
  79. example/example/admin.py +1 -1
  80. searchkit/__version__.py +1 -1
  81. searchkit/admin.py +4 -4
  82. searchkit/filters.py +10 -13
  83. searchkit/forms/__init__.py +5 -3
  84. searchkit/forms/search.py +37 -17
  85. searchkit/forms/searchkit.py +60 -95
  86. searchkit/forms/utils.py +44 -15
  87. searchkit/migrations/0002_rename_searchkitsearch_search.py +18 -0
  88. searchkit/models.py +2 -4
  89. searchkit/templatetags/searchkit.py +0 -27
  90. searchkit/tests.py +200 -50
  91. searchkit/urls.py +1 -2
  92. searchkit/views.py +11 -18
  93. django_searchkit-1.0.dist-info/RECORD +0 -66
  94. {django_searchkit-1.0.dist-info → django_searchkit-1.2.dist-info}/WHEEL +0 -0
  95. {django_searchkit-1.0.dist-info → django_searchkit-1.2.dist-info}/licenses/LICENCE +0 -0
  96. {django_searchkit-1.0.dist-info → django_searchkit-1.2.dist-info}/top_level.txt +0 -0
  97. {django_searchkit-1.0.dist-info → django_searchkit-1.2.dist-info}/zip-safe +0 -0
searchkit/tests.py CHANGED
@@ -1,19 +1,29 @@
1
+ from urllib.parse import urlencode
1
2
  from django.test import TestCase
2
3
  from django.contrib.contenttypes.models import ContentType
3
- from django import forms
4
+ from django.contrib.auth.models import User
5
+ from django.urls import reverse
4
6
  from example.models import ModelA
7
+ from example.management.commands.createtestdata import Command as CreateTestData
5
8
  from searchkit.forms.utils import FIELD_PLAN
6
9
  from searchkit.forms.utils import SUPPORTED_FIELDS
7
- from searchkit.forms.utils import SUPPORTED_RELATIONS
8
- from searchkit.forms import SearchkitSearchForm
9
- from searchkit.forms import SearchkitForm
10
- from searchkit.forms import SearchkitFormSet
10
+ from searchkit.forms.utils import ModelTree
11
+ from searchkit.forms import SearchForm
12
+ from searchkit.forms import SearchkitModelForm
13
+ from searchkit.forms import BaseSearchkitFormSet
14
+ from searchkit.forms import searchkit_formset_factory
15
+ from searchkit.models import Search
16
+ from searchkit import __version__
17
+
18
+
19
+ SearchkitFormSet = searchkit_formset_factory(model=ModelA)
20
+ SearchkitForm = SearchkitFormSet.form
11
21
 
12
22
 
13
23
  INITIAL_DATA = [
14
24
  dict(
15
25
  field='model_b__chars',
16
- operator='exact',
26
+ operator='contains',
17
27
  value='anytext',
18
28
  ),
19
29
  dict(
@@ -23,12 +33,12 @@ INITIAL_DATA = [
23
33
  ),
24
34
  dict(
25
35
  field='float',
26
- operator='exact',
36
+ operator='gt',
27
37
  value='0.3',
28
38
  ),
29
39
  dict(
30
40
  field='decimal',
31
- operator='exact',
41
+ operator='lte',
32
42
  value='1.23',
33
43
  ),
34
44
  dict(
@@ -43,20 +53,29 @@ INITIAL_DATA = [
43
53
  )
44
54
  ]
45
55
 
46
- add_prefix = lambda i: SearchkitFormSet(model=ModelA).add_prefix(i)
56
+ add_prefix = lambda i: SearchkitFormSet().add_prefix(i)
57
+ contenttype = ContentType.objects.get_for_model(ModelA)
47
58
  DEFAULT_PREFIX = SearchkitFormSet.get_default_prefix()
48
- FORM_DATA = {
49
- 'name': 'test search', # The name of the search.
50
- f'{DEFAULT_PREFIX}-TOTAL_FORMS': '6', # Data for the managment form.
51
- f'{DEFAULT_PREFIX}-INITIAL_FORMS': '1', # Data for the managment form.
52
- f'{DEFAULT_PREFIX}-contenttype': f'{ContentType.objects.get_for_model(ModelA).pk}',
53
- f'{add_prefix(1)}-value_0': '1', # Data for the range operator.
54
- f'{add_prefix(1)}-value_1': '123', # Data for the range operator.
55
- }
56
- for i, data in enumerate(INITIAL_DATA, 0):
57
- prefix = SearchkitFormSet(model=ModelA).add_prefix(i)
58
- for key, value in data.items():
59
- FORM_DATA.update({f'{prefix}-{key}': value})
59
+
60
+ def get_form_data(initial_data=INITIAL_DATA):
61
+ count = len(initial_data)
62
+ data = {
63
+ 'name': 'test search', # The name of the search.
64
+ 'searchkit_model': f'{contenttype.pk}', # Data for the searchkit-model form.
65
+ f'{DEFAULT_PREFIX}-TOTAL_FORMS': f'{count}', # Data for the managment form.
66
+ f'{DEFAULT_PREFIX}-INITIAL_FORMS': f'{count}', # Data for the managment form.
67
+ }
68
+ for i, d in enumerate(initial_data):
69
+ prefix = SearchkitFormSet().add_prefix(i)
70
+ for key, value in d.items():
71
+ if isinstance(value, list):
72
+ for i, v in enumerate(value):
73
+ data.update({f'{prefix}-{key}_{i}': v})
74
+ else:
75
+ data.update({f'{prefix}-{key}': value})
76
+ return data
77
+
78
+ FORM_DATA = get_form_data()
60
79
 
61
80
 
62
81
  class CheckFormMixin:
@@ -70,22 +89,19 @@ class CheckFormMixin:
70
89
  self.assertIn('value', form.fields)
71
90
  self.assertEqual(len(form.fields), 3)
72
91
 
73
- # Check choices of the model_field.
92
+ # Check field choices for the model.
74
93
  form_model_field = form.fields['field']
75
94
  self.assertTrue(form_model_field.choices)
76
95
  options = [c[0] for c in form_model_field.choices]
77
- for model_field in ModelA._meta.fields:
78
- if isinstance(model_field, tuple(SUPPORTED_FIELDS)):
79
- self.assertIn(model_field.name, options)
80
-
81
- # Check choices for relational lookups.
82
- for model_field in ModelA._meta.fields:
83
- if isinstance(model_field, tuple(SUPPORTED_RELATIONS)):
84
- remote_fields = model_field.remote_field.model._meta.fields
85
- for remote_field in remote_fields:
86
- if isinstance(model_field, tuple(SUPPORTED_FIELDS)):
87
- lookup_path = f'{model_field.name}__{remote_field.name}'
88
- self.assertIn(lookup_path, options)
96
+ tree = ModelTree(ModelA)
97
+ for node in tree.iterate():
98
+ for model_field in node.model._meta.fields:
99
+ if not any(isinstance(model_field, f) for f in SUPPORTED_FIELDS):
100
+ continue
101
+ if node.is_root:
102
+ self.assertIn(model_field.name, options)
103
+ else:
104
+ self.assertIn(f'{node.field_path}__{model_field.name}', options)
89
105
 
90
106
  # Check the field_plan choosen based on the model_field.
91
107
  field_plan = next(iter([p for t, p in FIELD_PLAN.items() if t(form.model_field)]))
@@ -102,7 +118,7 @@ class CheckFormMixin:
102
118
  class SearchkitFormTestCase(CheckFormMixin, TestCase):
103
119
 
104
120
  def test_blank_searchkitform(self):
105
- form = SearchkitForm(ModelA, prefix=add_prefix(0))
121
+ form = SearchkitForm(prefix=add_prefix(0))
106
122
  self.check_form(form)
107
123
 
108
124
  # Form should not be bound or valid.
@@ -113,7 +129,7 @@ class SearchkitFormTestCase(CheckFormMixin, TestCase):
113
129
  data = {
114
130
  f'{add_prefix(0)}-field': 'foobar',
115
131
  }
116
- form = SearchkitForm(ModelA, data, prefix=add_prefix(0))
132
+ form = SearchkitForm(data, prefix=add_prefix(0))
117
133
  self.check_form(form)
118
134
 
119
135
  # Form should be invalid.
@@ -127,7 +143,7 @@ class SearchkitFormTestCase(CheckFormMixin, TestCase):
127
143
  data = {
128
144
  f'{add_prefix(0)}-field': 'integer',
129
145
  }
130
- form = SearchkitForm(ModelA, data, prefix=add_prefix(0))
146
+ form = SearchkitForm(data, prefix=add_prefix(0))
131
147
  self.check_form(form)
132
148
 
133
149
  # Form should be invalid since no value data is provieded.
@@ -138,7 +154,7 @@ class SearchkitFormTestCase(CheckFormMixin, TestCase):
138
154
  f'{add_prefix(0)}-field': 'integer',
139
155
  f'{add_prefix(0)}-operator': 'foobar',
140
156
  }
141
- form = SearchkitForm(ModelA, data, prefix=add_prefix(0))
157
+ form = SearchkitForm(data, prefix=add_prefix(0))
142
158
  self.check_form(form)
143
159
 
144
160
  # Form should be invalid.
@@ -153,7 +169,7 @@ class SearchkitFormTestCase(CheckFormMixin, TestCase):
153
169
  f'{add_prefix(0)}-field': 'integer',
154
170
  f'{add_prefix(0)}-operator': 'exact',
155
171
  }
156
- form = SearchkitForm(ModelA, data, prefix=add_prefix(0))
172
+ form = SearchkitForm(data, prefix=add_prefix(0))
157
173
  self.check_form(form)
158
174
 
159
175
  # Form should be invalid since no value data is provieded.
@@ -165,7 +181,7 @@ class SearchkitFormTestCase(CheckFormMixin, TestCase):
165
181
  f'{add_prefix(0)}-operator': 'exact',
166
182
  f'{add_prefix(0)}-value': '123',
167
183
  }
168
- form = SearchkitForm(ModelA, data, prefix=add_prefix(0))
184
+ form = SearchkitForm(data, prefix=add_prefix(0))
169
185
  self.check_form(form)
170
186
 
171
187
  # Form should be valid.
@@ -177,7 +193,7 @@ class SearchkitFormTestCase(CheckFormMixin, TestCase):
177
193
  f'{add_prefix(0)}-operator': 'exact',
178
194
  f'{add_prefix(0)}-value': 'foobar',
179
195
  }
180
- form = SearchkitForm(ModelA, data, prefix=add_prefix(0))
196
+ form = SearchkitForm(data, prefix=add_prefix(0))
181
197
  self.check_form(form)
182
198
 
183
199
  # Form should be invalid.
@@ -204,7 +220,7 @@ class SearchkitFormSetTestCase(CheckFormMixin, TestCase):
204
220
  def test_searchkit_formset_with_invalid_data(self):
205
221
  data = FORM_DATA.copy()
206
222
  del data[f'{add_prefix(0)}-value']
207
- formset = SearchkitFormSet(data, model=ModelA)
223
+ formset = SearchkitFormSet(data)
208
224
  self.assertFalse(formset.is_valid())
209
225
 
210
226
  # Check error message in html.
@@ -212,7 +228,8 @@ class SearchkitFormSetTestCase(CheckFormMixin, TestCase):
212
228
  self.assertIn(errors, formset.forms[0].errors.values())
213
229
 
214
230
  def test_searchkit_formset_with_initial_data(self):
215
- formset = SearchkitFormSet(initial=INITIAL_DATA, model=ModelA)
231
+ formset_class = searchkit_formset_factory(model=ModelA, extra=0)
232
+ formset = formset_class(initial=INITIAL_DATA)
216
233
  self.assertFalse(formset.is_bound)
217
234
  self.assertFalse(formset.is_valid())
218
235
  self.assertEqual(len(formset.forms), len(INITIAL_DATA))
@@ -223,17 +240,17 @@ class SearchkitFormSetTestCase(CheckFormMixin, TestCase):
223
240
 
224
241
  class SearchkitSearchFormTestCase(TestCase):
225
242
  def test_searchkit_search_form_without_data(self):
226
- form = SearchkitSearchForm()
243
+ form = SearchForm()
227
244
  self.assertFalse(form.is_bound)
228
245
  self.assertFalse(form.is_valid())
229
- self.assertIsInstance(form.formset, SearchkitFormSet)
246
+ self.assertIsInstance(form.formset, BaseSearchkitFormSet)
230
247
  self.assertEqual(form.formset.model, None)
231
248
 
232
249
  def test_searchkit_search_form_with_data(self):
233
- form = SearchkitSearchForm(FORM_DATA)
250
+ form = SearchForm(FORM_DATA)
234
251
  self.assertTrue(form.is_bound)
235
252
  self.assertTrue(form.is_valid())
236
- self.assertIsInstance(form.formset, SearchkitFormSet)
253
+ self.assertIsInstance(form.formset, BaseSearchkitFormSet)
237
254
  self.assertEqual(form.formset.model, ModelA)
238
255
  self.assertEqual(form.instance.data, form.formset.cleaned_data)
239
256
 
@@ -242,9 +259,142 @@ class SearchkitSearchFormTestCase(TestCase):
242
259
  self.assertTrue(form.instance.pk)
243
260
 
244
261
  # Using the instance data as filter rules works.
245
- filter_rules = form.instance.get_filter_rules()
262
+ filter_rules = form.instance.as_lookups()
246
263
  self.assertEqual(len(filter_rules), len(INITIAL_DATA))
247
264
  for data in INITIAL_DATA:
248
265
  self.assertIn(f"{data['field']}__{data['operator']}", filter_rules)
249
- queryset = form.formset.model.objects.filter(**filter_rules)
250
- self.assertTrue(queryset.model == ModelA)
266
+
267
+
268
+ class SearchkitModelFormTestCase(TestCase):
269
+ def test_searchkit_model_form_choices(self):
270
+ form = SearchkitModelForm()
271
+ labels = [c[1] for c in form.fields['searchkit_model'].choices]
272
+ self.assertEqual(len(labels), 3)
273
+ self.assertEqual('select a model', labels[0].lower())
274
+ self.assertEqual('example | model a', labels[1].lower())
275
+ self.assertEqual('example | model b', labels[2].lower())
276
+
277
+
278
+ class AdminBackendTest(TestCase):
279
+ @classmethod
280
+ def setUpTestData(cls):
281
+ CreateTestData().handle()
282
+
283
+ def setUp(self):
284
+ admin = User.objects.get(username='admin')
285
+ self.client.force_login(admin)
286
+
287
+ def test_search_form(self):
288
+ url = reverse('admin:searchkit_search_add')
289
+ resp = self.client.get(url)
290
+ self.assertEqual(resp.status_code, 200)
291
+ select = b'<select name="searchkit_model" class="searchkit-reload-on-change" data-total-forms="1" required id="id_searchkit_model">'
292
+ for snippet in select.split(b' '):
293
+ self.assertIn(snippet, resp.content)
294
+
295
+ def test_search_form_with_initial(self):
296
+ url = reverse('admin:searchkit_search_add') + '?searchkit_model=1'
297
+ resp = self.client.get(url)
298
+ self.assertEqual(resp.status_code, 200)
299
+ select = '<select name="searchkit_model" class="searchkit-reload-on-change" data-total-forms="1" required id="id_searchkit_model">'
300
+ for snippet in select.split(' '):
301
+ self.assertIn(snippet, str(resp.content))
302
+ self.assertIn('<option value="1" selected>', str(resp.content))
303
+ self.assertIn('name="searchkit-example-modela-0-field"', str(resp.content))
304
+
305
+ def test_add_search(self):
306
+ # Create a search object via the admin backend.
307
+ url = reverse('admin:searchkit_search_add')
308
+ data = FORM_DATA.copy()
309
+ data['_save_and_apply'] = True
310
+ resp = self.client.post(url, data, follow=True)
311
+ self.assertEqual(resp.status_code, 200)
312
+ self.assertEqual(len(Search.objects.all()), 1)
313
+
314
+ # Change it via backend.
315
+ url = reverse('admin:searchkit_search_change', args=(1,))
316
+ data['name'] = 'Changed name'
317
+ data['searchkit-example-modela-0-field'] = 'boolean'
318
+ data['searchkit-example-modela-0-operator'] = 'exact'
319
+ data['searchkit-example-modela-0-value'] = 'true'
320
+ resp = self.client.post(url, data, follow=True)
321
+ self.assertEqual(resp.status_code, 200)
322
+ self.assertEqual(Search.objects.get(pk=1).name, data['name'])
323
+
324
+ # Will the search be listed in the admin filter?
325
+ url = reverse('admin:example_modela_changelist')
326
+ resp = self.client.get(url)
327
+ self.assertEqual(resp.status_code, 200)
328
+ self.assertIn('href="?search=1"', str(resp.content))
329
+ self.assertIn(data['name'], str(resp.content))
330
+
331
+
332
+ class SearchViewTest(TestCase):
333
+
334
+ def setUp(self):
335
+ self.initial = [
336
+ dict(
337
+ field='integer',
338
+ operator='exact',
339
+ value=1,
340
+ )
341
+ ]
342
+ self.initial_range = [
343
+ dict(
344
+ field='integer',
345
+ operator='range',
346
+ value=[1,3],
347
+ )
348
+ ]
349
+
350
+ def test_search_view_invalid_data(self):
351
+ initial = self.initial.copy()
352
+ initial[0]['value'] = 'no integer'
353
+ data = get_form_data(initial)
354
+ url_params = urlencode(data)
355
+ base_url = reverse('searchkit_form')
356
+ url = f'{base_url}?{url_params}'
357
+ resp = self.client.get(url)
358
+ self.assertEqual(resp.status_code, 200)
359
+ html_error = '<li>Enter a whole number.</li>'
360
+ self.assertInHTML(html_error, str(resp.content))
361
+
362
+ def test_search_view_missing_data(self):
363
+ initial = self.initial.copy()
364
+ del(initial[0]['value'])
365
+ data = get_form_data(initial)
366
+ url_params = urlencode(data)
367
+ base_url = reverse('searchkit_form')
368
+ url = f'{base_url}?{url_params}'
369
+ resp = self.client.get(url)
370
+ self.assertEqual(resp.status_code, 200)
371
+ html_error = '<li>This field is required.</li>'
372
+ self.assertInHTML(html_error, str(resp.content))
373
+
374
+ def test_search_view_with_range_operator(self):
375
+ data = get_form_data(self.initial_range)
376
+ url_params = urlencode(data)
377
+ base_url = reverse('searchkit_form')
378
+ url = f'{base_url}?{url_params}'
379
+ resp = self.client.get(url)
380
+ self.assertEqual(resp.status_code, 200)
381
+ html = '<input type="number" name="searchkit-example-modela-0-value_1" value="3" id="id_searchkit-example-modela-0-value_1">'
382
+ self.assertInHTML(html, str(resp.content))
383
+
384
+ def test_search_view_with_model(self):
385
+ data = get_form_data(self.initial)
386
+ data['searchkit_model'] = ContentType.objects.get_for_model(ModelA).pk
387
+ url_params = urlencode(data)
388
+ base_url = reverse('searchkit_form')
389
+ url = f'{base_url}?{url_params}'
390
+ resp = self.client.get(url)
391
+ self.assertEqual(resp.status_code, 200)
392
+
393
+ def test_search_view_with_invalid_model(self):
394
+ data = get_form_data(self.initial)
395
+ data['searchkit_model'] = 9999 # Non-existing content type.
396
+ url_params = urlencode(data)
397
+ base_url = reverse('searchkit_form')
398
+ url = f'{base_url}?{url_params}'
399
+ resp = self.client.get(url)
400
+ self.assertEqual(resp.status_code, 400)
searchkit/urls.py CHANGED
@@ -3,6 +3,5 @@ from .views import SearchkitAjaxView
3
3
 
4
4
 
5
5
  urlpatterns = [
6
- path("searchkit/form/", SearchkitAjaxView.as_view(), name="searchkit_form"),
7
- path("searchkit/form/<slug:app_label>/<slug:model_name>/", SearchkitAjaxView.as_view(), name="searchkit_form_model"),
6
+ path("searchkit/", SearchkitAjaxView.as_view(), name="searchkit_form"),
8
7
  ]
searchkit/views.py CHANGED
@@ -1,9 +1,11 @@
1
1
  from django.utils.translation import gettext_lazy as _
2
2
  from django.http import HttpResponse
3
- from django.http import Http404
3
+ from django.http import Http404, HttpResponseBadRequest
4
4
  from django.apps import apps
5
+ from django.contrib.contenttypes.models import ContentType
5
6
  from django.views.generic import View
6
- from .forms import SearchkitFormSet
7
+ from .forms import SearchkitModelForm
8
+ from .forms import searchkit_formset_factory
7
9
 
8
10
 
9
11
  # FIXME: Check permissions and authentication.
@@ -11,20 +13,11 @@ class SearchkitAjaxView(View):
11
13
  """
12
14
  Reload the formset via ajax.
13
15
  """
14
- def get_model(self, **kwargs):
15
- """
16
- Get the model from the URL parameters.
17
- """
18
- if all(k in kwargs for k in ('app_label', 'model_name')):
19
- app_label, model_name = kwargs['app_label'], kwargs['model_name']
20
- try:
21
- return apps.get_model(app_label=app_label, model_name=model_name)
22
- except LookupError:
23
- raise Http404(_('Model %s.%s not found') % (app_label, model_name))
24
- else:
25
- return None
26
-
27
16
  def get(self, request, **kwargs):
28
- model = self.get_model(**kwargs)
29
- formset = SearchkitFormSet(data=request.GET, model=model)
30
- return HttpResponse(formset.render())
17
+ model_form = SearchkitModelForm(data=self.request.GET)
18
+ if model_form.is_valid():
19
+ model = model_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,66 +0,0 @@
1
- build/lib/example/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- build/lib/example/example/admin.py,sha256=eTZ5mCA1ToGouqYgIf-6xDl2nvYAz7T9lU1Bnu_gP9c,462
3
- build/lib/example/example/asgi.py,sha256=mqLnzcCH9X-8UhDo5ZhcM8tF3jtALEGSjsBmphbkLcs,391
4
- build/lib/example/example/models.py,sha256=fB9X782cX01Yg2uIiPCVgrS8C7DkgG9U-9mxGtX3P7Q,1097
5
- build/lib/example/example/settings.py,sha256=du9bWbAceSvDkYeNYOc_3KxKL1p1se93TdAueT2RQ_E,3256
6
- build/lib/example/example/urls.py,sha256=D8LxjiTdJSA87AVh3BzpVo5E2aC30hlp20HshPDrfvU,800
7
- build/lib/example/example/wsgi.py,sha256=OAXHHeNQoj7FBDc5NMET39i_kIGpFrcdNDdY1thrh58,391
8
- build/lib/example/example/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- build/lib/example/example/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- build/lib/example/example/management/commands/createtestdata.py,sha256=IVtyUqak133h9Ua-Ag_GP8mw3dUdiDYAa2tGmLkKluU,2737
11
- build/lib/example/example/migrations/0001_initial.py,sha256=62tOi17oHGuqqLmSAYWLep6AMkxCJF6Tp13odghS7gI,2014
12
- build/lib/example/example/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
- build/lib/searchkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- build/lib/searchkit/__version__.py,sha256=gc1Zs90La5le9aifTQBFNfNHJn3SMI23TH8V2w-DQb4,661
15
- build/lib/searchkit/admin.py,sha256=75E1jff9LDmzRWNAJ-mpW4yChKtuE1L1WFXeCmCiibI,1218
16
- build/lib/searchkit/apps.py,sha256=17m6wPu5N59Dq4MLoWaQd0aNHEzMT4z4emQD632GIMA,150
17
- build/lib/searchkit/filters.py,sha256=TSsK8K0Oj7-gTfHLq7zLacZyXeGIM5MtLAHNMbjxYIo,1343
18
- build/lib/searchkit/models.py,sha256=pRCWOnwbxroUzyS8SFBSLyAUn0I7udiXP1wIxGB307A,894
19
- build/lib/searchkit/tests.py,sha256=kV4p6yoThhUH1Y_e8uH3bOrjG4Et9xcuyT9uwAl0Cqg,9237
20
- build/lib/searchkit/urls.py,sha256=bqF5UAcUFPfBCCi1CljHSLbjEqHqzj-ripmJ6VbtC7s,289
21
- build/lib/searchkit/views.py,sha256=EAA2yyRMsD8wva2F5lKo-X9-mxvFHOqn7bGazS-XNFk,1051
22
- build/lib/searchkit/forms/__init__.py,sha256=BEogTSn1yPliVfgQ5ywXG9lTo1wtGABlYNYPStBRP9w,116
23
- build/lib/searchkit/forms/fields.py,sha256=TpFZdAyWKaMhOoZFTuy5ODNrW6RLy289Lk1fcMSGJ20,1621
24
- build/lib/searchkit/forms/search.py,sha256=GaGW_srrYD2qth6smThjwW0CwO5bNBxh5kLME-atFZY,1316
25
- build/lib/searchkit/forms/searchkit.py,sha256=asiS2hEqCTagEWmZ_1NZk-DCVOACxbpbYDsHNh-iBNQ,7237
26
- build/lib/searchkit/forms/utils.py,sha256=QtCsCveq20eaHc-nWP36-ICPq_CgItr5ckNp2DQ6LAc,4381
27
- build/lib/searchkit/migrations/0001_initial.py,sha256=cP886X1UMrDTf-KxfHfuoQ4b1T1lhdNJyVNOGMNyWNw,1093
28
- build/lib/searchkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
- build/lib/searchkit/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
- build/lib/searchkit/templatetags/searchkit.py,sha256=QA-hqxsHJ7vRoRLqFmGeRqRErkywv1pmepVFKqZuKcg,1238
31
- django_searchkit-1.0.dist-info/licenses/LICENCE,sha256=oqmlRYPA5GHG2T1yp8fPFGQdOO-NnJPdGITeFiJYWLg,1521
32
- example/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- example/example/admin.py,sha256=eTZ5mCA1ToGouqYgIf-6xDl2nvYAz7T9lU1Bnu_gP9c,462
34
- example/example/asgi.py,sha256=mqLnzcCH9X-8UhDo5ZhcM8tF3jtALEGSjsBmphbkLcs,391
35
- example/example/models.py,sha256=fB9X782cX01Yg2uIiPCVgrS8C7DkgG9U-9mxGtX3P7Q,1097
36
- example/example/settings.py,sha256=du9bWbAceSvDkYeNYOc_3KxKL1p1se93TdAueT2RQ_E,3256
37
- example/example/urls.py,sha256=D8LxjiTdJSA87AVh3BzpVo5E2aC30hlp20HshPDrfvU,800
38
- example/example/wsgi.py,sha256=OAXHHeNQoj7FBDc5NMET39i_kIGpFrcdNDdY1thrh58,391
39
- example/example/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- example/example/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- example/example/management/commands/createtestdata.py,sha256=IVtyUqak133h9Ua-Ag_GP8mw3dUdiDYAa2tGmLkKluU,2737
42
- example/example/migrations/0001_initial.py,sha256=62tOi17oHGuqqLmSAYWLep6AMkxCJF6Tp13odghS7gI,2014
43
- example/example/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
44
- searchkit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
45
- searchkit/__version__.py,sha256=gc1Zs90La5le9aifTQBFNfNHJn3SMI23TH8V2w-DQb4,661
46
- searchkit/admin.py,sha256=75E1jff9LDmzRWNAJ-mpW4yChKtuE1L1WFXeCmCiibI,1218
47
- searchkit/apps.py,sha256=17m6wPu5N59Dq4MLoWaQd0aNHEzMT4z4emQD632GIMA,150
48
- searchkit/filters.py,sha256=TSsK8K0Oj7-gTfHLq7zLacZyXeGIM5MtLAHNMbjxYIo,1343
49
- searchkit/models.py,sha256=pRCWOnwbxroUzyS8SFBSLyAUn0I7udiXP1wIxGB307A,894
50
- searchkit/tests.py,sha256=kV4p6yoThhUH1Y_e8uH3bOrjG4Et9xcuyT9uwAl0Cqg,9237
51
- searchkit/urls.py,sha256=bqF5UAcUFPfBCCi1CljHSLbjEqHqzj-ripmJ6VbtC7s,289
52
- searchkit/views.py,sha256=EAA2yyRMsD8wva2F5lKo-X9-mxvFHOqn7bGazS-XNFk,1051
53
- searchkit/forms/__init__.py,sha256=BEogTSn1yPliVfgQ5ywXG9lTo1wtGABlYNYPStBRP9w,116
54
- searchkit/forms/fields.py,sha256=TpFZdAyWKaMhOoZFTuy5ODNrW6RLy289Lk1fcMSGJ20,1621
55
- searchkit/forms/search.py,sha256=GaGW_srrYD2qth6smThjwW0CwO5bNBxh5kLME-atFZY,1316
56
- searchkit/forms/searchkit.py,sha256=asiS2hEqCTagEWmZ_1NZk-DCVOACxbpbYDsHNh-iBNQ,7237
57
- searchkit/forms/utils.py,sha256=QtCsCveq20eaHc-nWP36-ICPq_CgItr5ckNp2DQ6LAc,4381
58
- searchkit/migrations/0001_initial.py,sha256=cP886X1UMrDTf-KxfHfuoQ4b1T1lhdNJyVNOGMNyWNw,1093
59
- searchkit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- searchkit/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
- searchkit/templatetags/searchkit.py,sha256=QA-hqxsHJ7vRoRLqFmGeRqRErkywv1pmepVFKqZuKcg,1238
62
- django_searchkit-1.0.dist-info/METADATA,sha256=L0LZJphBHkr9H_5emLV_bs01GcvqN6miju_RT0vXjv4,4015
63
- django_searchkit-1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
64
- django_searchkit-1.0.dist-info/top_level.txt,sha256=-4gF42VIaG-ckn2rb2wFa7LhxIZymHBsYVedNOr_NIY,29
65
- django_searchkit-1.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
66
- django_searchkit-1.0.dist-info/RECORD,,