django-unfold 0.67.0__py3-none-any.whl → 0.68.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 (39) hide show
  1. {django_unfold-0.67.0.dist-info → django_unfold-0.68.0.dist-info}/METADATA +8 -5
  2. {django_unfold-0.67.0.dist-info → django_unfold-0.68.0.dist-info}/RECORD +39 -36
  3. unfold/admin.py +73 -13
  4. unfold/components.py +2 -2
  5. unfold/contrib/filters/admin/choice_filters.py +13 -1
  6. unfold/contrib/filters/admin/mixins.py +3 -3
  7. unfold/contrib/filters/admin/numeric_filters.py +6 -6
  8. unfold/contrib/forms/widgets.py +5 -5
  9. unfold/contrib/inlines/admin.py +3 -3
  10. unfold/contrib/inlines/forms.py +5 -4
  11. unfold/dataclasses.py +13 -13
  12. unfold/datasets.py +69 -0
  13. unfold/decorators.py +19 -19
  14. unfold/fields.py +3 -5
  15. unfold/forms.py +19 -7
  16. unfold/mixins/action_model_admin.py +11 -10
  17. unfold/mixins/base_model_admin.py +6 -6
  18. unfold/sites.py +14 -17
  19. unfold/static/unfold/css/styles.css +1 -1
  20. unfold/static/unfold/js/app.js +3 -1
  21. unfold/styles.css +21 -16
  22. unfold/templates/admin/change_form.html +5 -1
  23. unfold/templates/admin/change_list_results.html +10 -62
  24. unfold/templates/admin/edit_inline/stacked.html +1 -1
  25. unfold/templates/admin/search_form.html +5 -3
  26. unfold/templates/unfold/helpers/change_list_headers.html +65 -0
  27. unfold/templates/unfold/helpers/dataset.html +19 -0
  28. unfold/templates/unfold/helpers/edit_inline/tabular_field.html +1 -1
  29. unfold/templates/unfold/helpers/empty_results.html +6 -4
  30. unfold/templates/unfold/helpers/field_readonly_value_file.html +1 -1
  31. unfold/templates/unfold/helpers/tab_items.html +6 -0
  32. unfold/templatetags/unfold.py +18 -13
  33. unfold/templatetags/unfold_list.py +64 -8
  34. unfold/typing.py +5 -6
  35. unfold/utils.py +9 -9
  36. unfold/views.py +15 -1
  37. unfold/widgets.py +30 -29
  38. {django_unfold-0.67.0.dist-info → django_unfold-0.68.0.dist-info}/WHEEL +0 -0
  39. {django_unfold-0.67.0.dist-info → django_unfold-0.68.0.dist-info}/licenses/LICENSE.md +0 -0
@@ -1,4 +1,5 @@
1
- from typing import Any, Callable, Optional
1
+ from collections.abc import Callable
2
+ from typing import Any
2
3
 
3
4
  from django.db.models import Model, QuerySet
4
5
  from django.forms import BaseModelFormSet, ModelForm, modelformset_factory
@@ -9,7 +10,7 @@ from unfold.forms import PaginationFormSetMixin
9
10
  class NonrelatedInlineModelFormSet(PaginationFormSetMixin, BaseModelFormSet):
10
11
  def __init__(
11
12
  self,
12
- instance: Optional[Model] = None,
13
+ instance: Model | None = None,
13
14
  save_as_new: bool = False,
14
15
  **kwargs: Any,
15
16
  ) -> None:
@@ -33,9 +34,9 @@ class NonrelatedInlineModelFormSet(PaginationFormSetMixin, BaseModelFormSet):
33
34
 
34
35
  def nonrelated_inline_formset_factory(
35
36
  model: Model,
36
- queryset: Optional[QuerySet] = None,
37
+ queryset: QuerySet | None = None,
37
38
  formset: BaseModelFormSet = NonrelatedInlineModelFormSet,
38
- save_new_instance: Optional[Callable] = None,
39
+ save_new_instance: Callable | None = None,
39
40
  **kwargs: Any,
40
41
  ) -> BaseModelFormSet:
41
42
  inline_formset = modelformset_factory(model, formset=formset, **kwargs)
unfold/dataclasses.py CHANGED
@@ -1,5 +1,5 @@
1
+ from collections.abc import Callable
1
2
  from dataclasses import dataclass
