django-unfold 0.47.0__tar.gz → 0.49.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {django_unfold-0.47.0 → django_unfold-0.49.0}/PKG-INFO +1 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/pyproject.toml +1 -1
- django_unfold-0.49.0/src/unfold/admin.py +183 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/dataclasses.py +8 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/decorators.py +14 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/fields.py +6 -5
- django_unfold-0.49.0/src/unfold/mixins/__init__.py +4 -0
- django_unfold-0.49.0/src/unfold/mixins/action_model_admin.py +329 -0
- django_unfold-0.49.0/src/unfold/mixins/base_model_admin.py +110 -0
- django_unfold-0.49.0/src/unfold/overrides.py +73 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/settings.py +2 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/sites.py +25 -5
- django_unfold-0.49.0/src/unfold/static/unfold/css/styles.css +1 -0
- django_unfold-0.49.0/src/unfold/static/unfold/fonts/inter/Inter-Bold.woff2 +0 -0
- django_unfold-0.49.0/src/unfold/static/unfold/fonts/inter/Inter-Medium.woff2 +0 -0
- django_unfold-0.49.0/src/unfold/static/unfold/fonts/inter/Inter-Regular.woff2 +0 -0
- django_unfold-0.49.0/src/unfold/static/unfold/fonts/inter/Inter-SemiBold.woff2 +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/app_index.html +1 -5
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/base_site.html +1 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/index.html +1 -5
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/login.html +1 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/search_form.html +4 -2
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/submit_line.html +7 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/account_links.html +1 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/actions_row.html +14 -4
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/language_switch.html +1 -1
- django_unfold-0.49.0/src/unfold/templates/unfold/helpers/navigation_header.html +26 -0
- django_unfold-0.49.0/src/unfold/templates/unfold/helpers/site_branding.html +9 -0
- django_unfold-0.49.0/src/unfold/templates/unfold/helpers/site_dropdown.html +19 -0
- django_unfold-0.49.0/src/unfold/templates/unfold/helpers/site_icon.html +31 -0
- django_unfold-0.49.0/src/unfold/templates/unfold/helpers/tab_action.html +38 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/theme_switch.html +1 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/layouts/base.html +1 -5
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/typing.py +2 -1
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/widgets.py +2 -0
- django_unfold-0.47.0/src/unfold/admin.py +0 -578
- django_unfold-0.47.0/src/unfold/static/unfold/css/styles.css +0 -1
- django_unfold-0.47.0/src/unfold/static/unfold/fonts/inter/Inter-Bold.woff2 +0 -0
- django_unfold-0.47.0/src/unfold/static/unfold/fonts/inter/Inter-Medium.woff2 +0 -0
- django_unfold-0.47.0/src/unfold/static/unfold/fonts/inter/Inter-Regular.woff2 +0 -0
- django_unfold-0.47.0/src/unfold/static/unfold/fonts/inter/Inter-SemiBold.woff2 +0 -0
- django_unfold-0.47.0/src/unfold/templates/unfold/helpers/navigation_header.html +0 -13
- django_unfold-0.47.0/src/unfold/templates/unfold/helpers/site_icon.html +0 -21
- django_unfold-0.47.0/src/unfold/templates/unfold/helpers/tab_action.html +0 -5
- {django_unfold-0.47.0 → django_unfold-0.49.0}/LICENSE.md +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/README.md +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/apps.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/checks.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/components.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin/autocomplete_filters.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin/datetime_filters.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin/dropdown_filters.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin/mixins.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin/numeric_filters.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin/text_filters.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/admin.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/apps.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/forms.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/static/unfold/filters/css/nouislider.min.css +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/static/unfold/filters/js/DateTimeShortcuts.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/static/unfold/filters/js/admin-numeric-filter.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/static/unfold/filters/js/nouislider.min.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/static/unfold/filters/js/wNumb.min.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_date_range.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_datetime_range.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_field.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_range.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_single.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/filters/templates/unfold/filters/filters_numeric_slider.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/apps.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/static/unfold/forms/css/trix.css +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/static/unfold/forms/js/trix.config.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/static/unfold/forms/js/trix.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/templates/unfold/forms/array.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/templates/unfold/forms/helpers/toolbar.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/templates/unfold/forms/wysiwyg.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/forms/widgets.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/apps.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/change_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/field.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_group.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/templates/admin/guardian/model/obj_perms_manage_user.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/templates/unfold/guardian/group_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/guardian/templates/unfold/guardian/user_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/apps.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/forms.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/base.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_export.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_export_item.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_import_export.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/change_list_import_item.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/export.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/import.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_confirm.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_errors.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_preview.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/import_validation.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/import_export/templates/admin/import_export/resource_fields_list.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/inlines/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/inlines/admin.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/inlines/apps.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/inlines/checks.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/inlines/forms.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/simple_history/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/simple_history/apps.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/simple_history/templates/simple_history/object_history_list.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/contrib/simple_history/templates/simple_history/submit_line.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/exceptions.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/forms.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/admin/js/admin/RelatedObjectLookups.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/admin/js/inlines.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/css/simplebar.css +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/fonts/inter/styles.css +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/fonts/material-symbols/Material-Symbols-Outlined.woff2 +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/fonts/material-symbols/styles.css +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/alpine.anchor.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/alpine.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/alpine.persist.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/alpine.resize.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/alpine.sort.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/app.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/chart.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/htmx.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/select2.init.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/static/unfold/js/simplebar.js +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/styles.css +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/actions.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/app_list.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/auth/user/add_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/auth/user/change_password.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/base.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/change_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/change_form_object_tools.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/change_list.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/change_list_object_tools.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/change_list_results.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/date_hierarchy.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/delete_confirmation.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/delete_selected_confirmation.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/edit_inline/stacked.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/edit_inline/tabular.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/filter.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/includes/fieldset.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/includes/object_delete_summary.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/nav_sidebar.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/object_history.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/admin/pagination.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/auth/widgets/read_only_password_hash.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/registration/logged_out.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/registration/password_change_done.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/registration/password_change_form.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/button.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/card.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/chart/bar.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/chart/cohort.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/chart/line.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/container.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/flex.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/icon.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/navigation.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/progress.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/separator.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/table.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/text.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/title.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/components/tracker.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/add_link.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/app_list.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/app_list_default.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/attrs.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/boolean.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/breadcrumb_item.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/change_list_actions.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/change_list_filter.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/change_list_filter_actions.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/delete_submit_line.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/display_header.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/display_label.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/edit_inline/tabular_delete.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/edit_inline/tabular_error.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/edit_inline/tabular_field.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/edit_inline/tabular_heading.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/edit_inline/tabular_row.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/edit_inline/tabular_title.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/field.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/field_readonly.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/field_readonly_value.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/fieldset_row.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/fieldset_row_checkbox.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/fieldset_row_field.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/fieldsets_tabs.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/form_errors.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/form_label.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/header.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/header_back_button.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/help_text.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/history.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/label.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/messages/debug.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/messages/error.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/messages/errornote.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/messages/info.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/messages/success.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/messages/warning.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/messages.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/navigation.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/pagination_current_item.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/pagination_ellipsis.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/search.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/search_results.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/site_logo.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/submit.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/tab_list.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/userlinks.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/helpers/welcomemsg.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/layouts/base_simple.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/layouts/skeleton.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/templatetags/preserve_changelist_filters.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/clearable_file_input.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/clearable_file_input_small.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/date.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/foreign_key_raw_id.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/radio.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/radio_option.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/range.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/related_widget_wrapper.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/split_datetime.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/split_datetime_vertical.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/split_money.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/textarea.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/textarea_expandable.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/time.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templates/unfold/widgets/url.html +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templatetags/__init__.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templatetags/unfold.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/templatetags/unfold_list.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/utils.py +0 -0
- {django_unfold-0.47.0 → django_unfold-0.49.0}/src/unfold/views.py +0 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
from functools import update_wrapper
|
2
|
+
from typing import Optional
|
3
|
+
|
4
|
+
from django import forms
|
5
|
+
from django.contrib.admin import ModelAdmin as BaseModelAdmin
|
6
|
+
from django.contrib.admin import StackedInline as BaseStackedInline
|
7
|
+
from django.contrib.admin import TabularInline as BaseTabularInline
|
8
|
+
from django.contrib.admin import display, helpers
|
9
|
+
from django.db.models import BLANK_CHOICE_DASH, Model
|
10
|
+
from django.http import HttpRequest, HttpResponse
|
11
|
+
from django.shortcuts import redirect
|
12
|
+
from django.urls import URLPattern, path
|
13
|
+
from django.utils.safestring import mark_safe
|
14
|
+
from django.utils.translation import gettext_lazy as _
|
15
|
+
from django.views import View
|
16
|
+
|
17
|
+
from unfold.checks import UnfoldModelAdminChecks
|
18
|
+
from unfold.fields import UnfoldAdminField, UnfoldAdminReadonlyField
|
19
|
+
from unfold.forms import ActionForm
|
20
|
+
from unfold.mixins import ActionModelAdminMixin, BaseModelAdminMixin
|
21
|
+
from unfold.overrides import FORMFIELD_OVERRIDES_INLINE
|
22
|
+
from unfold.typing import FieldsetsType
|
23
|
+
from unfold.widgets import UnfoldBooleanWidget
|
24
|
+
|
25
|
+
checkbox = UnfoldBooleanWidget(
|
26
|
+
{
|
27
|
+
"class": "action-select",
|
28
|
+
"aria-label": _("Select record"),
|
29
|
+
},
|
30
|
+
lambda value: False,
|
31
|
+
)
|
32
|
+
|
33
|
+
helpers.AdminField = UnfoldAdminField
|
34
|
+
|
35
|
+
helpers.AdminReadonlyField = UnfoldAdminReadonlyField
|
36
|
+
|
37
|
+
|
38
|
+
class ModelAdmin(BaseModelAdminMixin, ActionModelAdminMixin, BaseModelAdmin):
|
39
|
+
action_form = ActionForm
|
40
|
+
custom_urls = ()
|
41
|
+
add_fieldsets = ()
|
42
|
+
list_horizontal_scrollbar_top = False
|
43
|
+
list_filter_submit = False
|
44
|
+
list_filter_sheet = True
|
45
|
+
list_fullwidth = False
|
46
|
+
list_disable_select_all = False
|
47
|
+
list_before_template = None
|
48
|
+
list_after_template = None
|
49
|
+
change_form_before_template = None
|
50
|
+
change_form_after_template = None
|
51
|
+
change_form_outer_before_template = None
|
52
|
+
change_form_outer_after_template = None
|
53
|
+
compressed_fields = False
|
54
|
+
readonly_preprocess_fields = {}
|
55
|
+
warn_unsaved_form = False
|
56
|
+
checks_class = UnfoldModelAdminChecks
|
57
|
+
|
58
|
+
@property
|
59
|
+
def media(self):
|
60
|
+
media = super().media
|
61
|
+
additional_media = forms.Media()
|
62
|
+
|
63
|
+
for filter in self.list_filter:
|
64
|
+
if (
|
65
|
+
isinstance(filter, (tuple, list))
|
66
|
+
and hasattr(filter[1], "form_class")
|
67
|
+
and hasattr(filter[1].form_class, "Media")
|
68
|
+
):
|
69
|
+
additional_media += forms.Media(filter[1].form_class.Media)
|
70
|
+
elif hasattr(filter, "form_class") and hasattr(filter.form_class, "Media"):
|
71
|
+
additional_media += forms.Media(filter.form_class.Media)
|
72
|
+
|
73
|
+
return media + additional_media
|
74
|
+
|
75
|
+
def get_fieldsets(self, request: HttpRequest, obj=None) -> FieldsetsType:
|
76
|
+
if not obj and self.add_fieldsets:
|
77
|
+
return self.add_fieldsets
|
78
|
+
return super().get_fieldsets(request, obj)
|
79
|
+
|
80
|
+
def get_custom_urls(self) -> tuple[tuple[str, str, View], ...]:
|
81
|
+
"""
|
82
|
+
Method to get custom views for ModelAdmin with their urls
|
83
|
+
|
84
|
+
Format of custom_urls item:
|
85
|
+
("path_to_view", "name_of_view", view_itself)
|
86
|
+
"""
|
87
|
+
return () if self.custom_urls is None else self.custom_urls
|
88
|
+
|
89
|
+
def get_urls(self) -> list[URLPattern]:
|
90
|
+
urls = super().get_urls()
|
91
|
+
|
92
|
+
def wrap(view):
|
93
|
+
def wrapper(*args, **kwargs):
|
94
|
+
return self.admin_site.admin_view(view)(*args, **kwargs)
|
95
|
+
|
96
|
+
wrapper.model_admin = self
|
97
|
+
return update_wrapper(wrapper, view)
|
98
|
+
|
99
|
+
custom_urls = [
|
100
|
+
self._path_from_custom_url(custom_url)
|
101
|
+
for custom_url in self.get_custom_urls()
|
102
|
+
]
|
103
|
+
|
104
|
+
actions_list_urls = [
|
105
|
+
path(
|
106
|
+
action.path,
|
107
|
+
wrap(action.method),
|
108
|
+
name=action.action_name,
|
109
|
+
)
|
110
|
+
for action in self._get_base_actions_list()
|
111
|
+
]
|
112
|
+
|
113
|
+
action_detail_urls = [
|
114
|
+
path(
|
115
|
+
f"<path:object_id>/{action.path}/",
|
116
|
+
wrap(action.method),
|
117
|
+
name=action.action_name,
|
118
|
+
)
|
119
|
+
for action in self._get_base_actions_detail()
|
120
|
+
]
|
121
|
+
|
122
|
+
action_row_urls = [
|
123
|
+
path(
|
124
|
+
f"<path:object_id>/{action.path}",
|
125
|
+
wrap(action.method),
|
126
|
+
name=action.action_name,
|
127
|
+
)
|
128
|
+
for action in self._get_base_actions_row()
|
129
|
+
]
|
130
|
+
|
131
|
+
return (
|
132
|
+
custom_urls
|
133
|
+
+ action_row_urls
|
134
|
+
+ actions_list_urls
|
135
|
+
+ action_detail_urls
|
136
|
+
+ urls
|
137
|
+
)
|
138
|
+
|
139
|
+
def _path_from_custom_url(self, custom_url) -> URLPattern:
|
140
|
+
return path(
|
141
|
+
custom_url[0],
|
142
|
+
self.admin_site.admin_view(custom_url[2]),
|
143
|
+
{"model_admin": self},
|
144
|
+
name=custom_url[1],
|
145
|
+
)
|
146
|
+
|
147
|
+
def get_action_choices(
|
148
|
+
self, request: HttpRequest, default_choices=BLANK_CHOICE_DASH
|
149
|
+
):
|
150
|
+
default_choices = [("", _("Select action"))]
|
151
|
+
return super().get_action_choices(request, default_choices)
|
152
|
+
|
153
|
+
@display(description=mark_safe(checkbox.render("action_toggle_all", 1)))
|
154
|
+
def action_checkbox(self, obj: Model):
|
155
|
+
return checkbox.render(helpers.ACTION_CHECKBOX_NAME, str(obj.pk))
|
156
|
+
|
157
|
+
def response_change(self, request: HttpRequest, obj: Model) -> HttpResponse:
|
158
|
+
res = super().response_change(request, obj)
|
159
|
+
if "next" in request.GET:
|
160
|
+
return redirect(request.GET["next"])
|
161
|
+
return res
|
162
|
+
|
163
|
+
def response_add(
|
164
|
+
self, request: HttpRequest, obj: Model, post_url_continue: Optional[str] = None
|
165
|
+
) -> HttpResponse:
|
166
|
+
res = super().response_add(request, obj, post_url_continue)
|
167
|
+
if "next" in request.GET:
|
168
|
+
return redirect(request.GET["next"])
|
169
|
+
return res
|
170
|
+
|
171
|
+
|
172
|
+
class TabularInline(BaseModelAdminMixin, BaseTabularInline):
|
173
|
+
formfield_overrides = FORMFIELD_OVERRIDES_INLINE
|
174
|
+
readonly_preprocess_fields = {}
|
175
|
+
ordering_field = None
|
176
|
+
hide_ordering_field = False
|
177
|
+
|
178
|
+
|
179
|
+
class StackedInline(BaseModelAdminMixin, BaseStackedInline):
|
180
|
+
formfield_overrides = FORMFIELD_OVERRIDES_INLINE
|
181
|
+
readonly_preprocess_fields = {}
|
182
|
+
ordering_field = None
|
183
|
+
hide_ordering_field = False
|
@@ -12,6 +12,7 @@ class UnfoldAction:
|
|
12
12
|
path: str
|
13
13
|
attrs: Optional[dict] = None
|
14
14
|
object_id: Optional[Union[int, str]] = None
|
15
|
+
icon: Optional[str] = None
|
15
16
|
|
16
17
|
|
17
18
|
@dataclass
|
@@ -20,3 +21,10 @@ class Favicon:
|
|
20
21
|
rel: Optional[str] = None
|
21
22
|
type: Optional[str] = None
|
22
23
|
sizes: Optional[str] = None
|
24
|
+
|
25
|
+
|
26
|
+
@dataclass
|
27
|
+
class DropdownItem:
|
28
|
+
title: str
|
29
|
+
link: Union[str, Callable]
|
30
|
+
icon: Optional[str] = None
|
@@ -17,6 +17,7 @@ def action(
|
|
17
17
|
description: Optional[str] = None,
|
18
18
|
url_path: Optional[str] = None,
|
19
19
|
attrs: Optional[dict[str, Any]] = None,
|
20
|
+
icon: Optional[str] = None,
|
20
21
|
) -> ActionFunction:
|
21
22
|
def decorator(func: Callable) -> ActionFunction:
|
22
23
|
def inner(
|
@@ -33,8 +34,14 @@ def action(
|
|
33
34
|
# Permissions methods have following syntax: has_<some>_permission(self, request, obj=None):
|
34
35
|
# But obj is not examined by default in django admin and it would also require additional
|
35
36
|
# fetch from database, therefore it is not supported yet
|
36
|
-
|
37
|
+
has_object_argument = (
|
38
|
+
func.__name__ in model_admin.actions_detail
|
39
|
+
or func.__name__ in model_admin.actions_submit_line
|
40
|
+
)
|
41
|
+
if not all(
|
37
42
|
has_permission(request, kwargs.get("object_id"))
|
43
|
+
if has_object_argument
|
44
|
+
else has_permission(request)
|
38
45
|
for has_permission in permission_checks
|
39
46
|
):
|
40
47
|
raise PermissionDenied
|
@@ -42,10 +49,16 @@ def action(
|
|
42
49
|
|
43
50
|
if permissions is not None:
|
44
51
|
inner.allowed_permissions = permissions
|
52
|
+
|
45
53
|
if description is not None:
|
46
54
|
inner.short_description = description
|
55
|
+
|
47
56
|
if url_path is not None:
|
48
57
|
inner.url_path = url_path
|
58
|
+
|
59
|
+
if icon is not None:
|
60
|
+
inner.icon = icon
|
61
|
+
|
49
62
|
inner.attrs = attrs or {}
|
50
63
|
return inner
|
51
64
|
|
@@ -17,17 +17,18 @@ from django.utils.module_loading import import_string
|
|
17
17
|
from django.utils.safestring import SafeText, mark_safe
|
18
18
|
from django.utils.text import capfirst
|
19
19
|
|
20
|
-
from .
|
21
|
-
from .
|
22
|
-
from .
|
20
|
+
from unfold.mixins import BaseModelAdminMixin
|
21
|
+
from unfold.settings import get_config
|
22
|
+
from unfold.utils import display_for_field, prettify_json
|
23
|
+
from unfold.widgets import CHECKBOX_LABEL_CLASSES, LABEL_CLASSES
|
23
24
|
|
24
25
|
|
25
26
|
class UnfoldAdminReadonlyField(helpers.AdminReadonlyField):
|
26
27
|
def label_tag(self) -> SafeText:
|
27
|
-
from .admin import ModelAdmin
|
28
|
+
from .admin import ModelAdmin
|
28
29
|
|
29
30
|
if not isinstance(self.model_admin, ModelAdmin) and not isinstance(
|
30
|
-
self.model_admin,
|
31
|
+
self.model_admin, BaseModelAdminMixin
|
31
32
|
):
|
32
33
|
return super().label_tag()
|
33
34
|
|
@@ -0,0 +1,329 @@
|
|
1
|
+
from typing import Any, Callable, Optional, Union
|
2
|
+
|
3
|
+
from django.db.models import Model
|
4
|
+
from django.forms import Form
|
5
|
+
from django.http import HttpRequest
|
6
|
+
from django.template.response import TemplateResponse
|
7
|
+
from django.urls import reverse
|
8
|
+
|
9
|
+
from unfold.dataclasses import UnfoldAction
|
10
|
+
from unfold.exceptions import UnfoldException
|
11
|
+
|
12
|
+
|
13
|
+
class ActionModelAdminMixin:
|
14
|
+
"""
|
15
|
+
Adds support for various ModelAdmin actions (list, detail, row, submit line)
|
16
|
+
"""
|
17
|
+
|
18
|
+
actions_list = () # Displayed in changelist at the top
|
19
|
+
actions_row = () # Displayed in changelist for each row in the table
|
20
|
+
actions_detail = () # Displayed in changeform at the top
|
21
|
+
actions_submit_line = () # Displayed in changeform in the submit line (form buttons)
|
22
|
+
|
23
|
+
def changelist_view(
|
24
|
+
self, request: HttpRequest, extra_context: Optional[dict[str, str]] = None
|
25
|
+
) -> TemplateResponse:
|
26
|
+
"""
|
27
|
+
Changelist contains `actions_list` and `actions_row` custom actions. In case of `actions_row` they
|
28
|
+
are displayed in the each row of the table.
|
29
|
+
"""
|
30
|
+
extra_context = extra_context or {}
|
31
|
+
|
32
|
+
actions_row = [
|
33
|
+
{
|
34
|
+
"title": action.description,
|
35
|
+
"icon": action.icon,
|
36
|
+
"attrs": action.method.attrs,
|
37
|
+
# This is just a path name as string and in template is used in {% url %} tag
|
38
|
+
# with custom instance pk value
|
39
|
+
"raw_path": f"{self.admin_site.name}:{action.action_name}",
|
40
|
+
}
|
41
|
+
for action in self.get_actions_row(request)
|
42
|
+
]
|
43
|
+
|
44
|
+
# `actions_list` may contain custom structure with dropdowns so it is needed
|
45
|
+
# to use `_get_actions_navigation` to build the final structure for the template
|
46
|
+
actions_list = self._get_actions_navigation(
|
47
|
+
self.actions_list, self.get_actions_list(request)
|
48
|
+
)
|
49
|
+
|
50
|
+
extra_context.update(
|
51
|
+
{
|
52
|
+
"actions_list": actions_list,
|
53
|
+
"actions_row": actions_row,
|
54
|
+
}
|
55
|
+
)
|
56
|
+
|
57
|
+
return super().changelist_view(request, extra_context)
|
58
|
+
|
59
|
+
def changeform_view(
|
60
|
+
self,
|
61
|
+
request: HttpRequest,
|
62
|
+
object_id: Optional[str] = None,
|
63
|
+
form_url: str = "",
|
64
|
+
extra_context: Optional[dict[str, bool]] = None,
|
65
|
+
) -> Any:
|
66
|
+
"""
|
67
|
+
Changeform contains `actions_submit_line` and `actions_detail` custom actions.
|
68
|
+
"""
|
69
|
+
extra_context = extra_context or {}
|
70
|
+
|
71
|
+
# `actions_submit_line` is a list of actions that are displayed in the submit line they
|
72
|
+
# are displayed as form buttons
|
73
|
+
actions_submit_line = self.get_actions_submit_line(request, object_id)
|
74
|
+
|
75
|
+
# `actions_detail` may contain custom structure with dropdowns so it is needed
|
76
|
+
# to use `_get_actions_navigation` to build the final structure for the template
|
77
|
+
actions_detail = self._get_actions_navigation(
|
78
|
+
self.actions_detail,
|
79
|
+
self.get_actions_detail(request, object_id),
|
80
|
+
object_id,
|
81
|
+
)
|
82
|
+
|
83
|
+
extra_context.update(
|
84
|
+
{
|
85
|
+
"actions_submit_line": actions_submit_line,
|
86
|
+
"actions_detail": actions_detail,
|
87
|
+
}
|
88
|
+
)
|
89
|
+
|
90
|
+
return super().changeform_view(request, object_id, form_url, extra_context)
|
91
|
+
|
92
|
+
def save_model(
|
93
|
+
self, request: HttpRequest, obj: Model, form: Form, change: Any
|
94
|
+
) -> None:
|
95
|
+
"""
|
96
|
+
When saving object, run all appropriate actions from `actions_submit_line`
|
97
|
+
"""
|
98
|
+
super().save_model(request, obj, form, change)
|
99
|
+
|
100
|
+
# After saving object, check if any button from `actions_submit_line` was pressed
|
101
|
+
# and call the corresponding method
|
102
|
+
for action in self.get_actions_submit_line(request, obj.pk):
|
103
|
+
if action.action_name not in request.POST:
|
104
|
+
continue
|
105
|
+
|
106
|
+
action.method(request, obj)
|
107
|
+
|
108
|
+
def get_unfold_action(self, action: str) -> UnfoldAction:
|
109
|
+
"""
|
110
|
+
Converts action name into UnfoldAction object.
|
111
|
+
"""
|
112
|
+
method = self._get_instance_method(action)
|
113
|
+
|
114
|
+
return UnfoldAction(
|
115
|
+
action_name=f"{self.model._meta.app_label}_{self.model._meta.model_name}_{action}",
|
116
|
+
method=method,
|
117
|
+
description=self._get_action_description(method, action),
|
118
|
+
path=getattr(method, "url_path", action),
|
119
|
+
attrs=method.attrs if hasattr(method, "attrs") else None,
|
120
|
+
icon=method.icon if hasattr(method, "icon") else None,
|
121
|
+
)
|
122
|
+
|
123
|
+
def get_actions_list(self, request: HttpRequest) -> list[UnfoldAction]:
|
124
|
+
"""
|
125
|
+
Filters `actions_list` by permissions and returns list of UnfoldAction objects.
|
126
|
+
"""
|
127
|
+
return self._filter_unfold_actions_by_permissions(
|
128
|
+
request, self._get_base_actions_list()
|
129
|
+
)
|
130
|
+
|
131
|
+
def get_actions_detail(
|
132
|
+
self, request: HttpRequest, object_id: int
|
133
|
+
) -> list[UnfoldAction]:
|
134
|
+
"""
|
135
|
+
Filters `actions_detail` by permissions and returns list of UnfoldAction objects.
|
136
|
+
"""
|
137
|
+
return self._filter_unfold_actions_by_permissions(
|
138
|
+
request, self._get_base_actions_detail(), object_id
|
139
|
+
)
|
140
|
+
|
141
|
+
def get_actions_row(self, request: HttpRequest) -> list[UnfoldAction]:
|
142
|
+
"""
|
143
|
+
Filters `actions_row` by permissions and returns list of UnfoldAction objects.
|
144
|
+
"""
|
145
|
+
return self._filter_unfold_actions_by_permissions(
|
146
|
+
request, self._get_base_actions_row()
|
147
|
+
)
|
148
|
+
|
149
|
+
def get_actions_submit_line(
|
150
|
+
self, request: HttpRequest, object_id: int
|
151
|
+
) -> list[UnfoldAction]:
|
152
|
+
"""
|
153
|
+
Filters `actions_submit_line` by permissions and returns list of UnfoldAction objects.
|
154
|
+
"""
|
155
|
+
return self._filter_unfold_actions_by_permissions(
|
156
|
+
request, self._get_base_actions_submit_line(), object_id
|
157
|
+
)
|
158
|
+
|
159
|
+
def _extract_action_names(self, actions: list[Union[str, dict]]) -> list[str]:
|
160
|
+
"""
|
161
|
+
Gets the list of only actions names from the actions structure provided in ModelAdmin
|
162
|
+
"""
|
163
|
+
results = []
|
164
|
+
|
165
|
+
for action in actions or []:
|
166
|
+
if isinstance(action, dict) and "items" in action:
|
167
|
+
results.extend(action["items"])
|
168
|
+
else:
|
169
|
+
results.append(action)
|
170
|
+
|
171
|
+
return results
|
172
|
+
|
173
|
+
def _get_base_actions_list(self) -> list[UnfoldAction]:
|
174
|
+
"""
|
175
|
+
Returns list of UnfoldAction objects for `actions_list`.
|
176
|
+
"""
|
177
|
+
return [
|
178
|
+
self.get_unfold_action(action)
|
179
|
+
for action in self._extract_action_names(self.actions_list)
|
180
|
+
]
|
181
|
+
|
182
|
+
def _get_base_actions_detail(self) -> list[UnfoldAction]:
|
183
|
+
"""
|
184
|
+
Returns list of UnfoldAction objects for `actions_detail`.
|
185
|
+
"""
|
186
|
+
return [
|
187
|
+
self.get_unfold_action(action)
|
188
|
+
for action in self._extract_action_names(self.actions_detail) or []
|
189
|
+
]
|
190
|
+
|
191
|
+
def _get_base_actions_row(self) -> list[UnfoldAction]:
|
192
|
+
"""
|
193
|
+
Returns list of UnfoldAction objects for `actions_row`.
|
194
|
+
"""
|
195
|
+
return [
|
196
|
+
self.get_unfold_action(action)
|
197
|
+
for action in self._extract_action_names(self.actions_row) or []
|
198
|
+
]
|
199
|
+
|
200
|
+
def _get_base_actions_submit_line(self) -> list[UnfoldAction]:
|
201
|
+
"""
|
202
|
+
Returns list of UnfoldAction objects for `actions_submit_line`.
|
203
|
+
"""
|
204
|
+
return [
|
205
|
+
self.get_unfold_action(action)
|
206
|
+
for action in self._extract_action_names(self.actions_submit_line) or []
|
207
|
+
]
|
208
|
+
|
209
|
+
def _get_instance_method(self, method_name: str) -> Callable:
|
210
|
+
"""
|
211
|
+
Searches for method on self instance based on method_name and returns it if it exists.
|
212
|
+
If it does not exist or is not callable, it raises UnfoldException
|
213
|
+
"""
|
214
|
+
try:
|
215
|
+
method = getattr(self, method_name)
|
216
|
+
except AttributeError as e:
|
217
|
+
raise UnfoldException(
|
218
|
+
f"Method {method_name} specified does not exist on current object"
|
219
|
+
) from e
|
220
|
+
|
221
|
+
if not callable(method):
|
222
|
+
raise UnfoldException(f"{method_name} is not callable")
|
223
|
+
|
224
|
+
return method
|
225
|
+
|
226
|
+
def _get_actions_navigation(
|
227
|
+
self,
|
228
|
+
provided_actions: list[Union[str, dict]],
|
229
|
+
allowed_actions: list[UnfoldAction],
|
230
|
+
object_id: Optional[str] = None,
|
231
|
+
) -> list[Union[str, dict]]:
|
232
|
+
"""
|
233
|
+
Builds navigation structure for the actions which is going to be provided to the template.
|
234
|
+
"""
|
235
|
+
navigation = []
|
236
|
+
|
237
|
+
def get_action_by_name(name: str) -> UnfoldAction:
|
238
|
+
"""
|
239
|
+
Searches for an action in allowed_actions by its name.
|
240
|
+
"""
|
241
|
+
for action in allowed_actions:
|
242
|
+
full_action_name = (
|
243
|
+
f"{self.model._meta.app_label}_{self.model._meta.model_name}_{name}"
|
244
|
+
)
|
245
|
+
|
246
|
+
if action.action_name == full_action_name:
|
247
|
+
return action
|
248
|
+
|
249
|
+
def get_action_path(action: UnfoldAction) -> str:
|
250
|
+
"""
|
251
|
+
Returns the URL path for an action.
|
252
|
+
"""
|
253
|
+
path_name = f"{self.admin_site.name}:{action.action_name}"
|
254
|
+
|
255
|
+
if object_id:
|
256
|
+
return reverse(path_name, args=(object_id,))
|
257
|
+
|
258
|
+
return reverse(path_name)
|
259
|
+
|
260
|
+
def get_action_attrs(action: UnfoldAction) -> dict:
|
261
|
+
"""
|
262
|
+
Returns the attributes for an action which will be used in the template.
|
263
|
+
"""
|
264
|
+
return {
|
265
|
+
"title": action.description,
|
266
|
+
"icon": action.icon,
|
267
|
+
"attrs": action.method.attrs,
|
268
|
+
"path": get_action_path(action),
|
269
|
+
}
|
270
|
+
|
271
|
+
def build_dropdown(nav_item: dict) -> Optional[dict]:
|
272
|
+
"""
|
273
|
+
Builds a dropdown structure for the action.
|
274
|
+
"""
|
275
|
+
dropdown = {
|
276
|
+
"title": nav_item["title"],
|
277
|
+
"icon": nav_item.get("icon"),
|
278
|
+
"items": [],
|
279
|
+
}
|
280
|
+
|
281
|
+
for child in nav_item["items"]:
|
282
|
+
if action := get_action_by_name(child):
|
283
|
+
dropdown["items"].append(get_action_attrs(action))
|
284
|
+
|
285
|
+
if len(dropdown["items"]) > 0:
|
286
|
+
return dropdown
|
287
|
+
|
288
|
+
for nav_item in provided_actions:
|
289
|
+
if isinstance(nav_item, str):
|
290
|
+
if action := get_action_by_name(nav_item):
|
291
|
+
navigation.append(get_action_attrs(action))
|
292
|
+
elif isinstance(nav_item, dict):
|
293
|
+
if dropdown := build_dropdown(nav_item):
|
294
|
+
navigation.append(dropdown)
|
295
|
+
|
296
|
+
return navigation
|
297
|
+
|
298
|
+
def _filter_unfold_actions_by_permissions(
|
299
|
+
self,
|
300
|
+
request: HttpRequest,
|
301
|
+
actions: list[UnfoldAction],
|
302
|
+
object_id: Optional[Union[int, str]] = None,
|
303
|
+
) -> list[UnfoldAction]:
|
304
|
+
"""
|
305
|
+
Filters out actions that the user doesn't have access to.
|
306
|
+
"""
|
307
|
+
filtered_actions = []
|
308
|
+
|
309
|
+
for action in actions:
|
310
|
+
if not hasattr(action.method, "allowed_permissions"):
|
311
|
+
filtered_actions.append(action)
|
312
|
+
continue
|
313
|
+
|
314
|
+
permission_checks = (
|
315
|
+
getattr(self, f"has_{permission}_permission")
|
316
|
+
for permission in action.method.allowed_permissions
|
317
|
+
)
|
318
|
+
|
319
|
+
if object_id:
|
320
|
+
if all(
|
321
|
+
has_permission(request, object_id)
|
322
|
+
for has_permission in permission_checks
|
323
|
+
):
|
324
|
+
filtered_actions.append(action)
|
325
|
+
else:
|
326
|
+
if all(has_permission(request) for has_permission in permission_checks):
|
327
|
+
filtered_actions.append(action)
|
328
|
+
|
329
|
+
return filtered_actions
|