django-unfold 0.46.0__py3-none-any.whl → 0.47.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. {django_unfold-0.46.0.dist-info → django_unfold-0.47.0.dist-info}/METADATA +5 -6
  2. {django_unfold-0.46.0.dist-info → django_unfold-0.47.0.dist-info}/RECORD +38 -31
  3. {django_unfold-0.46.0.dist-info → django_unfold-0.47.0.dist-info}/WHEEL +1 -1
  4. unfold/admin.py +15 -16
  5. unfold/checks.py +4 -4
  6. unfold/components.py +5 -5
  7. unfold/contrib/filters/admin/__init__.py +43 -0
  8. unfold/contrib/filters/admin/autocomplete_filters.py +16 -0
  9. unfold/contrib/filters/admin/datetime_filters.py +212 -0
  10. unfold/contrib/filters/admin/dropdown_filters.py +100 -0
  11. unfold/contrib/filters/admin/mixins.py +146 -0
  12. unfold/contrib/filters/admin/numeric_filters.py +196 -0
  13. unfold/contrib/filters/admin/text_filters.py +65 -0
  14. unfold/contrib/filters/admin.py +32 -32
  15. unfold/contrib/filters/forms.py +68 -17
  16. unfold/contrib/forms/widgets.py +9 -9
  17. unfold/contrib/inlines/checks.py +2 -4
  18. unfold/contrib/simple_history/templates/simple_history/object_history.html +17 -1
  19. unfold/contrib/simple_history/templates/simple_history/object_history_list.html +1 -1
  20. unfold/dataclasses.py +2 -2
  21. unfold/decorators.py +4 -3
  22. unfold/settings.py +2 -2
  23. unfold/sites.py +156 -140
  24. unfold/static/unfold/css/styles.css +1 -1
  25. unfold/static/unfold/js/app.js +2 -2
  26. unfold/templates/admin/filter.html +1 -1
  27. unfold/templates/unfold/helpers/change_list_filter.html +2 -2
  28. unfold/templates/unfold/helpers/change_list_filter_actions.html +1 -1
  29. unfold/templates/unfold/helpers/header_back_button.html +2 -2
  30. unfold/templates/unfold/helpers/tab_list.html +7 -1
  31. unfold/templates/unfold/layouts/skeleton.html +1 -1
  32. unfold/templatetags/unfold.py +55 -22
  33. unfold/templatetags/unfold_list.py +2 -2
  34. unfold/typing.py +5 -4
  35. unfold/utils.py +3 -2
  36. unfold/views.py +2 -2
  37. unfold/widgets.py +27 -27
  38. {django_unfold-0.46.0.dist-info → django_unfold-0.47.0.dist-info}/LICENSE.md +0 -0
@@ -78,9 +78,9 @@ const filterForm = () => {
78
78
  }
79
79
 
80
80
  filterForm.addEventListener("formdata", (event) => {
81
- for (const [key, value] of event.formData.entries()) {
81
+ Array.from(event.formData.entries()).forEach(([key, value]) => {
82
82
  if (value === "") event.formData.delete(key);
83
- }
83
+ });
84
84
  });
85
85
  };
86
86
 
@@ -12,7 +12,7 @@
12
12
  {% endfor %}
13
13
 
14
14
  {% if spec|class_name == "BooleanFieldListFilter" %}
15
- <ul class="flex border min-w-20 rounded shadow-sm text-font-default-light dark:border-base-700 dark:text-font-default-dark w-full">
15
+ <ul class="dark:bg-base-900 border border-base-200 flex min-w-20 rounded shadow-sm text-font-default-light dark:border-base-700 dark:text-font-default-dark w-full">
16
16
  {% for choice in choices %}
17
17
  <li class="basis-1/3 border-r border-base-200 flex-grow truncate last:border-r-0 dark:border-base-700 {% if choice.selected %}font-semibold text-primary-600 dark:text-primary-500 {% else %}hover:text-base-700 dark:hover:text-base-200{% endif %}">
