django-unfold 0.29.1__tar.gz → 0.31.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {django_unfold-0.29.1 → django_unfold-0.31.0}/PKG-INFO +63 -19
- {django_unfold-0.29.1 → django_unfold-0.31.0}/README.md +62 -18
- {django_unfold-0.29.1 → django_unfold-0.31.0}/pyproject.toml +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/admin.py +32 -11
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +3 -3
- django_unfold-0.31.0/src/unfold/contrib/forms/static/unfold/forms/js/trix.js +5 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/templates/unfold/forms/array.html +3 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html +6 -6
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/templates/unfold/forms/wysiwyg.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/widgets.py +22 -11
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/templates/unfold/guardian/group_form.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/templates/unfold/guardian/user_form.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_form.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_errors.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_preview.html +3 -3
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_validation.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/inlines/forms.py +1 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history_list.html +9 -9
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/simple_history/templates/simple_history/submit_line.html +1 -1
- django_unfold-0.31.0/src/unfold/dataclasses.py +22 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/fields.py +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/forms.py +18 -3
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/settings.py +1 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/sites.py +39 -15
- django_unfold-0.31.0/src/unfold/static/unfold/css/styles.css +1 -0
- django_unfold-0.31.0/src/unfold/static/unfold/js/alpine.anchor.js +1 -0
- django_unfold-0.31.0/src/unfold/static/unfold/js/alpine.js +5 -0
- django_unfold-0.31.0/src/unfold/static/unfold/js/alpine.persist.js +1 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/js/app.js +45 -3
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/styles.css +15 -11
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/actions.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/app_list.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/base.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/change_list.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/change_list_results.html +3 -3
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/delete_confirmation.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/delete_selected_confirmation.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/edit_inline/stacked.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/edit_inline/tabular.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/filter.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/includes/fieldset.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/includes/object_delete_summary.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/login.html +8 -8
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/object_history.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/search_form.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/submit_line.html +7 -5
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/auth/widgets/read_only_password_hash.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/registration/logged_out.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/change_list_filter.html +10 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/account_links.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/actions_row.html +4 -4
- django_unfold-0.31.0/src/unfold/templates/unfold/helpers/app_list.html +99 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/app_list_default.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/breadcrumb_item.html +1 -1
- django_unfold-0.31.0/src/unfold/templates/unfold/helpers/field_readonly_value.html +1 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/fieldset_row.html +7 -7
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/fieldsets_tabs.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/header.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/help_text.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/history.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/label.html +1 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/search.html +7 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/search_results.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/tab_action.html +1 -1
- django_unfold-0.31.0/src/unfold/templates/unfold/helpers/tab_list.html +62 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/theme_switch.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/layouts/skeleton.html +6 -1
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/clearable_file_input.html +14 -6
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/clearable_file_input_small.html +4 -4
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/split_datetime.html +2 -2
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templatetags/unfold.py +33 -12
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templatetags/unfold_list.py +16 -6
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/widgets.py +11 -4
- django_unfold-0.29.1/src/unfold/contrib/forms/static/unfold/forms/js/trix.js +0 -5
- django_unfold-0.29.1/src/unfold/dataclasses.py +0 -13
- django_unfold-0.29.1/src/unfold/static/unfold/css/styles.css +0 -1
- django_unfold-0.29.1/src/unfold/static/unfold/js/alpine.js +0 -5
- django_unfold-0.29.1/src/unfold/static/unfold/js/alpine.persist.js +0 -1
- django_unfold-0.29.1/src/unfold/templates/unfold/helpers/app_list.html +0 -89
- django_unfold-0.29.1/src/unfold/templates/unfold/helpers/field_readonly_value.html +0 -1
- django_unfold-0.29.1/src/unfold/templates/unfold/helpers/tab_list.html +0 -40
- {django_unfold-0.29.1 → django_unfold-0.31.0}/LICENSE.md +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/apps.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/checks.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/admin.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/apps.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/forms.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/static/unfold/filters/css/nouislider.min.css +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/static/unfold/filters/js/DateTimeShortcuts.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/static/unfold/filters/js/admin-numeric-filter.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/static/unfold/filters/js/nouislider.min.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/static/unfold/filters/js/wNumb.min.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_field.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/apps.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/static/unfold/forms/css/trix.css +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/forms/static/unfold/forms/js/trix.config.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/apps.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/change_form.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/field.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/apps.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/forms.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/base.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_export.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_export_item.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_import_export.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_import_item.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/export.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/import.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_confirm.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_form.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/inlines/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/inlines/admin.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/inlines/apps.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/inlines/checks.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/simple_history/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/simple_history/apps.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history_form.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/decorators.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/exceptions.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/css/simplebar.css +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/fonts/inter/Inter-Bold.woff2 +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/fonts/inter/Inter-Medium.woff2 +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/fonts/inter/Inter-Regular.woff2 +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/fonts/inter/Inter-SemiBold.woff2 +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/fonts/inter/styles.css +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/fonts/material-symbols/styles.css +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/js/chart.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/js/htmx.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/static/unfold/js/simplebar.js +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/app_index.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/auth/user/add_form.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/auth/user/change_password.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/base_site.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/change_form.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/change_form_object_tools.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/change_list_object_tools.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/date_hierarchy.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/index.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/nav_sidebar.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/admin/pagination.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/registration/password_change_done.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/registration/password_change_form.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/button.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/card.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/chart/bar.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/chart/line.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/container.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/flex.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/navigation.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/progress.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/separator.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/text.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/components/title.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/add_link.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/attrs.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/boolean.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/display_header.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/display_label.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/field.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/field_readonly.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/form_errors.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/form_label.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/messages/error.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/messages/errornote.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/messages/info.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/messages.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/navigation.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/pagination_current_item.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/pagination_ellipsis.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/site_icon.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/site_logo.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/submit.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/userlinks.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/helpers/welcomemsg.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/layouts/base.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/layouts/base_simple.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/date.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/foreign_key_raw_id.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/radio.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/radio_option.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/range.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/related_widget_wrapper.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/split_datetime_vertical.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/split_money.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/textarea.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/textarea_expandable.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templates/unfold/widgets/time.html +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/templatetags/__init__.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/typing.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/utils.py +0 -0
- {django_unfold-0.29.1 → django_unfold-0.31.0}/src/unfold/views.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: django-unfold
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.31.0
|
4
4
|
Summary: Modern Django admin theme for seamless interface development
|
5
5
|
Home-page: https://unfoldadmin.com
|
6
6
|
License: MIT
|
@@ -33,7 +33,7 @@ Description-Content-Type: text/markdown
|
|
33
33
|

