django-unfold 0.37.0__py3-none-any.whl → 0.39.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. django_unfold-0.39.0.dist-info/METADATA +70 -0
  2. {django_unfold-0.37.0.dist-info → django_unfold-0.39.0.dist-info}/RECORD +59 -57
  3. unfold/admin.py +4 -1
  4. unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
  5. unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
  6. unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +1 -1
  7. unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +1 -1
  8. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +4 -4
  9. unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html +6 -6
  10. unfold/contrib/guardian/templates/unfold/guardian/group_form.html +5 -5
  11. unfold/contrib/guardian/templates/unfold/guardian/user_form.html +5 -5
  12. unfold/contrib/import_export/templates/admin/import_export/change_form.html +1 -1
  13. unfold/contrib/import_export/templates/admin/import_export/import_errors.html +1 -1
  14. unfold/contrib/simple_history/templates/simple_history/object_history.html +1 -1
  15. unfold/contrib/simple_history/templates/simple_history/object_history_list.html +8 -8
  16. unfold/contrib/simple_history/templates/simple_history/submit_line.html +1 -1
  17. unfold/forms.py +5 -1
  18. unfold/settings.py +26 -1
  19. unfold/static/admin/js/admin/RelatedObjectLookups.js +295 -0
  20. unfold/static/unfold/css/styles.css +1 -1
  21. unfold/static/unfold/js/app.js +3 -1
  22. unfold/styles.css +1 -1
  23. unfold/templates/admin/app_list.html +2 -2
  24. unfold/templates/admin/change_form.html +8 -1
  25. unfold/templates/admin/change_list.html +1 -6
  26. unfold/templates/admin/change_list_results.html +2 -2
  27. unfold/templates/admin/delete_confirmation.html +5 -5
  28. unfold/templates/admin/delete_selected_confirmation.html +5 -5
  29. unfold/templates/admin/edit_inline/stacked.html +2 -2
  30. unfold/templates/admin/edit_inline/tabular.html +3 -3
  31. unfold/templates/admin/filter.html +1 -1
  32. unfold/templates/admin/includes/fieldset.html +1 -1
  33. unfold/templates/admin/includes/object_delete_summary.html +1 -1
  34. unfold/templates/admin/login.html +2 -2
  35. unfold/templates/admin/search_form.html +1 -1
  36. unfold/templates/auth/widgets/read_only_password_hash.html +2 -2
  37. unfold/templates/registration/logged_out.html +1 -1
  38. unfold/templates/unfold/change_list_filter.html +19 -3
  39. unfold/templates/unfold/components/button.html +1 -1
  40. unfold/templates/unfold/components/card.html +1 -1
  41. unfold/templates/unfold/components/navigation.html +1 -1
  42. unfold/templates/unfold/components/title.html +1 -1
  43. unfold/templates/unfold/helpers/actions_row.html +1 -1
  44. unfold/templates/unfold/helpers/app_list.html +7 -7
  45. unfold/templates/unfold/helpers/fieldsets_tabs.html +2 -2
  46. unfold/templates/unfold/helpers/form_label.html +1 -1
  47. unfold/templates/unfold/helpers/messages.html +1 -1
  48. unfold/templates/unfold/helpers/navigation.html +1 -1
  49. unfold/templates/unfold/helpers/search.html +1 -1
  50. unfold/templates/unfold/helpers/tab_action.html +1 -1
  51. unfold/templates/unfold/helpers/welcomemsg.html +3 -3
  52. unfold/templates/unfold/layouts/skeleton.html +1 -1
  53. unfold/templates/unfold/widgets/clearable_file_input.html +2 -2
  54. unfold/templates/unfold/widgets/clearable_file_input_small.html +1 -1
  55. unfold/templates/unfold/widgets/url.html +7 -0
  56. unfold/templatetags/unfold_list.py +0 -7
  57. unfold/widgets.py +26 -6
  58. django_unfold-0.37.0.dist-info/METADATA +0 -1455
  59. {django_unfold-0.37.0.dist-info → django_unfold-0.39.0.dist-info}/LICENSE.md +0 -0
  60. {django_unfold-0.37.0.dist-info → django_unfold-0.39.0.dist-info}/WHEEL +0 -0
@@ -4,35 +4,35 @@
4
4
  {% load getattribute from getattributes %}
