django-unfold 0.46.0__py3-none-any.whl → 0.47.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 (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():