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
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, List, Optional, Tuple, Type
1
+ from typing import Any, Optional
2
2
 
3
3
  from django.contrib import admin
4
4
  from django.contrib.admin.options import ModelAdmin
@@ -36,18 +36,18 @@ class ValueMixin:
36
36
  return (
37
37
  self.lookup_val[0]
38
38
  if self.lookup_val not in EMPTY_VALUES
39
- and isinstance(self.lookup_val, List)
39
+ and isinstance(self.lookup_val, list)
40
40
  and len(self.lookup_val) > 0
41
41
  else self.lookup_val
42
42
  )
43
43
 
44
44
 
45
45
  class MultiValueMixin:
46
- def value(self) -> Optional[List[str]]:
46
+ def value(self) -> Optional[list[str]]:
47
47
  return (
48
48
  self.lookup_val
49
49
  if self.lookup_val not in EMPTY_VALUES
50
- and isinstance(self.lookup_val, List)
50
+ and isinstance(self.lookup_val, list)
51
51
  and len(self.lookup_val) > 0
52
52
  else self.lookup_val
53
53
  )
@@ -72,10 +72,10 @@ class TextFilter(admin.SimpleListFilter):
72
72
  def has_output(self) -> bool:
73
73
  return True
74
74
 
75
- def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Tuple:
75
+ def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> tuple:
76
76
  return ()
77
77
 
