django-search-visualizer 0.1__tar.gz → 0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/PKG-INFO +1 -1
  2. django_search_visualizer-0.2/README.md +54 -0
  3. django_search_visualizer-0.2/django_search_visualizer/admin.py +11 -0
  4. django_search_visualizer-0.2/django_search_visualizer/migrations/0001_initial.py +25 -0
  5. django_search_visualizer-0.2/django_search_visualizer/migrations/__init__.py +0 -0
  6. django_search_visualizer-0.2/django_search_visualizer/models.py +9 -0
  7. django_search_visualizer-0.2/django_search_visualizer/static/logic.js +401 -0
  8. django_search_visualizer-0.2/django_search_visualizer/templates/model_search.html +157 -0
  9. django_search_visualizer-0.2/django_search_visualizer/urls.py +11 -0
  10. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/views.py +25 -7
  11. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/PKG-INFO +1 -1
  12. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/SOURCES.txt +9 -1
  13. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/top_level.txt +1 -0
  14. django_search_visualizer-0.2/main/__init__.py +0 -0
  15. django_search_visualizer-0.2/main/asgi.py +16 -0
  16. django_search_visualizer-0.2/main/settings.py +118 -0
  17. django_search_visualizer-0.2/main/urls.py +23 -0
  18. django_search_visualizer-0.2/main/wsgi.py +16 -0
  19. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/setup.py +2 -2
  20. django_search_visualizer-0.1/README.md +0 -3
  21. django_search_visualizer-0.1/django_search_visualizer/admin.py +0 -3
  22. django_search_visualizer-0.1/django_search_visualizer/models.py +0 -3
  23. django_search_visualizer-0.1/django_search_visualizer/templates/model_search.html +0 -437
  24. django_search_visualizer-0.1/django_search_visualizer/urls.py +0 -11
  25. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/LICENSE +0 -0
  26. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/__init__.py +0 -0
  27. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/apps.py +0 -0
  28. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/tests.py +0 -0
  29. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/dependency_links.txt +0 -0
  30. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/requires.txt +0 -0
  31. {django_search_visualizer-0.1 → django_search_visualizer-0.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-search-visualizer
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: A Django web view for Models data search
5
5
  Home-page: https://github.com/filipefraqueiro/django-search-visualizer
6
6
  Author: Filipe Fraqueiro
@@ -0,0 +1,54 @@
1
+ # django-search-visualizer
2
+
3
+ A Django web view for Models data search
4
+
5
+ ## How To setup
6
+
7
+ 1. Add the app **django_search_visualizer** to the django **INSTALLED_APPS** in **settings.py**
8
+
9
+ ![howto_setup_1](images/howto_setup_1.png)
10
+
11
+ 2. Include the app urls **django_search_visualizer.urls** in the django main app **urlpatterns** list in **urls.py**
12
+
13
+ ![howto_setup_2](images/howto_setup_2.png)
14
+
15
+ ## How To Use
16
+
17
+ 1. Navigate to **/model-search**
18
+
19
+ ![step_1](images/howto_1.png)
20
+
21
+ 2. Select an App from the existing Apps list
22
+
23
+ ![step_2](images/howto_2.png)
24
+
25
+ 3. Select a Model from the existing Models list
26
+
27
+ ![step_3](images/howto_3.png)
28
+
29
+ 4. Select the fields that you want to see and configure the filters is required
30
+
31
+ ![step_4](images/howto_4.png)
32
+
33
+ The **Look For** options are the accepted ones by django filters
34
+
35
+ 5. Press "Submit" or "Download" button
36
+
37
+ ![step_5](images/howto_5.png)
38
+
39
+ 6. View the data
40
+
41
+ ![step_6](images/howto_6.png)
42
+
43
+ 7. You can click in the table headers to sort them
44
+
45
+ ![step_7](images/howto_7.png)
46
+
47
+ ## Notes
48
+
49
+ To use the filter **in** a list is required like:
50
+ ["test1", "test2"]
51
+
52
+ ## To Do
53
+
54
+ - Include an option to plot the data
@@ -0,0 +1,11 @@
1
+ from django.contrib import admin
2
+
3
+ import django_search_visualizer.models
4
+
5
+ # Register your models here.
6
+ class testAdmin(admin.ModelAdmin):
7
+ list_display = ["id", "created", "modified", "field1", "field2"]
8
+ list_filter = []
9
+ search_fields = []
10
+
11
+ admin.site.register(django_search_visualizer.models.test, testAdmin)
@@ -0,0 +1,25 @@
1
+ # Generated by Django 6.0.1 on 2026-02-06 09:08
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ initial = True
9
+
10
+ dependencies = [
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name='test',
16
+ fields=[
17
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
18
+ ('created', models.DateTimeField(auto_now_add=True)),
19
+ ('modified', models.DateTimeField(auto_now=True)),
20
+ ('field1', models.IntegerField(blank=True, null=True)),
21
+ ('field2', models.CharField(null=True)),
22
+ ('field3', models.DateField(auto_now_add=True, null=True)),
23
+ ],
24
+ ),
25
+ ]
@@ -0,0 +1,9 @@
1
+ from django.db import models
2
+
3
+ # Create your models here.
4
+ class test(models.Model):
5
+ created = models.DateTimeField(null=False, editable=False, auto_now_add=True)
6
+ modified = models.DateTimeField(null=False, auto_now=True)
7
+ field1 = models.IntegerField(null=True, blank=True)
8
+ field2 = models.CharField(null=True, blank=False, choices=None)
9
+ field3 = models.DateField(null=True, blank=False, auto_now_add=True)
@@ -0,0 +1,401 @@
1
+ function sortTable(colIndex, header) {
2
+ const table = document.getElementById("results_table");
3
+ const rows = Array.from(table.querySelectorAll("tbody tr"));
4
+ const isAsc = !header.classList.contains("sorted-asc");
5
+
6
+ // Remove previous sort indicators
7
+ table.querySelectorAll("th").forEach(th => th.classList.remove("sorted-asc", "sorted-desc"));
8
+
9
+ // Sort rows
10
+ rows.sort((a, b) => {
11
+ let x = a.children[colIndex].innerText.trim();
12
+ let y = b.children[colIndex].innerText.trim();
13
+
14
+ // Convert to numbers if numeric
15
+ if (!isNaN(x) && !isNaN(y)) { x = Number(x); y = Number(y); }
16
+
17
+ // Convert date strings to timestamps
18
+ if (!isNaN(Date.parse(x)) && !isNaN(Date.parse(y))) {
19
+ x = new Date(x);
20
+ y = new Date(y);
21
+ }
22
+
23
+ return isAsc ? (x > y ? 1 : -1) : (x < y ? 1 : -1);
24
+ });
25
+
26
+ // Append sorted rows
27
+ const tbody = table.querySelector("tbody");
28
+ rows.forEach(row => tbody.appendChild(row));
29
+
30
+ // Set sort class
31
+ header.classList.add(isAsc ? "sorted-asc" : "sorted-desc");
32
+ }
33
+
34
+
35
+ function toggleDarkMode(self) {
36
+ if (self.checked) {
37
+ document.documentElement.setAttribute('data-bs-theme', 'dark');
38
+ localStorage.setItem("darkMode", true);
39
+ }
40
+ else {
41
+ document.documentElement.setAttribute('data-bs-theme', 'light');
42
+ localStorage.setItem("darkMode", false);
43
+ }
44
+ }
45
+
46
+
47
+ // load saved setting
48
+ window.onload = function () {
49
+ if (localStorage.getItem("darkMode") === "true") {
50
+ document.documentElement.setAttribute('data-bs-theme', 'dark');
51
+ document.getElementById("darkModeToggle").checked = true;
52
+ }
53
+
54
+ document.getElementById("select_all").addEventListener("change", function () {
55
+ const fields_table = document.getElementById("fields_table");
56
+ const fields_cbs = Array.from(fields_table.getElementsByClassName("field_checkbox"));
57
+ fields_cbs.forEach(element => {
58
+ element.checked = this.checked;
59
+ });
60
+ });
61
+ };
62
+
63
+
64
+ async function updateModels() {
65
+ const app = document.getElementById("appSelect").value;
66
+
67
+ if (!app) return;
68
+
69
+ const response = await fetch(`/get-app-models?app=${app}`);
70
+ const data = await response.json();
71
+
72
+ const select_all = document.getElementById("select_all");
73
+ select_all.checked = false;
74
+
75
+ const fields_table = document.getElementById("fields_table");
76
+ fields_table.tHead.innerHTML = "";
77
+ fields_table.tBodies[0].innerHTML = "";
78
+
79
+ const results_count = document.getElementById("results_count");
80
+ results_count.innerHTML = "Results: ";
81
+
82
+ const results_cmd = document.getElementById("results_cmd");
83
+ results_cmd.innerHTML = "Command: ";
84
+
85
+ const results_table = document.getElementById("results_table");
86
+ results_table.tHead.innerHTML = "";
87
+ results_table.tBodies[0].innerHTML = "";
88
+
89
+ if (data.models) {
90
+ const modelSelect = document.getElementById("modelSelect");
91
+ modelSelect.innerHTML = ""
92
+
93
+ const opt = document.createElement("option");
94
+ opt.value = "";
95
+ opt.textContent = "-- Select Model --";
96
+ modelSelect.appendChild(opt);
97
+
98
+ data.models.forEach((model, i) => {
99
+ const opt = document.createElement("option");
100
+ opt.value = model.toLowerCase();
101
+ opt.textContent = model;
102
+ modelSelect.appendChild(opt);
103
+ });
104
+ }
105
+ }
106
+
107
+
108
+ async function updateFields() {
109
+ const app = document.getElementById("appSelect").value;
110
+ const model = document.getElementById("modelSelect").value;
111
+
112
+ if (!model) return;
113
+
114
+ const response = await fetch(`/get-model-fields?app=${app}&model=${model}`);
115
+ const data = await response.json();
116
+
117
+ const select_all = document.getElementById("select_all");
118
+ select_all.checked = false;
119
+
120
+ const results_count = document.getElementById("results_count");
121
+ results_count.innerHTML = "Results: ";
122
+
123
+ const results_table = document.getElementById("results_table");
124
+ results_table.tHead.innerHTML = "";
125
+ results_table.tBodies[0].innerHTML = "";
126
+
127
+ if (data.fields) {
128
+ const fields_table = document.getElementById("fields_table");
129
+ fields_table.tHead.innerHTML = "";
130
+ fields_table.tBodies[0].innerHTML = "";
131
+ fields_table.border = "1";
132
+ fields_table.style.borderCollapse = "collapse";
133
+ fields_table.style.marginTop = "10px";
134
+
135
+ tr = document.createElement("tr");
136
+
137
+ cell = document.createElement("th");
138
+ cell.style.padding = "6px 10px";
139
+ cell.textContent = "Fields";
140
+ tr.appendChild(cell);
141
+
142
+ cell = document.createElement("th");
143
+ cell.style.padding = "6px 10px";
144
+ cell.textContent = "Look For";
145
+ tr.appendChild(cell);
146
+
147
+ cell = document.createElement("th");
148
+ cell.style.padding = "6px 10px";
149
+ cell.textContent = "Look For Case";
150
+ tr.appendChild(cell);
151
+
152
+ fields_table.tHead.appendChild(tr);
153
+
154
+ data.fields.forEach((f, i) => {
155
+ var tr = document.createElement("tr");
156
+
157
+ // create the field cell
158
+ var cb = document.createElement("input");
159
+ cb.type = "checkbox";
160
+ cb.name = f;
161
+ cb.id = f;
162
+ cb.classList.add("field_checkbox");
163
+ cb.classList.add("form-check-input");
164
+
165
+ var lb = document.createElement("label");
166
+ lb.for = f;
167
+ lb.innerText = "-".repeat(f.split("__").length - 1) + f + " (" + data.fields_types[i] + ")";
168
+ lb.classList.add("form-check-label");
169
+
170
+ var div = document.createElement("div");
171
+ div.classList.add("form-check");
172
+ div.appendChild(cb);
173
+ div.appendChild(lb);
174
+
175
+ var cell = document.createElement("td");
176
+ cell.style.padding = "6px 10px";
177
+ cell.appendChild(div);
178
+ tr.appendChild(cell);
179
+
180
+ // create the look for case cell
181
+ const select = document.createElement("select");
182
+ select.id = "case_select";
183
+ select.classList.add("form-select");
184
+
185
+ const field_look_ups = ["", "contains", "icontains", "date", "day", "endswith", "iendswith", "exact", "iexact", "in", "isnull", "gt", "gte", "hour", "lt", "lte", "minute", "month", "quarter", "range", "regex", "iregex", "second", "startswith", "istartswith", "time", "week", "week_day", "iso_week_day", "year", "iso_year"];
186
+
187
+ field_look_ups.forEach(item => {
188
+ const opt = document.createElement("option");
189
+ opt.value = item.toLowerCase();
190
+ opt.textContent = item;
191
+ select.appendChild(opt);
192
+ });
193
+
194
+ cell = document.createElement("td");
195
+ cell.style.padding = "6px 10px";
196
+ cell.appendChild(select);
197
+ tr.appendChild(cell);
198
+
199
+ // create the look for cell
200
+ var look_for = document.createElement("input");
201
+ look_for.type = "text";
202
+ look_for.classList.add("form-control");
203
+
204
+ cell = document.createElement("td");
205
+ cell.style.padding = "6px 10px";
206
+ cell.appendChild(look_for);
207
+ tr.appendChild(cell);
208
+
209
+ // append the new row
210
+ fields_table.tBodies[0].appendChild(tr);
211
+ });
212
+
213
+ } else {
214
+ fieldList.innerHTML = `<li>No fields found</li>`;
215
+ }
216
+ }
217
+
218
+
219
+ async function get_data(download, page = 1) {
220
+ const app = document.getElementById("appSelect").value;
221
+ const model = document.getElementById("modelSelect").value;
222
+ const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
223
+
224
+ if (!model) return;
225
+
226
+ body = {
227
+ "app": app,
228
+ "model": model,
229
+ "fields": tableToJson("fields_table"),
230
+ "page": page,
231
+ }
232
+ // console.log(body);
233
+
234
+ if (download) {
235
+ const response = await fetch("/download-model-data", {
236
+ method: 'POST',
237
+ headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
238
+ body: JSON.stringify(body)
239
+ });
240
+
241
+ if (!response.ok) {
242
+ console.log(`Response status: ${response.status}`);
243
+ }
244
+
245
+ const data = await response.text();
246
+
247
+ if (data) {
248
+ const filename = model + "_" + Date.now();
249
+ downloadCSV(data, filename);
250
+ } else {
251
+ const results = document.getElementById("results");
252
+ results.innerHTML = `<li>Error</li>`;
253
+ }
254
+ }
255
+ else {
256
+ const response = await fetch("/get-model-data", {
257
+ method: 'POST',
258
+ headers: { 'Content-Type': 'application/json', 'X-CSRFToken': csrfToken },
259
+ body: JSON.stringify(body)
260
+ });
261
+
262
+ if (!response.ok) {
263
+ console.log(`Response status: ${response.status}`);
264
+ }
265
+
266
+ const data = await response.json();
267
+
268
+ const results_count = document.getElementById("results_count");
269
+ results_count.innerHTML = "Results: ";
270
+
271
+ const results_cmd = document.getElementById("results_cmd");
272
+ results_cmd.innerHTML = "Command: ";
273
+
274
+ const results_table = document.getElementById("results_table");
275
+ results_table.tHead.innerHTML = "";
276
+ results_table.tBodies[0].innerHTML = "";
277
+
278
+ const first_page = document.getElementById("first_page");
279
+ first_page.innerHTML = "";
280
+ const first_page_link = document.getElementById("first_page_link");
281
+ const previous_page = document.getElementById("previous_page");
282
+ previous_page.innerHTML = "";
283
+ const previous_page_link = document.getElementById("previous_page_link");
284
+ const current_page = document.getElementById("current_page");
285
+ current_page.innerHTML = "";
286
+ const current_page_link = document.getElementById("current_page_link");
287
+ const next_page = document.getElementById("next_page");
288
+ next_page.innerHTML = "";
289
+ const next_page_link = document.getElementById("next_page_link");
290
+ const last_page = document.getElementById("last_page");
291
+ last_page.innerHTML = "";
292
+ const last_page_link = document.getElementById("last_page_link");
293
+
294
+ if (data.count) {
295
+ results_count.innerHTML = "Results: " + data.count;
296
+ }
297
+
298
+ if (data.cmd) {
299
+ results_cmd.innerHTML = "Command: " + data.cmd;
300
+ }
301
+
302
+ if (data.pagination) {
303
+ // console.log(data.pagination);
304
+ first_page.innerHTML = 1;
305
+ first_page_link.setAttribute("onclick", "get_data(false, " + 1 + ")")
306
+ previous_page.innerHTML = data.pagination.previous_page_number;
307
+ previous_page_link.setAttribute("onclick", "get_data(false, " + data.pagination.previous_page_number + ")")
308
+ current_page.innerHTML = data.pagination.page_num;
309
+ current_page_link.setAttribute("onclick", "get_data(false, " + data.pagination.page_num + ")")
310
+ next_page.innerHTML = data.pagination.next_page_number;
311
+ next_page_link.setAttribute("onclick", "get_data(false, " + data.pagination.next_page_number + ")")
312
+ last_page.innerHTML = data.pagination.num_pages;
313
+ last_page_link.setAttribute("onclick", "get_data(false, " + data.pagination.num_pages + ")")
314
+ }
315
+
316
+ if (data.results) {
317
+ results_table.border = "1";
318
+ results_table.style.borderCollapse = "collapse";
319
+ results_table.style.marginTop = "10px";
320
+
321
+ data.results.forEach((row, i) => {
322
+ var tr = document.createElement("tr");
323
+
324
+ row.forEach((col, j) => {
325
+ if (i === 0) {
326
+ var cell = document.createElement("th");
327
+ cell.addEventListener("click", () => sortTable(j, cell));
328
+ cell.textContent = col;
329
+ cell.style.padding = "6px 10px";
330
+ tr.appendChild(cell);
331
+ results_table.tHead.appendChild(tr);
332
+ }
333
+ else {
334
+ var cell = document.createElement("td");
335
+ cell.textContent = col;
336
+ cell.style.padding = "6px 10px";
337
+ tr.appendChild(cell);
338
+ results_table.tBodies[0].appendChild(tr);
339
+ }
340
+ });
341
+ });
342
+ } else {
343
+ console.log("Error");
344
+ }
345
+ }
346
+ }
347
+
348
+
349
+ function downloadCSV(data, filename) {
350
+ const blob = new Blob([data], { type: "text/csv" });
351
+ const url = URL.createObjectURL(blob);
352
+ const a = document.createElement("a");
353
+ a.href = url;
354
+ a.download = filename;
355
+ a.click();
356
+ URL.revokeObjectURL(url);
357
+ }
358
+
359
+
360
+ function tableToJson(tableId) {
361
+ const table = document.getElementById(tableId);
362
+ const data = {};
363
+
364
+ const headers = Array.from(table.querySelectorAll("thead th")).map(th => th.textContent.trim());
365
+
366
+ const rows = table.querySelectorAll("tbody tr");
367
+
368
+ rows.forEach(row => {
369
+ const rowData = {};
370
+ const cells = row.querySelectorAll("td");
371
+
372
+ cells.forEach((cell, i) => {
373
+ const input = cell.getElementsByTagName("input");
374
+ const select = cell.getElementsByTagName("select");
375
+
376
+ if (input.length > 0) {
377
+ if (input[0].type == "checkbox") {
378
+ field = input[0].name;
379
+ rowData["checked"] = input[0].checked;
380
+ }
381
+ else if (input[0].type == "text") {
382
+ rowData["look_for"] = input[0].value;
383
+ }
384
+ }
385
+ else if (select.length > 0) {
386
+ rowData["case"] = select[0].value;
387
+ }
388
+ });
389
+
390
+ data[field] = rowData;
391
+ });
392
+
393
+ return data;
394
+ }
395
+
396
+
397
+ document.addEventListener("keydown", function (event) {
398
+ if (event.key === "Enter") {
399
+ document.getElementById("submit").click();
400
+ }
401
+ });
@@ -0,0 +1,157 @@
1
+ <!DOCTYPE html>
2
+ <html data-bs-theme="light">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <title>Django Search Visualizer</title>
7
+
8
+ <!-- bootstrap 5 -->
9
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet"
10
+ integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
11
+
12
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js"
13
+ integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI"
14
+ crossorigin="anonymous"></script>
15
+
16
+ {% load static %}
17
+
18
+ <script type="text/javascript" src="{% static "logic.js" %}"></script>
19
+
20
+ <style>
21
+ th {
22
+ cursor: pointer;
23
+ }
24
+
25
+ th.sorted-asc::after {
26
+ content: " ▲";
27
+ }
28
+
29
+ th.sorted-desc::after {
30
+ content: " ▼";
31
+ }
32
+ </style>
33
+ </head>
34
+
35
+ <body class="container" id="pageBody">
36
+ <br>
37
+ <input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
38
+
39
+ <div>
40
+ <div class="row">
41
+ <div class="col">
42
+ <h5>Select an APP:</h5>
43
+ <select class="form-select" aria-label="appSelect" id="appSelect" onclick="updateModels()">
44
+ <option value="">-- Select APP --</option>
45
+ {% for a in apps %}
46
+ <option value="{{ a }}">{{ a }}</option>
47
+ {% endfor %}
48
+ </select>
49
+ </div>
50
+
51
+ <div class="col">
52
+ <h5>Select a Model:</h5>
53
+ <select class="form-select" aria-label="modelSelect" id="modelSelect" onclick="updateFields()">
54
+ <option value="">-- Select Model --</option>
55
+ </select>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <br>
61
+
62
+ <div>
63
+ <div class="row">
64
+ <div class="col">
65
+ <h5>Select Fields:</h5>
66
+ <div class="form-check">
67
+ <input class="form-check-input" type="checkbox" id="select_all">
68
+ <label class="form-check-label" for="select_all">
69
+ Select All Fields
70
+ </label>
71
+ </div>
72
+
73
+ <div id="fields"></div>
74
+
75
+ <table class="table" id="fields_table">
76
+ <thead></thead>
77
+ <tbody></tbody>
78
+ </table>
79
+ </div>
80
+
81
+ <div class="col">
82
+ <h5>Choose an Action:</h5>
83
+ <div class="row">
84
+ <div class="col">
85
+ <button type="button" class="btn btn-primary" style="width: 100%;" onclick="get_data(false)"
86
+ id="submit">Submit</button>
87
+ </div>
88
+ <div class="col">
89
+ <button type="button" class="btn btn-primary" style="width: 100%;" onclick="get_data(true)"
90
+ id="download">Download</button>
91
+ </div>
92
+ </div>
93
+ <br>
94
+ <h5 id="results_count">Results:</h5>
95
+ <br>
96
+ <h5 id="results_cmd">Command:</h5>
97
+ <br>
98
+ <h5 id="results_error">Error:</h5>
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <br>
104
+
105
+ <table class="table table-striped" id="results_table">
106
+ <thead></thead>
107
+ <tbody></tbody>
108
+ </table>
109
+
110
+ <div id="pagination">
111
+ <div class="row">
112
+ <div class="col">
113
+ <a role="button" onclick="get_data(false, 1)" id="first_page_link">
114
+ <p>First Page: <b id="first_page"></b></p>
115
+ </a>
116
+ </div>
117
+ <div class="col">
118
+ <a role="button" onclick="get_data(false, 1)" id="previous_page_link">
119
+ <p>Previous Page: <b id="previous_page"></b></p>
120
+ </a>
121
+ </div>
122
+ <div class="col">
123
+ <a role="button" onclick="get_data(false, 1)" id="current_page_link">
124
+ <p>Current Page: <b id="current_page"></b></p>
125
+ </a>
126
+ </div>
127
+ <div class="col">
128
+ <a role="button" onclick="get_data(false, 1)" id="next_page_link">
129
+ <p>Next Page: <b id="next_page"></b></p>
130
+ </a>
131
+ </div>
132
+ <div class="col">
133
+ <a role="button" onclick="get_data(false, 1)" id="last_page_link">
134
+ <p>Last Page: <b id="last_page"></b></p>
135
+ </a>
136
+ </div>
137
+ </div>
138
+
139
+ <br>
140
+
141
+ <!-- Dark Mode Toggle -->
142
+ <div class="form-check">
143
+ <input class="form-check-input" type="checkbox" id="darkModeToggle" onclick="toggleDarkMode(this)">
144
+ <label class="form-check-label" for="darkModeToggle">
145
+ Dark Mode
146
+ </label>
147
+ </div>
148
+
149
+ <br>
150
+ <br>
151
+ </body>
152
+
153
+ <script>
154
+
155
+ </script>
156
+
157
+ </html>
@@ -0,0 +1,11 @@
1
+ from django.urls import path
2
+
3
+ from django_search_visualizer import views
4
+
5
+ urlpatterns = [
6
+ path("model-search", views.model_search, name="model_search"),
7
+ path("get-app-models", views.get_app_models, name="get_app_models"),
8
+ path("get-model-fields", views.get_model_fields, name="get_model_fields"),
9
+ path("get-model-data", views.get_model_data, name="get_model_data"),
10
+ path("download-model-data", views.download_model_data, name="download_model_data"),
11
+ ]