2
- from typing import Callable, Optional, Union
3
3
 
4
4
  from unfold.enums import ActionVariant
5
5
 
@@ -12,10 +12,10 @@ class UnfoldAction:
12
12
  method: ActionFunction
13
13
  description: str
14
14
  path: str
15
- attrs: Optional[dict] = None
16
- object_id: Optional[Union[int, str]] = None
17
- icon: Optional[str] = None
18
- variant: Optional[ActionVariant] = ActionVariant.DEFAULT
15
+ attrs: dict | None = None
16
+ object_id: int | str | None = None
17
+ icon: str | None = None
18
+ variant: ActionVariant | None = ActionVariant.DEFAULT
19
19
 
20
20
 
21
21
  @dataclass
@@ -23,20 +23,20 @@ class SearchResult:
23
23
  title: str
24
24
  description: str
25
25
  link: str
26
- icon: Optional[str]
26
+ icon: str | None
27
27
 
28
28
 
29
29
  @dataclass
30
30
  class Favicon:
31
- href: Union[str, Callable]
32
- rel: Optional[str] = None
33
- type: Optional[str] = None
34
- sizes: Optional[str] = None
31
+ href: str | Callable
32
+ rel: str | None = None
33
+ type: str | None = None
34
+ sizes: str | None = None
35
35
 
36
36
 
37
37
  @dataclass
38
38
  class DropdownItem:
39
39
  title: str
40
- link: Union[str, Callable]
41
- icon: Optional[str] = None
42
- attrs: Optional[dict] = None
40
+ link: str | Callable
41
+ icon: str | None = None
42
+ attrs: dict | None = None
unfold/datasets.py ADDED
@@ -0,0 +1,69 @@
1
+ from typing import Any
2
+
3
+ from django.contrib import admin
4
+ from django.http import HttpRequest
5
+ from django.template.loader import render_to_string
6
+
7
+ from unfold.views import DatasetChangeList
8
+
9
+
10
+ class BaseDataset:
11
+ tab = False
12
+
13
+ def __init__(
14
+ self, request: HttpRequest, extra_context: dict[str, Any] | None
15
+ ) -> None:
16
+ self.request = request
17
+ self.extra_context = extra_context
18
+
19
+ self.model_admin_instance = self.model_admin(
20
+ model=self.model, admin_site=admin.site
21
+ )
22
+ self.model_admin_instance.extra_context = self.extra_context
23
+
24
+ @property
25
+ def contents(self) -> str:
26
+ return render_to_string(
27
+ "unfold/helpers/dataset.html",
28
+ request=self.request,
29
+ context={
30
+ "dataset": self,
31
+ "cl": self.cl(),
32
+ "opts": self.model._meta,
33
+ },
34
+ )
35
+
36
+ def cl(self) -> DatasetChangeList:
37
+ list_display = self.model_admin_instance.get_list_display(self.request)
38
+ list_display_links = self.model_admin_instance.get_list_display_links(
39
+ self.request, list_display
40
+ )
41
+ sortable_by = self.model_admin_instance.get_sortable_by(self.request)
42
+ search_fields = self.model_admin_instance.get_search_fields(self.request)
43
+ cl = DatasetChangeList(
44
+ request=self.request,
45
+ model=self.model,
46
+ model_admin=self.model_admin_instance,
47
+ list_display=list_display,
48
+ list_display_links=list_display_links,
49
+ list_filter=[],
50
+ date_hierarchy=[],
51
+ search_fields=search_fields,
52
+ list_select_related=[],
53
+ list_per_page=10,
54
+ list_max_show_all=False,
55
+ list_editable=[],
56
+ sortable_by=sortable_by,
57
+ search_help_text=[],
58
+ )
59
+ cl.formset = None
60
+
61
+ return cl
62
+
63
+ @property
64
+ def model_name(self) -> str:
65
+ return self.model._meta.model_name
66
+
67
+ @property
68
+ def model_verbose_name(self) -> str:
69
+ return self.model._meta.verbose_name_plural
unfold/decorators.py CHANGED
@@ -1,5 +1,5 @@
1
- from collections.abc import Iterable
2
- from typing import Any, Callable, Optional, Union
1
+ from collections.abc import Callable, Iterable
2
+ from typing import Any
3
3
 
