django-unfold 0.63.0__py3-none-any.whl → 0.64.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.63.0.dist-info → django_unfold-0.64.0.dist-info}/METADATA +1 -1
- {django_unfold-0.63.0.dist-info → django_unfold-0.64.0.dist-info}/RECORD +55 -51
- unfold/admin.py +1 -1
- unfold/contrib/constance/templates/admin/constance/change_list.html +0 -18
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
- unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +0 -24
- unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +0 -26
- unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +0 -26
- unfold/contrib/import_export/templates/admin/import_export/export.html +0 -23
- unfold/contrib/import_export/templates/admin/import_export/import.html +0 -23
- unfold/contrib/simple_history/templates/simple_history/object_history_form.html +1 -33
- unfold/contrib/simple_history/templates/simple_history/object_history_list.html +0 -1
- unfold/dataclasses.py +8 -0
- unfold/settings.py +28 -22
- unfold/sites.py +120 -43
- unfold/static/unfold/css/styles.css +1 -1
- unfold/static/unfold/js/app.js +147 -9
- unfold/styles.css +32 -32
- unfold/templates/admin/app_index.html +0 -18
- unfold/templates/admin/auth/user/change_password.html +1 -26
- unfold/templates/admin/base.html +0 -16
- unfold/templates/admin/change_form.html +0 -33
- unfold/templates/admin/change_list.html +0 -19
- unfold/templates/admin/change_list_results.html +2 -2
- unfold/templates/admin/delete_confirmation.html +0 -21
- unfold/templates/admin/delete_selected_confirmation.html +0 -22
- unfold/templates/admin/index.html +0 -2
- unfold/templates/admin/object_history.html +0 -24
- unfold/templates/registration/password_change_done.html +0 -17
- unfold/templates/registration/password_change_form.html +0 -17
- unfold/templates/unfold/components/navigation.html +2 -2
- unfold/templates/unfold/components/table.html +55 -9
- unfold/templates/unfold/helpers/boolean.html +6 -6
- unfold/templates/unfold/helpers/command.html +53 -0
- unfold/templates/unfold/helpers/command_history.html +54 -0
- unfold/templates/unfold/helpers/command_results.html +50 -0
- unfold/templates/unfold/helpers/header_back_button.html +10 -2
- unfold/templates/unfold/helpers/header_title.html +11 -0
- unfold/templates/unfold/helpers/label.html +1 -1
- unfold/templates/unfold/helpers/navigation_header.html +2 -2
- unfold/templates/unfold/helpers/search.html +40 -22
- unfold/templates/unfold/helpers/search_results.html +2 -2
- unfold/templates/unfold/helpers/shortcut.html +1 -1
- unfold/templates/unfold/helpers/site_dropdown.html +1 -1
- unfold/templates/unfold/helpers/tab_items.html +15 -33
- unfold/templates/unfold/helpers/tab_list.html +1 -1
- unfold/templates/unfold/helpers/welcomemsg.html +6 -18
- unfold/templates/unfold/layouts/base_simple.html +0 -6
- unfold/templates/unfold/layouts/skeleton.html +4 -1
- unfold/templates/unfold/layouts/unauthenticated.html +0 -2
- unfold/templatetags/unfold.py +141 -0
- unfold/utils.py +25 -10
- unfold/views.py +4 -1
- {django_unfold-0.63.0.dist-info → django_unfold-0.64.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.63.0.dist-info → django_unfold-0.64.0.dist-info}/WHEEL +0 -0
unfold/sites.py
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
import copy
|
2
|
+
import time
|
2
3
|
from http import HTTPStatus
|
3
4
|
from typing import Any, Callable, Optional, Union
|
4
5
|
from urllib.parse import parse_qs, urlparse
|
5
6
|
|
6
7
|
from django.contrib.admin import AdminSite
|
8
|
+
from django.core.cache import cache
|
7
9
|
from django.core.validators import EMPTY_VALUES
|
8
10
|
from django.http import HttpRequest, HttpResponse
|
9
11
|
from django.template.response import TemplateResponse
|
10
12
|
from django.urls import URLPattern, path, reverse, reverse_lazy
|
11
13
|
from django.utils.functional import lazy
|
12
14
|
from django.utils.module_loading import import_string
|
15
|
+
from django.utils.text import slugify
|
13
16
|
|
14
|
-
from unfold.dataclasses import DropdownItem, Favicon
|
17
|
+
from unfold.dataclasses import DropdownItem, Favicon, SearchResult
|
15
18
|
|
16
19
|
try:
|
17
20
|
from django.contrib.auth.decorators import login_not_required
|
@@ -22,7 +25,7 @@ except ImportError:
|
|
22
25
|
|
23
26
|
|
24
27
|
from unfold.settings import get_config
|
25
|
-
from unfold.utils import
|
28
|
+
from unfold.utils import convert_color
|
26
29
|
from unfold.widgets import (
|
27
30
|
BUTTON_CLASSES,
|
28
31
|
CHECKBOX_CLASSES,
|
@@ -112,6 +115,12 @@ class UnfoldAdminSite(AdminSite):
|
|
112
115
|
"tab_list": self.get_tabs_list(request),
|
113
116
|
"styles": self._get_list("STYLES", request),
|
114
117
|
"scripts": self._get_list("SCRIPTS", request),
|
118
|
+
"command_show_history": self._get_config("COMMAND", request).get(
|
119
|
+
"show_history"
|
120
|
+
),
|
121
|
+
"sidebar_command_search": self._get_config("SIDEBAR", request).get(
|
122
|
+
"command_search"
|
123
|
+
),
|
115
124
|
"sidebar_show_all_applications": self._get_value(
|
116
125
|
sidebar_config.get("show_all_applications"), request
|
117
126
|
),
|
@@ -165,26 +174,21 @@ class UnfoldAdminSite(AdminSite):
|
|
165
174
|
|
166
175
|
return HttpResponse(status=HTTPStatus.OK)
|
167
176
|
|
168
|
-
def
|
169
|
-
self,
|
170
|
-
) ->
|
171
|
-
query = request.GET.get("s").lower()
|
172
|
-
app_list = super().get_app_list(request)
|
173
|
-
apps = []
|
177
|
+
def _search_apps(
|
178
|
+
self, app_list: list[dict[str, Any]], search_term: str
|
179
|
+
) -> list[SearchResult]:
|
174
180
|
results = []
|
175
|
-
|
176
|
-
if query in EMPTY_VALUES:
|
177
|
-
return HttpResponse()
|
181
|
+
apps = []
|
178
182
|
|
179
183
|
for app in app_list:
|
180
|
-
if
|
184
|
+
if search_term in app["name"].lower():
|
181
185
|
apps.append(app)
|
182
186
|
continue
|
183
187
|
|
184
188
|
models = []
|
185
189
|
|
186
190
|
for model in app["models"]:
|
187
|
-
if
|
191
|
+
if search_term in model["name"].lower():
|
188
192
|
models.append(model)
|
189
193
|
|
190
194
|
if len(models) > 0:
|
@@ -194,17 +198,111 @@ class UnfoldAdminSite(AdminSite):
|
|
194
198
|
for app in apps:
|
195
199
|
for model in app["models"]:
|
196
200
|
results.append(
|
197
|
-
|
198
|
-
"
|
199
|
-
"
|
200
|
-
|
201
|
+
SearchResult(
|
202
|
+
title=str(model["name"]),
|
203
|
+
description=app["name"],
|
204
|
+
link=model["admin_url"],
|
205
|
+
icon="tag",
|
206
|
+
)
|
207
|
+
)
|
208
|
+
|
209
|
+
return results
|
210
|
+
|
211
|
+
def _search_models(
|
212
|
+
self, request: HttpRequest, app_list: list[dict[str, Any]], search_term: str
|
213
|
+
) -> list[SearchResult]:
|
214
|
+
results = []
|
215
|
+
|
216
|
+
for app in app_list:
|
217
|
+
for model in app["models"]:
|
218
|
+
admin_instance = self._registry.get(model["model"])
|
219
|
+
search_fields = admin_instance.get_search_fields(request)
|
220
|
+
|
221
|
+
if not search_fields:
|
222
|
+
continue
|
223
|
+
|
224
|
+
pks = []
|
225
|
+
|
226
|
+
qs = admin_instance.get_queryset(request)
|
227
|
+
search_results, _has_duplicates = admin_instance.get_search_results(
|
228
|
+
request, qs, search_term
|
201
229
|
)
|
202
230
|
|
231
|
+
for item in search_results:
|
232
|
+
if item.pk in pks:
|
233
|
+
continue
|
234
|
+
|
235
|
+
pks.append(item.pk)
|
236
|
+
|
237
|
+
link = reverse_lazy(
|
238
|
+
f"{self.name}:{admin_instance.model._meta.app_label}_{admin_instance.model._meta.model_name}_change",
|
239
|
+
args=(item.pk,),
|
240
|
+
)
|
241
|
+
|
242
|
+
results.append(
|
243
|
+
SearchResult(
|
244
|
+
title=str(item),
|
245
|
+
description=f"{item._meta.app_label.capitalize()} - {item._meta.verbose_name.capitalize()}",
|
246
|
+
link=link,
|
247
|
+
icon="data_object",
|
248
|
+
)
|
249
|
+
)
|
250
|
+
|
251
|
+
return results
|
252
|
+
|
253
|
+
def search(
|
254
|
+
self, request: HttpRequest, extra_context: Optional[dict[str, Any]] = None
|
255
|
+
) -> TemplateResponse:
|
256
|
+
start_time = time.time()
|
257
|
+
|
258
|
+
CACHE_TIMEOUT = 10
|
259
|
+
|
260
|
+
search_term = request.GET.get("s")
|
261
|
+
extended_search = "extended" in request.GET
|
262
|
+
app_list = super().get_app_list(request)
|
263
|
+
template_name = "unfold/helpers/search_results.html"
|
264
|
+
|
265
|
+
if search_term in EMPTY_VALUES:
|
266
|
+
return HttpResponse()
|
267
|
+
|
268
|
+
search_term = search_term.lower()
|
269
|
+
cache_key = f"unfold_search_{request.user.pk}_{slugify(search_term)}"
|
270
|
+
cache_results = cache.get(cache_key)
|
271
|
+
|
272
|
+
if extended_search:
|
273
|
+
template_name = "unfold/helpers/command_results.html"
|
274
|
+
|
275
|
+
if cache_results:
|
276
|
+
results = cache_results
|
277
|
+
else:
|
278
|
+
results = self._search_apps(app_list, search_term)
|
279
|
+
search_models = self._get_config("COMMAND", request).get("search_models")
|
280
|
+
search_callback = self._get_config("COMMAND", request).get(
|
281
|
+
"search_callback"
|
282
|
+
)
|
283
|
+
|
284
|
+
if extended_search:
|
285
|
+
if search_callback:
|
286
|
+
results.extend(
|
287
|
+
self._get_value(search_callback, request, search_term)
|
288
|
+
)
|
289
|
+
|
290
|
+
if search_models is True:
|
291
|
+
results.extend(self._search_models(request, app_list, search_term))
|
292
|
+
|
293
|
+
cache.set(cache_key, results, timeout=CACHE_TIMEOUT)
|
294
|
+
|
295
|
+
execution_time = time.time() - start_time
|
296
|
+
|
203
297
|
return TemplateResponse(
|
204
298
|
request,
|
205
|
-
template=
|
299
|
+
template=template_name,
|
206
300
|
context={
|
207
301
|
"results": results,
|
302
|
+
"execution_time": execution_time,
|
303
|
+
"command_show_history": self._get_config("COMMAND", request).get(
|
304
|
+
"show_history"
|
305
|
+
),
|
208
306
|
},
|
209
307
|
headers={
|
210
308
|
"HX-Trigger": "search",
|
@@ -260,8 +358,8 @@ class UnfoldAdminSite(AdminSite):
|
|
260
358
|
# Checks if any tab item is active and then marks the sidebar link as active
|
261
359
|
if (
|
262
360
|
tabs
|
263
|
-
and
|
264
|
-
and
|
361
|
+
and self._get_is_tab_active(request, tabs, link)
|
362
|
+
and "active" not in item
|
265
363
|
):
|
266
364
|
item["active"] = True
|
267
365
|
|
@@ -408,7 +506,7 @@ class UnfoldAdminSite(AdminSite):
|
|
408
506
|
request, tab_item.get("link_callback") or tab_item["link"]
|
409
507
|
):
|
410
508
|
has_tab_link_active = True
|
411
|
-
|
509
|
+
continue
|
412
510
|
|
413
511
|
if has_primary_link and has_tab_link_active:
|
414
512
|
return True
|
@@ -440,33 +538,12 @@ class UnfoldAdminSite(AdminSite):
|
|
440
538
|
def _get_colors(self, key: str, *args) -> dict[str, dict[str, str]]:
|
441
539
|
colors = self._get_config(key, *args)
|
442
540
|
|
443
|
-
def rgb_to_values(value: str) -> str:
|
444
|
-
return ", ".join(
|
445
|
-
list(
|
446
|
-
map(
|
447
|
-
str.strip,
|
448
|
-
value.removeprefix("rgb(").removesuffix(")").split(","),
|
449
|
-
)
|
450
|
-
)
|
451
|
-
)
|
452
|
-
|
453
|
-
def hex_to_values(value: str) -> str:
|
454
|
-
return ", ".join(str(item) for item in hex_to_rgb(value))
|
455
|
-
|
456
541
|
for name, weights in colors.items():
|
457
542
|
weights = self._get_value(weights, *args)
|
458
543
|
colors[name] = weights
|
459
544
|
|
460
545
|
for weight, value in weights.items():
|
461
|
-
|
462
|
-
colors[name][weight] = hex_to_values(value)
|
463
|
-
elif value.startswith("rgb"):
|
464
|
-
colors[name][weight] = rgb_to_values(value)
|
465
|
-
elif isinstance(value, str) and all(
|
466
|
-
part.isdigit() for part in value.split()
|
467
|
-
):
|
468
|
-
colors[name][weight] = ", ".join(value.split(" "))
|
469
|
-
pass
|
546
|
+
colors[name][weight] = convert_color(value)
|
470
547
|
|
471
548
|
return colors
|
472
549
|
|