|
34
34
|

|
35
35
|
|
36
|
-
Unfold is theme for Django admin incorporating most common
|
36
|
+
Unfold is a theme for Django admin incorporating most common practices for building full-fledged admin areas. It is designed to work on top of default administration provided by Django.
|
37
37
|
|
38
38
|
- **Unfold:** demo site is available at [unfoldadmin.com](https://unfoldadmin.com?utm_medium=github&utm_source=unfold)
|
39
39
|
- **Formula:** repository with demo implementation at [github.com/unfoldadmin/formula](https://github.com/unfoldadmin/formula)
|
@@ -45,7 +45,7 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
45
45
|
|
46
46
|
## Features <!-- omit from toc -->
|
47
47
|
|
48
|
-
- **Visual**: provides new user interface based on Tailwind CSS framework
|
48
|
+
- **Visual**: provides a new user interface based on Tailwind CSS framework
|
49
49
|
- **Sidebar:** simplifies definition of custom sidebar navigation with icons
|
50
50
|
- **Dark mode:** supports both light and dark mode versions
|
51
51
|
- **Configuration:** most of the basic options can be changed in settings.py
|
@@ -55,14 +55,16 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
55
55
|
- **Array widget:** built-in widget for `django.contrib.postgres.fields.ArrayField`
|
56
56
|
- **Filters:** custom dropdown, numeric, datetime, and text fields
|
57
57
|
- **Dashboard:** custom components for rapid dashboard development
|
58
|
+
- **Inline tabs:** group inlines into tab navigation in the change form
|
58
59
|
- **Model tabs:** define custom tab navigations for models
|
59
|
-
- **Fieldset tabs:** merge several
|
60
|
-
- **Colors:** possibility to override default color scheme
|
61
|
-
- **Changeform modes:** display fields in
|
60
|
+
- **Fieldset tabs:** merge several fieldsets into tabs in the change form
|
61
|
+
- **Colors:** possibility to override the default color scheme
|
62
|
+
- **Changeform modes:** display fields in the change form in compressed mode
|
62
63
|
- **Third party packages:** default support for multiple popular applications
|
63
64
|
- **Environment label**: distinguish between environments by displaying a label
|
64
65
|
- **Nonrelated inlines**: displays nonrelated model as inline in changeform
|
65
66
|
- **Parallel admin**: support for default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/?utm_medium=github&utm_source=unfold)
|
67
|
+
- **Favicons**: built-in support for configuring various site favicons
|
66
68
|
- **VS Code**: project configuration and development container is included
|
67
69
|
|
68
70
|
## Table of contents <!-- omit from toc -->
|
@@ -111,7 +113,7 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
111
113
|
|
112
114
|
## Installation
|
113
115
|
|
114
|
-
The installation process is minimal. Everything
|
116
|
+
The installation process is minimal. Everything that is needed after installation is to put new application at the beginning of **INSTALLED_APPS**. The default admin configuration in urls.py can stay as it is, and no changes are required.
|
115
117
|
|
116
118
|
```python
|
117
119
|
# settings.py
|
@@ -170,17 +172,24 @@ class CustomAdminClass(ModelAdmin):
|
|
170
172
|
|
171
173
|
from django.contrib import admin
|
172
174
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
175
|
+
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
|
173
176
|
from django.contrib.auth.models import User
|
174
177
|
|
175
178
|
from unfold.admin import ModelAdmin
|
176
179
|
|
177
180
|
|
178
181
|
admin.site.unregister(User)
|
182
|
+
admin.site.unregister(Group)
|
179
183
|
|
180
184
|
|
181
185
|
@admin.register(User)
|
182
186
|
class UserAdmin(BaseUserAdmin, ModelAdmin):
|
183
187
|
pass
|
188
|
+
|
189
|
+
|
190
|
+
@admin.register(Group)
|
191
|
+
class GroupAdmin(BaseGroupAdmin, ModelAdmin):
|
192
|
+
pass
|
184
193
|
```
|
185
194
|
|
186
195
|
## Configuration
|
@@ -209,6 +218,14 @@ UNFOLD = {
|
|
209
218
|
"dark": lambda request: static("logo-dark.svg"), # dark mode
|
210
219
|
},
|
211
220
|
"SITE_SYMBOL": "speed", # symbol from icon set
|
221
|
+
"SITE_FAVICONS": [
|
222
|
+
{
|
223
|
+
"rel": "icon",
|
224
|
+
"sizes": "32x32",
|
225
|
+
"type": "image/svg+xml",
|
226
|
+
"href": lambda request: static("favicon.svg"),
|
227
|
+
},
|
228
|
+
],
|
212
229
|
"SHOW_HISTORY": True, # show/hide "History" button, default: True
|
213
230
|
"SHOW_VIEW_ON_SITE": True, # show/hide "View on site" button, default: True
|
214
231
|
"ENVIRONMENT": "sample_app.environment_callback",
|
@@ -255,6 +272,7 @@ UNFOLD = {
|
|
255
272
|
{
|
256
273
|
"title": _("Navigation"),
|
257
274
|
"separator": True, # Top border
|
275
|
+
"collapsible": True, # Collapsible group of links
|
258
276
|
"items": [
|
259
277
|
{
|
260
278
|
"title": _("Dashboard"),
|
@@ -345,6 +363,12 @@ class CustomAdminClass(ModelAdmin):
|
|
345
363
|
# Display submit button in filters
|
346
364
|
list_filter_submit = False
|
347
365
|
|
366
|
+
# Display changelist in fullwidth
|
367
|
+
list_fullwidth = False
|
368
|
+
|
369
|
+
# Position horizontal scrollbar in changelist at the top
|
370
|
+
list_horizontal_scrollbar_top = False
|
371
|
+
|
348
372
|
# Custom actions
|
349
373
|
actions_list = [] # Displayed above the results list
|
350
374
|
actions_row = [] # Displayed in a table row in results list
|
@@ -438,7 +462,7 @@ class UserAdmin(ModelAdmin):
|
|
438
462
|
actions_detail = ["change_detail_action_block"]
|
439
463
|
actions_submit_line = ["submit_line_action_activate"]
|
440
464
|
|
441
|
-
@action(description=_("Save & Activate"))
|
465
|
+
@action(description=_("Save & Activate"), permissions=["submit_line_action_activate"])
|
442
466
|
def submit_line_action_activate(self, request: HttpRequest, obj: User):
|
443
467
|
"""
|
444
468
|
If instance is modified in any way, it also needs to be saved,
|
@@ -450,6 +474,9 @@ class UserAdmin(ModelAdmin):
|
|
450
474
|
obj.is_active = True
|
451
475
|
obj.save()
|
452
476
|
|
477
|
+
def has_submit_line_action_activate_permission(self, request: HttpRequest, object_id: Union[str, int]):
|
478
|
+
pass
|
479
|
+
|
453
480
|
@action(description=_("Import"), url_path="import")
|
454
481
|
def changelist_global_action_import(self, request: HttpRequest):
|
455
482
|
"""
|
@@ -474,7 +501,7 @@ class UserAdmin(ModelAdmin):
|
|
474
501
|
"""
|
475
502
|
return redirect(f"https://example.com/{object_id}")
|
476
503
|
|
477
|
-
@action(description=_("Detail"), url_path="detail-action", attrs={"target": "_blank"})
|
504
|
+
@action(description=_("Detail"), url_path="detail-action", attrs={"target": "_blank"}, permissions=["change_detail_action_block"])
|
478
505
|
def change_detail_action_block(self, request: HttpRequest, object_id: int):
|
479
506
|
"""
|
480
507
|
Handler for detail action.
|
@@ -489,6 +516,10 @@ class UserAdmin(ModelAdmin):
|
|
489
516
|
return redirect(
|
490
517
|
reverse_lazy("admin:users_user_change", args=(object_id,))
|
491
518
|
)
|
519
|
+
|
520
|
+
|
521
|
+
def has_change_detail_action_block_permission(self, request: HttpRequest, object_id: Union[str, int]):
|
522
|
+
pass
|
492
523
|
```
|
493
524
|
|
494
525
|
### Action with form example
|
@@ -511,10 +542,10 @@ class SomeForm(forms.Form):
|
|
511
542
|
@register(User)
|
512
543
|
class UserAdmin(ModelAdmin):
|
513
544
|
actions_detail = ["change_detail_action_block"]
|
514
|
-
form = SomeForm(request.POST or None)
|
515
545
|
|
516
546
|
@action(description=_("Detail"))
|
517
547
|
def change_detail_action_block(self, request: HttpRequest, object_id: int) -> str:
|
548
|
+
form = SomeForm(request.POST or None)
|
518
549
|
user = User.objects.get(pk=object_id)
|
519
550
|
|
520
551
|
if request.method == "POST" and form.is_valid():
|
@@ -560,7 +591,7 @@ Text input field which allows filtering by the free string submitted by the user
|
|
560
591
|
|
561
592
|
`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)`.
|
562
593
|
|
563
|
-
In the case of the `TextFilter`, it is needed
|
594
|
+
In the case of the `TextFilter`, it is needed to 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.
|
564
595
|
|
565
596
|
```python
|
566
597
|
from django.contrib import admin
|
@@ -595,7 +626,7 @@ class MyAdmin(ModelAdmin):
|
|
595
626
|
|
596
627
|
Dropdown filters will display a select field with a list of options. Unfold contains two types of dropdowns: `ChoicesDropdownFilter` and `RelatedDropdownFilter`.
|
597
628
|
|
598
|
-
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
|
629
|
+
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 hand, `RelatedDropdownFilter` needs a one-to-many or many-to-many foreign key to display options.
|
599
630
|
|
600
631
|
**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.
|
601
632
|
|
@@ -904,6 +935,19 @@ class MyModelAdmin(ModelAdmin):
|
|
904
935
|
)
|
905
936
|
```
|
906
937
|
|
938
|
+
Inlines can be grouped into tab navigation by specifying `tab` attribute in the inline class.
|
939
|
+
|
940
|
+
```python
|
941
|
+
# admin.py
|
942
|
+
|
943
|
+
from unfold.admin import TabularInline
|
944
|
+
|
945
|
+
|
946
|
+
class MyInline(TabularInline):
|
947
|
+
model = User
|
948
|
+
tab = True
|
949
|
+
```
|
950
|
+
|
907
951
|
## Third party packages
|
908
952
|
|
909
953
|
### django-celery-beat
|
@@ -973,11 +1017,11 @@ class ClockedScheduleAdmin(BaseClockedScheduleAdmin, ModelAdmin):
|
|
973
1017
|
|
974
1018
|
### django-guardian
|
975
1019
|
|
976
|
-
Adding support for django-guardian is
|
1020
|
+
Adding support for django-guardian is quite straightforward in Unfold, just add `unfold.contrib.guardian` to `INSTALLED_APPS` at the beginning of the file. This action will override all templates coming from the django-guardian. Please note that **Object permissions** link is available in top right dropdown navigation.
|
977
1021
|
|
978
1022
|
### django-import-export
|
979
1023
|
|
980
|
-
1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the
|
1024
|
+
1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the beginning of the file. This action will override all templates coming from the application.
|
981
1025
|
2. Change `import_form_class` and `export_form_class` in ModelAdmin which is inheriting from `ImportExportModelAdmin`. This chunk of code is responsible for adding proper styling to form elements.
|
982
1026
|
|
983
1027
|
```python
|
@@ -1049,7 +1093,7 @@ This application is supported in Unfold by default. It is not needed to add any
|
|
1049
1093
|
|
1050
1094
|
### django-simple-history
|
1051
1095
|
|
1052
|
-
To make this application work, add `unfold.contrib.simple_history` into `settings.py` in `INSTALLED_APPS` variable before right after `unfold`. This app should ensure that templates coming from django-simple-history are
|
1096
|
+
To make this application work, add `unfold.contrib.simple_history` into `settings.py` in `INSTALLED_APPS` variable before right after `unfold`. This app should ensure that templates coming from django-simple-history are overridden by Unfold.
|
1053
1097
|
|
1054
1098
|
## User Admin Form
|
1055
1099
|
|
@@ -1130,7 +1174,7 @@ npx tailwindcss -o your_project/static/css/styles.css --watch --minify
|
|
1130
1174
|
|
1131
1175
|
### Overriding template
|
1132
1176
|
|
1133
|
-
Create `templates/admin/index.html` in your project and paste the base template below into it. By default, all your custom styles here are not compiled because CSS classes are located in your specific project. Here it is needed to set up the Tailwind for your project and all
|
1177
|
+
Create `templates/admin/index.html` in your project and paste the base template below into it. By default, all your custom styles here are not compiled because CSS classes are located in your specific project. Here it is needed to set up the Tailwind for your project and all required instructions are located in [Project Level Tailwind Stylesheet](#project-level-tailwind-stylesheet) chapter.
|
1134
1178
|
|
1135
1179
|
```html+django
|
1136
1180
|
{% extends 'unfold/layouts/base_simple.html' %}
|
@@ -1252,7 +1296,7 @@ Below you can find a more complex example which is using multiple components and
|
|
1252
1296
|
|
1253
1297
|
### Pre-commit
|
1254
1298
|
|
1255
|
-
Before adding any source code, it is recommended to have pre-commit installed on your local computer to check for all potential issues when
|
1299
|
+
Before adding any source code, it is recommended to have pre-commit installed on your local computer to check for all potential issues when committing the code.
|
1256
1300
|
|
1257
1301
|
```bash
|
1258
1302
|
pip install pre-commit
|
@@ -1272,7 +1316,7 @@ To add a new feature or fix the easiest approach is to use django-unfold in comb
|
|
1272
1316
|
|
1273
1317
|
### Compiling Tailwind
|
1274
1318
|
|
1275
|
-
At the moment project contains package.json with all dependencies required to compile new CSS file. Tailwind configuration file is set to check all html, js and py files for Tailwind's
|
1319
|
+
At the moment project contains package.json with all dependencies required to compile new CSS file. Tailwind configuration file is set to check all html, js and py files for Tailwind's classes occurrences.
|
1276
1320
|
|
1277
1321
|
```bash
|
1278
1322
|
npm install
|
@@ -1282,7 +1326,7 @@ npm run tailwind:watch # run after each change in code
|
|
1282
1326
|
npm run tailwind:build # run once
|
1283
1327
|
```
|
1284
1328
|
|
1285
|
-
Some components like datepickers, calendars or selectors in admin was not possible to style by overriding html templates so their default styles are
|
1329
|
+
Some components like datepickers, calendars or selectors in admin was not possible to style by overriding html templates so their default styles are overridden in **styles.css**.
|
1286
1330
|
|
1287
1331
|
**Note:** most of the custom styles located in style.css are created via `@apply some-tailwind-class;` as is not possible to manually add CSS class to element which are for example created via jQuery.
|
1288
1332
|
|
@@ -9,7 +9,7 @@
|
|
9
9
|

|
10
10
|

|
11
11
|
|
12
|
-
Unfold is theme for Django admin incorporating most common
|
12
|
+
Unfold is a theme for Django admin incorporating most common practices for building full-fledged admin areas. It is designed to work on top of default administration provided by Django.
|
13
13
|
|
14
14
|
- **Unfold:** demo site is available at [unfoldadmin.com](https://unfoldadmin.com?utm_medium=github&utm_source=unfold)
|
15
15
|
- **Formula:** repository with demo implementation at [github.com/unfoldadmin/formula](https://github.com/unfoldadmin/formula)
|
@@ -21,7 +21,7 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
21
21
|
|
22
22
|
## Features <!-- omit from toc -->
|
23
23
|
|
24
|
-
- **Visual**: provides new user interface based on Tailwind CSS framework
|
24
|
+
- **Visual**: provides a new user interface based on Tailwind CSS framework
|
25
25
|
- **Sidebar:** simplifies definition of custom sidebar navigation with icons
|
26
26
|
- **Dark mode:** supports both light and dark mode versions
|
27
27
|
- **Configuration:** most of the basic options can be changed in settings.py
|
@@ -31,14 +31,16 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
31
31
|
- **Array widget:** built-in widget for `django.contrib.postgres.fields.ArrayField`
|
32
32
|
- **Filters:** custom dropdown, numeric, datetime, and text fields
|
33
33
|
- **Dashboard:** custom components for rapid dashboard development
|
34
|
+
- **Inline tabs:** group inlines into tab navigation in the change form
|
34
35
|
- **Model tabs:** define custom tab navigations for models
|
35
|
-
- **Fieldset tabs:** merge several
|
36
|
-
- **Colors:** possibility to override default color scheme
|
37
|
-
- **Changeform modes:** display fields in
|
36
|
+
- **Fieldset tabs:** merge several fieldsets into tabs in the change form
|
37
|
+
- **Colors:** possibility to override the default color scheme
|
38
|
+
- **Changeform modes:** display fields in the change form in compressed mode
|
38
39
|
- **Third party packages:** default support for multiple popular applications
|
39
40
|
- **Environment label**: distinguish between environments by displaying a label
|
40
41
|
- **Nonrelated inlines**: displays nonrelated model as inline in changeform
|
41
42
|
- **Parallel admin**: support for default admin in parallel with Unfold. [Admin migration guide](https://unfoldadmin.com/blog/migrating-django-admin-unfold/?utm_medium=github&utm_source=unfold)
|
43
|
+
- **Favicons**: built-in support for configuring various site favicons
|
42
44
|
- **VS Code**: project configuration and development container is included
|
43
45
|
|
44
46
|
## Table of contents <!-- omit from toc -->
|
@@ -87,7 +89,7 @@ Did you decide to start using Unfold but you don't have time to make the switch
|
|
87
89
|
|
88
90
|
## Installation
|
89
91
|
|
90
|
-
The installation process is minimal. Everything
|
92
|
+
The installation process is minimal. Everything that is needed after installation is to put new application at the beginning of **INSTALLED_APPS**. The default admin configuration in urls.py can stay as it is, and no changes are required.
|
91
93
|
|
92
94
|
```python
|
93
95
|
# settings.py
|
@@ -146,17 +148,24 @@ class CustomAdminClass(ModelAdmin):
|
|
146
148
|
|
147
149
|
from django.contrib import admin
|
148
150
|
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
151
|
+
from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin
|
149
152
|
from django.contrib.auth.models import User
|
150
153
|
|
151
154
|
from unfold.admin import ModelAdmin
|
152
155
|
|
153
156
|
|
154
157
|
admin.site.unregister(User)
|
158
|
+
admin.site.unregister(Group)
|
155
159
|
|
156
160
|
|
157
161
|
@admin.register(User)
|
158
162
|
class UserAdmin(BaseUserAdmin, ModelAdmin):
|
159
163
|
pass
|
164
|
+
|
165
|
+
|
166
|
+
@admin.register(Group)
|
167
|
+
class GroupAdmin(BaseGroupAdmin, ModelAdmin):
|
168
|
+
pass
|
160
169
|
```
|
161
170
|
|
162
171
|
## Configuration
|
@@ -185,6 +194,14 @@ UNFOLD = {
|
|
185
194
|
"dark": lambda request: static("logo-dark.svg"), # dark mode
|
186
195
|
},
|
187
196
|
"SITE_SYMBOL": "speed", # symbol from icon set
|
197
|
+
"SITE_FAVICONS": [
|
198
|
+
{
|
199
|
+
"rel": "icon",
|
200
|
+
"sizes": "32x32",
|
201
|
+
"type": "image/svg+xml",
|
202
|
+
"href": lambda request: static("favicon.svg"),
|
203
|
+
},
|
204
|
+
],
|
188
205
|
"SHOW_HISTORY": True, # show/hide "History" button, default: True
|
189
206
|
"SHOW_VIEW_ON_SITE": True, # show/hide "View on site" button, default: True
|
190
207
|
"ENVIRONMENT": "sample_app.environment_callback",
|
@@ -231,6 +248,7 @@ UNFOLD = {
|
|
231
248
|
{
|
232
249
|
"title": _("Navigation"),
|
233
250
|
"separator": True, # Top border
|
251
|
+
"collapsible": True, # Collapsible group of links
|
234
252
|
"items": [
|
235
253
|
{
|
236
254
|
"title": _("Dashboard"),
|
@@ -321,6 +339,12 @@ class CustomAdminClass(ModelAdmin):
|
|
321
339
|
# Display submit button in filters
|
322
340
|
list_filter_submit = False
|
323
341
|
|
342
|
+
# Display changelist in fullwidth
|
343
|
+
list_fullwidth = False
|
344
|
+
|
345
|
+
# Position horizontal scrollbar in changelist at the top
|
346
|
+
list_horizontal_scrollbar_top = False
|
347
|
+
|
324
348
|
# Custom actions
|
325
349
|
actions_list = [] # Displayed above the results list
|
326
350
|
actions_row = [] # Displayed in a table row in results list
|
@@ -414,7 +438,7 @@ class UserAdmin(ModelAdmin):
|
|
414
438
|
actions_detail = ["change_detail_action_block"]
|
415
439
|
actions_submit_line = ["submit_line_action_activate"]
|
416
440
|
|
417
|
-
@action(description=_("Save & Activate"))
|
441
|
+
@action(description=_("Save & Activate"), permissions=["submit_line_action_activate"])
|
418
442
|
def submit_line_action_activate(self, request: HttpRequest, obj: User):
|
419
443
|
"""
|
420
444
|
If instance is modified in any way, it also needs to be saved,
|
@@ -426,6 +450,9 @@ class UserAdmin(ModelAdmin):
|
|
426
450
|
obj.is_active = True
|
427
451
|
obj.save()
|
428
452
|
|
453
|
+
def has_submit_line_action_activate_permission(self, request: HttpRequest, object_id: Union[str, int]):
|
454
|
+
pass
|
455
|
+
|
429
456
|
@action(description=_("Import"), url_path="import")
|
430
457
|
def changelist_global_action_import(self, request: HttpRequest):
|
431
458
|
"""
|
@@ -450,7 +477,7 @@ class UserAdmin(ModelAdmin):
|
|
450
477
|
"""
|
451
478
|
return redirect(f"https://example.com/{object_id}")
|
452
479
|
|
453
|
-
@action(description=_("Detail"), url_path="detail-action", attrs={"target": "_blank"})
|
480
|
+
@action(description=_("Detail"), url_path="detail-action", attrs={"target": "_blank"}, permissions=["change_detail_action_block"])
|
454
481
|
def change_detail_action_block(self, request: HttpRequest, object_id: int):
|
455
482
|
"""
|
456
483
|
Handler for detail action.
|
@@ -465,6 +492,10 @@ class UserAdmin(ModelAdmin):
|
|
465
492
|
return redirect(
|
466
493
|
reverse_lazy("admin:users_user_change", args=(object_id,))
|
467
494
|
)
|
495
|
+
|
496
|
+
|
497
|
+
def has_change_detail_action_block_permission(self, request: HttpRequest, object_id: Union[str, int]):
|
498
|
+
pass
|
468
499
|
```
|
469
500
|
|
470
501
|
### Action with form example
|
@@ -487,10 +518,10 @@ class SomeForm(forms.Form):
|
|
487
518
|
@register(User)
|
488
519
|
class UserAdmin(ModelAdmin):
|
489
520
|
actions_detail = ["change_detail_action_block"]
|
490
|
-
form = SomeForm(request.POST or None)
|
491
521
|
|
492
522
|
@action(description=_("Detail"))
|
493
523
|
def change_detail_action_block(self, request: HttpRequest, object_id: int) -> str:
|
524
|
+
form = SomeForm(request.POST or None)
|
494
525
|
user = User.objects.get(pk=object_id)
|
495
526
|
|
496
527
|
if request.method == "POST" and form.is_valid():
|
@@ -536,7 +567,7 @@ Text input field which allows filtering by the free string submitted by the user
|
|
536
567
|
|
537
568
|
`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)`.
|
538
569
|
|
539
|
-
In the case of the `TextFilter`, it is needed
|
570
|
+
In the case of the `TextFilter`, it is needed to 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.
|
540
571
|
|
541
572
|
```python
|
542
573
|
from django.contrib import admin
|
@@ -571,7 +602,7 @@ class MyAdmin(ModelAdmin):
|
|
571
602
|
|
572
603
|
Dropdown filters will display a select field with a list of options. Unfold contains two types of dropdowns: `ChoicesDropdownFilter` and `RelatedDropdownFilter`.
|
573
604
|
|
574
|
-
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
|
605
|
+
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 hand, `RelatedDropdownFilter` needs a one-to-many or many-to-many foreign key to display options.
|
575
606
|
|
576
607
|
**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.
|
577
608
|
|
@@ -880,6 +911,19 @@ class MyModelAdmin(ModelAdmin):
|
|
880
911
|
)
|
881
912
|
```
|
882
913
|
|
914
|
+
Inlines can be grouped into tab navigation by specifying `tab` attribute in the inline class.
|
915
|
+
|
916
|
+
```python
|
917
|
+
# admin.py
|
918
|
+
|
919
|
+
from unfold.admin import TabularInline
|
920
|
+
|
921
|
+
|
922
|
+
class MyInline(TabularInline):
|
923
|
+
model = User
|
924
|
+
tab = True
|
925
|
+
```
|
926
|
+
|
883
927
|
## Third party packages
|
884
928
|
|
885
929
|
### django-celery-beat
|
@@ -949,11 +993,11 @@ class ClockedScheduleAdmin(BaseClockedScheduleAdmin, ModelAdmin):
|
|
949
993
|
|
950
994
|
### django-guardian
|
951
995
|
|
952
|
-
Adding support for django-guardian is
|
996
|
+
Adding support for django-guardian is quite straightforward in Unfold, just add `unfold.contrib.guardian` to `INSTALLED_APPS` at the beginning of the file. This action will override all templates coming from the django-guardian. Please note that **Object permissions** link is available in top right dropdown navigation.
|
953
997
|
|
954
998
|
### django-import-export
|
955
999
|
|
956
|
-
1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the
|
1000
|
+
1. Add `unfold.contrib.import_export` to `INSTALLED_APPS` at the beginning of the file. This action will override all templates coming from the application.
|
957
1001
|
2. Change `import_form_class` and `export_form_class` in ModelAdmin which is inheriting from `ImportExportModelAdmin`. This chunk of code is responsible for adding proper styling to form elements.
|
958
1002
|
|
959
1003
|
```python
|
@@ -1025,7 +1069,7 @@ This application is supported in Unfold by default. It is not needed to add any
|
|
1025
1069
|
|
1026
1070
|
### django-simple-history
|
1027
1071
|
|
1028
|
-
To make this application work, add `unfold.contrib.simple_history` into `settings.py` in `INSTALLED_APPS` variable before right after `unfold`. This app should ensure that templates coming from django-simple-history are
|
1072
|
+
To make this application work, add `unfold.contrib.simple_history` into `settings.py` in `INSTALLED_APPS` variable before right after `unfold`. This app should ensure that templates coming from django-simple-history are overridden by Unfold.
|
1029
1073
|
|
1030
1074
|
## User Admin Form
|
1031
1075
|
|
@@ -1106,7 +1150,7 @@ npx tailwindcss -o your_project/static/css/styles.css --watch --minify
|
|
1106
1150
|
|
1107
1151
|
### Overriding template
|
1108
1152
|
|
1109
|
-
Create `templates/admin/index.html` in your project and paste the base template below into it. By default, all your custom styles here are not compiled because CSS classes are located in your specific project. Here it is needed to set up the Tailwind for your project and all
|
1153
|
+
Create `templates/admin/index.html` in your project and paste the base template below into it. By default, all your custom styles here are not compiled because CSS classes are located in your specific project. Here it is needed to set up the Tailwind for your project and all required instructions are located in [Project Level Tailwind Stylesheet](#project-level-tailwind-stylesheet) chapter.
|
1110
1154
|
|
1111
1155
|
```html+django
|
1112
1156
|
{% extends 'unfold/layouts/base_simple.html' %}
|
@@ -1228,7 +1272,7 @@ Below you can find a more complex example which is using multiple components and
|
|
1228
1272
|
|
1229
1273
|
### Pre-commit
|
1230
1274
|
|
1231
|
-
Before adding any source code, it is recommended to have pre-commit installed on your local computer to check for all potential issues when
|
1275
|
+
Before adding any source code, it is recommended to have pre-commit installed on your local computer to check for all potential issues when committing the code.
|
1232
1276
|
|
1233
1277
|
```bash
|
1234
1278
|
pip install pre-commit
|
@@ -1248,7 +1292,7 @@ To add a new feature or fix the easiest approach is to use django-unfold in comb
|
|
1248
1292
|
|
1249
1293
|
### Compiling Tailwind
|
1250
1294
|
|
1251
|
-
At the moment project contains package.json with all dependencies required to compile new CSS file. Tailwind configuration file is set to check all html, js and py files for Tailwind's
|
1295
|
+
At the moment project contains package.json with all dependencies required to compile new CSS file. Tailwind configuration file is set to check all html, js and py files for Tailwind's classes occurrences.
|
1252
1296
|
|
1253
1297
|
```bash
|
1254
1298
|
npm install
|
@@ -1258,7 +1302,7 @@ npm run tailwind:watch # run after each change in code
|
|
1258
1302
|
npm run tailwind:build # run once
|
1259
1303
|
```
|
1260
1304
|
|
1261
|
-
Some components like datepickers, calendars or selectors in admin was not possible to style by overriding html templates so their default styles are
|
1305
|
+
Some components like datepickers, calendars or selectors in admin was not possible to style by overriding html templates so their default styles are overridden in **styles.css**.
|
1262
1306
|
|
1263
1307
|
**Note:** most of the custom styles located in style.css are created via `@apply some-tailwind-class;` as is not possible to manually add CSS class to element which are for example created via jQuery.
|
1264
1308
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import copy
|
2
2
|
from functools import update_wrapper
|
3
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
4
4
|
|
5
5
|
from django import forms
|
6
6
|
from django.contrib.admin import ModelAdmin as BaseModelAdmin
|
@@ -148,6 +148,7 @@ class ModelAdminMixin:
|
|
148
148
|
else:
|
149
149
|
kwargs["widget"] = UnfoldAdminSelectWidget()
|
150
150
|
|
151
|
+
if "choices" not in kwargs:
|
151
152
|
kwargs["choices"] = db_field.get_choices(
|
152
153
|
include_blank=db_field.blank, blank_choice=[("", _("Select value"))]
|
153
154
|
)
|
@@ -227,7 +228,10 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
227
228
|
actions_submit_line = ()
|
228
229
|
custom_urls = ()
|
229
230
|
add_fieldsets = ()
|
231
|
+
list_horizontal_scrollbar_top = False
|
230
232
|
list_filter_submit = False
|
233
|
+
list_fullwidth = False
|
234
|
+
compressed_fields = False
|
231
235
|
readonly_preprocess_fields = {}
|
232
236
|
checks_class = UnfoldModelAdminChecks
|
233
237
|
|
@@ -253,7 +257,10 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
253
257
|
return super().get_fieldsets(request, obj)
|
254
258
|
|
255
259
|
def _filter_unfold_actions_by_permissions(
|
256
|
-
self,
|
260
|
+
self,
|
261
|
+
request: HttpRequest,
|
262
|
+
actions: List[UnfoldAction],
|
263
|
+
object_id: Optional[Union[int, str]] = None,
|
257
264
|
) -> List[UnfoldAction]:
|
258
265
|
"""Filter out any Unfold actions that the user doesn't have access to."""
|
259
266
|
filtered_actions = []
|
@@ -261,12 +268,22 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
261
268
|
if not hasattr(action.method, "allowed_permissions"):
|
262
269
|
filtered_actions.append(action)
|
263
270
|
continue
|
271
|
+
|
264
272
|
permission_checks = (
|
265
273
|
getattr(self, f"has_{permission}_permission")
|
266
274
|
for permission in action.method.allowed_permissions
|
267
275
|
)
|
268
|
-
|
269
|
-
|
276
|
+
|
277
|
+
if object_id:
|
278
|
+
if any(
|
279
|
+
has_permission(request, object_id)
|
280
|
+
for has_permission in permission_checks
|
281
|
+
):
|
282
|
+
filtered_actions.append(action)
|
283
|
+
else:
|
284
|
+
if any(has_permission(request) for has_permission in permission_checks):
|
285
|
+
filtered_actions.append(action)
|
286
|
+
|
270
287
|
return filtered_actions
|
271
288
|
|
272
289
|
def get_actions_list(self, request: HttpRequest) -> List[UnfoldAction]:
|
@@ -280,9 +297,11 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
280
297
|
"""
|
281
298
|
return [self.get_unfold_action(action) for action in self.actions_list or []]
|
282
299
|
|
283
|
-
def get_actions_detail(
|
300
|
+
def get_actions_detail(
|
301
|
+
self, request: HttpRequest, object_id: int
|
302
|
+
) -> List[UnfoldAction]:
|
284
303
|
return self._filter_unfold_actions_by_permissions(
|
285
|
-
request, self._get_base_actions_detail()
|
304
|
+
request, self._get_base_actions_detail(), object_id
|
286
305
|
)
|
287
306
|
|
288
307
|
def _get_base_actions_detail(self) -> List[UnfoldAction]:
|
@@ -302,9 +321,11 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
302
321
|
"""
|
303
322
|
return [self.get_unfold_action(action) for action in self.actions_row or []]
|
304
323
|
|
305
|
-
def get_actions_submit_line(
|
324
|
+
def get_actions_submit_line(
|
325
|
+
self, request: HttpRequest, object_id: int
|
326
|
+
) -> List[UnfoldAction]:
|
306
327
|
return self._filter_unfold_actions_by_permissions(
|
307
|
-
request, self._get_base_actions_submit_line()
|
328
|
+
request, self._get_base_actions_submit_line(), object_id
|
308
329
|
)
|
309
330
|
|
310
331
|
def _get_base_actions_submit_line(self) -> List[UnfoldAction]:
|
@@ -402,7 +423,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
402
423
|
|
403
424
|
actions = []
|
404
425
|
if object_id:
|
405
|
-
for action in self.get_actions_detail(request):
|
426
|
+
for action in self.get_actions_detail(request, object_id):
|
406
427
|
actions.append(
|
407
428
|
{
|
408
429
|
"title": action.description,
|
@@ -416,7 +437,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
416
437
|
|
417
438
|
extra_context.update(
|
418
439
|
{
|
419
|
-
"actions_submit_line": self.get_actions_submit_line(request),
|
440
|
+
"actions_submit_line": self.get_actions_submit_line(request, object_id),
|
420
441
|
"actions_detail": actions,
|
421
442
|
}
|
422
443
|
)
|
@@ -483,7 +504,7 @@ class ModelAdmin(ModelAdminMixin, BaseModelAdmin):
|
|
483
504
|
) -> None:
|
484
505
|
super().save_model(request, obj, form, change)
|
485
506
|
|
486
|
-
for action in self.get_actions_submit_line(request):
|
507
|
+
for action in self.get_actions_submit_line(request, obj.pk):
|
487
508
|
if action.action_name not in request.POST:
|
488
509
|
continue
|
489
510
|
|
@@ -9,11 +9,11 @@
|
|
9
9
|
|
10
10
|
{% if choice.min is not None and choice.max is not None and choice.step %}
|
11
11
|
<div class="admin-numeric-filter-slider-tooltips">
|
12
|
-
<span class="admin-numeric-filter-slider-tooltip-from border cursor-not-allowed flex flex-grow flex-row items-center mr-auto rounded-md shadow-sm px-3 py-2 w-full dark:bg-gray-900 dark:border-gray-700 dark:text-gray-
|
12
|
+
<span class="admin-numeric-filter-slider-tooltip-from border cursor-not-allowed flex flex-grow flex-row items-center mr-auto rounded-md shadow-sm px-3 py-2 w-full dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300">
|
13
13
|
{{ choice.value_from }}
|
14
14
|
</span>
|
15
15
|
|
16
|
-
<span class="admin-numeric-filter-slider-tooltip-to border cursor-not-allowed flex flex-grow flex-row items-center rounded-md shadow-sm px-3 py-2 w-full dark:bg-gray-900 dark:border-gray-700 dark:text-gray-
|
16
|
+
<span class="admin-numeric-filter-slider-tooltip-to border cursor-not-allowed flex flex-grow flex-row items-center rounded-md shadow-sm px-3 py-2 w-full dark:bg-gray-900 dark:border-gray-700 dark:text-gray-300">
|
17
17
|
{{ choice.value_to }}
|
18
18
|
</span>
|
19
19
|
</div>
|
@@ -25,7 +25,7 @@
|
|
25
25
|
{{ choice.form.as_p }}
|
26
26
|
</div>
|
27
27
|
{% else %}
|
28
|
-
<div class="admin-numeric-filter-slider-error dark:text-gray-
|
28
|
+
<div class="admin-numeric-filter-slider-error dark:text-gray-300">
|
29
29
|
<p>
|
30
30
|
{% trans 'Not enough data.' %}
|
31
31
|
</p>
|