5
5
 
6
6
  <table id="change-history" class="border-gray-200 border-spacing-none border-separate mb-6 w-full lg:border lg:rounded-md lg:shadow-sm lg:dark:border-gray-800">
7
- <thead class="hidden text-gray-900 lg:table-header-group dark:text-gray-100">
7
+ <thead class="hidden text-font-important-light lg:table-header-group dark:text-font-important-dark">
8
8
  <tr>
9
- <th class="align-middle font-medium px-3 py-2 text-left ">
9
+ <th class="align-middle font-semibold px-3 py-2 text-left ">
10
10
  {% trans 'Object' %}
11
11
  </th>
12
12
 
13
13
  {% for column in history_list_display %}
14
- <th class="align-middle font-medium px-3 py-2 text-left ">
14
+ <th class="align-middle font-semibold px-3 py-2 text-left ">
15
15
  {% trans column %}
16
16
  </th>
17
17
  {% endfor %}
18
18
 
19
- <th class="align-middle font-medium px-3 py-2 text-left">
19
+ <th class="align-middle font-semibold px-3 py-2 text-left">
20
20
  {% trans 'Date/time' %}
21
21
  </th>
22
22
 
23
- <th class="align-middle font-medium px-3 py-2 text-left">
23
+ <th class="align-middle font-semibold px-3 py-2 text-left">
24
24
  {% trans 'Comment' %}
25
25
  </th>
26
26
 
27
- <th class="align-middle font-medium px-3 py-2 text-left">
27
+ <th class="align-middle font-semibold px-3 py-2 text-left">
28
28
  {% trans 'Changed by' %}
29
29
  </th>
30
30
 
31
- <th class="align-middle font-medium px-3 py-2 text-left">
31
+ <th class="align-middle font-semibold px-3 py-2 text-left">
32
32
  {% trans 'Change reason' %}
33
33
  </th>
34
34
 
35
- <th class="align-middle font-medium px-3 py-2 text-left">
35
+ <th class="align-middle font-semibold px-3 py-2 text-left">
36
36
  {% trans 'Changes' %}
37
37
  </th>
38
38
  </tr>
@@ -16,7 +16,7 @@
16
16
  </button>
17
17
  {% endif %}
18
18
 
19
- <a href="{{ history_url }}" class="border font-medium mr-auto px-3 py-2 rounded-md text-sm text-gray-500 text-center transition-all w-full hover:bg-gray-50 lg:block lg:w-auto dark:border-gray-700 dark:text-gray-300 dark:hover:text-gray-200 dark:hover:bg-gray-900">
19
+ <a href="{{ history_url }}" class="border font-medium mr-auto px-3 py-2 rounded-md text-sm text-gray-500 text-center transition-all w-full hover:bg-gray-50 lg:block lg:w-auto dark:border-gray-700 dark:text-font-default-dark dark:hover:text-gray-200 dark:hover:bg-gray-900">
20
20
  {% trans 'Close' %}
21
21
  </a>
22
22
  </div>
