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.
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/PKG-INFO +1 -1
- django_search_visualizer-0.2/README.md +54 -0
- django_search_visualizer-0.2/django_search_visualizer/admin.py +11 -0
- django_search_visualizer-0.2/django_search_visualizer/migrations/0001_initial.py +25 -0
- django_search_visualizer-0.2/django_search_visualizer/migrations/__init__.py +0 -0
- django_search_visualizer-0.2/django_search_visualizer/models.py +9 -0
- django_search_visualizer-0.2/django_search_visualizer/static/logic.js +401 -0
- django_search_visualizer-0.2/django_search_visualizer/templates/model_search.html +157 -0
- django_search_visualizer-0.2/django_search_visualizer/urls.py +11 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/views.py +25 -7
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/PKG-INFO +1 -1
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/SOURCES.txt +9 -1
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/top_level.txt +1 -0
- django_search_visualizer-0.2/main/__init__.py +0 -0
- django_search_visualizer-0.2/main/asgi.py +16 -0
- django_search_visualizer-0.2/main/settings.py +118 -0
- django_search_visualizer-0.2/main/urls.py +23 -0
- django_search_visualizer-0.2/main/wsgi.py +16 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/setup.py +2 -2
- django_search_visualizer-0.1/README.md +0 -3
- django_search_visualizer-0.1/django_search_visualizer/admin.py +0 -3
- django_search_visualizer-0.1/django_search_visualizer/models.py +0 -3
- django_search_visualizer-0.1/django_search_visualizer/templates/model_search.html +0 -437
- django_search_visualizer-0.1/django_search_visualizer/urls.py +0 -11
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/LICENSE +0 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/__init__.py +0 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/apps.py +0 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer/tests.py +0 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/dependency_links.txt +0 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/django_search_visualizer.egg-info/requires.txt +0 -0
- {django_search_visualizer-0.1 → django_search_visualizer-0.2}/setup.cfg +0 -0
|
@@ -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
|
+

|
|
10
|
+
|
|
11
|
+
2. Include the app urls **django_search_visualizer.urls** in the django main app **urlpatterns** list in **urls.py**
|
|
12
|
+
|
|
13
|
+

|
|
14
|
+
|
|
15
|
+
## How To Use
|
|
16
|
+
|
|
17
|
+
1. Navigate to **/model-search**
|
|
18
|
+
|
|
19
|
+

|
|
20
|
+
|
|
21
|
+
2. Select an App from the existing Apps list
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
3. Select a Model from the existing Models list
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
4. Select the fields that you want to see and configure the filters is required
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
The **Look For** options are the accepted ones by django filters
|
|
34
|
+
|
|
35
|
+
5. Press "Submit" or "Download" button
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
6. View the data
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
7. You can click in the table headers to sort them
|
|
44
|
+
|
|
45
|
+

|
|
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
|
+
]
|
|
File without changes
|
|
@@ -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
|
+
]
|