4
4
  from django.contrib.admin.options import BaseModelAdmin
5
5
  from django.core.exceptions import PermissionDenied
@@ -12,14 +12,14 @@ from unfold.typing import ActionFunction
12
12
 
13
13
 
14
14
  def action(
15
- function: Optional[Callable] = None,
15
+ function: Callable | None = None,
16
16
  *,
17
- permissions: Optional[Iterable[str]] = None,
18
- description: Optional[str] = None,
19
- url_path: Optional[str] = None,
20
- attrs: Optional[dict[str, Any]] = None,
21
- icon: Optional[str] = None,
22
- variant: Optional[ActionVariant] = ActionVariant.DEFAULT,
17
+ permissions: Iterable[str] | None = None,
18
+ description: str | None = None,
19
+ url_path: str | None = None,
20
+ attrs: dict[str, Any] | None = None,
21
+ icon: str | None = None,
22
+ variant: ActionVariant | None = ActionVariant.DEFAULT,
23
23
  ) -> ActionFunction:
24
24
  def decorator(func: Callable) -> ActionFunction:
25
25
  def inner(
@@ -27,7 +27,7 @@ def action(
27
27
  request: HttpRequest,
28
28
  *args: Any,
29
29
  **kwargs,
30
- ) -> Optional[HttpResponse]:
30
+ ) -> HttpResponse | None:
31
31
  if permissions:
32
32
  permission_rules = []
33
33
 