unfold/forms.py CHANGED
@@ -10,9 +10,13 @@ from django.contrib.admin.forms import (
10
10
  from django.contrib.auth.forms import (
11
11
  AdminPasswordChangeForm as BaseAdminPasswordChangeForm,
12
12
  )
13
+
14
+ try:
15
+ from django.contrib.auth.forms import AdminUserCreationForm as BaseUserCreationForm
16
+ except ImportError:
17
+ from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm
13
18
  from django.contrib.auth.forms import ReadOnlyPasswordHashWidget
14
19
  from django.contrib.auth.forms import UserChangeForm as BaseUserChangeForm
15
- from django.contrib.auth.forms import UserCreationForm as BaseUserCreationForm
16
20
  from django.http import HttpRequest
17
21
  from django.utils.safestring import mark_safe
18
22
  from django.utils.translation import gettext_lazy as _
unfold/settings.py CHANGED
@@ -1,3 +1,5 @@
1
+ from typing import Any, Dict
2
+
1
3
  from django.conf import settings
2
4
 
3
5
  CONFIG_DEFAULTS = {
@@ -11,6 +13,14 @@ CONFIG_DEFAULTS = {
11
13
  "SHOW_HISTORY": True,
12
14
  "SHOW_VIEW_ON_SITE": True,
13
15
  "COLORS": {
16
+ "font": {
17
+ "subtle-light": "107 114 128", # text-gray-500
18
+ "subtle-dark": "156 163 175", # text-gray-400
19
+ "default-light": "75 85 99", # text-gray-600
20
+ "default-dark": "209 213 219", # text-gray-300
21
+ "important-light": "17 24 39", # text-gray-900
22
+ "important-dark": "243 244 246", # text-gray-100
23
+ },
14
24
  "primary": {
15
25
  "50": "250 245 255",
16
26
  "100": "243 232 255",
@@ -47,4 +57,19 @@ def get_config(settings_name=None):
47
57
  if settings_name is None:
48
58
  settings_name = "UNFOLD"
49
59
 
50
- return {**CONFIG_DEFAULTS, **getattr(settings, settings_name, {})}
60
+ def merge_dicts(dict1: Dict[str, Any], dict2: Dict[str, Any]) -> Dict[str, Any]:
61
+ result = dict1.copy()
62
+
63
+ for key, value in dict2.items():
64
+ if (
65
+ key in result
66
+ and isinstance(result[key], dict)
67
+ and isinstance(value, dict)
68
+ ):
69
+ result[key] = merge_dicts(result[key], value)
70
+ else:
71
+ result[key] = value
72
+
73
+ return result
74
+
75
+ return merge_dicts(CONFIG_DEFAULTS, getattr(settings, settings_name, {}))
@@ -0,0 +1,295 @@
1
+ /*global SelectBox, interpolate*/
2
+ // Handles related-objects functionality: lookup link for raw_id_fields
3
+ // and Add Another links.
4
+ "use strict";
5
+ {
6
+ const $ = django.jQuery;
7
+ let popupIndex = 0;
8
+ const relatedWindows = [];
9
+
10
+ function dismissChildPopups() {
11
+ relatedWindows.forEach(function (win) {
12
+ if (!win.closed) {
13
+ win.dismissChildPopups();
14
+ win.close();
15
+ }
16
+ });
17
+ }
18
+
19
+ function setPopupIndex() {
20
+ if (document.getElementsByName("_popup").length > 0) {
21
+ const index = window.name.lastIndexOf("__") + 2;
22
+ popupIndex = parseInt(window.name.substring(index));
23
+ } else {
24
+ popupIndex = 0;
25
+ }
26
+ }
27
+
28
+ function addPopupIndex(name) {
29
+ return name + "__" + (popupIndex + 1);
30
+ }
31
+
32
+ function removePopupIndex(name) {
33
+ return name.replace(new RegExp("__" + (popupIndex + 1) + "$"), "");
34
+ }
35
+
36
+ function showAdminPopup(triggeringLink, name_regexp, add_popup) {
37
+ const name = addPopupIndex(triggeringLink.id.replace(name_regexp, ""));
38
+ const href = new URL(triggeringLink.href);
39
+ if (add_popup) {
40
+ href.searchParams.set("_popup", 1);
41
+ }
42
+ const win = window.open(
43
+ href,
44
+ name,
45
+ "height=768,width=1024,resizable=yes,scrollbars=yes"
46
+ );
47
+ relatedWindows.push(win);
48
+ win.focus();
49
+ return false;
50
+ }
51
+
52
+ function showRelatedObjectLookupPopup(triggeringLink) {
53
+ return showAdminPopup(triggeringLink, /^lookup_/, true);
54
+ }
55
+
56
+ function dismissRelatedLookupPopup(win, chosenId) {
57
+ const name = removePopupIndex(win.name);
58
+ const elem = document.getElementById(name);
59
+ if (elem.classList.contains("vManyToManyRawIdAdminField") && elem.value) {
60
+ elem.value += "," + chosenId;
61
+ } else {
62
+ document.getElementById(name).value = chosenId;
63
+ }
64
+ const index = relatedWindows.indexOf(win);
65
+ if (index > -1) {
66
+ relatedWindows.splice(index, 1);
67
+ }
68
+ win.close();
69
+ }
70
+
71
+ function showRelatedObjectPopup(triggeringLink) {
72
+ return showAdminPopup(triggeringLink, /^(change|add|delete)_/, false);
73
+ }
74
+
75
+ function updateRelatedObjectLinks(triggeringLink) {
76
+ const $this = $(triggeringLink);
77
+ const siblings = $this.nextAll(
78
+ ".view-related, .change-related, .delete-related"
79
+ );
80
+ if (!siblings.length) {
81
+ return;
82
+ }
83
+ const value = $this.val();
84
+ if (value) {
85
+ siblings.each(function () {
86
+ const elm = $(this);
87
+ elm.attr(
88
+ "href",
89
+ elm.attr("data-href-template").replace("__fk__", value)
90
+ );
91
+ elm.removeAttr("aria-disabled");
92
+ });
93
+ } else {
94
+ siblings.removeAttr("href");
95
+ siblings.attr("aria-disabled", true);
96
+ }
97
+ }
98
+
99
+ function updateRelatedSelectsOptions(
100
+ currentSelect,
101
+ win,
102
+ objId,
103
+ newRepr,
104
+ newId,
105
+ skipIds = []
106
+ ) {
107
+ // After create/edit a model from the options next to the current
108
+ // select (+ or :pencil:) update ForeignKey PK of the rest of selects
109
+ // in the page.
110
+
111
+ const path = win.location.pathname;
112
+ // Extract the model from the popup url '.../<model>/add/' or
113
+ // '.../<model>/<id>/change/' depending the action (add or change).
114
+ const modelName = path.split("/")[path.split("/").length - (objId ? 4 : 3)];
115
+ // Select elements with a specific model reference and context of "available-source".
116
+ const selectsRelated = document.querySelectorAll(
117
+ `[data-model-ref="${modelName}"] [data-context="available-source"]`
118
+ );
119
+
120
+ selectsRelated.forEach(function (select) {
121
+ if (
122
+ currentSelect === select ||
123
+ (skipIds && skipIds.includes(select.id))
124
+ ) {
125
+ return;
126
+ }
127
+
128
+ let option = select.querySelector(`option[value="${objId}"]`);
129
+
130
+ if (!option) {
131
+ option = new Option(newRepr, newId);
132
+ select.options.add(option);
133
+ // Update SelectBox cache for related fields.
134
+ if (
135
+ window.SelectBox !== undefined &&
136
+ !SelectBox.cache[currentSelect.id]
137
+ ) {
138
+ SelectBox.add_to_cache(select.id, option);
139
+ SelectBox.redisplay(select.id);
140
+ }
141
+ return;
142
+ }
143
+
144
+ option.textContent = newRepr;
145
+ option.value = newId;
146
+ });
147
+ }
148
+
149
+ function dismissAddRelatedObjectPopup(win, newId, newRepr) {
150
+ const name = removePopupIndex(win.name);
151
+ const elem = document.getElementById(name);
152
+ if (elem) {
153
+ const elemName = elem.nodeName.toUpperCase();
154
+ if (elemName === "SELECT") {
155
+ elem.options[elem.options.length] = new Option(
156
+ newRepr,
157
+ newId,
158
+ true,
159
+ true
160
+ );
161
+ updateRelatedSelectsOptions(elem, win, null, newRepr, newId);
162
+ } else if (elemName === "INPUT") {
163
+ if (
164
+ elem.classList.contains("vManyToManyRawIdAdminField") &&
165
+ elem.value
166
+ ) {
167
+ elem.value += "," + newId;
168
+ } else {
169
+ elem.value = newId;
170
+ }
171
+ }
172
+ // Trigger a change event to update related links if required.
173
+ $(elem).trigger("change");
174
+ } else {
175
+ const toId = name + "_to";
176
+ const toElem = document.getElementById(toId);
177
+ const o = new Option(newRepr, newId);
178
+ SelectBox.add_to_cache(toId, o);
179
+ SelectBox.redisplay(toId);
180
+ if (toElem && toElem.nodeName.toUpperCase() === "SELECT") {
181
+ const skipIds = [name + "_from"];
182
+ updateRelatedSelectsOptions(toElem, win, null, newRepr, newId, skipIds);
183
+ }
184
+ }
185
+ const index = relatedWindows.indexOf(win);
186
+ if (index > -1) {
187
+ relatedWindows.splice(index, 1);
188
+ }
189
+ win.close();
190
+ }
191
+
192
+ function dismissChangeRelatedObjectPopup(win, objId, newRepr, newId) {
193
+ const id = removePopupIndex(win.name.replace(/^edit_/, ""));
194
+ const selectsSelector = interpolate("#%s, #%s_from, #%s_to", [id, id, id]);
195
+ const selects = $(selectsSelector);
196
+ selects
197
+ .find("option")
198
+ .each(function () {
199
+ if (this.value === objId) {
200
+ this.textContent = newRepr;
201
+ this.value = newId;
202
+ }
203
+ })
204
+ .trigger("change");
205
+ updateRelatedSelectsOptions(selects[0], win, objId, newRepr, newId);
206
+ selects
207
+ .next()
208
+ .find(".select2-selection__rendered")
209
+ .each(function () {
210
+ // The element can have a clear button as a child.
211
+ // Use the lastChild to modify only the displayed value.
212
+ this.lastChild.textContent = newRepr;
213
+ this.title = newRepr;
214
+ });
215
+ const index = relatedWindows.indexOf(win);
216
+ if (index > -1) {
217
+ relatedWindows.splice(index, 1);
218
+ }
219
+ win.close();
220
+ }
221
+
222
+ function dismissDeleteRelatedObjectPopup(win, objId) {
223
+ const id = removePopupIndex(win.name.replace(/^delete_/, ""));
224
+ const selectsSelector = interpolate("#%s, #%s_from, #%s_to", [id, id, id]);
225
+ const selects = $(selectsSelector);
226
+ selects
227
+ .find("option")
228
+ .each(function () {
229
+ if (this.value === objId) {
230
+ $(this).remove();
231
+ }
232
+ })
233
+ .trigger("change");
234
+ const index = relatedWindows.indexOf(win);
235
+ if (index > -1) {
236
+ relatedWindows.splice(index, 1);
237
+ }
238
+ win.close();
239
+ }
240
+
241
+ window.showRelatedObjectLookupPopup = showRelatedObjectLookupPopup;
242
+ window.dismissRelatedLookupPopup = dismissRelatedLookupPopup;
243
+ window.showRelatedObjectPopup = showRelatedObjectPopup;
244
+ window.updateRelatedObjectLinks = updateRelatedObjectLinks;
245
+ window.dismissAddRelatedObjectPopup = dismissAddRelatedObjectPopup;
246
+ window.dismissChangeRelatedObjectPopup = dismissChangeRelatedObjectPopup;
247
+ window.dismissDeleteRelatedObjectPopup = dismissDeleteRelatedObjectPopup;
248
+ window.dismissChildPopups = dismissChildPopups;
249
+
250
+ // Kept for backward compatibility
251
+ window.showAddAnotherPopup = showRelatedObjectPopup;
252
+ window.dismissAddAnotherPopup = dismissAddRelatedObjectPopup;
253
+
254
+ window.addEventListener("unload", function (evt) {
255
+ window.dismissChildPopups();
256
+ });
257
+
258
+ $(document).ready(function () {
259
+ setPopupIndex();
260
+ $("a[data-popup-opener]").on("click", function (event) {
261
+ event.preventDefault();
262
+ opener.dismissRelatedLookupPopup(window, $(this).data("popup-opener"));
263
+ });
264
+ $("body").on(
265
+ "click",
266
+ '.related-widget-wrapper-link[data-popup="yes"]',
267
+ function (e) {
268
+ e.preventDefault();
269
+ if (this.href) {
270
+ const event = $.Event("django:show-related", { href: this.href });
271
+ $(this).trigger(event);
272
+ if (!event.isDefaultPrevented()) {
273
+ showRelatedObjectPopup(this);
274
+ }
275
+ }
276
+ }
277
+ );
278
+ $("body").on("change", ".related-widget-wrapper select", function (e) {
279
+ const event = $.Event("django:update-related");
280
+ $(this).trigger(event);
281
+ if (!event.isDefaultPrevented()) {
282
+ updateRelatedObjectLinks(this);
283
+ }
284
+ });
285
+ $(".related-widget-wrapper select").trigger("change");
286
+ $("body").on("click", ".related-lookup", function (e) {
287
+ e.preventDefault();
288
+ const event = $.Event("django:lookup-related");
289
+ $(this).trigger(event);
290
+ if (!event.isDefaultPrevented()) {
291
+ showRelatedObjectLookupPopup(this);
292
+ }
293
+ });
294
+ });
295
+ }