django-unfold 0.23.0__py3-none-any.whl → 0.25.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. {django_unfold-0.23.0.dist-info → django_unfold-0.25.0.dist-info}/METADATA +97 -4
  2. {django_unfold-0.23.0.dist-info → django_unfold-0.25.0.dist-info}/RECORD +26 -24
  3. unfold/admin.py +3 -3
  4. unfold/contrib/filters/admin.py +116 -0
  5. unfold/contrib/filters/forms.py +29 -1
  6. unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +1 -1
  7. unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +1 -1
  8. unfold/contrib/filters/templates/unfold/filters/filters_field.html +7 -0
  9. unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +1 -1
  10. unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +1 -1
  11. unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +1 -1
  12. unfold/contrib/import_export/admin.py +1 -1
  13. unfold/contrib/import_export/forms.py +8 -3
  14. unfold/contrib/import_export/templates/admin/import_export/export.html +1 -1
  15. unfold/contrib/import_export/templates/admin/import_export/import_form.html +9 -16
  16. unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +24 -0
  17. unfold/static/unfold/css/styles.css +1 -1
  18. unfold/styles.css +1 -1
  19. unfold/templates/admin/change_list_results.html +67 -65
  20. unfold/templates/admin/edit_inline/stacked.html +2 -2
  21. unfold/templates/admin/edit_inline/tabular.html +111 -109
  22. unfold/templates/admin/filter.html +2 -2
  23. unfold/templates/unfold/helpers/display_header.html +16 -13
  24. unfold/widgets.py +2 -2
  25. {django_unfold-0.23.0.dist-info → django_unfold-0.25.0.dist-info}/LICENSE.md +0 -0
  26. {django_unfold-0.23.0.dist-info → django_unfold-0.25.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: django-unfold
3
- Version: 0.23.0
3
+ Version: 0.25.0
4
4
  Summary: Modern Django admin theme for seamless interface development
5
5
  Home-page: https://unfoldadmin.com
6
6
  License: MIT
@@ -52,14 +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
62
+ - **Parallel admin**: support for having default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/)
63
63
  - **VS Code**: project configuration and development container is included
64
64
 
65
65
  ## Table of contents <!-- omit from toc -->