18
18
  <a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}" class="block px-3 py-2 text-center hover:text-primary-600 dark:hover:text-primary-500">
@@ -10,8 +10,8 @@
10
10
  {% preserve_filters %}
11
11
  {% endif %}
12
12
 
13
- <div class="flex flex-col grow gap-4 overflow-auto *:mb-0" {% if cl.model_admin.list_filter_sheet %}data-simplebar data-simplebar-direction='rtl'{% endif %}>
14
- <div class="flex flex-col gap-4 {% if cl.model_admin.list_filter_sheet %}px-3 py-2.5{% endif %} *:mb-0">
13
+ <div class="flex flex-col grow gap-4 overflow-auto *:mb-0" data-simplebar data-simplebar-direction="rtl">
14
+ <div class="flex flex-col gap-4 mx-1 px-2 py-2.5 {% if not cl.model_admin.list_filter_sheet %} 2xl:px-0 2xl:py-0{% endif %} *:mb-0">
15
15
  {% for spec in cl.filter_specs %}
16
16
  {% admin_list_filter cl spec %}
17
17
  {% endfor %}
@@ -1,6 +1,6 @@
1
1
  {% load i18n %}
2
2
 
3
- <div class="{% if cl.model_admin.list_filter_sheet %}bg-white border-t border-base-200 p-3 py-2.5 dark:bg-base-800 dark:border-base-700{% else %}mt-6{% endif %}">
3
+ <div class="bg-white border-t border-base-200 p-3 py-2.5 dark:bg-base-800 dark:border-base-700{% if not cl.model_admin.list_filter_sheet %} 2xl:!border-t-0 2xl:!bg-transparent 2xl:px-0{% endif %}">
4
4
  {% if cl.model_admin.list_filter_submit %}
5
5
  <button type="submit" class="bg-primary-600 block border border-transparent font-medium px-3 py-2 rounded text-white w-full">
6
6
  {% trans "Apply Filters" %}
@@ -2,9 +2,9 @@
2
2
 
3
3
  {% if show_back_button %}
4
4
  {% if opts and adminform %}
5
- {% url opts|admin_urlname:'changelist' as link %}
5
+ {% url opts|admin_urlname:'changelist' as changelist_url %}
6
6
 
7
- <a href="{{ link }}" class="block h-4.5 mr-3" title="{% trans "Go back" %}">
7
+ <a href="{% add_preserved_filters changelist_url %}" class="block h-4.5 mr-3" title="{% trans "Go back" %}">
8
8
  <span class="material-symbols-outlined">
9
9
  arrow_back
10
10
  </span>
@@ -8,7 +8,13 @@
8
8
  {% for item in tabs_list %}
9
9
  {% if item.has_permission %}
10
10
  <li class="border-b last:border-b-0 md:border-b-0 md:mr-8 dark:border-base-800">
11
- <a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}" class="block px-3 py-2 md:py-4 md:px-0 dark:border-base-800 {% if item.active %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}">
11
+ <a href="{% if item.link_callback %}{{ item.link_callback }}{% else %}{{ item.link }}{% endif %}"
12
+ class="block px-3 py-2 md:py-4 md:px-0 dark:border-base-800 {% if item.active and not item.inline %} border-b font-semibold -mb-px text-primary-600 hover:text-primary-600 dark:text-primary-500 dark:hover:text-primary-500 md:border-primary-500 dark:md:!border-primary-600{% else %}font-medium hover:text-primary-600 dark:hover:text-primary-500{% endif %}"
13
+ {% if item.inline %}
14
+ x-on:click="activeTab = '{{ item.inline }}'"
15
+ x-bind:class="{'border-b border-base-200 dark:border-base-800 md:border-primary-500 dark:md:!border-primary-600 font-semibold -mb-px text-primary-600 dark:text-primary-500': activeTab == '{{ item.inline }}'}"
16
+ {% endif %}
17
+ >
12
18
  {{ item.title }}
13
19
  </a>
14
20
  </li>
@@ -59,7 +59,7 @@
59
59
  {% endblock %}
60
60
  </head>