@@ -96,16 +96,16 @@ def action(
96
96
 
97
97
 
98
98
  def display(
99
- function: Optional[Callable[[Model], Any]] = None,
99
+ function: Callable[[Model], Any] | None = None,
100
100
  *,
101
- boolean: Optional[bool] = None,
102
- image: Optional[bool] = None,
103
- ordering: Optional[Union[str, Combinable, BaseExpression]] = None,
104
- description: Optional[str] = None,
105
- empty_value: Optional[str] = None,
106
- dropdown: Optional[bool] = None,
107
- label: Optional[Union[bool, str, dict[str, str]]] = None,
108
- header: Optional[bool] = None,
101
+ boolean: bool | None = None,
102
+ image: bool | None = None,
103
+ ordering: str | Combinable | BaseExpression | None = None,
104
+ description: str | None = None,
105
+ empty_value: str | None = None,
106
+ dropdown: bool | None = None,
107
+ label: bool | str | dict[str, str] | None = None,
108
+ header: bool | None = None,
109
109
  ) -> Callable:
110
110
  def decorator(func: Callable[[Model], Any]) -> Callable:
111
111
  if boolean is not None and empty_value is not None:
unfold/fields.py CHANGED
@@ -1,5 +1,3 @@
1
- from typing import Union
2
-
3
1
  from django.contrib.admin import helpers
4
2
  from django.contrib.admin.utils import lookup_field, quote
5
3
  from django.core.exceptions import ObjectDoesNotExist
@@ -40,7 +38,7 @@ class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
40
38
  return format_html("<label{}>{}</label>", flatatt(attrs), capfirst(label))
41
39
 
42
40
  @property
43
- def url(self) -> Union[str, bool]:
41
+ def url(self) -> str | bool:
44
42
  field, obj, model_admin = (
45
43
  self.field["field"],
46
44
  self.form.instance,
@@ -112,7 +110,7 @@ class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
112
110
  except (AttributeError, ValueError, ObjectDoesNotExist):
113
111
  return False
114
112
 
115
- return isinstance(f, (ImageField, FileField))
113
+ return isinstance(f, ImageField | FileField)
116
114
 
117
115
  def contents(self) -> str:
118
116
  contents = self._get_contents()
@@ -167,7 +165,7 @@ class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
167
165
  if isinstance(f.remote_field, ManyToManyRel) and value is not None:
168
166
  result_repr = ", ".join(map(str, value.all()))
169
167
  elif (
170
- isinstance(f.remote_field, (ForeignObjectRel, OneToOneField))
168
+ isinstance(f.remote_field, ForeignObjectRel | OneToOneField)
171
169
  and value is not None
172
170
  ):
173
171
  result_repr = self.get_admin_url(f.remote_field, value)
unfold/forms.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Generator
2
- from typing import Optional, Union
2
+ from typing import Any, Union
3
3
 
4
4
  from django import forms
5
5
  from django.contrib.admin.forms import (
@@ -8,6 +8,7 @@ from django.contrib.admin.forms import (
8
8
  from django.contrib.admin.forms import (
9
9
  AdminPasswordChangeForm as BaseAdminOwnPasswordChangeForm,
10
10
  )
11
+ from django.contrib.admin.views.main import ChangeListSearchForm
11
12
  from django.contrib.auth.forms import (
12
13
  AdminPasswordChangeForm as BaseAdminPasswordChangeForm,
13
14
  )
@@ -85,7 +86,7 @@ class ActionForm(forms.Form):
85
86
  class AuthenticationForm(AdminAuthenticationForm):
86
87
  def __init__(
87
88
  self,
88
- request: Optional[HttpRequest] = None,
89
+ request: HttpRequest | None = None,
89
90
  *args,
90
91
  **kwargs,
91
92
  ) -> None:
@@ -192,14 +193,14 @@ class Fieldline(BaseFieldline):
192
193
 
193
194
 
194
195
  class PaginationFormSetMixin:
195
- queryset: Optional[QuerySet] = None
196
- request: Optional[HttpRequest] = None
197
- per_page: Optional[int] = None
196
+ queryset: QuerySet | None = None
197
+ request: HttpRequest | None = None
198
+ per_page: int | None = None
198
199
 
199
200
  def __init__(
200
201
  self,
201
- request: Optional[HttpRequest] = None,
202
- per_page: Optional[int] = None,
202
+ request: HttpRequest | None = None,
203
+ per_page: int | None = None,
203
204
  *args,
204
205
  **kwargs,
205
206
  ):
@@ -240,3 +241,14 @@ class PaginationInlineFormSet(PaginationFormSetMixin, BaseInlineFormSet):
240
241
 
241
242
  class PaginationGenericInlineFormSet(PaginationFormSetMixin, BaseGenericInlineFormSet):
242
243
  pass
244
+
245
+
246
+ class DatasetChangeListSearchForm(ChangeListSearchForm):
247
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
248
+ super().__init__(*args, **kwargs)
249
+
250
+ from django.contrib.admin.views.main import SEARCH_VAR
251
+
252
+ self.fields = {
253
+ SEARCH_VAR: forms.CharField(required=False, strip=False),
254
+ }
@@ -1,4 +1,5 @@
1
- from typing import Any, Callable, Optional, Union
1
+ from collections.abc import Callable
2
+ from typing import Any
2
3
 
3
4
  from django.db.models import Model
4
5
  from django.forms import Form
@@ -22,7 +23,7 @@ class ActionModelAdminMixin:
22
23
  actions_submit_line = () # Displayed in changeform in the submit line (form buttons)
23
24
 
24
25
  def changelist_view(
25
- self, request: HttpRequest, extra_context: Optional[dict[str, str]] = None
26
+ self, request: HttpRequest, extra_context: dict[str, str] | None = None
26
27
  ) -> TemplateResponse:
27
28
  """
28
29
  Changelist contains `actions_list` and `actions_row` custom actions. In case of `actions_row` they
@@ -60,9 +61,9 @@ class ActionModelAdminMixin:
60
61
  def changeform_view(
61
62
  self,
62
63
  request: HttpRequest,
63
- object_id: Optional[str] = None,
64
+ object_id: str | None = None,
64
65
  form_url: str = "",
65
- extra_context: Optional[dict[str, bool]] = None,
66
+ extra_context: dict[str, Any] | None = None,
66
67
  ) -> Any:
67
68
  """
68
69
  Changeform contains `actions_submit_line` and `actions_detail` custom actions.
@@ -159,7 +160,7 @@ class ActionModelAdminMixin:
159
160
  request, self._get_base_actions_submit_line(), object_id
160
161
  )
161
162
 
162
- def _extract_action_names(self, actions: list[Union[str, dict]]) -> list[str]:
163
+ def _extract_action_names(self, actions: list[str | dict]) -> list[str]:
163
164
  """
164
165
  Gets the list of only actions names from the actions structure provided in ModelAdmin
165
166
  """
@@ -228,10 +229,10 @@ class ActionModelAdminMixin:
228
229
 
229
230
  def _get_actions_navigation(
230
231
  self,
231
- provided_actions: list[Union[str, dict]],
232
+ provided_actions: list[str | dict],
232
233
  allowed_actions: list[UnfoldAction],
233
- object_id: Optional[str] = None,
234
- ) -> list[Union[str, dict]]:
234
+ object_id: str | None = None,
235
+ ) -> list[str | dict]:
235
236
  """
236
237
  Builds navigation structure for the actions which is going to be provided to the template.
237
238
  """
@@ -272,7 +273,7 @@ class ActionModelAdminMixin:
272
273
  "path": get_action_path(action),
273
274
  }
274
275
 
275
- def build_dropdown(nav_item: dict) -> Optional[dict]:
276
+ def build_dropdown(nav_item: dict) -> dict | None:
276
277
  """
277
278
  Builds a dropdown structure for the action.
278
279
  """
@@ -304,7 +305,7 @@ class ActionModelAdminMixin:
304
305
  self,
305
306
  request: HttpRequest,
306
307
  actions: list[UnfoldAction],
307
- object_id: Optional[Union[int, str]] = None,
308
+ object_id: int | str | None = None,
308
309
  ) -> list[UnfoldAction]:
309
310
  """
310
311
  Filters out actions that the user doesn't have access to.
@@ -1,5 +1,5 @@
1
1
  import copy
2
- from typing import Any, Optional
2
+ from typing import Any
3
3
 
4
4
  from django.contrib.admin import helpers
5
5
  from django.contrib.admin.sites import AdminSite
@@ -31,9 +31,9 @@ class BaseModelAdminMixin:
31
31
  def changeform_view(
32
32
  self,
33
33
  request: HttpRequest,
34
- object_id: Optional[str] = None,
34
+ object_id: str | None = None,
35
35
  form_url: str = "",
36
- extra_context: Optional[dict[str, Any]] = None,
36
+ extra_context: dict[str, Any] | None = None,
37
37
  ) -> Any:
38
38
  from unfold.forms import AdminForm, Fieldline
39
39
 
@@ -61,7 +61,7 @@ class BaseModelAdminMixin:
61
61
 
62
62
  def formfield_for_foreignkey(
63
63
  self, db_field: ForeignKey, request: HttpRequest, **kwargs
64
- ) -> Optional[ModelChoiceField]:
64
+ ) -> ModelChoiceField | None:
65
65
  db = kwargs.get("using")
66
66
 
67
67
  # Overrides widgets for all related fields
@@ -102,7 +102,7 @@ class BaseModelAdminMixin:
102
102
 
103
103
  def formfield_for_nullboolean_field(
104
104
  self, db_field: Field, request: HttpRequest, **kwargs
105
- ) -> Optional[Field]:
105
+ ) -> Field | None:
106
106
  if "widget" not in kwargs:
107
107
  if db_field.choices:
108
108
  kwargs["widget"] = widgets.UnfoldAdminSelectWidget(
@@ -115,7 +115,7 @@ class BaseModelAdminMixin:
115
115
 
116
116
  def formfield_for_dbfield(
117
117
  self, db_field: Field, request: HttpRequest, **kwargs
118
- ) -> Optional[Field]:
118
+ ) -> Field | None:
119
119
  if isinstance(db_field, models.BooleanField) and db_field.null is True:
120
120
  return self.formfield_for_nullboolean_field(db_field, request, **kwargs)
121
121
 
unfold/sites.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import copy
2
2
  import time
3
+ from collections.abc import Callable
3
4
  from http import HTTPStatus
4
- from typing import Any, Callable, Optional, Union
5
+ from typing import Any
5
6
  from urllib.parse import parse_qs, urlparse
6
7
 
7
8
  from django.contrib.admin import AdminSite
@@ -141,7 +142,7 @@ class UnfoldAdminSite(AdminSite):
141
142
  return context
142
143
 
143
144
  def index(
144
- self, request: HttpRequest, extra_context: Optional[dict[str, Any]] = None
145
+ self, request: HttpRequest, extra_context: dict[str, Any] | None = None
145
146
  ) -> TemplateResponse:
146
147
  app_list = self.get_app_list(request)
147
148
 
@@ -166,7 +167,7 @@ class UnfoldAdminSite(AdminSite):
166
167
  )
167
168
 
168
169
  def toggle_sidebar(
169
- self, request: HttpRequest, extra_context: Optional[dict[str, Any]] = None
170
+ self, request: HttpRequest, extra_context: dict[str, Any] | None = None
170
171
  ) -> HttpResponse:
171
172
  if "toggle_sidebar" not in request.session:
172
173
  request.session["toggle_sidebar"] = True
@@ -214,14 +215,14 @@ class UnfoldAdminSite(AdminSite):
214
215
  request: HttpRequest,
215
216
  app_list: list[dict[str, Any]],
216
217
  search_term: str,
217
- allowed_models: Optional[list[str]] = None,
218
+ allowed_models: list[str] | None = None,
218
219
  ) -> list[SearchResult]:
219
220
  results = []
220
221
 
221
222
  for app in app_list:
222
223
  for model in app["models"]:
223
224
  # Skip models which are not allowed
224
- if isinstance(allowed_models, (list, tuple)):
225
+ if isinstance(allowed_models, list | tuple):
225
226
  if model["model"]._meta.label.lower() not in [
226
227
  m.lower() for m in allowed_models
227
228
  ]:
@@ -263,7 +264,7 @@ class UnfoldAdminSite(AdminSite):
263
264
  return results
264
265
 
265
266
  def search(
266
- self, request: HttpRequest, extra_context: Optional[dict[str, Any]] = None
267
+ self, request: HttpRequest, extra_context: dict[str, Any] | None = None
267
268
  ) -> TemplateResponse:
268
269
  start_time = time.time()
269
270
 
@@ -302,10 +303,10 @@ class UnfoldAdminSite(AdminSite):
302
303
  self._get_config("COMMAND", request).get("search_models"), request
303
304
  )
304
305
 
305
- if search_models is True or isinstance(search_models, (list, tuple)):
306
+ if search_models is True or isinstance(search_models, list | tuple):
306
307
  allowed_models = (
307
308
  search_models
308
- if isinstance(search_models, (list, tuple))
309
+ if isinstance(search_models, list | tuple)
309
310
  else None
310
311
  )
311
312
 
@@ -340,7 +341,7 @@ class UnfoldAdminSite(AdminSite):
340
341
  )
341
342
 
342
343
  def password_change(
343
- self, request: HttpRequest, extra_context: Optional[dict[str, Any]] = None
344
+ self, request: HttpRequest, extra_context: dict[str, Any] | None = None
344
345
  ) -> HttpResponse:
345
346
  from django.contrib.auth.views import PasswordChangeView
346
347
 
@@ -462,7 +463,7 @@ class UnfoldAdminSite(AdminSite):
462
463
  return tabs
463
464
 
464
465
  def _call_permission_callback(
465
- self, callback: Union[str, Callable, None], request: HttpRequest
466
+ self, callback: str | Callable | None, request: HttpRequest
466
467
  ) -> bool:
467
468
  if callback is None:
468
469
  return True
@@ -490,7 +491,7 @@ class UnfoldAdminSite(AdminSite):
490
491
  return target
491
492
 
492
493
  def _get_is_active(
493
- self, request: HttpRequest, link: Union[str, Callable], is_tab: bool = False
494
+ self, request: HttpRequest, link: str | Callable, is_tab: bool = False
494
495
  ) -> bool:
495
496
  if not isinstance(link, str):
496
497
  link = str(link)
@@ -545,9 +546,7 @@ class UnfoldAdminSite(AdminSite):
545
546
  if key in config and config[key]:
546
547
  return self._get_value(config[key], *args)
547
548
 
548
- def _get_theme_images(
549
- self, key: str, *args: Any
550
- ) -> Union[dict[str, str], str, None]:
549
+ def _get_theme_images(self, key: str, *args: Any) -> dict[str, str] | str | None:
551
550
  images = self._get_config(key, *args)
552
551
 
553
552
  if isinstance(images, dict):
@@ -613,9 +612,7 @@ class UnfoldAdminSite(AdminSite):
613
612
  for item in items
614
613
  ]
615
614
 
616
- def _get_value(
617
- self, value: Union[str, Callable, lazy, None], *args: Any
618
- ) -> Optional[str]:
615
+ def _get_value(self, value: str | Callable | None, *args: Any) -> str | None:
619
616
  if value is None:
620
617
  return None
621
618