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.
Files changed (44) hide show
  1. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/METADATA +102 -4
  2. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/RECORD +44 -43
  3. unfold/admin.py +29 -22
  4. unfold/apps.py +5 -0
  5. unfold/checks.py +1 -1
  6. unfold/contrib/filters/admin.py +116 -0
  7. unfold/contrib/filters/forms.py +29 -1
  8. unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
  9. unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
  10. unfold/contrib/filters/templates/unfold/filters/filters_field.html +7 -0
  11. unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +1 -1
  12. unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +1 -1
  13. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
  14. unfold/contrib/import_export/admin.py +1 -1
  15. unfold/contrib/import_export/forms.py +6 -7
  16. unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
  17. unfold/contrib/import_export/templates/admin/import_export/import_form.html +2 -2
  18. unfold/forms.py +6 -1
  19. unfold/sites.py +3 -3
  20. unfold/static/unfold/css/styles.css +1 -1
  21. unfold/styles.css +11 -4
  22. unfold/templates/admin/actions.html +9 -9
  23. unfold/templates/admin/app_list.html +6 -8
  24. unfold/templates/admin/base.html +1 -3
  25. unfold/templates/admin/change_form.html +1 -2
  26. unfold/templates/admin/change_list.html +2 -2
  27. unfold/templates/admin/change_list_results.html +15 -3
  28. unfold/templates/admin/edit_inline/tabular.html +3 -3
  29. unfold/templates/admin/filter.html +2 -2
  30. unfold/templates/admin/search_form.html +10 -12
  31. unfold/templates/unfold/helpers/display_header.html +16 -13
  32. unfold/templates/unfold/helpers/tab_list.html +1 -1
  33. unfold/templates/unfold/layouts/base_simple.html +1 -1
  34. unfold/templates/unfold/widgets/date.html +1 -1
  35. unfold/templates/{admin → unfold}/widgets/related_widget_wrapper.html +4 -4
  36. unfold/templates/unfold/widgets/time.html +1 -1
  37. unfold/templatetags/unfold_list.py +8 -6
  38. unfold/widgets.py +18 -5
  39. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/LICENSE.md +0 -0
  40. {django_unfold-0.22.0.dist-info → django_unfold-0.24.0.dist-info}/WHEEL +0 -0
  41. /unfold/templates/{admin → unfold}/widgets/clearable_file_input.html +0 -0
  42. /unfold/templates/{admin → unfold}/widgets/radio.html +0 -0
  43. /unfold/templates/{admin → unfold}/widgets/radio_option.html +0 -0
  44. /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.22.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
- - **Custom filters:** widgets for filtering number & datetime values
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 "First main heading", "Smaller additional description", "AB"
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=EyxkRKXWunWhPd6RdkUqdGZprTK1HPe6_xKWsh2LenY,23449
3
- unfold/apps.py,sha256=LXJVMj1WIoQXjRmiz2bFtsVc9gKhdby7UQczdtjieY0,307
4
- unfold/checks.py,sha256=_Y9VoDBIj_VdombQ-iyN-s9cRxkW_9vQTfHQ4yYuG-8,1672
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=7FJl86b407ylTjgkRN2EwXGHnnVNwBiGvA1_822L5uc,16412
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=-Tv1vWh-u8eLPAJqSX6c8R3IY0GmgOoFrzVQ8E8gO_s,4055
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=6OcC2JZAlegj5rYoUqpRT5mLanFxRy7bB2RBAe-NGiI,661
16
- unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html,sha256=6OcC2JZAlegj5rYoUqpRT5mLanFxRy7bB2RBAe-NGiI,661
17
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html,sha256=pwoSmezSZhvhG2fiAHMYaYdT586IJ13EcnOoHMkc9n4,639
18
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html,sha256=3njmCOx1HLNBz6VWDQtBWXwBUcclY2Y3D1vRVTwNn8c,576
19
- unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html,sha256=LqN3D483HbeT33SI4Uoy8YxKLP0uWbKyt8_bi1EWz3w,1681
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=73Jz93HtENiZe7jzuKSPl4JvasXCpDSGZw_9LvJ2QCU,1156
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=dLqLv7YP0i8_CrxupEfh3VncJ8CJHf7O1eQoWFOcydU,672
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=GHzvpihEakCcdKtF1fxkzFkrYi3KZYBWHB1P6PrVAbc,1791
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=wyV-j0EDIxLzFaJaS-OU1YuTYn6536bg5ohILsDcXoc,1312
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=PTXH_CaWwVKpHw0ODEYgL8zC77n8aPja2_6v6N5QnFI,3360
62
+ unfold/forms.py,sha256=SzdyBC5wqE5IcoZWS2oXObycmfmICHsqlS3X03mw4QA,3468
62
63
  unfold/settings.py,sha256=--TdTSWdOA8TQGW4-vjJkjy_zEyd_kZwBr3BIuQ8hzI,1208