61
61
 
62
- <body class="antialiased bg-white font-sans text-font-default-light text-sm dark:bg-base-900 dark:text-font-default-dark {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{ mainWidth: 0, activeTab: 'general', sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }" x-init="activeTab = window.location.hash?.replace('#', '') || 'general'">
62
+ <body class="antialiased bg-white font-sans text-font-default-light text-sm dark:bg-base-900 dark:text-font-default-dark {% if is_popup %}popup {% endif %}{% block bodyclass %}{% endblock %}" data-admin-utc-offset="{% now "Z" %}" x-data="{ mainWidth: 0, {% if opts %}activeTab: 'general',{% endif %} sidebarMobileOpen: false, sidebarDesktopOpen: {% if request.session.toggle_sidebar == False %}false{% else %}true{% endif %} }" x-init="activeTab = {% if opts %}window.location.hash?.replace('#', '') || 'general'{% else %}''{% endif %}">
63
63
  {% if colors %}
64
64
  <style id="unfold-theme-colors">
65
65
  :root {
@@ -1,8 +1,10 @@
1
- from typing import Any, Dict, List, Mapping, Optional, Set, Union
1
+ from collections.abc import Mapping
2
+ from typing import Any, Optional, Union
2
3
 
3
4
  from django import template
4
5
  from django.contrib.admin.helpers import AdminForm, Fieldset
5
6
  from django.contrib.admin.views.main import ChangeList
7
+ from django.db.models.options import Options
6
8
  from django.forms import Field
7
9
  from django.http import HttpRequest
8
10
  from django.template import Context, Library, Node, RequestContext, TemplateSyntaxError
@@ -15,9 +17,44 @@ from unfold.components import ComponentRegistry
15
17
  register = Library()
16
18
 
17
19
 
18
- @register.simple_tag(name="tab_list", takes_context=True)
19
- def tab_list(context, page, opts) -> str:
20
+ def _get_tabs_list(
21
+ context: RequestContext, page: str, opts: Optional[Options] = None
22
+ ) -> list:
20
23
  tabs_list = []
24
+ page_id = None
25
+
26
+ if page not in ["changeform", "changelist"]:
27
+ page_id = page
28
+
29
+ for tab in context.get("tab_list", []):
30
+ if page_id:
31
+ if tab.get("page") == page_id:
32
+ tabs_list = tab["items"]
33
+ break
34
+
35
+ continue
36
+
37
+ if "models" not in tab:
38
+ continue
39
+
40
+ for tab_model in tab["models"]:
41
+ if isinstance(tab_model, str):
42
+ if str(opts) == tab_model and page == "changelist":
43
+ tabs_list = tab["items"]
44
+ break
45
+ elif isinstance(tab_model, dict) and str(opts) == tab_model["name"]:
46
+ is_detail = tab_model.get("detail", False)
47
+
48
+ if (page == "changeform" and is_detail) or (
49
+ page == "changelist" and not is_detail
50
+ ):
51
+ tabs_list = tab["items"]
52
+ break
53
+ return tabs_list
54
+
55
+
56
+ @register.simple_tag(name="tab_list", takes_context=True)
57
+ def tab_list(context: RequestContext, page: str, opts: Optional[Options] = None) -> str:
21
58
  inlines_list = []
22
59
 
23
60
  data = {
@@ -26,22 +63,18 @@ def tab_list(context, page, opts) -> str:
26
63
  "actions_list": context.get("actions_list"),
27
64
  "actions_items": context.get("actions_items"),
28
65
  "is_popup": context.get("is_popup"),
66
+ "tabs_list": _get_tabs_list(context, page, opts),
29
67
  }
30
68
 
31
- for tab in context.get("tab_list", []):
32
- if str(opts) in tab["models"]:
33
- tabs_list = tab["items"]
34
- break
35
-
36
- if page == "changelist":
37
- data["tabs_list"] = tabs_list
38
-
39
- for inline in context.get("inline_admin_formsets", []):
40
- if hasattr(inline.opts, "tab"):
41
- inlines_list.append(inline)
69
+ # If the changeform is rendered and there are no custom tab navigation
70
+ # specified, check for inlines to put into tabs
71
+ if page == "changeform" and len(data["tabs_list"]) == 0:
72
+ for inline in context.get("inline_admin_formsets", []):
73
+ if opts and hasattr(inline.opts, "tab"):
74
+ inlines_list.append(inline)
42
75
 
43
- if page == "changeform" and len(inlines_list) > 0:
44
- data["inlines_list"] = inlines_list
76
+ if len(inlines_list) > 0:
77
+ data["inlines_list"] = inlines_list
45
78
 
46
79
  return render_to_string(
47
80
  "unfold/helpers/tab_list.html",
@@ -70,7 +103,7 @@ def index(indexable: Mapping[int, Any], i: int) -> Any:
70
103
 
71
104
 
72
105
  @register.filter
73
- def tabs(adminform: AdminForm) -> List[Fieldset]:
106
+ def tabs(adminform: AdminForm) -> list[Fieldset]:
74
107
  result = []
75
108
 
76
109
  for fieldset in adminform:
@@ -86,7 +119,7 @@ class CaptureNode(Node):
86
119
  self.varname = varname
87
120
  self.silent = silent
88
121
 
89
- def render(self, context: Dict[str, Any]) -> Union[str, SafeText]:
122
+ def render(self, context: dict[str, Any]) -> Union[str, SafeText]:
90
123
  output = self.nodelist.render(context)
91
124
  context[self.varname] = output
92
125
  if self.silent:
@@ -155,7 +188,7 @@ class RenderComponentNode(template.Node):
155
188
  self,
156
189
  template_name: str,
157
190
  nodelist: NodeList,
158
- extra_context: Optional[Dict] = None,
191
+ extra_context: Optional[dict] = None,
159
192
  include_context: bool = False,
160
193
  *args,
161
194
  **kwargs,
@@ -252,7 +285,7 @@ def add_css_class(field: Field, classes: Union[list, tuple]) -> Field:
252
285
  takes_context=True,
253
286
  name="preserve_filters",
254
287
  )
255
- def preserve_changelist_filters(context: Context) -> Dict[str, Dict[str, str]]:
288
+ def preserve_changelist_filters(context: Context) -> dict[str, dict[str, str]]:
256
289
  """
257
290
  Generate hidden input fields to preserve filters for POST forms.
258
291
  """
@@ -262,10 +295,10 @@ def preserve_changelist_filters(context: Context) -> Dict[str, Dict[str, str]]:
262
295
  if not request or not changelist:
263
296
  return {"params": {}}
264
297
 
265
- used_params: Set[str] = {
298
+ used_params: set[str] = {
266
299
  param for spec in changelist.filter_specs for param in spec.used_parameters
267
300
  }
268
- preserved_params: Dict[str, str] = {
301
+ preserved_params: dict[str, str] = {
269
302
  param: value for param, value in request.GET.items() if param not in used_params
270
303
  }
271
304
 
@@ -1,5 +1,5 @@
1
1
  import datetime
2
- from typing import Any, Dict, Optional, Union
2
+ from typing import Any, Optional, Union
3
3
 
4
4
  from django.contrib.admin.templatetags.admin_list import (
5
5
  ResultList,
@@ -355,7 +355,7 @@ def results(cl: ChangeList):
355
355
  yield UnfoldResultList(pk_value, None, items_for_result(cl, res, None))
356
356
 
357
357
 
358
- def result_list(context: Dict[str, Any], cl: ChangeList) -> Dict[str, Any]:
358
+ def result_list(context: dict[str, Any], cl: ChangeList) -> dict[str, Any]:
359
359
  """
360
360
  Display the headers and data list together.
361
361
  """
unfold/typing.py CHANGED
@@ -1,4 +1,5 @@
1
- from typing import Any, Dict, Iterable, List, Protocol, Tuple, Union
1
+ from collections.abc import Iterable
2
+ from typing import Any, Protocol, Union
2
3
 
3
4
 
4
5
  class ActionFunction(Protocol):
@@ -11,13 +12,13 @@ class ActionFunction(Protocol):
11
12
  allowed_permissions: Iterable[str]
12
13
  short_description: str
13
14
  url_path: str
14
- attrs: Dict[str, Any]
15
+ attrs: dict[str, Any]
15
16
 
16
17
  def __call__(self, *args, **kwargs):
17
18
  pass
18
19
 
19
20
 
20
21
  FieldsetsType = Union[
21
- List[Tuple[Union[str, None], Dict[str, Any]]],
22
- Tuple[Tuple[Union[str, None], Dict[str, Any]]],
22
+ list[tuple[Union[str, None], dict[str, Any]]],
23
+ tuple[tuple[Union[str, None], dict[str, Any]]],
23
24
  ]
unfold/utils.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import datetime
2
2
  import decimal
3
3
  import json
4
- from typing import Any, Iterable, List, Optional
4
+ from collections.abc import Iterable
5
+ from typing import Any, Optional
5
6
 
6
7
  from django.conf import settings
7
8
  from django.db import models
@@ -114,7 +115,7 @@ def display_for_field(value: Any, field: Any, empty_value_display: str) -> str:
114
115
  return display_for_value(value, empty_value_display)
115
116
 
116
117
 
117
- def hex_to_rgb(hex_color: str) -> List[int]:
118
+ def hex_to_rgb(hex_color: str) -> list[int]:
118
119
  hex_color = hex_color.lstrip("#")
119
120
 
120
121
  r = int(hex_color[0:2], 16)
unfold/views.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict
1
+ from typing import Any
2
2
 
3
3
  from django.contrib.auth.mixins import PermissionRequiredMixin
4
4
 
@@ -16,7 +16,7 @@ class UnfoldModelAdminViewMixin(PermissionRequiredMixin):
16
16
  self.model_admin = model_admin
17
17
  super().__init__(**kwargs)
18
18
 
19
- def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
19
+ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
20
20
  if not hasattr(self, "model_admin"):
21
21
  raise UnfoldException(
22
22
  "UnfoldModelAdminViewMixin was not provided with 'model_admin' argument"
unfold/widgets.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, Callable, Dict, Optional, Tuple, Union
1
+ from typing import Any, Callable, Optional, Union
2
2
 
3
3
  from django.contrib.admin.options import VERTICAL
4
4
  from django.contrib.admin.sites import AdminSite
@@ -255,7 +255,7 @@ SWITCH_CLASSES = [
255
255
 
256
256
 
257
257
  class UnfoldAdminTextInputWidget(AdminTextInputWidget):
258
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
258
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
259
259
  super().__init__(
260
260
  attrs={
261
261
  **(attrs or {}),
@@ -269,7 +269,7 @@ class UnfoldAdminTextInputWidget(AdminTextInputWidget):
269
269
  class UnfoldAdminURLInputWidget(AdminURLFieldWidget):
270
270
  template_name = "unfold/widgets/url.html"
271
271
 
272
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
272
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
273
273
  super().__init__(
274
274
  attrs={
275
275
  **(attrs or {}),
@@ -281,7 +281,7 @@ class UnfoldAdminURLInputWidget(AdminURLFieldWidget):
281
281
 
282
282
 
283
283
  class UnfoldAdminColorInputWidget(AdminTextInputWidget):
284
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
284
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
285
285
  super().__init__(
286
286
  attrs={
287
287
  **(attrs or {}),
@@ -294,7 +294,7 @@ class UnfoldAdminColorInputWidget(AdminTextInputWidget):
294
294
 
295
295
 
296
296
  class UnfoldAdminUUIDInputWidget(AdminUUIDInputWidget):
297
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
297
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
298
298
  super().__init__(
299
299
  attrs={
300
300
  **(attrs or {}),
@@ -308,7 +308,7 @@ class UnfoldAdminUUIDInputWidget(AdminUUIDInputWidget):
308
308
  class UnfoldAdminIntegerRangeWidget(MultiWidget):
309
309
  template_name = "unfold/widgets/range.html"
310
310
 
311
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
311
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
312
312
  if attrs is None:
313
313
  attrs = {}
314
314
 
@@ -320,14 +320,14 @@ class UnfoldAdminIntegerRangeWidget(MultiWidget):
320
320
 
321
321
  super().__init__(_widgets, attrs)
322
322
 
323
- def decompress(self, value: Union[str, None]) -> Tuple[Optional[Callable], ...]:
323
+ def decompress(self, value: Union[str, None]) -> tuple[Optional[Callable], ...]:
324
324
  if value:
325
325
  return value.lower, value.upper
326
326
  return None, None
327
327
 
328
328
 
329
329
  class UnfoldAdminEmailInputWidget(AdminEmailInputWidget):
330
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
330
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
331
331
  super().__init__(
332
332
  attrs={
333
333
  **(attrs or {}),
@@ -374,7 +374,7 @@ class UnfoldAdminDateWidget(AdminDateWidget):
374
374
  template_name = "unfold/widgets/date.html"
375
375
 
376
376
  def __init__(
377
- self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
377
+ self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
378
378
  ) -> None:
379
379
  attrs = {
380
380
  **(attrs or {}),
@@ -394,7 +394,7 @@ class UnfoldAdminSingleDateWidget(AdminDateWidget):
394
394
  template_name = "unfold/widgets/date.html"
395
395
 
396
396
  def __init__(
397
- self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
397
+ self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
398
398
  ) -> None:
399
399
  attrs = {
400
400
  **(attrs or {}),
@@ -414,7 +414,7 @@ class UnfoldAdminTimeWidget(AdminTimeWidget):
414
414
  template_name = "unfold/widgets/time.html"
415
415
 
416
416
  def __init__(
417
- self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
417
+ self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
418
418
  ) -> None:
419
419
  attrs = {
420
420
  **(attrs or {}),
@@ -434,7 +434,7 @@ class UnfoldAdminSingleTimeWidget(AdminTimeWidget):
434
434
  template_name = "unfold/widgets/time.html"
435
435
 
436
436
  def __init__(
437
- self, attrs: Optional[Dict[str, Any]] = None, format: Optional[str] = None
437
+ self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
438
438
  ) -> None:
439
439
  attrs = {
440
440
  **(attrs or {}),
@@ -453,7 +453,7 @@ class UnfoldAdminSingleTimeWidget(AdminTimeWidget):
453
453
  class UnfoldAdminTextareaWidget(AdminTextareaWidget):
454
454
  template_name = "unfold/widgets/textarea.html"
455
455
 
456
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
456
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
457
457
  attrs = attrs or {}
458
458
 
459
459
  super().__init__(
@@ -473,7 +473,7 @@ class UnfoldAdminTextareaWidget(AdminTextareaWidget):
473
473
  class UnfoldAdminExpandableTextareaWidget(AdminTextareaWidget):
474
474
  template_name = "unfold/widgets/textarea_expandable.html"
475
475
 
476
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
476
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
477
477
  attrs = attrs or {}
478
478
 
479
479
  attrs.update({"rows": 2})
@@ -496,7 +496,7 @@ class UnfoldAdminExpandableTextareaWidget(AdminTextareaWidget):
496
496
  class UnfoldAdminSplitDateTimeWidget(AdminSplitDateTime):
497
497
  template_name = "unfold/widgets/split_datetime.html"
498
498
 
499
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
499
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
500
500
  widgets = [UnfoldAdminDateWidget, UnfoldAdminTimeWidget]
501
501
  MultiWidget.__init__(self, widgets, attrs)
502
502
 
@@ -506,9 +506,9 @@ class UnfoldAdminSplitDateTimeVerticalWidget(AdminSplitDateTime):
506
506
 
507
507
  def __init__(
508
508
  self,
509
- attrs: Optional[Dict[str, Any]] = None,
510
- date_attrs: Optional[Dict[str, Any]] = None,
511
- time_attrs: Optional[Dict[str, Any]] = None,
509
+ attrs: Optional[dict[str, Any]] = None,
510
+ date_attrs: Optional[dict[str, Any]] = None,
511
+ time_attrs: Optional[dict[str, Any]] = None,
512
512
  date_label: Optional[str] = None,
513
513
  time_label: Optional[str] = None,
514
514
  ) -> None:
@@ -522,8 +522,8 @@ class UnfoldAdminSplitDateTimeVerticalWidget(AdminSplitDateTime):
522
522
  MultiWidget.__init__(self, widgets, attrs)
523
523
 
524
524
  def get_context(
525
- self, name: str, value: Any, attrs: Optional[Dict[str, Any]]
526
- ) -> Dict[str, Any]:
525
+ self, name: str, value: Any, attrs: Optional[dict[str, Any]]
526
+ ) -> dict[str, Any]:
527
527
  context = super().get_context(name, value, attrs)
528
528
 
529
529
  if self.date_label is not None:
@@ -540,7 +540,7 @@ class UnfoldAdminSplitDateTimeVerticalWidget(AdminSplitDateTime):
540
540
 
541
541
 
542
542
  class UnfoldAdminIntegerFieldWidget(AdminIntegerFieldWidget):
543
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
543
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
544
544
  super().__init__(
545
545
  attrs={
546
546
  **(attrs or {}),
@@ -552,7 +552,7 @@ class UnfoldAdminIntegerFieldWidget(AdminIntegerFieldWidget):
552
552
 
553
553
 
554
554
  class UnfoldAdminDecimalFieldWidget(AdminIntegerFieldWidget):
555
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
555
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
556
556
  super().__init__(
557
557
  attrs={
558
558
  **(attrs or {}),
@@ -564,7 +564,7 @@ class UnfoldAdminDecimalFieldWidget(AdminIntegerFieldWidget):
564
564
 
565
565
 
566
566
  class UnfoldAdminBigIntegerFieldWidget(AdminBigIntegerFieldWidget):
567
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
567
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
568
568
  super().__init__(
569
569
  attrs={
570
570
  **(attrs or {}),
@@ -621,7 +621,7 @@ class UnfoldAdminRadioSelectWidget(AdminRadioSelect):
621
621
  self.radio_style = radio_style
622
622
  self.attrs["class"] = " ".join([*RADIO_CLASSES, self.attrs.get("class", "")])
623
623
 
624
- def get_context(self, *args, **kwargs) -> Dict[str, Any]:
624
+ def get_context(self, *args, **kwargs) -> dict[str, Any]:
625
625
  context = super().get_context(*args, **kwargs)
626
626
  context.update({"radio_style": self.radio_style})
627
627
  return context
@@ -666,7 +666,7 @@ except ImportError:
666
666
 
667
667
  class UnfoldBooleanWidget(CheckboxInput):
668
668
  def __init__(
669
- self, attrs: Optional[Dict[str, Any]] = None, check_test: Callable = None
669
+ self, attrs: Optional[dict[str, Any]] = None, check_test: Callable = None
670
670
  ) -> None:
671
671
  if attrs is None:
672
672
  attrs = {}
@@ -684,7 +684,7 @@ class UnfoldBooleanWidget(CheckboxInput):
684
684
 
685
685
  class UnfoldBooleanSwitchWidget(CheckboxInput):
686
686
  def __init__(
687
- self, attrs: Optional[Dict[str, Any]] = None, check_test: Callable = None
687
+ self, attrs: Optional[dict[str, Any]] = None, check_test: Callable = None
688
688
  ) -> None:
689
689
  super().__init__(
690
690
  attrs={
@@ -708,7 +708,7 @@ class UnfoldForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
708
708
  self,
709
709
  rel: ForeignObjectRel,
710
710
  admin_site: AdminSite,
711
- attrs: Optional[Dict] = None,
711
+ attrs: Optional[dict] = None,
712
712
  using: Optional[Any] = None,
713
713
  ) -> None:
714
714
  attrs = {