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,6 +1,6 @@
1
1
  import datetime
2
2
  from collections.abc import Generator
3
- from typing import Any, Optional, Union
3
+ from typing import Any
4
4
 
5
5
  from django.contrib.admin.templatetags.admin_list import (
6
6
  ResultList,
@@ -11,8 +11,10 @@ from django.contrib.admin.templatetags.admin_urls import add_preserved_filters
11
11
  from django.contrib.admin.templatetags.base import InclusionAdminNode
12
12
  from django.contrib.admin.utils import label_for_field, lookup_field
13
13
  from django.contrib.admin.views.main import (
14
+ IS_POPUP_VAR,
14
15
  ORDER_VAR,
15
16
  PAGE_VAR,
17
+ SEARCH_VAR,
16
18
  ChangeList,
17
19
  )
18
20
  from django.core.exceptions import ObjectDoesNotExist
@@ -34,8 +36,15 @@ from unfold.utils import (
34
36
  display_for_label,
35
37
  display_for_value,
36
38
  )
39
+ from unfold.views import DatasetChangeList
37
40
  from unfold.widgets import UnfoldBooleanWidget
38
41
 
42
+ try:
43
+ from django.contrib.admin.views.main import IS_FACETS_VAR
44
+ except ImportError:
45
+ # TODO: remove once django 4.x is not supported
46
+ IS_FACETS_VAR = None
47
+
39
48
  register = Library()
40
49
 
41
50
  LINK_CLASSES = [
@@ -99,6 +108,9 @@ def result_headers(cl):
99
108
  Generate the list column headers.
100
109
  """
101
110
  ordering_field_columns = cl.get_ordering_field_columns()
111
+ ordering_field = getattr(cl.model_admin, "ordering_field", None)
112
+ hide_ordering_field = getattr(cl.model_admin, "hide_ordering_field", False)
113
+
102
114
  for i, field_name in enumerate(cl.list_display):
103
115
  text, attr = label_for_field(
104
116
  field_name, cl.model, model_admin=cl.model_admin, return_attr=True
@@ -145,6 +157,10 @@ def result_headers(cl):
145
157
  order_type = ""
146
158
  new_order_type = "asc"
147
159
  sort_priority = 0
160
+
161
+ if ordering_field and field_name == ordering_field and hide_ordering_field:
162
+ th_classes.append("!hidden")
163
+
148
164
  # Is it currently being sorted on?
149
165
  is_sorted = i in ordering_field_columns
150
166
  if is_sorted:
@@ -209,6 +225,9 @@ def items_for_result(
209
225
 
210
226
  for field_index, field_name in enumerate(cl.list_display):
211
227
  empty_value_display = cl.model_admin.get_empty_value_display()
228
+ ordering_field = getattr(cl.model_admin, "ordering_field", None)
229
+ hide_ordering_field = getattr(cl.model_admin, "hide_ordering_field", False)
230
+
212
231
  row_classes = [
213
232
  f"field-{_coerce_field_name(field_name, field_index)}",
214
233
  *ROW_CLASSES,
@@ -241,7 +260,7 @@ def items_for_result(
241
260
  else:
242
261
  result_repr = display_for_value(value, empty_value_display, boolean)
243
262
 
244
- if isinstance(value, (datetime.date, datetime.time)):
263
+ if isinstance(value, datetime.date | datetime.time):
245
264
  row_classes.append("nowrap")
246
265
  else:
247
266
  if isinstance(f.remote_field, models.ManyToOneRel):
@@ -253,7 +272,7 @@ def items_for_result(
253
272
  else:
254
273
  result_repr = display_for_field(value, f, empty_value_display)
255
274
  if isinstance(
256
- f, (models.DateField, models.TimeField, models.ForeignKey)
275
+ f, models.DateField | models.TimeField | models.ForeignKey
257
276
  ):
258
277
  row_classes.append("nowrap")
259
278
 
@@ -321,6 +340,9 @@ def items_for_result(
321
340
  if bf.errors:
322
341
  row_classes += ["group", "errors"]
323
342
 
343
+ if ordering_field and field_name == ordering_field and hide_ordering_field:
344
+ row_classes.append("!hidden")
345
+
324
346
  row_class = mark_safe(f' class="{" ".join(row_classes)}"')
325
347
 
326
348
  if field_index != 0:
@@ -346,7 +368,7 @@ class UnfoldResultList(ResultList):
346
368
  def __init__(
347
369
  self,
348
370
  instance: Model,
349
- form: Optional[Form],
371
+ form: Form | None,
350
372
  *items: Any,
351
373
  ) -> None:
352
374
  self.instance = instance
@@ -395,23 +417,57 @@ def result_list_tag(parser: Parser, token: Token) -> InclusionAdminNode:
395
417
 
396
418
 
397
419
  @register.simple_tag
398
- def paginator_number(cl: ChangeList, i: Union[str, int]) -> Union[str, SafeText]:
420
+ def paginator_number(cl: ChangeList, i: str | int) -> str | SafeText:
399
421
  """
400
422
  Generate an individual page index link in a paginated list.
401
423
  """
402
424
  if i == cl.paginator.ELLIPSIS:
403
425
  return render_to_string(
404
426
  "unfold/helpers/pagination_ellipsis.html",
405
- {"ellipsis": cl.paginator.ELLIPSIS},
427
+ {
428
+ "ellipsis": cl.paginator.ELLIPSIS,
429
+ },
406
430
  )
407
431
  elif i == cl.page_num:
408
432
  return render_to_string(
409
433
  "unfold/helpers/pagination_current_item.html", {"number": i}
410
434
  )
411
435
  else:
436
+ page_param = PAGE_VAR
437
+
438
+ if isinstance(cl, DatasetChangeList):
439
+ page_param = f"{cl.model._meta.model_name}-p"
440
+
412
441
  return format_html(
413
- '<a href="{}"{}>{}</a> ',
414
- cl.get_query_string({PAGE_VAR: i}),
442
+ '<a href="{}"{} x-data x-on:click.prevent="window.location.href = $el.href + window.location.hash">{}</a> ',
443
+ cl.get_query_string(
444
+ {
445
+ page_param: i,
446
+ }
447
+ ),
415
448
  mark_safe(' class="end"' if i == cl.paginator.num_pages else ""),
416
449
  i,
417
450
  )
451
+
452
+
453
+ def unfold_search_form(cl):
454
+ model_name = cl.model_admin.model._meta.model_name
455
+
456
+ return {
457
+ "cl": cl,
458
+ "show_result_count": cl.result_count != cl.full_result_count,
459
+ "search_var": f"{model_name}-{SEARCH_VAR}",
460
+ "is_popup_var": IS_POPUP_VAR,
461
+ "is_facets_var": IS_FACETS_VAR,
462
+ }
463
+
464
+
465
+ @register.tag(name="unfold_search_form")
466
+ def unfold_search_form_tag(parser, token):
467
+ return InclusionAdminNode(
468
+ parser,
469
+ token,
470
+ func=unfold_search_form,
471
+ template_name="search_form.html",
472
+ takes_context=False,
473
+ )
unfold/typing.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Iterable
2
- from typing import Any, Optional, Protocol, Union
2
+ from typing import Any, Protocol
3
3
 
4
4
 
5
5
  class ActionFunction(Protocol):
@@ -13,13 +13,12 @@ class ActionFunction(Protocol):
13
13
  short_description: str
14
14
  url_path: str
15
15
  attrs: dict[str, Any]
16
- icon: Optional[str] = None
16
+ icon: str | None = None
17
17
 
18
18
  def __call__(self, *args, **kwargs):
19
19
  pass
20
20
 
21
21
 
22
- FieldsetsType = Union[
23
- list[tuple[Union[str, None], dict[str, Any]]],
24
- tuple[tuple[Union[str, None], dict[str, Any]]],
25
- ]
22
+ FieldsetsType = (
23
+ list[tuple[str | None, dict[str, Any]]] | tuple[tuple[str | None, dict[str, Any]]]
24
+ )
unfold/utils.py CHANGED
@@ -2,7 +2,7 @@ import datetime
2
2
  import decimal
3
3
  import json
4
4
  from collections.abc import Iterable
5
- from typing import Any, Optional
5
+ from typing import Any
6
6
 
7
7
  from django.conf import settings
8
8
  from django.db import models
@@ -94,13 +94,13 @@ def display_for_value(
94
94
  return str(value)
95
95
  elif isinstance(value, datetime.datetime):
96
96
  return formats.localize(timezone.template_localtime(value))
97
- elif isinstance(value, (datetime.date, datetime.time)):
97
+ elif isinstance(value, datetime.date | datetime.time):
98
98
  return formats.localize(value)
99
99
  elif Money is not None and isinstance(value, Money):
100
100
  return str(value)
101
- elif isinstance(value, (int, decimal.Decimal, float)):
101
+ elif isinstance(value, int | decimal.Decimal | float):
102
102
  return formats.number_format(value)
103
- elif isinstance(value, (list, tuple)):
103
+ elif isinstance(value, list | tuple):
104
104
  return ", ".join(str(v) for v in value)
105
105
  else:
106
106
  return str(value)
@@ -121,13 +121,13 @@ def display_for_field(value: Any, field: Any, empty_value_display: str) -> str:
121
121
  return empty_value_display
122
122
  elif isinstance(field, models.DateTimeField):
123
123
  return formats.localize(timezone.template_localtime(value))
124
- elif isinstance(field, (models.DateField, models.TimeField)):
124
+ elif isinstance(field, models.DateField | models.TimeField):
125
125
  return formats.localize(value)
126
126
  elif MoneyField is not None and isinstance(field, MoneyField):
127
127
  return str(value)
128
128
  elif isinstance(field, models.DecimalField):
129
129
  return formats.number_format(value, field.decimal_places)
130
- elif isinstance(field, (models.IntegerField, models.FloatField)):
130
+ elif isinstance(field, models.IntegerField | models.FloatField):
131
131
  return formats.number_format(value)
132
132
  elif isinstance(field, models.FileField) and value:
133
133
  return format_html('<a href="{}">{}</a>', value.url, value)
@@ -140,7 +140,7 @@ def display_for_field(value: Any, field: Any, empty_value_display: str) -> str:
140
140
  return display_for_value(value, empty_value_display)
141
141
 
142
142
 
143
- def prettify_json(data: Any, encoder: Any) -> Optional[str]:
143
+ def prettify_json(data: Any, encoder: Any) -> str | None:
144
144
  try:
145
145
  from pygments import highlight
146
146
  from pygments.formatters import HtmlFormatter
@@ -165,7 +165,7 @@ def prettify_json(data: Any, encoder: Any) -> Optional[str]:
165
165
  )
166
166
 
167
167
 
168
- def parse_date_str(value: str) -> Optional[datetime.date]:
168
+ def parse_date_str(value: str) -> datetime.date | None:
169
169
  for format in settings.DATE_INPUT_FORMATS:
170
170
  try:
171
171
  return datetime.datetime.strptime(value, format).date()
@@ -173,7 +173,7 @@ def parse_date_str(value: str) -> Optional[datetime.date]:
173
173
  continue
174
174
 
175
175
 
176
- def parse_datetime_str(value: str) -> Optional[datetime.datetime]:
176
+ def parse_datetime_str(value: str) -> datetime.datetime | None:
177
177
  for format in settings.DATETIME_INPUT_FORMATS:
178
178
  try:
179
179
  return datetime.datetime.strptime(value, format)
unfold/views.py CHANGED
@@ -1,15 +1,18 @@
1
1
  from typing import Any
2
2
 
3
3
  import django
4
+ from django.contrib.admin.views import main
4
5
  from django.contrib.admin.views.main import ERROR_FLAG, PAGE_VAR
5
6
  from django.contrib.admin.views.main import ChangeList as BaseChangeList
6
7
  from django.contrib.auth.mixins import PermissionRequiredMixin
8
+ from django.http import HttpRequest
7
9
 
8
10
  from unfold.exceptions import UnfoldException
11
+ from unfold.forms import DatasetChangeListSearchForm
9
12
 
10
13
 
11
14
  class ChangeList(BaseChangeList):
12
- def __init__(self, request, *args, **kwargs):
15
+ def __init__(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
13
16
  super().__init__(request, *args, **kwargs)
14
17
 
15
18
  if django.VERSION < (5, 0):
@@ -18,6 +21,17 @@ class ChangeList(BaseChangeList):
18
21
  self.filter_params.pop(ERROR_FLAG, None)
19
22
 
20
23
 
24
+ class DatasetChangeList(ChangeList):
25
+ is_dataset = True
26
+ search_form_class = DatasetChangeListSearchForm
27
+
28
+ def __init__(self, request: HttpRequest, *args: Any, **kwargs: Any) -> None:
29
+ # Monkeypatch SEARCH_VAR and PAGE_VAR for custom datasets
30
+ main.SEARCH_VAR = f"{kwargs.get('model')._meta.model_name}-q"
31
+ main.PAGE_VAR = f"{kwargs.get('model')._meta.model_name}-p"
32
+ super().__init__(request, *args, **kwargs)
33
+
34
+
21
35
  class UnfoldModelAdminViewMixin(PermissionRequiredMixin):
22
36
  """
23
37
  Prepares views to be displayed in admin
unfold/widgets.py CHANGED
@@ -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.contrib.admin.options import VERTICAL
4
5
  from django.contrib.admin.sites import AdminSite
@@ -337,7 +338,7 @@ class UnfoldPrefixSuffixMixin:
337
338
  class UnfoldAdminTextInputWidget(UnfoldPrefixSuffixMixin, AdminTextInputWidget):
338
339
  template_name = "unfold/widgets/text.html"
339
340
 
340
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
341
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
341
342
  super().__init__(
342
343
  attrs={
343
344
  **(attrs or {}),
@@ -351,7 +352,7 @@ class UnfoldAdminTextInputWidget(UnfoldPrefixSuffixMixin, AdminTextInputWidget):
351
352
  class UnfoldAdminURLInputWidget(AdminURLFieldWidget):
352
353
  template_name = "unfold/widgets/url.html"
353
354
 
354
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
355
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
355
356
  super().__init__(
356
357
  attrs={
357
358
  **(attrs or {}),
@@ -363,7 +364,7 @@ class UnfoldAdminURLInputWidget(AdminURLFieldWidget):
363
364
 
364
365
 
365
366
  class UnfoldAdminColorInputWidget(AdminTextInputWidget):
366
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
367
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
367
368
  super().__init__(
368
369
  attrs={
369
370
  **(attrs or {}),
@@ -376,7 +377,7 @@ class UnfoldAdminColorInputWidget(AdminTextInputWidget):
376
377
 
377
378
 
378
379
  class UnfoldAdminUUIDInputWidget(AdminUUIDInputWidget):
379
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
380
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
380
381
  super().__init__(
381
382
  attrs={
382
383
  **(attrs or {}),
@@ -390,7 +391,7 @@ class UnfoldAdminUUIDInputWidget(AdminUUIDInputWidget):
390
391
  class UnfoldAdminIntegerRangeWidget(MultiWidget):
391
392
  template_name = "unfold/widgets/range.html"
392
393
 
393
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
394
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
394
395
  if attrs is None:
395
396
  attrs = {}
396
397
 
@@ -402,14 +403,14 @@ class UnfoldAdminIntegerRangeWidget(MultiWidget):
402
403
 
403
404
  super().__init__(_widgets, attrs)
404
405
 
405
- def decompress(self, value: Union[str, None]) -> tuple[Optional[Callable], ...]:
406
+ def decompress(self, value: str | None) -> tuple[Callable | None, ...]:
406
407
  if value:
407
408
  return value.lower, value.upper
408
409
  return None, None
409
410
 
410
411
 
411
412
  class UnfoldAdminEmailInputWidget(AdminEmailInputWidget):
412
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
413
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
413
414
  super().__init__(
414
415
  attrs={
415
416
  **(attrs or {}),
@@ -459,7 +460,7 @@ class UnfoldAdminDateWidget(AdminDateWidget):
459
460
  template_name = "unfold/widgets/date.html"
460
461
 
461
462
  def __init__(
462
- self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
463
+ self, attrs: dict[str, Any] | None = None, format: str | None = None
463
464
  ) -> None:
464
465
  attrs = {
465
466
  **(attrs or {}),
@@ -486,7 +487,7 @@ class UnfoldAdminSingleDateWidget(AdminDateWidget):
486
487
  template_name = "unfold/widgets/date.html"
487
488
 
488
489
  def __init__(
489
- self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
490
+ self, attrs: dict[str, Any] | None = None, format: str | None = None
490
491
  ) -> None:
491
492
  attrs = {
492
493
  **(attrs or {}),
@@ -506,7 +507,7 @@ class UnfoldAdminTimeWidget(AdminTimeWidget):
506
507
  template_name = "unfold/widgets/time.html"
507
508
 
508
509
  def __init__(
509
- self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
510
+ self, attrs: dict[str, Any] | None = None, format: str | None = None
510
511
  ) -> None:
511
512
  attrs = {
512
513
  **(attrs or {}),
@@ -533,7 +534,7 @@ class UnfoldAdminSingleTimeWidget(AdminTimeWidget):
533
534
  template_name = "unfold/widgets/time.html"
534
535
 
535
536
  def __init__(
536
- self, attrs: Optional[dict[str, Any]] = None, format: Optional[str] = None
537
+ self, attrs: dict[str, Any] | None = None, format: str | None = None
537
538
  ) -> None:
538
539
  attrs = {
539
540
  **(attrs or {}),
@@ -552,7 +553,7 @@ class UnfoldAdminSingleTimeWidget(AdminTimeWidget):
552
553
  class UnfoldAdminTextareaWidget(AdminTextareaWidget):
553
554
  template_name = "unfold/widgets/textarea.html"
554
555
 
555
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
556
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
556
557
  attrs = attrs or {}
557
558
 
558
559
  super().__init__(
@@ -570,7 +571,7 @@ class UnfoldAdminTextareaWidget(AdminTextareaWidget):
570
571
 
571
572
 
572
573
  class UnfoldAdminExpandableTextareaWidget(UnfoldAdminTextareaWidget):
573
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
574
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
574
575
  attrs = attrs or {}
575
576
 
576
577
  attrs.update({"rows": 2})
@@ -593,7 +594,7 @@ class UnfoldAdminExpandableTextareaWidget(UnfoldAdminTextareaWidget):
593
594
  class UnfoldAdminSplitDateTimeWidget(AdminSplitDateTime):
594
595
  template_name = "unfold/widgets/split_datetime.html"
595
596
 
596
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
597
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
597
598
  widgets = [
598
599
  UnfoldAdminDateWidget(attrs={"placeholder": _("Date")}),
599
600
  UnfoldAdminTimeWidget(attrs={"placeholder": _("Time")}),
@@ -613,11 +614,11 @@ class UnfoldAdminSplitDateTimeVerticalWidget(AdminSplitDateTime):
613
614
 
614
615
  def __init__(
615
616
  self,
616
- attrs: Optional[dict[str, Any]] = None,
617
- date_attrs: Optional[dict[str, Any]] = None,
618
- time_attrs: Optional[dict[str, Any]] = None,
619
- date_label: Optional[str] = None,
620
- time_label: Optional[str] = None,
617
+ attrs: dict[str, Any] | None = None,
618
+ date_attrs: dict[str, Any] | None = None,
619
+ time_attrs: dict[str, Any] | None = None,
620
+ date_label: str | None = None,
621
+ time_label: str | None = None,
621
622
  ) -> None:
622
623
  self.date_label = date_label
623
624
  self.time_label = time_label
@@ -629,7 +630,7 @@ class UnfoldAdminSplitDateTimeVerticalWidget(AdminSplitDateTime):
629
630
  MultiWidget.__init__(self, widgets, attrs)
630
631
 
631
632
  def get_context(
632
- self, name: str, value: Any, attrs: Optional[dict[str, Any]]
633
+ self, name: str, value: Any, attrs: dict[str, Any] | None
633
634
  ) -> dict[str, Any]:
634
635
  context = super().get_context(name, value, attrs)
635
636
 
@@ -647,7 +648,7 @@ class UnfoldAdminSplitDateTimeVerticalWidget(AdminSplitDateTime):
647
648
 
648
649
 
649
650
  class UnfoldAdminIntegerFieldWidget(AdminIntegerFieldWidget):
650
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
651
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
651
652
  super().__init__(
652
653
  attrs={
653
654
  **(attrs or {}),
@@ -659,7 +660,7 @@ class UnfoldAdminIntegerFieldWidget(AdminIntegerFieldWidget):
659
660
 
660
661
 
661
662
  class UnfoldAdminDecimalFieldWidget(AdminIntegerFieldWidget):
662
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
663
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
663
664
  super().__init__(
664
665
  attrs={
665
666
  **(attrs or {}),
@@ -671,7 +672,7 @@ class UnfoldAdminDecimalFieldWidget(AdminIntegerFieldWidget):
671
672
 
672
673
 
673
674
  class UnfoldAdminBigIntegerFieldWidget(AdminBigIntegerFieldWidget):
674
- def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
675
+ def __init__(self, attrs: dict[str, Any] | None = None) -> None:
675
676
  super().__init__(
676
677
  attrs={
677
678
  **(attrs or {}),
@@ -773,7 +774,7 @@ class UnfoldAdminRadioSelectWidget(AdminRadioSelect):
773
774
  template_name = "unfold/widgets/radio.html"
774
775
  option_template_name = "unfold/widgets/radio_option.html"
775
776
 
776
- def __init__(self, radio_style: Optional[int] = None, *args, **kwargs):
777
+ def __init__(self, radio_style: int | None = None, *args, **kwargs):
777
778
  super().__init__(*args, **kwargs)
778
779
 
779
780
  if radio_style is None:
@@ -832,7 +833,7 @@ except ImportError:
832
833
 
833
834
  class UnfoldBooleanWidget(CheckboxInput):
834
835
  def __init__(
835
- self, attrs: Optional[dict[str, Any]] = None, check_test: Callable = None
836
+ self, attrs: dict[str, Any] | None = None, check_test: Callable = None
836
837
  ) -> None:
837
838
  if attrs is None:
838
839
  attrs = {}
@@ -850,7 +851,7 @@ class UnfoldBooleanWidget(CheckboxInput):
850
851
 
851
852
  class UnfoldBooleanSwitchWidget(CheckboxInput):
852
853
  def __init__(
853
- self, attrs: Optional[dict[str, Any]] = None, check_test: Callable = None
854
+ self, attrs: dict[str, Any] | None = None, check_test: Callable = None
854
855
  ) -> None:
855
856
  super().__init__(
856
857
  attrs={
@@ -874,8 +875,8 @@ class UnfoldForeignKeyRawIdWidget(ForeignKeyRawIdWidget):
874
875
  self,
875
876
  rel: ForeignObjectRel,
876
877
  admin_site: AdminSite,
877
- attrs: Optional[dict] = None,
878
- using: Optional[Any] = None,
878
+ attrs: dict | None = None,
879
+ using: Any | None = None,
879
880
  ) -> None:
880
881
  attrs = {
881
882
  **(attrs or {}),