63
- unfold/sites.py,sha256=tqQUiDVAhCghCSXT-0Vk3F1XG2e2K2h7EmMqLUn4UMU,12565
64
- unfold/static/unfold/css/styles.css,sha256=YUkTpu6wt0c4gsmJyNa9BE9igcD9FqJ2CggYlAr8d-A,90603
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=RV8leykkFHm6nF-dM2k_XVGWepIL2UuRX2H4kx4K36s,17403
78
- unfold/templates/admin/actions.html,sha256=Z5I2X6YkbAW8tVP6rANEaA3_8gYSt4lHIdGe9IWXWsg,2528
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=BrWZaAu-EmtMtSVAvdBkkFFW5jckU7RaVu_D6yp3IZ8,3178
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=e2hq4G-E35f_ZvlS7kZk_pwvE62pxJ-ZlaS5UpZVgOo,2304
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=Pmi8NbCj98EufSYSzSGdk_bIkRib1hxjAjd_9jeIhT8,5327
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=vxEHcObpjMYlJbnnrtSpUfSCJOh1TJL28vSTY5Za0p8,5102
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=oGokE28awo8YOkPqIojpsI_vwvaIy1oZHmVpCDvfGU0,4255
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=1VWpXzY9sRz95r7_ZPQoQbL-jlJv598MWlXqAoqTpAY,12261
95
- unfold/templates/admin/filter.html,sha256=JAp95Mg6W2Pfdrr-gxEZld9EvgMGLj8etMdSl84l4ro,1686
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=GyI0nQvkMyCYJMMrsJoVm8uSyRF1hDCrVWcA4boR0oU,1180
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=E-yG9ydyb6rRIR5TT4FxekD3qokilfoOwaEaB7np8WI,433
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=Y7BtJqyVAgMaulSBfy55-surgp7TniLwW3H8eXh54Z8,2029
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=PHUuMVBToK9yWpdHBSWIfhdC_NEDPZTF_Ft9iDKfIEQ,985
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=Dq9s-Kyfkw7p4TfchR-XFVOX6pOS7GkXPOWuP9CLRVw,106
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=Dq9s-Kyfkw7p4TfchR-XFVOX6pOS7GkXPOWuP9CLRVw,106
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=FhSEGWs2KuqfsqQpAcH3N1SihxIOq_QwVRhmSP1bAEU,13835
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=FThowQCfPVmsFT260j0ufGwPBNxF6JCIEp6QvJ-L0yI,13720
176
- django_unfold-0.22.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
177
- django_unfold-0.22.0.dist-info/METADATA,sha256=QeUai0GE0Wxa0x8UOWPAVat69hy2vLOwSb5xbQxpLA8,42659
178
- django_unfold-0.22.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
179
- django_unfold-0.22.0.dist-info/RECORD,,
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"] = forms.Select(
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"] = forms.TextInput(
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"] = forms.Select(
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"] = forms.TextInput(
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"] = forms.NullBooleanSelect(
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
- return super().formfield_for_dbfield(db_field, request, **kwargs)
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_%s_permission" % permission)
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"admin:{action.action_name}", args=(object_id,)
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"admin:{action.action_name}"),
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"admin:{action.action_name}",
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_%s_permission" % permission
33
+ method_name = f"has_{permission}_permission"
34
34
  if not hasattr(obj, method_name):
35
35
  errors.append(
36
36
  checks.Error(
@@ -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