@@ -74,6 +74,8 @@ Did you decide to start using Unfold but you don't have time to make the switch
74
74
  - [Action handler functions](#action-handler-functions)
75
75
  - [Action examples](#action-examples)
76
76
  - [Filters](#filters)
77
+ - [Text filters](#text-filters)
78
+ - [Dropdown filters](#dropdown-filters)
77
79
  - [Numeric filters](#numeric-filters)
78
80
  - [Date/time filters](#datetime-filters)
79
81
  - [Display decorator](#display-decorator)
@@ -481,6 +483,88 @@ By default, Django admin handles all filters as regular HTML links pointing at t
481
483
 
482
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.
483
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
+
484
568
  ### Numeric filters
485
569
 
486
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.
@@ -611,7 +695,16 @@ class UserAdmin(ModelAdmin):
611
695
  """
612
696
  Third argument is short text which will appear as prefix in circle
613
697
  """
614
- 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
+ ]
615
708
  ```
616
709
 
617
710
  ## Change form tabs
@@ -1,22 +1,23 @@
1
1
  unfold/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- unfold/admin.py,sha256=JjfiJhWSdhDBMK3cf6IsCyGwv5povQgAZtzeHWKrEtA,23792
2
+ unfold/admin.py,sha256=iNlDiZy_kl4lk2We4VDKOK5lMam4GeNMOA-bo67ykqU,23810
3
3
  unfold/apps.py,sha256=SlBXPYrUd2uXn67qFbRvbXSUk3XFWrF4-5WELgDCvho,381
4
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,20 +36,21 @@ 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=o1dH1F6hOdpWdeFCxVRGe0Y5iA9BA7CaWxVHWX_WlvM,756
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=-oyUFQCLZG5Q3RVQE6ad07ccRXVE006f5YeqoWTHc18,1114
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
53
+ unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html,sha256=NuIp058PRnp7upIwXYeGX-u2tMv_iJKciRZ1MVmORlQ,871
52
54
  unfold/contrib/simple_history/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
55
  unfold/contrib/simple_history/apps.py,sha256=eF_KVYb60CAnGgWk2Z1YKYGfgA3TJBMr229qI7e2pgU,153
54
56
  unfold/contrib/simple_history/templates/simple_history/_object_history_list.html,sha256=aXOQ1zwsRBlFmzODsZApvMtb8t1IPXim6i4plXUR5XE,5112
@@ -61,7 +63,7 @@ unfold/exceptions.py,sha256=gcCj1ox61E137bk_0Cqy4YC3SttdPgB-fiJUqpmyHSE,43
61
63
  unfold/forms.py,sha256=SzdyBC5wqE5IcoZWS2oXObycmfmICHsqlS3X03mw4QA,3468
62
64
  unfold/settings.py,sha256=--TdTSWdOA8TQGW4-vjJkjy_zEyd_kZwBr3BIuQ8hzI,1208
63
65
  unfold/sites.py,sha256=Gy_i43j2nizW2g8-mas5icvtk-beKism_CznATW6Ia8,12586
64
- unfold/static/unfold/css/styles.css,sha256=DjRQV4lwg81SmOyR1YRs1QWHNA0FdV7XYRiYP33lTig,91399
66
+ unfold/static/unfold/css/styles.css,sha256=HfOY9Ylgrlzs5o3KVIVx29gKNpBdsmml8VERROruJuQ,91363
65
67
  unfold/static/unfold/fonts/inter/Inter-Bold.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
66
68
  unfold/static/unfold/fonts/inter/Inter-Medium.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
67
69
  unfold/static/unfold/fonts/inter/Inter-Regular.woff2,sha256=O88EyjAeRPE_QEyKBKpK5wf2epUOEu8wwjj5bnhCZqE,46552
@@ -74,7 +76,7 @@ unfold/static/unfold/js/alpine.persist.js,sha256=84PZYnPi25AFm7wIWRe1gzA74c5Rv2V
74
76
  unfold/static/unfold/js/app.js,sha256=CIitJoFqpeZYPw8icGVXYX9tVRUgqFxcPZ2WjWS8Ylk,5288
75
77
  unfold/static/unfold/js/chart.js,sha256=22W6cFERR-CElMOKRgMMicueMVP0Vf7FBEBYH8Z8tCk,200633
76
78
  unfold/static/unfold/js/htmx.js,sha256=XOLqvnZiyEx46EW9vaJTBUaaWg8CGVVfXJkVsUmJbpI,42820
77
- unfold/styles.css,sha256=dlTJBzFvZEfH9xSs2AJ9nfzYTnxf8muCMa8COifqmzU,17598
79
+ unfold/styles.css,sha256=NfhUTzvCY01TO3uRxotLA4wCmXI4C92MENXJTg2c75E,17593
78
80
  unfold/templates/admin/actions.html,sha256=1tVlUpLoM72K2Ew4vQGcRwPjHuAtO5Jm4QdDsDLOq0I,2625
79
81
  unfold/templates/admin/app_index.html,sha256=lVjMIFsspHQ09LGHKfdfg7TlqlL39AX5LbwoeoZjFhk,1335
80
82
  unfold/templates/admin/app_list.html,sha256=7u2Ex0ArdC0CJNCTDnTEZpAXI_xbNQ1nrzAH4BuInNA,3009
@@ -86,13 +88,13 @@ unfold/templates/admin/change_form.html,sha256=-X_fQOGaa6k0WVbMO7he9bZIdIY3oj8Py
86
88
  unfold/templates/admin/change_form_object_tools.html,sha256=eyeH-i2HgEM0Yi-OJA2D1VnKJyC19A_my1IDGxxoP8Y,593
87
89
  unfold/templates/admin/change_list.html,sha256=18GDZswc1c0xtw2BcKti9SX95Ar9e1BX_HSY0K79g_8,5102
88
90
  unfold/templates/admin/change_list_object_tools.html,sha256=cmMiT2nT20Ph5yfpj9aHPr76Z-JP4aSXp0o-Rnad28s,147
89
- unfold/templates/admin/change_list_results.html,sha256=32zSgJ6W5uQlfzI-0N1FmCFagw2VYaXuiUZyZbxUIag,5004
91
+ unfold/templates/admin/change_list_results.html,sha256=1NeZibVKz9roYtdpYJGrlHj7HnyCQGzM_zSUg8JNGcg,5329
90
92
  unfold/templates/admin/date_hierarchy.html,sha256=BfUPbsLpHZVa40BHBahz1H9RSVuz36Jc3yrlobOiIpw,1306
91
93
  unfold/templates/admin/delete_confirmation.html,sha256=hpa2E14oZEXBBs6W1qdNQuF650TIO2Rhr52Q6UfwVeQ,5166
92
94
  unfold/templates/admin/delete_selected_confirmation.html,sha256=Foka2yvwAMEZre-Kh1KNadRzrCotdKM2U4e6AJQYZu8,4941
93
- unfold/templates/admin/edit_inline/stacked.html,sha256=U6O_r9-5Hp7mT0l1EgTeBB8tc9nEAAi0etL2aCU2pBM,4415
94
- unfold/templates/admin/edit_inline/tabular.html,sha256=YOrCgAQ9apLZf8VtFmUhK-WFoQomPtlilT9ktkEFBQA,12315
95
- unfold/templates/admin/filter.html,sha256=JAp95Mg6W2Pfdrr-gxEZld9EvgMGLj8etMdSl84l4ro,1686
95
+ unfold/templates/admin/edit_inline/stacked.html,sha256=_6kR4ANSiZomJnjgCXB7zEiFbPIuwZoaC0KYoN3is0A,4465
96
+ unfold/templates/admin/edit_inline/tabular.html,sha256=rkwd4RwvCU9rg_sH9MiNV20knpd_Hyx7EViavhHR6fk,12892
97
+ unfold/templates/admin/filter.html,sha256=dkrFkei-EAlldIU8DrgvSChzWQuUOu6-LS_qlZxdfFw,1708
96
98
  unfold/templates/admin/includes/fieldset.html,sha256=r4XjcZAOkWxHQExHZBWoCGtO3LYL0Iwkw1C55oR5V6M,2898
97
99
  unfold/templates/admin/includes/object_delete_summary.html,sha256=Nv69SCzyJHFX14iJFfodxKM0IIpQegKZH0fvKB15QJI,468
98
100
  unfold/templates/admin/index.html,sha256=pkGdKWdD3zzOvkRdELvdb15sleSpfl4eHPA14PAh7z0,684
@@ -124,7 +126,7 @@ unfold/templates/unfold/helpers/app_list.html,sha256=FvL-cEOVxwckP5TqzLEYL34EMlY
124
126
  unfold/templates/unfold/helpers/app_list_default.html,sha256=vZkw1F7oHOKReNkdHRYjhuNdA1nNdvSD4wbDmf0bnsM,4102
125
127
  unfold/templates/unfold/helpers/boolean.html,sha256=p_WOlytoXvDwta76WgcV4JSWKpBgKf4amhqmHF798F8,564
126
128
  unfold/templates/unfold/helpers/breadcrumb_item.html,sha256=k_1j57UV0WtzFFlMKaewj4NLbR_DhXI6RzCHThblZLw,234
127
- unfold/templates/unfold/helpers/display_header.html,sha256=E-yG9ydyb6rRIR5TT4FxekD3qokilfoOwaEaB7np8WI,433
129
+ unfold/templates/unfold/helpers/display_header.html,sha256=J49ReIVl8Z29714HMIH-zFfaQdraMRS32VL1Ewg4W9M,808
128
130
  unfold/templates/unfold/helpers/display_label.html,sha256=LS9DWzYjHkYLV27sZDwyXlg2sLJ0AlId9FbjnXpsbfg,317
129
131
  unfold/templates/unfold/helpers/field.html,sha256=oEhGUrLZi2hiuLaC96R2zdwD8DNZqX2_sJIxTpPTJDM,340
130
132
  unfold/templates/unfold/helpers/field_readonly.html,sha256=v7-2oSSDgOsuYpP70y8DqdBqbRybubAfSDzstveoBuw,382
@@ -172,8 +174,8 @@ unfold/templatetags/unfold_list.py,sha256=5xAjQX0_JnVwDaj-wGkGqbjOAtp-a18koWIKj5
172
174
  unfold/typing.py,sha256=1P8PWM2oeaceUJtA5j071RbKEBpHYaux441u7Hd6wv4,643
173
175
  unfold/utils.py,sha256=5OIgDcwvIJQbwbnnqHx61cHh-2T1h184mTAuNq5WXLI,4088
174
176
  unfold/views.py,sha256=Ml3XlEoHLcbEWof59Dw8ihKBMcmp-gBAibThtBFj55A,708
175
- unfold/widgets.py,sha256=e2yGwpyO7VT1NoSj0VzzRlmQZIzREAKOgp7CkyZ4hv0,14204
176
- django_unfold-0.23.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
177
- django_unfold-0.23.0.dist-info/METADATA,sha256=j8ueC2Ym74SeXEcdOxhx1KT4JfRLK7kO1pjj1rmlqdI,42827
178
- django_unfold-0.23.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
179
- django_unfold-0.23.0.dist-info/RECORD,,
177
+ unfold/widgets.py,sha256=JfcWOWtPPxqosXMyP-BcfO6UK53eoEU3obRpMZijED4,14216
178
+ django_unfold-0.25.0.dist-info/LICENSE.md,sha256=Ltk_quRyyvV3J5v3brtOqmibeZSw2Hrb8bY1W3ya0Ik,1077
179
+ django_unfold-0.25.0.dist-info/METADATA,sha256=6O8FhlWME8UWFxFe0HpOacXPUi0pIwfYLtMrvFIMRH4,46669
180
+ django_unfold-0.25.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
181
+ django_unfold-0.25.0.dist-info/RECORD,,
unfold/admin.py CHANGED
@@ -62,7 +62,7 @@ from .widgets import (
62
62
  UnfoldAdminMoneyWidget,
63
63
  UnfoldAdminNullBooleanSelectWidget,
64
64
  UnfoldAdminRadioSelectWidget,
65
- UnfoldAdminSelect,
65
+ UnfoldAdminSelectWidget,
66
66
  UnfoldAdminSingleDateWidget,
67
67
  UnfoldAdminSingleTimeWidget,
68
68
  UnfoldAdminSplitDateTimeWidget,
@@ -294,7 +294,7 @@ class ModelAdminMixin:
294
294
  radio_style=self.radio_fields[db_field.name]
295
295
  )
296
296
  else:
297
- kwargs["widget"] = UnfoldAdminSelect()
297
+ kwargs["widget"] = UnfoldAdminSelectWidget()
298
298
 
299
299
  kwargs["choices"] = db_field.get_choices(
300
300
  include_blank=db_field.blank, blank_choice=[("", _("Select value"))]
@@ -313,7 +313,7 @@ class ModelAdminMixin:
313
313
  db_field.name not in self.get_autocomplete_fields(request)
314
314
  and db_field.name not in self.radio_fields
315
315
  ):
316
- kwargs["widget"] = UnfoldAdminSelect()
316
+ kwargs["widget"] = UnfoldAdminSelectWidget()
317
317
  kwargs["empty_label"] = _("Select value")
318
318
 
319
319
  return super().formfield_for_foreignkey(db_field, request, **kwargs)
@@ -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
@@ -1,7 +1,35 @@
1
1
  from django import forms
2
2
  from django.utils.translation import gettext_lazy as _
3
3
 
4
- from ...widgets import INPUT_CLASSES, UnfoldAdminSplitDateTimeVerticalWidget
4
+ from ...widgets import (
5
+ INPUT_CLASSES,
6
+ UnfoldAdminSelectWidget,
7
+ UnfoldAdminSplitDateTimeVerticalWidget,
8
+ UnfoldAdminTextInputWidget,
9
+ )
10
+
11
+
12
+ class SearchForm(forms.Form):
13
+ def __init__(self, name, label, *args, **kwargs):
14
+ super().__init__(*args, **kwargs)
15
+
16
+ self.fields[name] = forms.CharField(
17
+ label=label,
18
+ required=False,
19
+ widget=UnfoldAdminTextInputWidget,
20
+ )
21
+
22
+
23
+ class DropdownForm(forms.Form):
24
+ def __init__(self, name, label, choices, *args, **kwargs):
25
+ super().__init__(*args, **kwargs)
26
+
27
+ self.fields[name] = forms.ChoiceField(
28
+ label=label,
29
+ required=False,
30
+ choices=choices,
31
+ widget=UnfoldAdminSelectWidget,
32
+ )
5
33
 
6
34
 
7
35
  class SingleNumericForm(forms.Form):
@@ -2,7 +2,7 @@
2
2
 
3
3
  {% with choices.0 as choice %}
4
4
  <div class="flex flex-col mb-6">
5
- <h3 class="font-medium mb-4 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
5
+ <h3 class="font-medium mb-2 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
6
6
 
7
7
  <div class="flex flex-col space-y-4">
8
8
  {% for field in choice.form %}
@@ -2,7 +2,7 @@
2
2
 
3
3
  {% with choices.0 as choice %}
4
4
  <div class="flex flex-col mb-6">
5
- <h3 class="font-medium mb-4 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
5
+ <h3 class="font-medium mb-2 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
6
6
 
7
7
  <div class="flex flex-col space-y-4">
8
8
  {% for field in choice.form %}
@@ -0,0 +1,7 @@
1
+ {% load i18n %}
2
+
3
+ {% with choices.0 as choice %}
4
+ {% for field in choice.form %}
5
+ {% include "unfold/helpers/field.html" %}
6
+ {% endfor %}
7
+ {% endwith %}
@@ -2,7 +2,7 @@
2
2
 
3
3
  {% with choices.0 as choice %}
4
4
  <div class="flex flex-col mb-6">
5
- <h3 class="font-medium mb-4 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
5
+ <h3 class="font-medium mb-2 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
6
6
 
7
7
  <div class="flex flex-row gap-4">
8
8
  {% for field in choice.form %}
@@ -2,7 +2,7 @@
2
2
 
3
3
  {% with choices.0 as choice %}
4
4
  <div class="flex flex-col mb-6">
5
- <h3 class="font-medium mb-4 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
5
+ <h3 class="font-medium mb-2 text-gray-700 text-sm dark:text-gray-200">{% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}</h3>
6
6
 
7
7
  {% for field in choice.form %}
8
8
  <div class="flex flex-row flex-wrap group relative{% if field.errors %} errors{% endif %}">
@@ -3,7 +3,7 @@
3
3
 
4
4
  {% with choices.0 as choice %}
5
5
  <div class="admin-numeric-filter-wrapper mb-6">
6
- <h3 class="font-medium mb-4 text-gray-700 text-sm dark:text-gray-200">
6
+ <h3 class="font-medium mb-2 text-gray-700 text-sm dark:text-gray-200">
7
7
  {% blocktrans with filter_title=title %}By {{ filter_title }}{% endblocktrans %}
8
8
  </h3>
9
9
 
@@ -7,7 +7,7 @@ from unfold.widgets import SELECT_CLASSES
7
7
 
8
8
  def export_action_form_factory(formats):
9
9
  class _ExportActionForm(ActionForm):
10
- file_format = forms.ChoiceField(
10
+ format = forms.ChoiceField(
11
11
  label=" ",
12
12
  choices=formats,
13
13
  required=False,
@@ -1,16 +1,21 @@
1
1
  from import_export.forms import ExportForm as BaseExportForm
2
2
  from import_export.forms import ImportForm as BaseImportForm
3
- from unfold.widgets import SELECT_CLASSES, UnfoldAdminFileFieldWidget
3
+ from unfold.widgets import (
4
+ SELECT_CLASSES,
5
+ UnfoldAdminFileFieldWidget,
6
+ )
4
7
 
5
8
 
6
9
  class ImportForm(BaseImportForm):
7
10
  def __init__(self, *args, **kwargs):
8
11
  super().__init__(*args, **kwargs)
12
+
13
+ self.fields["resource"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
9
14
  self.fields["import_file"].widget = UnfoldAdminFileFieldWidget()
10
- self.fields["input_format"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
15
+ self.fields["format"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
11
16
 
12
17
 
13
18
  class ExportForm(BaseExportForm):
14
19
  def __init__(self, *args, **kwargs):
15
20
  super().__init__(*args, **kwargs)
16
- self.fields["file_format"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
21
+ self.fields["format"].widget.attrs["class"] = " ".join(SELECT_CLASSES)
@@ -37,7 +37,7 @@
37
37
  {% csrf_token %}
38
38
 
39
39
  <fieldset class="border border-gray-200 mb-8 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800">
40
- {% include "unfold/helpers/field.html" with field=form.file_format %}
40
+ {% include "unfold/helpers/field.html" with field=form.format %}
41
41
  </fieldset>
42
42
 
43
43
  <button type="submit" class="bg-primary-600 border border-transparent font-medium px-3 py-2 rounded-md text-sm text-white">
@@ -4,27 +4,20 @@
4
4
  <form action="" method="post" enctype="multipart/form-data">
5
5
  {% csrf_token %}
6
6
 
7
- <p class="bg-blue-50 mb-8 text-blue-500 px-3 py-3 rounded-md text-sm dark:bg-blue-500/20 dark:border-blue-500/10">
8
- {% trans "This importer will import the following fields: " %}
7
+ {% include "admin/import_export/resource_fields_list.html" with import_or_export="import" %}
9
8
 
10
- {% if fields_list|length <= 1 %}
11
- <span class="font-medium">
12
- {{ fields_list.0.1|join:", " }}
13
- </code>
9
+ <fieldset class="border border-gray-200 mb-8 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800">
10
+ {% if form.resource.field.widget.attrs.readonly %}
11
+ {% include "unfold/helpers/field_readonly.html" with title=form.resource.field.label value=form.resource.field.value %}
12
+ {{ form.resource.as_hidden }}
14
13
  {% else %}
15
- <dl>
16
- {% for resource, fields in fields_list %}
17
- <dt>{{ resource }}</dt>
18
- <dd><code>{{ fields|join:", " }}</code></dd>
19
- {% endfor %}
20
- </dl>
14
+ {% include "unfold/helpers/field.html" with field=form.resource %}
21
15
  {% endif %}
22
- </p>
23
16
 
24
- <fieldset class="border border-gray-200 mb-8 rounded-md pt-3 px-3 shadow-sm dark:border-gray-800">
25
- {% include "unfold/helpers/field.html" with field=form.import_file %}
26
17
 
27
- {% include "unfold/helpers/field.html" with field=form.input_format %}
18
+ {% include "unfold/helpers/field.html" with field=form.import_file %}
19
+
20
+ {% include "unfold/helpers/field.html" with field=form.format %}
28
21
  </fieldset>
29
22
 
30
23
 
@@ -0,0 +1,24 @@
1
+ {% load i18n %}
2
+
3
+ {% block fields_help %}
4
+ <div class="bg-blue-50 mb-8 text-blue-500 px-3 py-3 rounded-md text-sm dark:bg-blue-500/20 dark:border-blue-500/10">
5
+ {% if import_or_export == "export" %}
6
+ {% trans "This exporter will export the following fields: " %}
7
+ {% elif import_or_export == "import" %}
8
+ {% trans "This importer will import the following fields: " %}
9
+ {% endif %}
10
+
11
+ {% if fields_list|length <= 1 %}
12
+ <code class="font-medium">
13
+ {{ fields_list.0.1|join:", " }}
14
+ </code>
15
+ {% else %}
16
+ <dl>
17
+ {% for resource, fields in fields_list %}
18
+ <dt>{{ resource }}</dt>
19
+ <dd><code>{{ fields|join:", " }}</code></dd>
20
+ {% endfor %}
21
+ </dl>
22
+ {% endif %}
23
+ </div>
24
+ {% endblock %}