django-unfold 0.68.0__py3-none-any.whl → 0.69.0__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.
- {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/METADATA +31 -42
- {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/RECORD +33 -29
- unfold/admin.py +8 -37
- unfold/datasets.py +22 -1
- unfold/forms.py +22 -15
- unfold/mixins/__init__.py +2 -1
- unfold/mixins/dataset_model_admin.py +62 -0
- unfold/settings.py +1 -0
- unfold/sites.py +5 -1
- unfold/static/admin/js/actions.js +246 -0
- unfold/static/unfold/css/styles.css +2 -2
- unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
- unfold/templates/admin/actions.html +2 -2
- unfold/templates/admin/change_form.html +8 -4
- unfold/templates/admin/change_list.html +1 -1
- unfold/templates/admin/change_list_results.html +1 -1
- unfold/templates/admin/dataset_actions.html +50 -0
- unfold/templates/admin/edit_inline/stacked.html +1 -7
- unfold/templates/admin/edit_inline/tabular.html +1 -7
- unfold/templates/admin/includes/fieldset.html +1 -3
- unfold/templates/admin/search_form.html +1 -1
- unfold/templates/registration/password_change_done.html +3 -4
- unfold/templates/registration/password_change_form.html +10 -6
- unfold/templates/unfold/helpers/change_list_actions.html +1 -1
- unfold/templates/unfold/helpers/dataset.html +19 -7
- unfold/templates/unfold/helpers/fieldsets_tabs.html +9 -11
- unfold/templates/unfold/helpers/inline_heading.html +11 -0
- unfold/templates/unfold/helpers/tab_items.html +6 -4
- unfold/templatetags/unfold.py +47 -70
- unfold/templatetags/unfold_list.py +12 -0
- unfold/widgets.py +1 -2
- {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/WHEEL +0 -0
- {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/*global gettext, interpolate, ngettext, Actions*/
|
|
2
|
+
"use strict";
|
|
3
|
+
{
|
|
4
|
+
function show(options, selector) {
|
|
5
|
+
options.parent.querySelectorAll(selector).forEach(function (el) {
|
|
6
|
+
el.classList.remove("hidden");
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function hide(options, selector) {
|
|
11
|
+
options.parent.querySelectorAll(selector).forEach(function (el) {
|
|
12
|
+
el.classList.add("hidden");
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function showQuestion(options) {
|
|
17
|
+
hide(options, options.acrossClears);
|
|
18
|
+
show(options, options.acrossQuestions);
|
|
19
|
+
hide(options, options.allContainer);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function showClear(options) {
|
|
23
|
+
show(options, options.acrossClears);
|
|
24
|
+
hide(options, options.acrossQuestions);
|
|
25
|
+
options.parent
|
|
26
|
+
.querySelector(options.actionContainer)
|
|
27
|
+
.classList.remove(options.selectedClass);
|
|
28
|
+
show(options, options.allContainer);
|
|
29
|
+
hide(options, options.counterContainer);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function reset(options) {
|
|
33
|
+
hide(options, options.acrossClears);
|
|
34
|
+
hide(options, options.acrossQuestions);
|
|
35
|
+
hide(options, options.allContainer);
|
|
36
|
+
show(options, options.counterContainer);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function clearAcross(options) {
|
|
40
|
+
reset(options);
|
|
41
|
+
const acrossInputs = options.parent.querySelectorAll(options.acrossInput);
|
|
42
|
+
acrossInputs.forEach(function (acrossInput) {
|
|
43
|
+
acrossInput.value = 0;
|
|
44
|
+
acrossInput.dispatchEvent(new Event("input"));
|
|
45
|
+
});
|
|
46
|
+
options.parent
|
|
47
|
+
.querySelector(options.actionContainer)
|
|
48
|
+
.classList.remove(options.selectedClass);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function checker(actionCheckboxes, options, checked) {
|
|
52
|
+
if (checked) {
|
|
53
|
+
showQuestion(options);
|
|
54
|
+
} else {
|
|
55
|
+
reset(options);
|
|
56
|
+
}
|
|
57
|
+
actionCheckboxes.forEach(function (el) {
|
|
58
|
+
el.checked = checked;
|
|
59
|
+
el.closest("tr").classList.toggle(options.selectedClass, checked);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function updateCounter(actionCheckboxes, options) {
|
|
64
|
+
const sel = Array.from(actionCheckboxes).filter(function (el) {
|
|
65
|
+
return el.checked;
|
|
66
|
+
}).length;
|
|
67
|
+
const counter = options.parent.querySelector(options.counterContainer);
|
|
68
|
+
// data-actions-icnt is defined in the generated HTML
|
|
69
|
+
// and contains the total amount of objects in the queryset
|
|
70
|
+
const actions_icnt = Number(counter.dataset.actionsIcnt);
|
|
71
|
+
counter.textContent = interpolate(
|
|
72
|
+
ngettext(
|
|
73
|
+
"%(sel)s of %(cnt)s selected",
|
|
74
|
+
"%(sel)s of %(cnt)s selected",
|
|
75
|
+
sel
|
|
76
|
+
),
|
|
77
|
+
{
|
|
78
|
+
sel: sel,
|
|
79
|
+
cnt: actions_icnt,
|
|
80
|
+
},
|
|
81
|
+
true
|
|
82
|
+
);
|
|
83
|
+
const allToggle = options.parent.querySelector(".action-toggle");
|
|
84
|
+
allToggle.checked = sel === actionCheckboxes.length;
|
|
85
|
+
if (allToggle.checked) {
|
|
86
|
+
showQuestion(options);
|
|
87
|
+
} else {
|
|
88
|
+
clearAcross(options);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const defaults = {
|
|
93
|
+
actionContainer: "div.actions",
|
|
94
|
+
counterContainer: "span.action-counter",
|
|
95
|
+
allContainer: "div.actions span.all",
|
|
96
|
+
acrossInput: "div.actions input.select-across",
|
|
97
|
+
acrossQuestions: "div.actions span.question",
|
|
98
|
+
acrossClears: "div.actions span.clear",
|
|
99
|
+
allToggleId: "action-toggle",
|
|
100
|
+
selectedClass: "selected",
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
window.Actions = function (actionCheckboxes, options) {
|
|
104
|
+
options = Object.assign({}, defaults, options);
|
|
105
|
+
let list_editable_changed = false;
|
|
106
|
+
let lastChecked = null;
|
|
107
|
+
let shiftPressed = false;
|
|
108
|
+
|
|
109
|
+
document.addEventListener("keydown", (event) => {
|
|
110
|
+
shiftPressed = event.shiftKey;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
document.addEventListener("keyup", (event) => {
|
|
114
|
+
shiftPressed = event.shiftKey;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const allToggle = options.parent.querySelector(".action-toggle");
|
|
118
|
+
allToggle.addEventListener("click", function (event) {
|
|
119
|
+
checker(actionCheckboxes, options, this.checked);
|
|
120
|
+
updateCounter(actionCheckboxes, options);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
options.parent
|
|
124
|
+
.querySelectorAll(options.acrossQuestions + " a")
|
|
125
|
+
.forEach(function (el) {
|
|
126
|
+
el.addEventListener("click", function (event) {
|
|
127
|
+
event.preventDefault();
|
|
128
|
+
const acrossInputs = options.parent.querySelectorAll(
|
|
129
|
+
options.acrossInput
|
|
130
|
+
);
|
|
131
|
+
acrossInputs.forEach(function (acrossInput) {
|
|
132
|
+
acrossInput.value = 1;
|
|
133
|
+
acrossInput.dispatchEvent(new Event("input"));
|
|
134
|
+
});
|
|
135
|
+
showClear(options);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
options.parent
|
|
140
|
+
.querySelectorAll(options.acrossClears + " a")
|
|
141
|
+
.forEach(function (el) {
|
|
142
|
+
el.addEventListener("click", function (event) {
|
|
143
|
+
event.preventDefault();
|
|
144
|
+
options.parent.querySelector(".action-toggle").checked = false;
|
|
145
|
+
clearAcross(options);
|
|
146
|
+
checker(actionCheckboxes, options, false);
|
|
147
|
+
updateCounter(actionCheckboxes, options);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
function affectedCheckboxes(target, withModifier) {
|
|
152
|
+
const multiSelect = lastChecked && withModifier && lastChecked !== target;
|
|
153
|
+
if (!multiSelect) {
|
|
154
|
+
return [target];
|
|
155
|
+
}
|
|
156
|
+
const checkboxes = Array.from(actionCheckboxes);
|
|
157
|
+
const targetIndex = checkboxes.findIndex((el) => el === target);
|
|
158
|
+
const lastCheckedIndex = checkboxes.findIndex((el) => el === lastChecked);
|
|
159
|
+
const startIndex = Math.min(targetIndex, lastCheckedIndex);
|
|
160
|
+
const endIndex = Math.max(targetIndex, lastCheckedIndex);
|
|
161
|
+
const filtered = checkboxes.filter(
|
|
162
|
+
(el, index) => startIndex <= index && index <= endIndex
|
|
163
|
+
);
|
|
164
|
+
return filtered;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const resultList = options.parent.querySelector(".result-list").tBodies;
|
|
168
|
+
Array.from(resultList).forEach(function (el) {
|
|
169
|
+
el.addEventListener("change", function (event) {
|
|
170
|
+
const target = event.target;
|
|
171
|
+
if (target.classList.contains("action-select")) {
|
|
172
|
+
const checkboxes = affectedCheckboxes(target, shiftPressed);
|
|
173
|
+
checker(checkboxes, options, target.checked);
|
|
174
|
+
updateCounter(actionCheckboxes, options);
|
|
175
|
+
lastChecked = target;
|
|
176
|
+
} else {
|
|
177
|
+
list_editable_changed = true;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
options.parent
|
|
183
|
+
.querySelector("button[name=index]")
|
|
184
|
+
.addEventListener("click", function (event) {
|
|
185
|
+
if (list_editable_changed) {
|
|
186
|
+
const confirmed = confirm(
|
|
187
|
+
gettext(
|
|
188
|
+
"You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
if (!confirmed) {
|
|
192
|
+
event.preventDefault();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const el = options.parent.querySelector("input[name=_save]");
|
|
198
|
+
|
|
199
|
+
// The button does not exist if no fields are editable.
|
|
200
|
+
if (el) {
|
|
201
|
+
el.addEventListener("click", function (event) {
|
|
202
|
+
if (document.querySelector("[name=action]").value) {
|
|
203
|
+
const text = list_editable_changed
|
|
204
|
+
? gettext(
|
|
205
|
+
"You have selected an action, but you haven’t saved your changes to individual fields yet. Please click OK to save. You’ll need to re-run the action."
|
|
206
|
+
)
|
|
207
|
+
: gettext(
|
|
208
|
+
"You have selected an action, and you haven’t made any changes on individual fields. You’re probably looking for the Go button rather than the Save button."
|
|
209
|
+
);
|
|
210
|
+
if (!confirm(text)) {
|
|
211
|
+
event.preventDefault();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Sync counter when navigating to the page, such as through the back
|
|
218
|
+
// button.
|
|
219
|
+
window.addEventListener("pageshow", (event) =>
|
|
220
|
+
updateCounter(actionCheckboxes, options)
|
|
221
|
+
);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// Call function fn when the DOM is loaded and ready. If it is already
|
|
225
|
+
// loaded, call the function now.
|
|
226
|
+
// http://youmightnotneedjquery.com/#ready
|
|
227
|
+
function ready(fn) {
|
|
228
|
+
if (document.readyState !== "loading") {
|
|
229
|
+
fn();
|
|
230
|
+
} else {
|
|
231
|
+
document.addEventListener("DOMContentLoaded", fn);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
ready(function () {
|
|
236
|
+
document.querySelectorAll(".result-list-wrapper").forEach(function (el) {
|
|
237
|
+
const actionsEls = el.querySelectorAll("tr input.action-select");
|
|
238
|
+
|
|
239
|
+
if (actionsEls.length > 0) {
|
|
240
|
+
Actions(actionsEls, {
|
|
241
|
+
parent: el,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
}
|