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.
Files changed (33) hide show
  1. {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/METADATA +31 -42
  2. {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/RECORD +33 -29
  3. unfold/admin.py +8 -37
  4. unfold/datasets.py +22 -1
  5. unfold/forms.py +22 -15
  6. unfold/mixins/__init__.py +2 -1
  7. unfold/mixins/dataset_model_admin.py +62 -0
  8. unfold/settings.py +1 -0
  9. unfold/sites.py +5 -1
  10. unfold/static/admin/js/actions.js +246 -0
  11. unfold/static/unfold/css/styles.css +2 -2
  12. unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
  13. unfold/templates/admin/actions.html +2 -2
  14. unfold/templates/admin/change_form.html +8 -4
  15. unfold/templates/admin/change_list.html +1 -1
  16. unfold/templates/admin/change_list_results.html +1 -1
  17. unfold/templates/admin/dataset_actions.html +50 -0
  18. unfold/templates/admin/edit_inline/stacked.html +1 -7
  19. unfold/templates/admin/edit_inline/tabular.html +1 -7
  20. unfold/templates/admin/includes/fieldset.html +1 -3
  21. unfold/templates/admin/search_form.html +1 -1
  22. unfold/templates/registration/password_change_done.html +3 -4
  23. unfold/templates/registration/password_change_form.html +10 -6
  24. unfold/templates/unfold/helpers/change_list_actions.html +1 -1
  25. unfold/templates/unfold/helpers/dataset.html +19 -7
  26. unfold/templates/unfold/helpers/fieldsets_tabs.html +9 -11
  27. unfold/templates/unfold/helpers/inline_heading.html +11 -0
  28. unfold/templates/unfold/helpers/tab_items.html +6 -4
  29. unfold/templatetags/unfold.py +47 -70
  30. unfold/templatetags/unfold_list.py +12 -0
  31. unfold/widgets.py +1 -2
  32. {django_unfold-0.68.0.dist-info → django_unfold-0.69.0.dist-info}/WHEEL +0 -0
  33. {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
+ }