django-unfold 0.22.0__py3-none-any.whl → 0.24.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.
- {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/METADATA +102 -4
- {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/RECORD +44 -43
- unfold/admin.py +29 -22
- unfold/apps.py +5 -0
- unfold/checks.py +1 -1
- unfold/contrib/filters/admin.py +116 -0
- unfold/contrib/filters/forms.py +29 -1
- unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_field.html +7 -0
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +1 -1
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
- unfold/contrib/import_export/admin.py +1 -1
- unfold/contrib/import_export/forms.py +6 -7
- unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
- unfold/contrib/import_export/templates/admin/import_export/import_form.html +2 -2
- unfold/forms.py +6 -1
- unfold/sites.py +3 -3
- unfold/static/unfold/css/styles.css +1 -1
- unfold/styles.css +11 -4
- unfold/templates/admin/actions.html +9 -9
- unfold/templates/admin/app_list.html +6 -8
- unfold/templates/admin/base.html +1 -3
- unfold/templates/admin/change_form.html +1 -2
- unfold/templates/admin/change_list.html +2 -2
- unfold/templates/admin/change_list_results.html +15 -3
- unfold/templates/admin/edit_inline/tabular.html +3 -3
- unfold/templates/admin/filter.html +2 -2
- unfold/templates/admin/search_form.html +10 -12
- unfold/templates/unfold/helpers/display_header.html +16 -13
- unfold/templates/unfold/helpers/tab_list.html +1 -1
- unfold/templates/unfold/layouts/base_simple.html +1 -1
- unfold/templates/unfold/widgets/date.html +1 -1
- unfold/templates/{admin → unfold}/widgets/related_widget_wrapper.html +4 -4
- unfold/templates/unfold/widgets/time.html +1 -1
- unfold/templatetags/unfold_list.py +8 -6
- unfold/widgets.py +18 -5
- {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/LICENSE.md +0 -0
- {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/WHEEL +0 -0
- /unfold/templates/{admin → unfold}/widgets/clearable_file_input.html +0 -0
- /unfold/templates/{admin → unfold}/widgets/radio.html +0 -0
- /unfold/templates/{admin → unfold}/widgets/radio_option.html +0 -0
- /unfold/templates/{admin → unfold}/widgets/split_datetime.html +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-unfold
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.24.0
|
4
4
|
Summary: Modern Django admin theme for seamless interface development
|
5
5
|
Home-page: https://unfoldadmin.com
|
6
6
|
License: MIT
|
@@ -52,13 +52,14 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
52
52
|
- **Dependencies:** completely based only on `django.contrib.admin`
|
53
53
|
- **Actions:** multiple ways how to define actions within different parts of admin
|
54
54
|
- **WYSIWYG:** built-in support for WYSIWYG (Trix)
|
55
|
-
- **
|
55
|
+
- **Filters:** custom dropdown, numeric, datetime, and text fields
|
56
56
|
- **Dashboard:** custom components for rapid dashboard development
|
57
57
|
- **Model tabs:** define custom tab navigations for models
|
58
58
|
- **Fieldset tabs:** merge several fielsets into tabs in change form
|
59
59
|
- **Colors:** possibility to override default color scheme
|
60
60
|
- **Third party packages:** default support for multiple popular applications
|
61
61
|
- **Environment label**: distinguish between environments by displaying a label
|
62
|
+
- **Parallel admin**: support for having default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/)
|
62
63
|
- **VS Code**: project configuration and development container is included
|
63
64
|
|
64
65
|
## Table of contents <!-- omit from toc -->
|
@@ -73,6 +74,8 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
73
74
|
- [Action handler functions](#action-handler-functions)
|
74
75
|
- [Action examples](#action-examples)
|
75
76
|
- [Filters](#filters)
|
77
|
+
- [Text filters](#text-filters)
|
78
|
+
- [Dropdown filters](#dropdown-filters)
|
76
79
|
- [Numeric filters](#numeric-filters)
|
77
80
|
- [Date/time filters](#datetime-filters)
|
78
81
|
- [Display decorator](#display-decorator)
|
@@ -480,6 +483,88 @@ By default, Django admin handles all filters as regular HTML links pointing at t
|
|
480
483
|
|
481
484
|
**Note:** when implementing a filter which contains input fields, there is a no way that user can submit the values, because default filters does not contain submit button. To implement submit button, `unfold.admin.ModelAdmin` contains boolean `list_filter_submit` flag which enables submit button in filter form.
|
482
485
|
|
486
|
+
### Text filters
|
487
|
+
|
488
|
+
Text input field which allows filtering by the free string submitted by the user. There are two different variants of this filter: `FieldTextFilter` and `TextFilter`.
|
489
|
+
|
490
|
+
`FieldTextFilter` requires just a model field name and the filter will make `__icontains` search on this field. There are no other things to configure so the integration in `list_filter` will be just one new row looking like `("model_field_name", FieldTextFilter)`.
|
491
|
+
|
492
|
+
In the case of the `TextFilter`, it is needed the write a whole new class inheriting from `TextFilter` with a custom implementation of the `queryset` method and the `parameter_name` attribute. This attribute will be a representation of the search query parameter name in URI. The benefit of the `TextFilter` is the possibility of writing complex queries.
|
493
|
+
|
494
|
+
```python
|
495
|
+
from django.contrib import admin
|
496
|
+
from django.contrib.auth.models import User
|
497
|
+
from django.core.validators import EMPTY_VALUES
|
498
|
+
from django.utils.translation import gettext_lazy as _
|
499
|
+
from unfold.admin import ModelAdmin
|
500
|
+
from unfold.contrib.filters.admin import TextFilter, FieldTextFilter
|
501
|
+
|
502
|
+
class CustomTextFilter(TextFilter):
|
503
|
+
title = _("Custom filter")
|
504
|
+
parameter_name = "query_param_in_uri"
|
505
|
+
|
506
|
+
def queryset(self, request, queryset):
|
507
|
+
if self.value() not in EMPTY_VALUES:
|
508
|
+
# Here write custom query
|
509
|
+
return queryset.filter(your_field=self.value())
|
510
|
+
|
511
|
+
return queryset
|
512
|
+
|
513
|
+
|
514
|
+
@admin.register(User)
|
515
|
+
class MyAdmin(ModelAdmin):
|
516
|
+
list_filter_submit = True
|
517
|
+
list_filter = [
|
518
|
+
("model_charfield", FieldTextFilter),
|
519
|
+
CustomTextFilter
|
520
|
+
]
|
521
|
+
```
|
522
|
+
|
523
|
+
### Dropdown filters
|
524
|
+
|
525
|
+
Dropdown filters will display a select field with a list of options. Unfold contains two types of dropdowns: `ChoicesDropdownFilter` and `RelatedDropdownFilter`.
|
526
|
+
|
527
|
+
The difference between them is that `ChoicesDropdownFilter` will collect a list of options based on the `choices` attribute of the model field so most commonly it will be used in combination with `CharField` with specified `choices`. On the other side, `RelatedDropdownFilter` needs a one-to-many or many-to-many foreign key to display options.
|
528
|
+
|
529
|
+
**Note:** At the moment Unfold does not implement a dropdown with an autocomplete functionality, so it is important not to use dropdowns displaying large datasets.
|
530
|
+
|
531
|
+
```python
|
532
|
+
# admin.py
|
533
|
+
|
534
|
+
from django.contrib import admin
|
535
|
+
from django.contrib.auth.models import User
|
536
|
+
from unfold.admin import ModelAdmin
|
537
|
+
from unfold.contrib.filters.admin import ChoicesDropdownFilter, RelatedDropdownFilter, DropdownFilter
|
538
|
+
|
539
|
+
|
540
|
+
class CustomDropdownFilter(DropdownFilter):
|
541
|
+
title = _("Custom dropdown filter")
|
542
|
+
parameter_name = "query_param_in_uri"
|
543
|
+
|
544
|
+
def lookups(self, request, model_admin):
|
545
|
+
return [
|
546
|
+
["option_1", _("Option 1")],
|
547
|
+
["option_2", _("Option 2")],
|
548
|
+
]
|
549
|
+
|
550
|
+
def queryset(self, request, queryset):
|
551
|
+
if self.value() not in EMPTY_VALUES:
|
552
|
+
# Here write custom query
|
553
|
+
return queryset.filter(your_field=self.value())
|
554
|
+
|
555
|
+
return queryset
|
556
|
+
|
557
|
+
|
558
|
+
@admin.register(User)
|
559
|
+
class MyAdmin(ModelAdmin):
|
560
|
+
list_filter_submit = True
|
561
|
+
list_filter = [
|
562
|
+
CustomDropdownFilter,
|
563
|
+
("modelfield_with_choices", ChoicesDropdownFilter),
|
564
|
+
("modelfield_with_foreign_key", RelatedDropdownFilter)
|
565
|
+
]
|
566
|
+
```
|
567
|
+
|
483
568
|
### Numeric filters
|
484
569
|
|
485
570
|
Currently, Unfold implements numeric filters inside `unfold.contrib.filters` application. In order to use these filters, it is required to add this application into `INSTALLED_APPS` in `settings.py` right after `unfold` application.
|
@@ -610,7 +695,16 @@ class UserAdmin(ModelAdmin):
|
|
610
695
|
"""
|
611
696
|
Third argument is short text which will appear as prefix in circle
|
612
697
|
"""
|
613
|
-
return
|
698
|
+
return [
|
699
|
+
"First main heading",
|
700
|
+
"Smaller additional description", # Use None in case you don't need it
|
701
|
+
"AB", # Short text which will appear in front of
|
702
|
+
# Image instead of initials. Initials are ignored if image is available
|
703
|
+
{
|
704
|
+
"path": "some/path/picture.jpg,
|
705
|
+
"squared": True, # Picture is displayed in square format, if empty circle
|
706
|
+
}
|
707
|
+
]
|
614
708
|
```
|
615
709
|
|
616
710
|
## Change form tabs
|
@@ -708,6 +802,10 @@ class CrontabScheduleAdmin(ModelAdmin):
|
|
708
802
|
@admin.register(SolarSchedule)
|
709
803
|
class SolarScheduleAdmin(ModelAdmin):
|
710
804
|
pass
|
805
|
+
|
806
|
+
@admin.register(ClockedSchedule)
|
807
|
+
class ClockedScheduleAdmin(ModelAdmin):
|
808
|
+
pass
|
711
809
|
```
|
712
810
|
|
713
811
|
### django-guardian
|
@@ -737,7 +835,7 @@ When implementing `import_export.admin.ExportActionModelAdmin` class in admin pa
|
|
737
835
|
admin.py
|
738
836
|
|
739
837
|
from unfold.admin import ModelAdmin
|
740
|
-
from unfold.contrib.import_export import ExportActionModelAdmin
|
838
|
+
from unfold.contrib.import_export.admin import ExportActionModelAdmin
|
741
839
|
|
742
840
|
class ExampleAdmin(ModelAdmin, ExportActionModelAdmin):
|
743
841
|
pass
|
@@ -1,22 +1,23 @@
|
|
1
1
|
unfold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
unfold/admin.py,sha256=
|
3
|
-
unfold/apps.py,sha256=
|
4
|
-
unfold/checks.py,sha256=
|
2
|
+
unfold/admin.py,sha256=iNlDiZy_kl4lk2We4VDKOK5lMam4GeNMOA-bo67ykqU,23810
|
3
|
+
unfold/apps.py,sha256=SlBXPYrUd2uXn67qFbRvbXSUk3XFWrF4-5WELgDCvho,381
|
4
|
+
unfold/checks.py,sha256=Smgji9w19hnYjJElJ_FJnnyTEAE-E-OUB6otHu7lasY,1670
|
5
5
|
unfold/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
unfold/contrib/filters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
|
-
unfold/contrib/filters/admin.py,sha256=
|
7
|
+
unfold/contrib/filters/admin.py,sha256=hkrw-CthPsSubwsDpInqOiNw5vsl_zbQeYW23DnKSsY,20068
|
8
8
|
unfold/contrib/filters/apps.py,sha256=wEySJy0gMLzFLb9XNKE-RexiO05X7NaQ5QmxZyziJ_k,136
|
9
|
-
unfold/contrib/filters/forms.py,sha256
|
9
|
+
unfold/contrib/filters/forms.py,sha256=cdBFNB45PPgKVzAbjrwbZVdYF6rZBUQPxxWMwZaKCpM,4736
|
10
10
|
unfold/contrib/filters/static/unfold/filters/css/nouislider.min.css,sha256=rddL_jOGGVEY6wR-aw0VYovAfz5fPeAIsulrlSNb1hc,4221
|
11
11
|
unfold/contrib/filters/static/unfold/filters/js/DateTimeShortcuts.js,sha256=jgFNBDf6aHvUlyv0LEDHggXO-xA8pWOCWFWcVupdA30,19332
|
12
12
|
unfold/contrib/filters/static/unfold/filters/js/admin-numeric-filter.js,sha256=nTkiiJk4Abn9d6KigxPSEsereFhFq-v5n_boiiY1eII,1668
|
13
13
|
unfold/contrib/filters/static/unfold/filters/js/nouislider.min.js,sha256=aIEt5UlLNnEv4-HPyxcLqu9dTS7gXiMm35Mm0rFmQ0Q,26683
|
14
14
|
unfold/contrib/filters/static/unfold/filters/js/wNumb.min.js,sha256=gayD3el5iizhy0DlsyKfZvfOfOZHDmb7BPvOcT97GSg,2236
|
15
|
-
unfold/contrib/filters/templates/unfold/filters/filters_date_range.html,sha256=
|
16
|
-
unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html,sha256=
|
17
|
-
unfold/contrib/filters/templates/unfold/filters/
|
18
|
-
unfold/contrib/filters/templates/unfold/filters/
|
19
|
-
unfold/contrib/filters/templates/unfold/filters/
|
15
|
+
unfold/contrib/filters/templates/unfold/filters/filters_date_range.html,sha256=BVUsF4vCtDpxpXxevf--y3n8kO1FH3FMFbLG1qEUe6g,661
|
16
|
+
unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html,sha256=BVUsF4vCtDpxpXxevf--y3n8kO1FH3FMFbLG1qEUe6g,661
|
17
|
+
unfold/contrib/filters/templates/unfold/filters/filters_field.html,sha256=UTlSZlpg-gAc_a-EJLLF0NI_ofuSHQ2kMMoAs99nL2E,164
|
18
|
+
unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html,sha256=NoJwm36x1J65Pq8cLk_g_qJ0Cil3CtDwja9dqZgRX8g,639
|
19
|
+
unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html,sha256=EeQv2fHYX6MK9wwM0lgGkKGfmyDo82VLtx_E0M9MUtI,576
|
20
|
+
unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html,sha256=SpkLgq_m1-7WSdZzf3IPcySXxdaqe0z6qljAhhZSHec,1681
|
20
21
|
unfold/contrib/forms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
22
|
unfold/contrib/forms/apps.py,sha256=Di0TMzVuRpVxLG-8Bjdq5ALCSf5r7u2xVhD0jU6H5Sc,132
|
22
23
|
unfold/contrib/forms/static/unfold/forms/css/trix.css,sha256=TH9WdnaZrmwI8hAEydwjobdrBzSw_KYdRTSQDuD-8hE,20027
|
@@ -35,18 +36,18 @@ unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.htm
|
|
35
36
|
unfold/contrib/guardian/templates/unfold/guardian/group_form.html,sha256=P8WMC5EejUHV5AxEiIQ2LOGzefLHk5J5UHiNq9wnBgY,4145
|
36
37
|
unfold/contrib/guardian/templates/unfold/guardian/user_form.html,sha256=ci7FRrhTEKbFKKxsJ-07_dWXBYz4mqXPoqu5HfqYLaM,4132
|
37
38
|
unfold/contrib/import_export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
|
-
unfold/contrib/import_export/admin.py,sha256=
|
39
|
+
unfold/contrib/import_export/admin.py,sha256=h6CKRuvloEdxcVScycTSAShXEfzEAsL75uMj2ullhOM,1151
|
39
40
|
unfold/contrib/import_export/apps.py,sha256=SdJu6Qh90VqGWY19FSDhhpUqhTbaIYsJKny3zX5baHI,149
|
40
|
-
unfold/contrib/import_export/forms.py,sha256=
|
41
|
+
unfold/contrib/import_export/forms.py,sha256=DCTjCqeWT0jyPAMZgDDad0LoWtKRGu1Q5TohNpnpMAA,637
|
41
42
|
unfold/contrib/import_export/templates/admin/import_export/base.html,sha256=loL2qcV-f8aAzkHss_I4IkwfgemVW2CjOu_aiBxdwX0,357
|
42
43
|
unfold/contrib/import_export/templates/admin/import_export/change_list_export_item.html,sha256=pTDeqPKOlCPKH2dxMIfPnWuc2wVDzB7AzL73WbxSnRY,257
|
43
44
|
unfold/contrib/import_export/templates/admin/import_export/change_list_import_export.html,sha256=JdKd6P2Ot9Ou4yg4CywTauuE1UiTz_mRvDwlx3vj3LI,229
|
44
45
|
unfold/contrib/import_export/templates/admin/import_export/change_list_import_item.html,sha256=XUuRxnsx9YQbKvW-E_JGl_ha7kpTSGSoRefOTTizuX0,233
|
45
|
-
unfold/contrib/import_export/templates/admin/import_export/export.html,sha256=
|
46
|
+
unfold/contrib/import_export/templates/admin/import_export/export.html,sha256=W-ZId8LJCzA3Kuzxun7v-RNzqo7q8cxRk-vmd8xaC0s,1786
|
46
47
|
unfold/contrib/import_export/templates/admin/import_export/import.html,sha256=P54_f3s96PV87Bo-FCZfmsn9DkRXLOB36r7HYF6y7GM,2075
|
47
48
|
unfold/contrib/import_export/templates/admin/import_export/import_confirm.html,sha256=M-acK4XSLHuPFD_NJashGYvPPeJrJsC-3LMvHs3lRis,867
|
48
49
|
unfold/contrib/import_export/templates/admin/import_export/import_errors.html,sha256=0DmJvZs31u-E2Y53yySci86cTnG9aUnOzvfYrOo0lYA,1422
|
49
|
-
unfold/contrib/import_export/templates/admin/import_export/import_form.html,sha256=
|
50
|
+
unfold/contrib/import_export/templates/admin/import_export/import_form.html,sha256=F0cLh5AAyVpgxPClKcIXSt1bLRR1lESTdaSMU0Z_6G4,1303
|
50
51
|
unfold/contrib/import_export/templates/admin/import_export/import_preview.html,sha256=pNuLDW6zc5yOF1jurL2EgR0j05RL9ZVJLZiV4R21GJc,2413
|
51
52
|
unfold/contrib/import_export/templates/admin/import_export/import_validation.html,sha256=1wQOiXN_Ga9VO6GGyl__KEiuJlCh4gTqzZdzIbmKxG0,4880
|
52
53
|
unfold/contrib/simple_history/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -58,10 +59,10 @@ unfold/contrib/simple_history/templates/simple_history/submit_line.html,sha256=n
|
|
58
59
|
unfold/dataclasses.py,sha256=JJdGYzQ8MpeOe2FQPJqrMn_UJcxUz1VJgHCuCtkZCA8,199
|
59
60
|
unfold/decorators.py,sha256=BVDlxhZxB4ND3f5-5oiENRTv_W_Q_Eu-gZlsrYKOxiU,3272
|
60
61
|
unfold/exceptions.py,sha256=gcCj1ox61E137bk_0Cqy4YC3SttdPgB-fiJUqpmyHSE,43
|
61
|
-
unfold/forms.py,sha256=
|
62
|
+
unfold/forms.py,sha256=SzdyBC5wqE5IcoZWS2oXObycmfmICHsqlS3X03mw4QA,3468
|
62
63
|
unfold/settings.py,sha256=--TdTSWdOA8TQGW4-vjJkjy_zEyd_kZwBr3BIuQ8hzI,1208
|
63
|
-
unfold/sites.py,sha256=
|
64
|
-
unfold/static/unfold/css/styles.css,sha256=
|
64
|
+
unfold/sites.py,sha256=Gy_i43j2nizW2g8-mas5icvtk-beKism_CznATW6Ia8,12586
|
65
|
+
unfold/static/unfold/css/styles.css,sha256=DjRQV4lwg81SmOyR1YRs1QWHNA0FdV7XYRiYP33lTig,91399
|
65
66
|
unfold/static/unfold/fonts/inter/Inter-Bold.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
|
66
67
|
unfold/static/unfold/fonts/inter/Inter-Medium.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
|
67
68
|
unfold/static/unfold/fonts/inter/Inter-Regular.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
|
@@ -74,25 +75,25 @@ unfold/static/unfold/js/alpine.persist.js,sha256=84PZYnPi25AFm7wIWRe1gzA74c5Rv2V
|
|
74
75
|
unfold/static/unfold/js/app.js,sha256=CIitJoFqpeZYPw8icGVXYX9tVRUgqFxcPZ2WjWS8Ylk,5288
|
75
76
|
unfold/static/unfold/js/chart.js,sha256=22W6cFERR-CElMOKRgMMicueMVP0Vf7FBEBYH8Z8tCk,200633
|
76
77
|
unfold/static/unfold/js/htmx.js,sha256=XOLqvnZiyEx46EW9vaJTBUaaWg8CGVVfXJkVsUmJbpI,42820
|
77
|
-
unfold/styles.css,sha256=
|
78
|
-
unfold/templates/admin/actions.html,sha256=
|
78
|
+
unfold/styles.css,sha256=dlTJBzFvZEfH9xSs2AJ9nfzYTnxf8muCMa8COifqmzU,17598
|
79
|
+
unfold/templates/admin/actions.html,sha256=1tVlUpLoM72K2Ew4vQGcRwPjHuAtO5Jm4QdDsDLOq0I,2625
|
79
80
|
unfold/templates/admin/app_index.html,sha256=lVjMIFsspHQ09LGHKfdfg7TlqlL39AX5LbwoeoZjFhk,1335
|
80
|
-
unfold/templates/admin/app_list.html,sha256=
|
81
|
+
unfold/templates/admin/app_list.html,sha256=7u2Ex0ArdC0CJNCTDnTEZpAXI_xbNQ1nrzAH4BuInNA,3009
|
81
82
|
unfold/templates/admin/auth/user/add_form.html,sha256=iLig-vd2YExXsj0xGBwYhZ4kGUihwYtLtNVhzObgSzg,478
|
82
83
|
unfold/templates/admin/auth/user/change_password.html,sha256=-Wa9ml3yss-kDz0YQxCiwoxs91KQD8eetCt5l6xekWM,2892
|
83
|
-
unfold/templates/admin/base.html,sha256=
|
84
|
+
unfold/templates/admin/base.html,sha256=MGqtCcydXZPnp6dasaWktyd9D6rdUYX01rFGAv7Zkm4,2226
|
84
85
|
unfold/templates/admin/base_site.html,sha256=3ckWrcAdd7Pw1hk6Zwyknab_Qb-rteV9-mXhMnfo6VI,361
|
85
|
-
unfold/templates/admin/change_form.html,sha256
|
86
|
+
unfold/templates/admin/change_form.html,sha256=-X_fQOGaa6k0WVbMO7he9bZIdIY3oj8PyRSV4dnt6qA,5320
|
86
87
|
unfold/templates/admin/change_form_object_tools.html,sha256=eyeH-i2HgEM0Yi-OJA2D1VnKJyC19A_my1IDGxxoP8Y,593
|
87
|
-
unfold/templates/admin/change_list.html,sha256=
|
88
|
+
unfold/templates/admin/change_list.html,sha256=18GDZswc1c0xtw2BcKti9SX95Ar9e1BX_HSY0K79g_8,5102
|
88
89
|
unfold/templates/admin/change_list_object_tools.html,sha256=cmMiT2nT20Ph5yfpj9aHPr76Z-JP4aSXp0o-Rnad28s,147
|
89
|
-
unfold/templates/admin/change_list_results.html,sha256=
|
90
|
+
unfold/templates/admin/change_list_results.html,sha256=32zSgJ6W5uQlfzI-0N1FmCFagw2VYaXuiUZyZbxUIag,5004
|
90
91
|
unfold/templates/admin/date_hierarchy.html,sha256=BfUPbsLpHZVa40BHBahz1H9RSVuz36Jc3yrlobOiIpw,1306
|
91
92
|
unfold/templates/admin/delete_confirmation.html,sha256=hpa2E14oZEXBBs6W1qdNQuF650TIO2Rhr52Q6UfwVeQ,5166
|
92
93
|
unfold/templates/admin/delete_selected_confirmation.html,sha256=Foka2yvwAMEZre-Kh1KNadRzrCotdKM2U4e6AJQYZu8,4941
|
93
94
|
unfold/templates/admin/edit_inline/stacked.html,sha256=U6O_r9-5Hp7mT0l1EgTeBB8tc9nEAAi0etL2aCU2pBM,4415
|
94
|
-
unfold/templates/admin/edit_inline/tabular.html,sha256=
|
95
|
-
unfold/templates/admin/filter.html,sha256=
|
95
|
+
unfold/templates/admin/edit_inline/tabular.html,sha256=YOrCgAQ9apLZf8VtFmUhK-WFoQomPtlilT9ktkEFBQA,12315
|
96
|
+
unfold/templates/admin/filter.html,sha256=dkrFkei-EAlldIU8DrgvSChzWQuUOu6-LS_qlZxdfFw,1708
|
96
97
|
unfold/templates/admin/includes/fieldset.html,sha256=r4XjcZAOkWxHQExHZBWoCGtO3LYL0Iwkw1C55oR5V6M,2898
|
97
98
|
unfold/templates/admin/includes/object_delete_summary.html,sha256=Nv69SCzyJHFX14iJFfodxKM0IIpQegKZH0fvKB15QJI,468
|
98
99
|
unfold/templates/admin/index.html,sha256=pkGdKWdD3zzOvkRdELvdb15sleSpfl4eHPA14PAh7z0,684
|
@@ -100,13 +101,8 @@ unfold/templates/admin/login.html,sha256=WdOfFLofwBWj9VKCq1U22uLY19J2YQY6vRaE4OO
|
|
100
101
|
unfold/templates/admin/nav_sidebar.html,sha256=63lUhsHfrjZowplnOEq4BkGD9jlX0bVQw5VrAMJqf5M,1178
|
101
102
|
unfold/templates/admin/object_history.html,sha256=PsbhXFd_3SCB9YkSJeHESp2VqjNlHtUW26mRtaZ-MD0,5069
|
102
103
|
unfold/templates/admin/pagination.html,sha256=KWTPV7_hVSZ1374a-pqHXhnOueNQKu1UnSUYirrWtCk,1173
|
103
|
-
unfold/templates/admin/search_form.html,sha256=
|
104
|
+
unfold/templates/admin/search_form.html,sha256=8fJlPYHQDCm4Je05wwdNJuJQR6ChLgghWmo-yHSybMs,1099
|
104
105
|
unfold/templates/admin/submit_line.html,sha256=Oi7lUe2oh8O4--ZM0eQzVtZQnRKVeVXylc1ECGA7zKI,4247
|
105
|
-
unfold/templates/admin/widgets/clearable_file_input.html,sha256=vXsyP0-YD-z3z6VL4vXW9pJH9_-ZU9u-3AnmZkni-R4,1994
|
106
|
-
unfold/templates/admin/widgets/radio.html,sha256=3WcmclQNg7R_pRjEHL1dHkGjAzWlWNYnhHkAirC4nuA,646
|
107
|
-
unfold/templates/admin/widgets/radio_option.html,sha256=IZgPx-aWKJuxrSalJ3K50RFd1vwSpb9Qk0yZwfV78_A,368
|
108
|
-
unfold/templates/admin/widgets/related_widget_wrapper.html,sha256=U6RaeR86xbi1AWUrMm1SbjlXGwpC3PZdNLTbk3alxXI,3799
|
109
|
-
unfold/templates/admin/widgets/split_datetime.html,sha256=eXLFZyCv84LCTFWAUhNO3xAIzWvGBvI1ZpYbB38_HOI,862
|
110
106
|
unfold/templates/auth/widgets/read_only_password_hash.html,sha256=Li9efo-3cFC5zj9im0SPfc62R4ZNVPQhs24H1U7xmD8,785
|
111
107
|
unfold/templates/registration/logged_out.html,sha256=E7RHtB6AGQwgUIiV7dwJ1DbdfNvEhzJARONnB6jRLhQ,1028
|
112
108
|
unfold/templates/registration/password_change_done.html,sha256=i1ZzfTwZHWNWoN9_xHZDdcgLdTOVbTFFD1HUSuG0LkY,1062
|
@@ -129,7 +125,7 @@ unfold/templates/unfold/helpers/app_list.html,sha256=FvL-cEOVxwckP5TqzLEYL34EMlY
|
|
129
125
|
unfold/templates/unfold/helpers/app_list_default.html,sha256=vZkw1F7oHOKReNkdHRYjhuNdA1nNdvSD4wbDmf0bnsM,4102
|
130
126
|
unfold/templates/unfold/helpers/boolean.html,sha256=p_WOlytoXvDwta76WgcV4JSWKpBgKf4amhqmHF798F8,564
|
131
127
|
unfold/templates/unfold/helpers/breadcrumb_item.html,sha256=k_1j57UV0WtzFFlMKaewj4NLbR_DhXI6RzCHThblZLw,234
|
132
|
-
unfold/templates/unfold/helpers/display_header.html,sha256=
|
128
|
+
unfold/templates/unfold/helpers/display_header.html,sha256=J49ReIVl8Z29714HMIH-zFfaQdraMRS32VL1Ewg4W9M,808
|
133
129
|
unfold/templates/unfold/helpers/display_label.html,sha256=LS9DWzYjHkYLV27sZDwyXlg2sLJ0AlId9FbjnXpsbfg,317
|
134
130
|
unfold/templates/unfold/helpers/field.html,sha256=oEhGUrLZi2hiuLaC96R2zdwD8DNZqX2_sJIxTpPTJDM,340
|
135
131
|
unfold/templates/unfold/helpers/field_readonly.html,sha256=v7-2oSSDgOsuYpP70y8DqdBqbRybubAfSDzstveoBuw,382
|
@@ -153,27 +149,32 @@ unfold/templates/unfold/helpers/site_icon.html,sha256=RO-R5yRt6yOx41Z8dpDP4lzwMX
|
|
153
149
|
unfold/templates/unfold/helpers/site_logo.html,sha256=05tqXzHy--pluceRQ2jDUZCFX9DjPHdBqGaieUv9sXk,424
|
154
150
|
unfold/templates/unfold/helpers/submit.html,sha256=oSzq85LRLhdOlbFtFZFhYm6ucT95u6LunTeSTDClszQ,206
|
155
151
|
unfold/templates/unfold/helpers/tab_action.html,sha256=wanUlr_CKqSjxvKC7_moHFWK-hd7aT3KDdNATkJE2XE,337
|
156
|
-
unfold/templates/unfold/helpers/tab_list.html,sha256=
|
152
|
+
unfold/templates/unfold/helpers/tab_list.html,sha256=WMFSx0HEvylI_AOIPtFuWff1ePJkY6HS-PhRwo0EBUk,2029
|
157
153
|
unfold/templates/unfold/helpers/theme_switch.html,sha256=skkl6fYUnYLM7fAPivHxWjnOnuQSKa-7aDxh7I8dGIg,2266
|
158
154
|
unfold/templates/unfold/helpers/userlinks.html,sha256=qWjtBt9Q_tU8a874ii0Qqg8t_d-SSYBTB_3QZfNlx9g,634
|
159
155
|
unfold/templates/unfold/helpers/welcomemsg.html,sha256=noRysgSENef4_53pXaTiBCy2or6lQm1ZtmCQVODAB1c,1120
|
160
|
-
unfold/templates/unfold/layouts/base_simple.html,sha256=
|
156
|
+
unfold/templates/unfold/layouts/base_simple.html,sha256=rki7n7QagHFAaCEn488pTOj9dpNL9AwwzKps8Ipiubk,993
|
161
157
|
unfold/templates/unfold/layouts/skeleton.html,sha256=D35u-G6-h5SwTMzIZaEURedsPObSjoB198lLsJwmZWs,3278
|
158
|
+
unfold/templates/unfold/widgets/clearable_file_input.html,sha256=vXsyP0-YD-z3z6VL4vXW9pJH9_-ZU9u-3AnmZkni-R4,1994
|
162
159
|
unfold/templates/unfold/widgets/clearable_file_input_small.html,sha256=rqUnHF4jwL8_RySUuq2aXgj-0P_usgo1HeVT_IcfyFY,2531
|
163
|
-
unfold/templates/unfold/widgets/date.html,sha256=
|
160
|
+
unfold/templates/unfold/widgets/date.html,sha256=WXo2LG1v_gBZBSg-zocj7oujMKI0MWLYCIFfB04HMLQ,122
|
161
|
+
unfold/templates/unfold/widgets/radio.html,sha256=3WcmclQNg7R_pRjEHL1dHkGjAzWlWNYnhHkAirC4nuA,646
|
162
|
+
unfold/templates/unfold/widgets/radio_option.html,sha256=IZgPx-aWKJuxrSalJ3K50RFd1vwSpb9Qk0yZwfV78_A,368
|
164
163
|
unfold/templates/unfold/widgets/range.html,sha256=28FBtSUgUcG82vpk_I27Lbs5oWZOV_oMzVhx4wj3-Ik,262
|
164
|
+
unfold/templates/unfold/widgets/related_widget_wrapper.html,sha256=0I6wSu8z_sJPqmX6uZev4mQGIIM336d6kvHdHj36ny4,3831
|
165
|
+
unfold/templates/unfold/widgets/split_datetime.html,sha256=eXLFZyCv84LCTFWAUhNO3xAIzWvGBvI1ZpYbB38_HOI,862
|
165
166
|
unfold/templates/unfold/widgets/split_datetime_vertical.html,sha256=xinCH4kkQ-yKUqcSI7-m-_UEzOEKWqvLTjUa3i-e8EM,881
|
166
167
|
unfold/templates/unfold/widgets/split_money.html,sha256=AFLUBmzGbY-RXgsfz7gaDxVRhplaIPhXjg_hWYo9xcY,352
|
167
168
|
unfold/templates/unfold/widgets/textarea.html,sha256=4xIGWb20DuwiwumndByrAyh7xF-ReXKLulSpDImX_cs,459
|
168
|
-
unfold/templates/unfold/widgets/time.html,sha256=
|
169
|
+
unfold/templates/unfold/widgets/time.html,sha256=WXo2LG1v_gBZBSg-zocj7oujMKI0MWLYCIFfB04HMLQ,122
|
169
170
|
unfold/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
170
171
|
unfold/templatetags/unfold.py,sha256=HFe0GrTD4va0lLzsZhxqjEOONmehqOWdf5vulkxgfGU,6302
|
171
|
-
unfold/templatetags/unfold_list.py,sha256=
|
172
|
+
unfold/templatetags/unfold_list.py,sha256=5xAjQX0_JnVwDaj-wGkGqbjOAtp-a18koWIKj5VfBz0,13867
|
172
173
|
unfold/typing.py,sha256=1P8PWM2oeaceUJtA5j071RbKEBpHYaux441u7Hd6wv4,643
|
173
174
|
unfold/utils.py,sha256=5OIgDcwvIJQbwbnnqHx61cHh-2T1h184mTAuNq5WXLI,4088
|
174
175
|
unfold/views.py,sha256=Ml3XlEoHLcbEWof59Dw8ihKBMcmp-gBAibThtBFj55A,708
|
175
|
-
unfold/widgets.py,sha256=
|
176
|
-
django_unfold-0.
|
177
|
-
django_unfold-0.
|
178
|
-
django_unfold-0.
|
179
|
-
django_unfold-0.
|
176
|
+
unfold/widgets.py,sha256=JfcWOWtPPxqosXMyP-BcfO6UK53eoEU3obRpMZijED4,14216
|
177
|
+
django_unfold-0.24.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
|
178
|
+
django_unfold-0.24.0.dist-info/METADATA,sha256=ph7g7IiHyDYbuoztj_K9eodilCfHAXFTjU_29nuGwHg,46669
|
179
|
+
django_unfold-0.24.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
180
|
+
django_unfold-0.24.0.dist-info/RECORD,,
|
unfold/admin.py
CHANGED
@@ -8,6 +8,7 @@ from django.contrib.admin import StackedInline as BaseStackedInline
|
|
8
8
|
from django.contrib.admin import TabularInline as BaseTabularInline
|
9
9
|
from django.contrib.admin import display, helpers
|
10
10
|
from django.contrib.admin.utils import lookup_field
|
11
|
+
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
|
11
12
|
from django.core.exceptions import ObjectDoesNotExist
|
12
13
|
from django.db import models
|
13
14
|
from django.db.models import (
|
@@ -39,7 +40,6 @@ from django.utils.safestring import SafeText, mark_safe
|
|
39
40
|
from django.utils.text import capfirst
|
40
41
|
from django.utils.translation import gettext_lazy as _
|
41
42
|
from django.views import View
|
42
|
-
from unfold.utils import display_for_field
|
43
43
|
|
44
44
|
from .checks import UnfoldModelAdminChecks
|
45
45
|
from .dataclasses import UnfoldAction
|
@@ -47,9 +47,9 @@ from .exceptions import UnfoldException
|
|
47
47
|
from .forms import ActionForm
|
48
48
|
from .settings import get_config
|
49
49
|
from .typing import FieldsetsType
|
50
|
+
from .utils import display_for_field
|
50
51
|
from .widgets import (
|
51
52
|
CHECKBOX_LABEL_CLASSES,
|
52
|
-
INPUT_CLASSES,
|
53
53
|
LABEL_CLASSES,
|
54
54
|
SELECT_CLASSES,
|
55
55
|
UnfoldAdminBigIntegerFieldWidget,
|
@@ -62,6 +62,7 @@ from .widgets import (
|
|
62
62
|
UnfoldAdminMoneyWidget,
|
63
63
|
UnfoldAdminNullBooleanSelectWidget,
|
64
64
|
UnfoldAdminRadioSelectWidget,
|
65
|
+
UnfoldAdminSelectWidget,
|
65
66
|
UnfoldAdminSingleDateWidget,
|
66
67
|
UnfoldAdminSingleTimeWidget,
|
67
68
|
UnfoldAdminSplitDateTimeWidget,
|
@@ -142,6 +143,11 @@ class UnfoldAdminField(helpers.AdminField):
|
|
142
143
|
def label_tag(self) -> SafeText:
|
143
144
|
classes = []
|
144
145
|
|
146
|
+
if not self.field.field.widget.__class__.__name__.startswith(
|
147
|
+
"Unfold"
|
148
|
+
) and not self.field.field.widget.template_name.startswith("unfold"):
|
149
|
+
return super().label_tag()
|
150
|
+
|
145
151
|
# TODO load config from current AdminSite (override Fieldline.__iter__ method)
|
146
152
|
for lang, flag in get_config()["EXTENSIONS"]["modeltranslation"][
|
147
153
|
"flags"
|
@@ -175,6 +181,9 @@ helpers.AdminField = UnfoldAdminField
|
|
175
181
|
|
176
182
|
class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
|
177
183
|
def label_tag(self) -> SafeText:
|
184
|
+
if not isinstance(self.model_admin, ModelAdmin):
|
185
|
+
return super().label_tag()
|
186
|
+
|
178
187
|
attrs = {
|
179
188
|
"class": " ".join(LABEL_CLASSES + ["mb-2"]),
|
180
189
|
}
|
@@ -285,9 +294,7 @@ class ModelAdminMixin:
|
|
285
294
|
radio_style=self.radio_fields[db_field.name]
|
286
295
|
)
|
287
296
|
else:
|
288
|
-
kwargs["widget"] =
|
289
|
-
attrs={"class": " ".join(SELECT_CLASSES)}
|
290
|
-
)
|
297
|
+
kwargs["widget"] = UnfoldAdminSelectWidget()
|
291
298
|
|
292
299
|
kwargs["choices"] = db_field.get_choices(
|
293
300
|
include_blank=db_field.blank, blank_choice=[("", _("Select value"))]
|
@@ -301,16 +308,12 @@ class ModelAdminMixin:
|
|
301
308
|
# Overrides widgets for all related fields
|
302
309
|
if "widget" not in kwargs:
|
303
310
|
if db_field.name in self.raw_id_fields:
|
304
|
-
kwargs["widget"] =
|
305
|
-
attrs={"class": " ".join(INPUT_CLASSES)}
|
306
|
-
)
|
311
|
+
kwargs["widget"] = UnfoldAdminTextInputWidget()
|
307
312
|
elif (
|
308
313
|
db_field.name not in self.get_autocomplete_fields(request)
|
309
314
|
and db_field.name not in self.radio_fields
|
310
315
|
):
|
311
|
-
kwargs["widget"] =
|
312
|
-
attrs={"class": " ".join(SELECT_CLASSES)}
|
313
|
-
)
|
316
|
+
kwargs["widget"] = UnfoldAdminSelectWidget()
|
314
317
|
kwargs["empty_label"] = _("Select value")
|
315
318
|
|
316
319
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
@@ -323,9 +326,7 @@ class ModelAdminMixin:
|
|
323
326
|
) -> ModelMultipleChoiceField:
|
324
327
|
if "widget" not in kwargs:
|
325
328
|
if db_field.name in self.raw_id_fields:
|
326
|
-
kwargs["widget"] =
|
327
|
-
attrs={"class": " ".join(INPUT_CLASSES)}
|
328
|
-
)
|
329
|
+
kwargs["widget"] = UnfoldAdminTextInputWidget()
|
329
330
|
|
330
331
|
form_field = super().formfield_for_manytomany(db_field, request, **kwargs)
|
331
332
|
|
@@ -342,9 +343,7 @@ class ModelAdminMixin:
|
|
342
343
|
self, db_field: Field, request: HttpRequest, **kwargs
|
343
344
|
) -> Optional[Field]:
|
344
345
|
if "widget" not in kwargs:
|
345
|
-
kwargs["widget"] =
|
346
|
-
attrs={"class": " ".join(SELECT_CLASSES)}
|
347
|
-
)
|
346
|
+
kwargs["widget"] = UnfoldAdminNullBooleanSelectWidget()
|
348
347
|
|
349
348
|
return db_field.formfield(**kwargs)
|
350
349
|
|
@@ -354,7 +353,14 @@ class ModelAdminMixin:
|
|
354
353
|
if isinstance(db_field, models.BooleanField) and db_field.null is True:
|
355
354
|
return self.formfield_for_nullboolean_field(db_field, request, **kwargs)
|
356
355
|
|
357
|
-
|
356
|
+
formfield = super().formfield_for_dbfield(db_field, request, **kwargs)
|
357
|
+
|
358
|
+
if formfield and isinstance(formfield.widget, RelatedFieldWidgetWrapper):
|
359
|
+
formfield.widget.template_name = (
|
360
|
+
"unfold/widgets/related_widget_wrapper.html"
|
361
|
+
)
|
362
|
+
|
363
|
+
return formfield
|
358
364
|
|
359
365
|
|
360
366
|
class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
@@ -400,7 +406,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
400
406
|
filtered_actions.append(action)
|
401
407
|
continue
|
402
408
|
permission_checks = (
|
403
|
-
getattr(self, "has_
|
409
|
+
getattr(self, f"has_{permission}_permission")
|
404
410
|
for permission in action.method.allowed_permissions
|
405
411
|
)
|
406
412
|
if any(has_permission(request) for has_permission in permission_checks):
|
@@ -546,7 +552,8 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
546
552
|
"title": action.description,
|
547
553
|
"attrs": action.method.attrs,
|
548
554
|
"path": reverse(
|
549
|
-
f"
|
555
|
+
f"{self.admin_site.name}:{action.action_name}",
|
556
|
+
args=(object_id,),
|
550
557
|
),
|
551
558
|
}
|
552
559
|
)
|
@@ -570,7 +577,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
570
577
|
{
|
571
578
|
"title": action.description,
|
572
579
|
"attrs": action.method.attrs,
|
573
|
-
"path": reverse(f"
|
580
|
+
"path": reverse(f"{self.admin_site.name}:{action.action_name}"),
|
574
581
|
}
|
575
582
|
for action in self.get_actions_list(request)
|
576
583
|
]
|
@@ -579,7 +586,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
579
586
|
{
|
580
587
|
"title": action.description,
|
581
588
|
"attrs": action.method.attrs,
|
582
|
-
"raw_path": f"
|
589
|
+
"raw_path": f"{self.admin_site.name}:{action.action_name}",
|
583
590
|
}
|
584
591
|
for action in self.get_actions_row(request)
|
585
592
|
]
|
unfold/apps.py
CHANGED
@@ -7,9 +7,14 @@ from .sites import UnfoldAdminSite
|
|
7
7
|
|
8
8
|
class DefaultAppConfig(AppConfig):
|
9
9
|
name = "unfold"
|
10
|
+
default = True
|
10
11
|
|
11
12
|
def ready(self):
|
12
13
|
site = UnfoldAdminSite()
|
13
14
|
|
14
15
|
admin.site = site
|
15
16
|
sites.site = site
|
17
|
+
|
18
|
+
|
19
|
+
class BasicAppConfig(AppConfig):
|
20
|
+
name = "unfold"
|
unfold/checks.py
CHANGED
@@ -30,7 +30,7 @@ class UnfoldModelAdminChecks(ModelAdminChecks):
|
|
30
30
|
if not hasattr(action.method, "allowed_permissions"):
|
31
31
|
continue
|
32
32
|
for permission in action.method.allowed_permissions:
|
33
|
-
method_name = "has_
|
33
|
+
method_name = f"has_{permission}_permission"
|
34
34
|
if not hasattr(obj, method_name):
|
35
35
|
errors.append(
|
36
36
|
checks.Error(
|
unfold/contrib/filters/admin.py
CHANGED
@@ -17,16 +17,132 @@ from django.db.models.fields import (
|
|
17
17
|
from django.forms import ValidationError
|
18
18
|
from django.http import HttpRequest
|
19
19
|
from django.utils.dateparse import parse_datetime
|
20
|
+
from django.utils.translation import gettext_lazy as _
|
20
21
|
|
21
22
|
from .forms import (
|
23
|
+
DropdownForm,
|
22
24
|
RangeDateForm,
|
23
25
|
RangeDateTimeForm,
|
24
26
|
RangeNumericForm,
|
27
|
+
SearchForm,
|
25
28
|
SingleNumericForm,
|
26
29
|
SliderNumericForm,
|
27
30
|
)
|
28
31
|
|
29
32
|
|
33
|
+
class ValueMixin:
|
34
|
+
def value(self) -> Optional[str]:
|
35
|
+
return (
|
36
|
+
self.lookup_val[0]
|
37
|
+
if self.lookup_val not in EMPTY_VALUES
|
38
|
+
and isinstance(self.lookup_val, List)
|
39
|
+
and len(self.lookup_val) > 0
|
40
|
+
else self.lookup_val
|
41
|
+
)
|
42
|
+
|
43
|
+
|
44
|
+
class DropdownMixin:
|
45
|
+
template = "unfold/filters/filters_field.html"
|
46
|
+
form_class = DropdownForm
|
47
|
+
all_option = ["", _("All")]
|
48
|
+
|
49
|
+
def queryset(self, request, queryset) -> QuerySet:
|
50
|
+
if self.value() not in EMPTY_VALUES:
|
51
|
+
return super().queryset(request, queryset)
|
52
|
+
|
53
|
+
return queryset
|
54
|
+
|
55
|
+
|
56
|
+
class TextFilter(admin.SimpleListFilter):
|
57
|
+
template = "unfold/filters/filters_field.html"
|
58
|
+
form_class = SearchForm
|
59
|
+
|
60
|
+
def has_output(self) -> bool:
|
61
|
+
return True
|
62
|
+
|
63
|
+
def lookups(self, request: HttpRequest, model_admin: ModelAdmin) -> Tuple:
|
64
|
+
return ()
|
65
|
+
|
66
|
+
def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
|
67
|
+
return (
|
68
|
+
{
|
69
|
+
"form": self.form_class(
|
70
|
+
name=self.parameter_name,
|
71
|
+
label=_("By {}").format(self.title),
|
72
|
+
data={self.parameter_name: self.value()},
|
73
|
+
),
|
74
|
+
},
|
75
|
+
)
|
76
|
+
|
77
|
+
|
78
|
+
class FieldTextFilter(ValueMixin, admin.FieldListFilter):
|
79
|
+
template = "unfold/filters/filters_field.html"
|
80
|
+
form_class = SearchForm
|
81
|
+
|
82
|
+
def __init__(self, field, request, params, model, model_admin, field_path):
|
83
|
+
self.lookup_kwarg = f"{field_path}__icontains"
|
84
|
+
self.lookup_val = params.get(self.lookup_kwarg)
|
85
|
+
super().__init__(field, request, params, model, model_admin, field_path)
|
86
|
+
|
87
|
+
def expected_parameters(self) -> List[str]:
|
88
|
+
return [self.lookup_kwarg]
|
89
|
+
|
90
|
+
def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
|
91
|
+
return (
|
92
|
+
{
|
93
|
+
"form": self.form_class(
|
94
|
+
label=_("By {}").format(self.title),
|
95
|
+
name=self.lookup_kwarg,
|
96
|
+
data={self.lookup_kwarg: self.value()},
|
97
|
+
),
|
98
|
+
},
|
99
|
+
)
|
100
|
+
|
101
|
+
|
102
|
+
class DropdownFilter(admin.SimpleListFilter):
|
103
|
+
template = "unfold/filters/filters_field.html"
|
104
|
+
form_class = DropdownForm
|
105
|
+
all_option = ["", _("All")]
|
106
|
+
|
107
|
+
def choices(self, changelist: ChangeList) -> Tuple[Dict[str, Any], ...]:
|
108
|
+
return (
|
109
|
+
{
|
110
|
+
"form": self.form_class(
|
111
|
+
label=_("By {}").format(self.title),
|
112
|
+
name=self.parameter_name,
|
113
|
+
choices=[self.all_option, *self.lookup_choices],
|
114
|
+
data={self.parameter_name: self.value()},
|
115
|
+
),
|
116
|
+
},
|
117
|
+
)
|
118
|
+
|
119
|
+
|
120
|
+
class ChoicesDropdownFilter(ValueMixin, DropdownMixin, admin.ChoicesFieldListFilter):
|
121
|
+
def choices(self, changelist: ChangeList):
|
122
|
+
choices = [self.all_option, *self.field.flatchoices]
|
123
|
+
|
124
|
+
yield {
|
125
|
+
"form": self.form_class(
|
126
|
+
label=_("By {}").format(self.title),
|
127
|
+
name=self.lookup_kwarg,
|
128
|
+
choices=choices,
|
129
|
+
data={self.lookup_kwarg: self.value()},
|
130
|
+
),
|
131
|
+
}
|
132
|
+
|
133
|
+
|
134
|
+
class RelatedDropdownFilter(ValueMixin, DropdownMixin, admin.RelatedFieldListFilter):
|
135
|
+
def choices(self, changelist: ChangeList):
|
136
|
+
yield {
|
137
|
+
"form": self.form_class(
|
138
|
+
label=_("By {}").format(self.title),
|
139
|
+
name=self.lookup_kwarg,
|
140
|
+
choices=[self.all_option, *self.lookup_choices],
|
141
|
+
data={self.lookup_kwarg: self.value()},
|
142
|
+
),
|
143
|
+
}
|
144
|
+
|
145
|
+
|
30
146
|
class SingleNumericFilter(admin.FieldListFilter):
|
31
147
|
request = None
|
32
148
|
parameter_name = None
|