78
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
78
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
79
79
  return (
80
80
  {
81
81
  "form": self.form_class(
@@ -96,10 +96,10 @@ class FieldTextFilter(ValueMixin, admin.FieldListFilter):
96
96
  self.lookup_val = params.get(self.lookup_kwarg)
97
97
  super().__init__(field, request, params, model, model_admin, field_path)
98
98
 
99
- def expected_parameters(self) -> List[str]:
99
+ def expected_parameters(self) -> list[str]:
100
100
  return [self.lookup_kwarg]
101
101
 
102
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
102
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
103
103
  return (
104
104
  {
105
105
  "form": self.form_class(
@@ -116,7 +116,7 @@ class DropdownFilter(admin.SimpleListFilter):
116
116
  form_class = DropdownForm
117
117
  all_option = ["", _("All")]
118
118
 
119
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
119
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
120
120
  return (
121
121
  {
122
122
  "form": self.form_class(
@@ -186,8 +186,8 @@ class SingleNumericFilter(admin.FieldListFilter):
186
186
  self,
187
187
  field: Field,
188
188
  request: HttpRequest,
189
- params: Dict[str, str],
190
- model: Type[Model],
189
+ params: dict[str, str],
190
+ model: type[Model],
191
191
  model_admin: ModelAdmin,
192
192
  field_path: str,
193
193
  ) -> None:
@@ -222,10 +222,10 @@ class SingleNumericFilter(admin.FieldListFilter):
222
222
  def value(self) -> Any:
223
223
  return self.used_parameters.get(self.parameter_name, None)
224
224
 
225
- def expected_parameters(self) -> List[Optional[str]]:
225
+ def expected_parameters(self) -> list[Optional[str]]:
226
226
  return [self.parameter_name]
227
227
 
228
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
228
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
229
229
  return (
230
230
  {
231
231
  "request": self.request,
@@ -242,7 +242,7 @@ class RangeNumericMixin:
242
242
  parameter_name = None
243
243
  template = "unfold/filters/filters_numeric_range.html"
244
244
 
245
- def init_used_parameters(self, params: Dict[str, Any]) -> None:
245
+ def init_used_parameters(self, params: dict[str, Any]) -> None:
246
246
  if self.parameter_name + "_from" in params:
247
247
  value = params.pop(self.parameter_name + "_from")
248
248
 
@@ -284,13 +284,13 @@ class RangeNumericMixin:
284
284
  except (ValueError, ValidationError):
285
285
  return None
286
286
 
287
- def expected_parameters(self) -> List[str]:
287
+ def expected_parameters(self) -> list[str]:
288
288
  return [
289
289
  f"{self.parameter_name}_from",
290
290
  f"{self.parameter_name}_to",
291
291
  ]
292
292
 
293
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
293
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
294
294
  return (
295
295
  {
296
296
  "request": self.request,
@@ -314,8 +314,8 @@ class RangeNumericListFilter(RangeNumericMixin, admin.SimpleListFilter):
314
314
  def __init__(
315
315
  self,
316
316
  request: HttpRequest,
317
- params: Dict[str, str],
318
- model: Type[Model],
317
+ params: dict[str, str],
318
+ model: type[Model],
319
319
  model_admin: ModelAdmin,
320
320
  ) -> None:
321
321
  super().__init__(request, params, model, model_admin)
@@ -327,7 +327,7 @@ class RangeNumericListFilter(RangeNumericMixin, admin.SimpleListFilter):
327
327
 
328
328
  def lookups(
329
329
  self, request: HttpRequest, model_admin: ModelAdmin
330
- ) -> Tuple[Tuple[str, str], ...]:
330
+ ) -> tuple[tuple[str, str], ...]:
331
331
  return (("dummy", "dummy"),)
332
332
 
333
333
 
@@ -336,8 +336,8 @@ class RangeNumericFilter(RangeNumericMixin, admin.FieldListFilter):
336
336
  self,
337
337
  field: Field,
338
338
  request: HttpRequest,
339
- params: Dict[str, str],
340
- model: Type[Model],
339
+ params: dict[str, str],
340
+ model: type[Model],
341
341
  model_admin: ModelAdmin,
342
342
  field_path: str,
343
343
  ) -> None:
@@ -366,8 +366,8 @@ class SliderNumericFilter(RangeNumericFilter):
366
366
  self,
367
367
  field: Field,
368
368
  request: HttpRequest,
369
- params: Dict[str, str],
370
- model: Type[Model],
369
+ params: dict[str, str],
370
+ model: type[Model],
371
371
  model_admin: ModelAdmin,
372
372
  field_path: str,
373
373
  ) -> None:
@@ -376,7 +376,7 @@ class SliderNumericFilter(RangeNumericFilter):
376
376
  self.field = field
377
377
  self.q = model_admin.get_queryset(request)
378
378
 
379
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
379
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
380
380
  total = self.q.all().count()
381
381
  min_value = self.q.all().aggregate(min=Min(self.parameter_name)).get("min", 0)
382
382
 
@@ -437,8 +437,8 @@ class RangeDateFilter(admin.FieldListFilter):
437
437
  self,
438
438
  field: Field,
439
439
  request: HttpRequest,
440
- params: Dict[str, str],
441
- model: Type[Model],
440
+ params: dict[str, str],
441
+ model: type[Model],
442
442
  model_admin: ModelAdmin,
443
443
  field_path: str,
444
444
  ) -> None:
@@ -482,13 +482,13 @@ class RangeDateFilter(admin.FieldListFilter):
482
482
  except (ValueError, ValidationError):
483
483
  return None
484
484
 
485
- def expected_parameters(self) -> List[str]:
485
+ def expected_parameters(self) -> list[str]:
486
486
  return [
487
487
  f"{self.parameter_name}_from",
488
488
  f"{self.parameter_name}_to",
489
489
  ]
490
490
 
491
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
491
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
492
492
  return (
493
493
  {
494
494
  "request": self.request,
@@ -518,8 +518,8 @@ class RangeDateTimeFilter(admin.FieldListFilter):
518
518
  self,
519
519
  field: Field,
520
520
  request: HttpRequest,
521
- params: Dict[str, str],
522
- model: Type[Model],
521
+ params: dict[str, str],
522
+ model: type[Model],
523
523
  model_admin: ModelAdmin,
524
524
  field_path: str,
525
525
  ) -> None:
@@ -553,7 +553,7 @@ class RangeDateTimeFilter(admin.FieldListFilter):
553
553
  value = value[0] if isinstance(value, list) else value
554
554
  self.used_parameters[self.field_path + "_to_1"] = value
555
555
 
556
- def expected_parameters(self) -> List[str]:
556
+ def expected_parameters(self) -> list[str]:
557
557
  return [
558
558
  f"{self.parameter_name}_from_0",
559
559
  f"{self.parameter_name}_from_1",
@@ -593,7 +593,7 @@ class RangeDateTimeFilter(admin.FieldListFilter):
593
593
  except (ValueError, ValidationError):
594
594
  return None
595
595
 
596
- def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
596
+ def choices(self, changelist: ChangeList) -> tuple[dict[str, Any], ...]:
597
597
  return (
598
598
  {
599
599
  "request": self.request,
@@ -1,7 +1,12 @@
1
1
  from django import forms
2
- from django.forms import ChoiceField, MultipleChoiceField
2
+ from django.contrib.admin.widgets import AutocompleteSelect, AutocompleteSelectMultiple
3
+ from django.db.models import Field as ModelField
4
+ from django.forms import ChoiceField, ModelMultipleChoiceField, MultipleChoiceField
5
+ from django.http import HttpRequest
3
6
  from django.utils.translation import gettext_lazy as _
4
7
 
8
+ from unfold.admin import ModelAdmin
9
+
5
10
  from ...widgets import (
6
11
  INPUT_CLASSES,
7
12
  UnfoldAdminSelectMultipleWidget,
@@ -12,7 +17,7 @@ from ...widgets import (
12
17
 
13
18
 
14
19
  class SearchForm(forms.Form):
15
- def __init__(self, name, label, *args, **kwargs):
20
+ def __init__(self, name: str, label: str, *args, **kwargs) -> None:
16
21
  super().__init__(*args, **kwargs)
17
22
 
18
23
  self.fields[name] = forms.CharField(
@@ -22,6 +27,50 @@ class SearchForm(forms.Form):
22
27
  )
23
28
 
24
29
 
30
+ class AutocompleteDropdownForm(forms.Form):
31
+ field = forms.ModelChoiceField
32
+ widget = AutocompleteSelect
33
+
34
+ def __init__(
35
+ self,
36
+ request: HttpRequest,
37
+ name: str,
38
+ label: str,
39
+ choices: tuple,
40
+ field: ModelField,
41
+ model_admin: ModelAdmin,
42
+ multiple: bool = False,
43
+ *args,
44
+ **kwargs,
45
+ ) -> None:
46
+ super().__init__(*args, **kwargs)
47
+
48
+ if multiple:
49
+ self.field = ModelMultipleChoiceField
50
+ self.widget = AutocompleteSelectMultiple
51
+
52
+ self.fields[name] = self.field(
53
+ label=label,
54
+ required=False,
55
+ queryset=field.remote_field.model.objects.all(),
56
+ widget=self.widget(field, model_admin.admin_site),
57
+ )
58
+
59
+ class Media:
60
+ js = (
61
+ "admin/js/vendor/jquery/jquery.js",
62
+ "admin/js/vendor/select2/select2.full.js",
63
+ "admin/js/jquery.init.js",
64
+ "admin/js/autocomplete.js",
65
+ )
66
+ css = {
67
+ "screen": (
68
+ "admin/css/vendor/select2/select2.css",
69
+ "admin/css/autocomplete.css",
70
+ ),
71
+ }
72
+
73
+
25
74
  class DropdownForm(forms.Form):
26
75
  widget = UnfoldAdminSelectWidget(
27
76
  attrs={
@@ -31,7 +80,15 @@ class DropdownForm(forms.Form):
31
80
  )
32
81
  field = ChoiceField
33
82
 
34
- def __init__(self, name, label, choices, multiple=False, *args, **kwargs):
83
+ def __init__(
84
+ self,
85
+ name: str,
86
+ label: str,
87
+ choices: tuple,
88
+ multiple: bool = False,
89
+ *args,
90
+ **kwargs,
91
+ ) -> None:
35
92
  super().__init__(*args, **kwargs)
36
93
 
37
94
  if multiple:
@@ -66,8 +123,8 @@ class DropdownForm(forms.Form):
66
123
 
67
124
 
68
125
  class SingleNumericForm(forms.Form):
69
- def __init__(self, *args, **kwargs):
70
- name = kwargs.pop("name")
126
+ def __init__(self, name: str, *args, **kwargs) -> None:
127
+ self.name = name
71
128
  super().__init__(*args, **kwargs)
72
129
 
73
130
  self.fields[name] = forms.FloatField(
@@ -80,10 +137,8 @@ class SingleNumericForm(forms.Form):
80
137
 
81
138
 
82
139
  class RangeNumericForm(forms.Form):
83
- name = None
84
-
85
- def __init__(self, *args, **kwargs):
86
- self.name = kwargs.pop("name")
140
+ def __init__(self, name: str, *args, **kwargs) -> None:
141
+ self.name = name
87
142
  super().__init__(*args, **kwargs)
88
143
 
89
144
  self.fields[self.name + "_from"] = forms.FloatField(
@@ -113,16 +168,14 @@ class SliderNumericForm(RangeNumericForm):
113
168
 
114
169
 
115
170
  class RangeDateForm(forms.Form):
116
- name = None
117
-
118
171
  class Media:
119
172
  js = [
120
173
  "admin/js/calendar.js",
121
174
  "unfold/filters/js/DateTimeShortcuts.js",
122
175
  ]
123
176
 
124
- def __init__(self, *args, **kwargs):
125
- self.name = kwargs.pop("name")
177
+ def __init__(self, name: str, *args, **kwargs) -> None:
178
+ self.name = name
126
179
  super().__init__(*args, **kwargs)
127
180
 
128
181
  self.fields[self.name + "_from"] = forms.DateField(
@@ -148,16 +201,14 @@ class RangeDateForm(forms.Form):
148
201
 
149
202
 
150
203
  class RangeDateTimeForm(forms.Form):
151
- name = None
152
-
153
204
  class Media:
154
205
  js = [
155
206
  "admin/js/calendar.js",
156
207
  "unfold/filters/js/DateTimeShortcuts.js",
157
208
  ]
158
209
 
159
- def __init__(self, *args, **kwargs):
160
- self.name = kwargs.pop("name")
210
+ def __init__(self, name: str, *args, **kwargs) -> None:
211
+ self.name = name
161
212
  super().__init__(*args, **kwargs)
162
213
 
163
214
  self.fields[self.name + "_from"] = forms.SplitDateTimeField(
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict, List, Optional, Union
1
+ from typing import Any, Optional, Union
2
2
 
3
3
  from django.core.validators import EMPTY_VALUES
4
4
  from django.forms import MultiWidget, Widget
@@ -46,7 +46,7 @@ class ArrayWidget(MultiWidget):
46
46
 
47
47
  return UnfoldAdminTextInputWidget()
48
48
 
49
- def get_context(self, name: str, value: str, attrs: Dict) -> Dict:
49
+ def get_context(self, name: str, value: str, attrs: dict) -> dict:
50
50
  self._resolve_widgets(value)
51
51
  context = super().get_context(name, value, attrs)
52
52
  context.update(
@@ -56,7 +56,7 @@ class ArrayWidget(MultiWidget):
56
56
 
57
57
  def value_from_datadict(
58
58
  self, data: QueryDict, files: MultiValueDict, name: str
59
- ) -> List:
59
+ ) -> list:
60
60
  values = []
61
61
 
62
62
  for item in data.getlist(name):
@@ -67,22 +67,22 @@ class ArrayWidget(MultiWidget):
67
67
 
68
68
  def value_omitted_from_data(
69
69
  self, data: QueryDict, files: MultiValueDict, name: str
70
- ) -> List:
70
+ ) -> list:
71
71
  return data.getlist(name) not in [[""], *EMPTY_VALUES]
72
72
 
73
- def decompress(self, value: Union[str, List]) -> List:
74
- if isinstance(value, List):
73
+ def decompress(self, value: Union[str, list]) -> list:
74
+ if isinstance(value, list):
75
75
  return value
76
76
  elif isinstance(value, str):
77
77
  return value.split(",")
78
78
 
79
79
  return []
80
80
 
81
- def _resolve_widgets(self, value: Optional[Union[List, str]]) -> None:
81
+ def _resolve_widgets(self, value: Optional[Union[list, str]]) -> None:
82
82
  if value is None:
83
83
  value = []
84
84
 
85
- elif isinstance(value, List):
85
+ elif isinstance(value, list):
86
86
  self.widgets = [self.get_widget_instance() for item in value]
87
87
  else:
88
88
  self.widgets = [self.get_widget_instance() for item in value.split(",")]
@@ -101,7 +101,7 @@ class WysiwygWidget(Widget):
101
101
  "unfold/forms/js/trix.config.js",
102
102
  )
103
103
 
104
- def __init__(self, attrs: Optional[Dict[str, Any]] = None) -> None:
104
+ def __init__(self, attrs: Optional[dict[str, Any]] = None) -> None:
105
105
  super().__init__(attrs)
106
106
 
107
107
  self.attrs.update(
@@ -1,5 +1,3 @@
1
- from typing import List
2
-
3
1
  from django.contrib.admin.checks import InlineModelAdminChecks
4
2
  from django.contrib.admin.options import InlineModelAdmin
5
3
  from django.core.checks import CheckMessage
@@ -9,10 +7,10 @@ from django.db.models import Model
9
7
  class NonrelatedModelAdminChecks(InlineModelAdminChecks):
10
8
  def _check_exclude_of_parent_model(
11
9
  self, obj: InlineModelAdmin, parent_model: Model
12
- ) -> List[CheckMessage]:
10
+ ) -> list[CheckMessage]:
13
11
  return []
14
12
 
15
13
  def _check_relation(
16
14
  self, obj: InlineModelAdmin, parent_model: Model
17
- ) -> List[CheckMessage]:
15
+ ) -> list[CheckMessage]:
18
16
  return []
@@ -9,8 +9,24 @@
9
9
  </p>
10
10
  {% endif %}
11
11
 
12
- {% if historical_records %}
12
+ {% if page_obj.object_list %}
13
13
  {% include object_history_list_template %}
14
+ <div class="bg-base-50 flex my-4 items-center p-3 rounded dark:bg-base-800">
15
+ {% if pagination_required %}
16
+ {% for i in page_range %}
17
+ <span class="pr-4">
18
+ {% if i == page_obj.paginator.ELLIPSIS %}
19
+ {{ page_obj.paginator.ELLIPSIS }}
20
+ {% elif i == page_obj.number %}
21
+ <span class="font-medium text-primary-600">{{ i }}</span>
22
+ {% else %}
23
+ <a href="?{{ page_var }}={{ i }}" {% if i == page_obj.paginator.num_pages %} class="end" {% endif %}>{{ i }}</a>
24
+ {% endif %}
25
+ </span>
26
+ {% endfor %}
27
+ {% endif %}
28
+ {{ page_obj.paginator.count }} {% blocktranslate count counter=page_obj.paginator.count %}entry{% plural %}entries{% endblocktranslate %}
29
+ </div>
14
30
  {% else %}
15
31
  <p class="mb-3 px-3 py-3 rounded text-sm last:mb-8 bg-amber-100 text-amber-600 dark:bg-amber-600/20 dark:border-amber-600/10">
16
32
  {% trans "This object doesn't have a change history." %}
@@ -39,7 +39,7 @@
39
39
  </thead>
40
40
 
41
41
  <tbody>
42
- {% for record in historical_records %}
42
+ {% for record in page_obj %}
43
43
  <tr class="block border mb-3 rounded shadow-sm lg:table-row lg:border-none lg:mb-0 lg:shadow-none dark:border-base-800">
44
44
  <td class="align-middle flex border-t border-base-200 font-normal px-3 py-2 text-left before:flex before:capitalize before:content-[attr(data-label)] before:items-center before:mr-auto first:border-t-0 lg:before:hidden lg:first:border-t lg:py-3 lg:table-cell dark:border-base-800" data-label="{% trans 'Object' %}">
45
45
  <a href="{% url opts|admin_urlname:'simple_history' object.pk record.pk %}">
unfold/dataclasses.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import Callable, Dict, Optional, Union
2
+ from typing import Callable, Optional, Union
3
3
 
4
4
  from .typing import ActionFunction
5
5
 
@@ -10,7 +10,7 @@ class UnfoldAction:
10
10
  method: ActionFunction
11
11
  description: str
12
12
  path: str
13
- attrs: Optional[Dict] = None
13
+ attrs: Optional[dict] = None
14
14
  object_id: Optional[Union[int, str]] = None
15
15
 
16
16
 
unfold/decorators.py CHANGED
@@ -1,4 +1,5 @@
1
- from typing import Any, Callable, Dict, Iterable, Optional, Union
1
+ from collections.abc import Iterable
2
+ from typing import Any, Callable, Optional, Union
2
3
 
3
4
  from django.contrib.admin.options import BaseModelAdmin
4
5
  from django.core.exceptions import PermissionDenied
@@ -15,7 +16,7 @@ def action(
15
16
  permissions: Optional[Iterable[str]] = None,
16
17
  description: Optional[str] = None,
17
18
  url_path: Optional[str] = None,
18
- attrs: Optional[Dict[str, Any]] = None,
19
+ attrs: Optional[dict[str, Any]] = None,
19
20
  ) -> ActionFunction:
20
21
  def decorator(func: Callable) -> ActionFunction:
21
22
  def inner(
@@ -62,7 +63,7 @@ def display(
62
63
  ordering: Optional[Union[str, Combinable, BaseExpression]] = None,
63
64
  description: Optional[str] = None,
64
65
  empty_value: Optional[str] = None,
65
- label: Optional[Union[bool, str, Dict[str, str]]] = None,
66
+ label: Optional[Union[bool, str, dict[str, str]]] = None,
66
67
  header: Optional[bool] = None,
67
68
  ) -> Callable:
68
69
  def decorator(func: Callable[[Model], Any]) -> Callable:
unfold/settings.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Any, Dict
1
+ from typing import Any
2
2
 
3
3
  from django.conf import settings
4
4
 
@@ -72,7 +72,7 @@ def get_config(settings_name=None):
72
72
  if settings_name is None:
73
73
  settings_name = "UNFOLD"
74
74
 
75
- def merge_dicts(dict1: Dict[str, Any], dict2: Dict[str, Any]) -> Dict[str, Any]:
75
+ def merge_dicts(dict1: dict[str, Any], dict2: dict[str, Any]) -> dict[str, Any]:
76
76
  result = dict1.copy()
77
77
 
78
78
  for key, value in dict2.items():