nautobot 2.4.14__py3-none-any.whl → 2.4.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nautobot/apps/choices.py +8 -0
- nautobot/apps/ui.py +14 -0
- nautobot/core/api/views.py +2 -0
- nautobot/core/choices.py +4 -0
- nautobot/core/filters.py +21 -41
- nautobot/core/management/commands/check_job_approval_status.py +47 -0
- nautobot/core/management/commands/generate_test_data.py +1 -1
- nautobot/core/management/commands/migrate.py +1 -1
- nautobot/core/models/tree_queries.py +17 -0
- nautobot/core/settings.py +2 -2
- nautobot/core/tables.py +25 -2
- nautobot/core/templates/base_django.html +1 -1
- nautobot/core/templates/components/panel/header_extra_content_table.html +1 -1
- nautobot/core/templates/generic/object_list.html +17 -20
- nautobot/core/templates/inc/breadcrumbs.html +14 -0
- nautobot/core/templatetags/buttons.py +2 -4
- nautobot/core/templatetags/helpers.py +29 -6
- nautobot/core/templatetags/ui_framework.py +21 -0
- nautobot/core/testing/filters.py +20 -3
- nautobot/core/testing/forms.py +1 -1
- nautobot/core/tests/integration/test_filters.py +2 -2
- nautobot/core/tests/test_breadcrumbs.py +366 -0
- nautobot/core/tests/test_commands.py +40 -0
- nautobot/core/tests/test_filters.py +51 -1
- nautobot/core/tests/test_forms.py +1 -1
- nautobot/core/tests/test_graphql.py +4 -4
- nautobot/core/tests/test_titles.py +183 -0
- nautobot/core/tests/test_tree_queries.py +30 -0
- nautobot/core/tests/test_views.py +2 -2
- nautobot/core/tests/test_views_utils.py +1 -1
- nautobot/core/ui/breadcrumbs.py +538 -0
- nautobot/core/ui/bulk_buttons.py +53 -0
- nautobot/core/ui/object_detail.py +31 -8
- nautobot/core/ui/titles.py +127 -0
- nautobot/core/ui/utils.py +25 -0
- nautobot/core/utils/migrations.py +1 -1
- nautobot/core/views/__init__.py +1 -1
- nautobot/core/views/mixins.py +26 -1
- nautobot/core/views/renderers.py +20 -2
- nautobot/core/views/utils.py +13 -12
- nautobot/dcim/api/serializers.py +9 -0
- nautobot/dcim/choices.py +53 -0
- nautobot/dcim/filters/__init__.py +15 -3
- nautobot/dcim/forms.py +120 -7
- nautobot/dcim/management/commands/trace_paths.py +1 -1
- nautobot/dcim/migrations/0072_alter_powerfeed_options_and_more.py +97 -0
- nautobot/dcim/models/device_component_templates.py +8 -0
- nautobot/dcim/models/device_components.py +31 -12
- nautobot/dcim/models/devices.py +1 -1
- nautobot/dcim/models/power.py +171 -10
- nautobot/dcim/models/racks.py +7 -4
- nautobot/dcim/tables/devices.py +2 -0
- nautobot/dcim/tables/devicetypes.py +1 -0
- nautobot/dcim/tables/power.py +30 -2
- nautobot/dcim/templates/dcim/device.html +2 -2
- nautobot/dcim/templates/dcim/devicetype_retrieve.html +1 -214
- nautobot/dcim/templates/dcim/location_retrieve.html +2 -2
- nautobot/dcim/templates/dcim/powerfeed_edit.html +8 -0
- nautobot/dcim/templates/dcim/powerfeed_retrieve.html +1 -1
- nautobot/dcim/tests/integration/test_device_bulk_operations.py +61 -0
- nautobot/dcim/tests/test_api.py +24 -4
- nautobot/dcim/tests/test_filters.py +91 -13
- nautobot/dcim/tests/test_models.py +262 -0
- nautobot/dcim/tests/test_views.py +20 -12
- nautobot/dcim/utils.py +9 -0
- nautobot/dcim/views.py +390 -77
- nautobot/extras/factory.py +19 -20
- nautobot/extras/filters/__init__.py +3 -2
- nautobot/extras/filters/mixins.py +15 -1
- nautobot/extras/forms/__init__.py +2 -1
- nautobot/extras/forms/forms.py +62 -0
- nautobot/extras/managers.py +4 -1
- nautobot/extras/migrations/0125_jobresult_date_started.py +18 -0
- nautobot/extras/models/customfields.py +1 -2
- nautobot/extras/models/datasources.py +1 -2
- nautobot/extras/models/jobs.py +7 -3
- nautobot/extras/plugins/views.py +24 -1
- nautobot/extras/secrets/__init__.py +1 -1
- nautobot/extras/tables.py +21 -0
- nautobot/extras/templates/extras/customfield.html +2 -129
- nautobot/extras/templates/extras/customfield_edit.html +2 -108
- nautobot/extras/templates/extras/customfield_retrieve.html +129 -0
- nautobot/extras/templates/extras/customfield_update.html +108 -0
- nautobot/extras/templates/extras/inc/jobresult.html +7 -3
- nautobot/extras/templates/extras/jobresult.html +2 -155
- nautobot/extras/templates/extras/jobresult_retrieve.html +155 -0
- nautobot/extras/templates/extras/marketplace.html +5 -6
- nautobot/extras/templates/extras/note.html +2 -53
- nautobot/extras/templates/extras/note_retrieve.html +53 -0
- nautobot/extras/templates/extras/plugins_list.html +5 -6
- nautobot/extras/templates/extras/secretsgroup_retrieve.html +2 -29
- nautobot/extras/templatetags/custom_links.py +2 -2
- nautobot/extras/templatetags/job_buttons.py +1 -1
- nautobot/extras/templatetags/plugins.py +1 -1
- nautobot/extras/tests/integration/test_computedfields.py +2 -2
- nautobot/extras/tests/integration/test_customfields.py +14 -11
- nautobot/extras/tests/integration/test_dynamicgroups.py +1 -1
- nautobot/extras/tests/integration/test_notes.py +1 -1
- nautobot/extras/tests/integration/test_plugins.py +6 -6
- nautobot/extras/tests/integration/test_relationships.py +2 -2
- nautobot/extras/tests/test_filters.py +9 -0
- nautobot/extras/tests/test_forms.py +2 -2
- nautobot/extras/tests/test_plugins.py +14 -3
- nautobot/extras/tests/test_relationships.py +7 -7
- nautobot/extras/tests/test_views.py +172 -1
- nautobot/extras/urls.py +3 -59
- nautobot/extras/utils.py +1 -1
- nautobot/extras/views.py +96 -182
- nautobot/ipam/tables.py +8 -15
- nautobot/ipam/tests/migration/test_migrations.py +8 -8
- nautobot/ipam/tests/test_api.py +2 -2
- nautobot/ipam/tests/test_models.py +1 -1
- nautobot/project-static/docs/404.html +23 -0
- nautobot/project-static/docs/apps/index.html +23 -0
- nautobot/project-static/docs/apps/nautobot-apps.html +23 -0
- nautobot/project-static/docs/assets/_mkdocstrings.css +44 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +28 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +25 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +128 -20
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +37 -4
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +39 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +25 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +24 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +32 -5
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +41 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +39 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +43 -10
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +74 -59
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +143 -28
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +43 -12
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +135 -53
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +229 -36
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +27 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +30 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +162 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +258 -51
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +5987 -2620
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +25 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +154 -55
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +150 -35
- nautobot/project-static/docs/development/apps/api/configuration-view.html +23 -0
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/global-search.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/graphql.html +23 -0
- nautobot/project-static/docs/development/apps/api/models/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +23 -0
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +23 -0
- nautobot/project-static/docs/development/apps/api/prometheus.html +23 -0
- nautobot/project-static/docs/development/apps/api/setup.html +23 -0
- nautobot/project-static/docs/development/apps/api/testing.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +23 -0
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/base-template.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/index.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +31 -2
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/notes.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +23 -0
- nautobot/project-static/docs/development/apps/api/views/urls.html +23 -0
- nautobot/project-static/docs/development/apps/index.html +23 -0
- nautobot/project-static/docs/development/apps/migration/code-updates.html +23 -0
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +23 -0
- nautobot/project-static/docs/development/apps/migration/from-v1.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +23 -0
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +23 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +26 -3
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +10544 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +23 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +23 -0
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +23 -0
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +26 -3
- nautobot/project-static/docs/development/core/application-registry.html +23 -0
- nautobot/project-static/docs/development/core/best-practices.html +23 -0
- nautobot/project-static/docs/development/core/bootstrap-ui.html +23 -0
- nautobot/project-static/docs/development/core/caching.html +23 -0
- nautobot/project-static/docs/development/core/controllers.html +23 -0
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +23 -0
- nautobot/project-static/docs/development/core/generic-views.html +23 -0
- nautobot/project-static/docs/development/core/getting-started.html +23 -0
- nautobot/project-static/docs/development/core/homepage.html +23 -0
- nautobot/project-static/docs/development/core/index.html +23 -0
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +23 -0
- nautobot/project-static/docs/development/core/model-checklist.html +23 -0
- nautobot/project-static/docs/development/core/model-features.html +23 -0
- nautobot/project-static/docs/development/core/natural-keys.html +23 -0
- nautobot/project-static/docs/development/core/navigation-menu.html +23 -0
- nautobot/project-static/docs/development/core/release-checklist.html +23 -0
- nautobot/project-static/docs/development/core/role-internals.html +23 -0
- nautobot/project-static/docs/development/core/settings.html +23 -0
- nautobot/project-static/docs/development/core/style-guide.html +23 -0
- nautobot/project-static/docs/development/core/templates.html +23 -0
- nautobot/project-static/docs/development/core/testing.html +23 -0
- nautobot/project-static/docs/development/core/ui-component-framework.html +713 -255
- nautobot/project-static/docs/development/core/user-preferences.html +23 -0
- nautobot/project-static/docs/development/index.html +23 -0
- nautobot/project-static/docs/development/jobs/getting-started.html +23 -0
- nautobot/project-static/docs/development/jobs/index.html +23 -0
- nautobot/project-static/docs/development/jobs/installation.html +23 -0
- nautobot/project-static/docs/development/jobs/job-extensions.html +23 -0
- nautobot/project-static/docs/development/jobs/job-logging.html +23 -0
- nautobot/project-static/docs/development/jobs/job-patterns.html +23 -0
- nautobot/project-static/docs/development/jobs/job-structure.html +23 -0
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -0
- nautobot/project-static/docs/development/jobs/testing.html +23 -0
- nautobot/project-static/docs/index.html +23 -0
- nautobot/project-static/docs/media/development/core/ui-component-framework/breadcrumbs-titles-data-flow.png +0 -0
- nautobot/project-static/docs/media/power_distribution.png +0 -0
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +23 -0
- nautobot/project-static/docs/overview/design_philosophy.html +23 -0
- nautobot/project-static/docs/release-notes/index.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.0.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.1.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.2.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.3.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.4.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.5.html +23 -0
- nautobot/project-static/docs/release-notes/version-1.6.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.0.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.1.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.2.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.3.html +23 -0
- nautobot/project-static/docs/release-notes/version-2.4.html +306 -0
- nautobot/project-static/docs/requirements.txt +2 -2
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +303 -299
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +23 -0
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +23 -0
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +23 -0
- nautobot/project-static/docs/user-guide/administration/installation/services.html +23 -0
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +23 -0
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +23 -0
- nautobot/project-static/docs/user-guide/administration/security/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/security/notices.html +23 -0
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +284 -219
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +23 -0
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +305 -5
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +24 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +136 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +41 -1
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +40 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +23 -0
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +23 -0
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +23 -0
- nautobot/project-static/docs/user-guide/index.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +24 -1
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +23 -0
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +23 -0
- nautobot/users/tests/test_api.py +2 -2
- nautobot/virtualization/templates/virtualization/virtualmachine.html +2 -252
- nautobot/virtualization/templates/virtualization/virtualmachine_edit.html +2 -75
- nautobot/virtualization/templates/virtualization/virtualmachine_retrieve.html +252 -0
- nautobot/virtualization/templates/virtualization/virtualmachine_update.html +75 -0
- nautobot/virtualization/urls.py +3 -61
- nautobot/virtualization/views.py +48 -72
- {nautobot-2.4.14.dist-info → nautobot-2.4.16.dist-info}/METADATA +24 -24
- {nautobot-2.4.14.dist-info → nautobot-2.4.16.dist-info}/RECORD +434 -417
- {nautobot-2.4.14.dist-info → nautobot-2.4.16.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.14.dist-info → nautobot-2.4.16.dist-info}/NOTICE +0 -0
- {nautobot-2.4.14.dist-info → nautobot-2.4.16.dist-info}/WHEEL +0 -0
- {nautobot-2.4.14.dist-info → nautobot-2.4.16.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import logging
|
|
3
|
+
from typing import Any, Callable, Literal, Optional, Protocol, Type, Union
|
|
4
|
+
from urllib.parse import urlencode
|
|
5
|
+
|
|
6
|
+
from django.db.models import Model
|
|
7
|
+
from django.template import Context
|
|
8
|
+
from django.urls import NoReverseMatch, reverse
|
|
9
|
+
|
|
10
|
+
from nautobot.core.templatetags import helpers
|
|
11
|
+
from nautobot.core.ui.utils import get_absolute_url, render_component_template
|
|
12
|
+
from nautobot.core.utils import lookup
|
|
13
|
+
from nautobot.core.utils.lookup import get_model_for_view_name, get_model_from_name
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class WithStr(Protocol):
|
|
19
|
+
def __str__(self) -> str: ...
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
ViewNameType = Union[str, Callable[[Context], str], None]
|
|
23
|
+
ModelLabelType = Literal["plural", "singular"]
|
|
24
|
+
ModelType = Union[str, Model, Type[Model], None]
|
|
25
|
+
LabelType = Union[Callable[[Context], str], WithStr, None]
|
|
26
|
+
BreadcrumbItemsType = dict[str, list["BaseBreadcrumbItem"]]
|
|
27
|
+
ReverseParams = Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class BaseBreadcrumbItem:
|
|
32
|
+
"""
|
|
33
|
+
Base interface for breadcrumb items.
|
|
34
|
+
|
|
35
|
+
Attributes:
|
|
36
|
+
should_render (Callable[[Context], bool]): Callable to decide whether this item should be rendered or not.
|
|
37
|
+
label (Union[Callable[[Context], str], WithStr, None]): Optional override for the display label in the breadcrumb.
|
|
38
|
+
label_key (Optional[str]): Optional key to take label from the context.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
should_render: Callable[[Context], bool] = lambda context: True
|
|
42
|
+
label: LabelType = None
|
|
43
|
+
label_key: Optional[str] = None
|
|
44
|
+
|
|
45
|
+
def get_url(self, context: Context) -> Optional[str]:
|
|
46
|
+
"""
|
|
47
|
+
Get the URL for the breadcrumb item.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
context (Context): The current template context.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
Optional[str]: The URL as a string, or None.
|
|
54
|
+
"""
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def get_label(self, context: Context) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Get the label (display text) for the breadcrumb.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
context (Context): The current template context.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
str: Label as a string.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
if self.label:
|
|
69
|
+
return str(self.label(context)) if callable(self.label) else str(self.label)
|
|
70
|
+
if self.label_key:
|
|
71
|
+
return str(context.get(self.label_key, ""))
|
|
72
|
+
return ""
|
|
73
|
+
|
|
74
|
+
def reverse_view_name(
|
|
75
|
+
self,
|
|
76
|
+
view_name: str,
|
|
77
|
+
context: Context,
|
|
78
|
+
reverse_kwargs: ReverseParams = None,
|
|
79
|
+
reverse_query_params: ReverseParams = None,
|
|
80
|
+
) -> Optional[str]:
|
|
81
|
+
"""
|
|
82
|
+
Reverse a Django view name into a URL, optionally adding query parameters.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
view_name (str): Django view name to reverse.
|
|
86
|
+
context (Context): Template context, used to resolve params if needed.
|
|
87
|
+
reverse_kwargs (ReverseParams): URL kwargs for reversing.
|
|
88
|
+
reverse_query_params (ReverseParams): Query parameters to append.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Optional[str]: The resolved URL as a string, or None if reversing fails.
|
|
92
|
+
"""
|
|
93
|
+
if view_name == "":
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
# TODO: refactor after Django 5.2 upgrade
|
|
98
|
+
# query params can be passed directly to the `reverse` function instead of merging two strings
|
|
99
|
+
# reverse(view_name, query=query_params, kwargs=...)
|
|
100
|
+
# https://docs.djangoproject.com/en/5.2/ref/urlresolvers/#reverse
|
|
101
|
+
url = reverse(view_name, kwargs=self.resolve_reverse_params(reverse_kwargs, context))
|
|
102
|
+
if query_params := self.resolve_reverse_params(reverse_query_params, context):
|
|
103
|
+
return f"{url}?{urlencode(query_params)}"
|
|
104
|
+
return url
|
|
105
|
+
except NoReverseMatch as err:
|
|
106
|
+
logger.error('No reverse match for: "%s". Exc: %s', view_name, err)
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def resolve_reverse_params(params: ReverseParams, context: Context) -> dict[str, Any]:
|
|
111
|
+
"""
|
|
112
|
+
Resolves parameters for URL reversing, calling if callable, or returning as-is.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
params (ReverseParams): Dict or callable to resolve.
|
|
116
|
+
context (Context): Context for callables.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
dict[str, Any]: Dictionary of parameters for URL reversing.
|
|
120
|
+
"""
|
|
121
|
+
if callable(params):
|
|
122
|
+
return params(context)
|
|
123
|
+
if params:
|
|
124
|
+
return params
|
|
125
|
+
return {}
|
|
126
|
+
|
|
127
|
+
def as_pair(self, context: Context) -> tuple[str, str]:
|
|
128
|
+
"""
|
|
129
|
+
Construct the (URL, label) pair for the breadcrumb.
|
|
130
|
+
|
|
131
|
+
Combines `get_url()` and `get_label()` and applies title casing to the label.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
context (Context): Context object used to resolve the breadcrumb parts.
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
tuple[str, Optional[str]]: A tuple of (URL, label), where URL may be an empty string
|
|
138
|
+
if unresolved, and label is title-cased.
|
|
139
|
+
"""
|
|
140
|
+
url = self.get_url(context) or ""
|
|
141
|
+
label = helpers.bettertitle(self.get_label(context))
|
|
142
|
+
return url, label
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@dataclass
|
|
146
|
+
class ViewNameBreadcrumbItem(BaseBreadcrumbItem):
|
|
147
|
+
"""
|
|
148
|
+
Breadcrumb via raw view name and optional params.
|
|
149
|
+
|
|
150
|
+
From raw viewname string that will be passed to the reverse method. You can pass reverse kwargs or query params.
|
|
151
|
+
Label won't be generated automatically.
|
|
152
|
+
|
|
153
|
+
Attributes:
|
|
154
|
+
view_name (Union[str, Callable[[Context], str], None]): Django view name to reverse or callable taking context.
|
|
155
|
+
Can be used as fallback if `view_name_key` won't be found in the context.
|
|
156
|
+
view_name_key: (Optional[str]): Key to get the `view_name` from the context.
|
|
157
|
+
reverse_kwargs (Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]): Keyword arguments passed to `reverse()`.
|
|
158
|
+
reverse_query_params (Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]): Keyword arguments added to the url.
|
|
159
|
+
should_render (Callable[[Context], bool]): Callable to decide whether this item should be rendered or not.
|
|
160
|
+
label (Union[Callable[[Context], str], WithStr, None]): Optional override for the display label in the breadcrumb.
|
|
161
|
+
label_key (Optional[str]): Optional key to take label from the context.
|
|
162
|
+
label_from_view_name (bool): Try to resolve given view name and get the label from assosiacted model.
|
|
163
|
+
|
|
164
|
+
Examples:
|
|
165
|
+
>>> ViewNameBreadcrumbItem(view_name="dcim:device_list")
|
|
166
|
+
("/dcim/devices/", "") # No label automatically generated
|
|
167
|
+
>>> ViewNameBreadcrumbItem(view_name="dcim:device_list", reverse_query_params={"filter": "some_value"}, label="Link")
|
|
168
|
+
("/dcim/devices/?filter=some_value", "Link")
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
view_name: ViewNameType = None
|
|
172
|
+
view_name_key: Optional[str] = None
|
|
173
|
+
reverse_kwargs: ReverseParams = None
|
|
174
|
+
reverse_query_params: ReverseParams = None
|
|
175
|
+
label_from_view_name: bool = False
|
|
176
|
+
|
|
177
|
+
def get_url(self, context: Context) -> Optional[str]:
|
|
178
|
+
"""
|
|
179
|
+
Get the URL for the breadcrumb item based on the configuration: view name, context, reverse kwargs, query params.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
context (Context): The current template context.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Optional[str]: The URL as a string, or None.
|
|
186
|
+
"""
|
|
187
|
+
view_name = self.get_view_name(context)
|
|
188
|
+
if not view_name:
|
|
189
|
+
return None
|
|
190
|
+
|
|
191
|
+
return self.reverse_view_name(view_name, context, self.reverse_kwargs, self.reverse_query_params)
|
|
192
|
+
|
|
193
|
+
def get_label(self, context: Context) -> str:
|
|
194
|
+
if self.label_from_view_name:
|
|
195
|
+
model = get_model_for_view_name(self.get_view_name(context))
|
|
196
|
+
if model is not None:
|
|
197
|
+
return model._meta.verbose_name_plural
|
|
198
|
+
return super().get_label(context)
|
|
199
|
+
|
|
200
|
+
def get_view_name(self, context: Context) -> Optional[str]:
|
|
201
|
+
if self.view_name_key:
|
|
202
|
+
return context.get(self.view_name_key, self.view_name)
|
|
203
|
+
if callable(self.view_name):
|
|
204
|
+
return self.view_name(context)
|
|
205
|
+
|
|
206
|
+
return self.view_name
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@dataclass
|
|
210
|
+
class ModelBreadcrumbItem(BaseBreadcrumbItem):
|
|
211
|
+
"""
|
|
212
|
+
Breadcrumb via model class / instance / name.
|
|
213
|
+
|
|
214
|
+
Based on model class, content type or dotted model name passed directly or taken automatically from context.
|
|
215
|
+
It will generate label based on model `verbose_name` or `verbose_name_plural` depending on `model_label_type`.
|
|
216
|
+
If `label` is set explicitly, it's returned as-is or called if callable.
|
|
217
|
+
|
|
218
|
+
Attributes:
|
|
219
|
+
model (Union[str, Type[Model], None, Callable[[Context], Union[str, Type[Model], None]]): Django model class, instance, or dotted path string or callable that returns one of this.
|
|
220
|
+
model_key (Optional[str]): Context key to fetch a model class, instance or dotted path string.
|
|
221
|
+
action (str): Action to use when resolving a model-based route (default: "list").
|
|
222
|
+
label_type (Literal["singular", "plural"]): Whether to use `verbose_name` or `verbose_name_plural`.
|
|
223
|
+
reverse_kwargs (Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]): Keyword arguments passed to `reverse()`.
|
|
224
|
+
reverse_query_params (Union[dict[str, Any], Callable[[Context], dict[str, Any]], None]): Keyword arguments added to the url.
|
|
225
|
+
should_render (Callable[[Context], bool]): Callable to decide whether this item should be rendered or not.
|
|
226
|
+
label (Union[Callable[[Context], str], WithStr, None]): Optional override for the display label in the breadcrumb.
|
|
227
|
+
label_key (Optional[str]): Optional key to take label from the context.
|
|
228
|
+
|
|
229
|
+
Examples:
|
|
230
|
+
>>> ModelBreadcrumbItem(model=Device)
|
|
231
|
+
("/dcim/devices/", "Devices")
|
|
232
|
+
>>> ModelBreadcrumbItem(model="dcim.device")
|
|
233
|
+
("/dcim/devices/", "Devices")
|
|
234
|
+
>>> ModelBreadcrumbItem(model="dcim.device", label_type="singular", action="add")
|
|
235
|
+
("/dcim/devices/add", "Device")
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
model: Union[ModelType, Callable[[Context], ModelType]] = None
|
|
239
|
+
model_key: Optional[str] = None
|
|
240
|
+
action: str = "list"
|
|
241
|
+
label_type: ModelLabelType = "plural"
|
|
242
|
+
reverse_kwargs: ReverseParams = None
|
|
243
|
+
reverse_query_params: ReverseParams = None
|
|
244
|
+
|
|
245
|
+
def get_url(self, context: Context) -> Optional[str]:
|
|
246
|
+
"""
|
|
247
|
+
Get the URL for the breadcrumb item based on the configuration: model, action, reverse kwargs, query params.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
context (Context): The current template context.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Optional[str]: The URL as a string, or None.
|
|
254
|
+
"""
|
|
255
|
+
model_obj = self.get_model(context)
|
|
256
|
+
if not model_obj:
|
|
257
|
+
return None
|
|
258
|
+
view_name = lookup.get_route_for_model(model_obj, self.action)
|
|
259
|
+
return self.reverse_view_name(view_name, context, self.reverse_kwargs, self.reverse_query_params)
|
|
260
|
+
|
|
261
|
+
def get_label(self, context: Context) -> str:
|
|
262
|
+
"""
|
|
263
|
+
Get the display name from the model's metadata.
|
|
264
|
+
|
|
265
|
+
Depending on the `model_label_type`, either the `verbose_name` or `verbose_name_plural`
|
|
266
|
+
will be returned. Accepts model class, instance or dotted path string.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
context (Context): The current template context.
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
str: The verbose name of the model class for use as a label.
|
|
273
|
+
"""
|
|
274
|
+
if self.label or self.label_key:
|
|
275
|
+
return super().get_label(context)
|
|
276
|
+
|
|
277
|
+
model_obj = self.get_model(context)
|
|
278
|
+
name_attr = "verbose_name" if self.label_type == "singular" else "verbose_name_plural"
|
|
279
|
+
|
|
280
|
+
if model_obj is not None:
|
|
281
|
+
if isinstance(model_obj, str):
|
|
282
|
+
model_cls = get_model_from_name(model_obj)
|
|
283
|
+
return getattr(model_cls._meta, name_attr)
|
|
284
|
+
return getattr(model_obj._meta, name_attr)
|
|
285
|
+
return ""
|
|
286
|
+
|
|
287
|
+
def get_model(self, context: Context) -> ModelType:
|
|
288
|
+
if self.model_key:
|
|
289
|
+
return context.get(self.model_key)
|
|
290
|
+
if self.model:
|
|
291
|
+
if callable(self.model):
|
|
292
|
+
return self.model(context)
|
|
293
|
+
return self.model
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
@dataclass
|
|
298
|
+
class InstanceBreadcrumbItem(BaseBreadcrumbItem):
|
|
299
|
+
"""
|
|
300
|
+
Breadcrumb via object instance from context.
|
|
301
|
+
|
|
302
|
+
Detail url for object instance taken from context. By default, `instance_key` is set to `object`.
|
|
303
|
+
Label will be generated from object, but you can still override it.
|
|
304
|
+
|
|
305
|
+
Attributes:
|
|
306
|
+
instance_key (Optional[str]): Context key to fetch a Django model instance for building the breadcrumb.
|
|
307
|
+
instance (Callable[[Context], Optional[Model]): Callable to fetch the instance from context. If
|
|
308
|
+
should_render (Callable[[Context], bool]): Callable to decide whether this item should be rendered or not.
|
|
309
|
+
label (Union[Callable[[Context], str], WithStr, None]): Optional override for the display label in the breadcrumb.
|
|
310
|
+
label_key (Optional[str]): Optional key to take label from the context.
|
|
311
|
+
|
|
312
|
+
Examples:
|
|
313
|
+
>>> InstanceBreadcrumbItem()
|
|
314
|
+
("/dcim/devices/1234", "My Device") # Assuming that under "object" there is a Device instance
|
|
315
|
+
>>> InstanceBreadcrumbItem(label="Custom Device Label")
|
|
316
|
+
("/dcim/devices/1234", "Custom Device Label") # Assuming that under "object" there is a Device instance
|
|
317
|
+
"""
|
|
318
|
+
|
|
319
|
+
instance_key: str = "object"
|
|
320
|
+
instance: Optional[Callable[[Context], Optional[Model]]] = None
|
|
321
|
+
label: Union[Callable[[Context], str], WithStr, None] = None
|
|
322
|
+
|
|
323
|
+
def get_url(self, context: Context) -> Optional[str]:
|
|
324
|
+
"""
|
|
325
|
+
Resolve the URL for the breadcrumb item based on the instance.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
context (Context): The current template context.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
Optional[str]: The URL as a string, or None.
|
|
332
|
+
"""
|
|
333
|
+
instance = self.get_instance(context)
|
|
334
|
+
return get_absolute_url(instance) if instance else None
|
|
335
|
+
|
|
336
|
+
def get_label(self, context: Context) -> str:
|
|
337
|
+
"""
|
|
338
|
+
Get the label (display text) for the breadcrumb from instance.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
context (Context): The current template context.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
str: Label as a string.
|
|
345
|
+
"""
|
|
346
|
+
if self.label or self.label_key:
|
|
347
|
+
return super().get_label(context)
|
|
348
|
+
instance = self.get_instance(context)
|
|
349
|
+
if not instance:
|
|
350
|
+
return ""
|
|
351
|
+
return getattr(instance, "display", str(instance))
|
|
352
|
+
|
|
353
|
+
def get_instance(self, context: Context) -> Optional[Model]:
|
|
354
|
+
"""
|
|
355
|
+
Get the instance depending on the settings.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
context (Context): The current template context.
|
|
359
|
+
|
|
360
|
+
Returns:
|
|
361
|
+
Optional[Model]: Instance from context.
|
|
362
|
+
"""
|
|
363
|
+
if self.instance:
|
|
364
|
+
return self.instance(context)
|
|
365
|
+
|
|
366
|
+
return context.get(self.instance_key)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class Breadcrumbs:
|
|
370
|
+
"""
|
|
371
|
+
Base class responsible for generating and rendering breadcrumbs for a page.
|
|
372
|
+
|
|
373
|
+
This class supports flexible breadcrumb configuration through:
|
|
374
|
+
- `items`: Default breadcrumb items per view action.
|
|
375
|
+
|
|
376
|
+
You can add more information to the breadcrumbs trail by passing appropriate
|
|
377
|
+
`BreadcrumbItem` objects grouped by view action (e.g., "*", "list", "add", "edit").
|
|
378
|
+
|
|
379
|
+
Special breadcrumb item actions:
|
|
380
|
+
- `*` - if no other action was found, items from `*` will be used
|
|
381
|
+
- `detail` action is used when there is no dedicated action for given request
|
|
382
|
+
and there is `context['detail'] = True` set in context
|
|
383
|
+
|
|
384
|
+
!!! important
|
|
385
|
+
This class automatically adds the:
|
|
386
|
+
- `InstanceBreadcrumbItem` at the end of `detail` breadcrumbs
|
|
387
|
+
- `ModelBreadcrumbItem` at the beginning of `list` and `detail` breadcrumbs
|
|
388
|
+
|
|
389
|
+
You can override this behavior by subclassing this class and updating
|
|
390
|
+
the `list_breadcrumb_item` or `detail_breadcrumb_item` attributes.
|
|
391
|
+
|
|
392
|
+
If you're using custom action other than `list` / `detail` you need to remember to add above breadcrumbs
|
|
393
|
+
if you need them in your custom action.
|
|
394
|
+
|
|
395
|
+
Attributes:
|
|
396
|
+
template (str): Path to the template used to render the breadcrumb component.
|
|
397
|
+
items (dict[str, list[BreadcrumbItem]]): Default breadcrumb items per view action.
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
breadcrumb_items: list[BaseBreadcrumbItem] = [
|
|
401
|
+
# Default breadcrumb if view defines `list_url` in the Context
|
|
402
|
+
ViewNameBreadcrumbItem(
|
|
403
|
+
view_name_key="list_url",
|
|
404
|
+
label_from_view_name=True,
|
|
405
|
+
should_render=lambda context: context.get("list_url") is not None,
|
|
406
|
+
),
|
|
407
|
+
# Fallback if there is no `list_url` in the Context
|
|
408
|
+
ModelBreadcrumbItem(model_key="model", should_render=lambda context: context.get("list_url") is None),
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
def __init__(
|
|
412
|
+
self,
|
|
413
|
+
items: BreadcrumbItemsType = None,
|
|
414
|
+
template: str = "inc/breadcrumbs.html",
|
|
415
|
+
):
|
|
416
|
+
"""
|
|
417
|
+
Initialize the Breadcrumbs configuration.
|
|
418
|
+
|
|
419
|
+
Args:
|
|
420
|
+
items (Optional[dict[str, list[BreadcrumbItem]]]): Default breadcrumb items for each action.
|
|
421
|
+
template (str): The template used to render the breadcrumbs.
|
|
422
|
+
"""
|
|
423
|
+
self.template = template
|
|
424
|
+
|
|
425
|
+
# Set the default breadcrumbs
|
|
426
|
+
self.items = {
|
|
427
|
+
"list": [*self.breadcrumb_items],
|
|
428
|
+
"detail": [*self.breadcrumb_items],
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
# If custom items are present, merge with defaults
|
|
432
|
+
if items:
|
|
433
|
+
self.items = {**self.items, **items}
|
|
434
|
+
|
|
435
|
+
# Built-in feature: always add the instance details at the end of breadcrumbs path
|
|
436
|
+
self.items["detail"].append(InstanceBreadcrumbItem())
|
|
437
|
+
|
|
438
|
+
def get_breadcrumbs_items(self, context: Context) -> list[tuple[str, str]]:
|
|
439
|
+
"""
|
|
440
|
+
Compute the list of breadcrumb items for the given context.
|
|
441
|
+
|
|
442
|
+
Items are determined based on the `view_action` in context.
|
|
443
|
+
|
|
444
|
+
Args:
|
|
445
|
+
context (Context): The view or template context that holds `view_action` and related state.
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
(list[tuple[str, str]]): A list of (url, label) tuples representing breadcrumb entries.
|
|
449
|
+
"""
|
|
450
|
+
action = context.get("view_action", "list")
|
|
451
|
+
detail = context.get("detail", False)
|
|
452
|
+
items = self.get_items_for_action(self.items, action, detail)
|
|
453
|
+
return [item.as_pair(context) for item in items if item.should_render(context)]
|
|
454
|
+
|
|
455
|
+
def filter_breadcrumbs_items(self, items: list[tuple[str, str]], context: Context) -> list[tuple[str, str]]:
|
|
456
|
+
"""
|
|
457
|
+
Filters out all items that both label and url are None or empty str.
|
|
458
|
+
|
|
459
|
+
Args:
|
|
460
|
+
items (list[tuple[str, str]]): breadcrumb items pair.s
|
|
461
|
+
context (Context): The view or template context.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
(list[tuple[str, str]]): A list of filtered breadcrumb items pairs.
|
|
465
|
+
"""
|
|
466
|
+
return [(url, label) for url, label in items if self.is_label_not_blank(label)]
|
|
467
|
+
|
|
468
|
+
@staticmethod
|
|
469
|
+
def is_label_not_blank(label: str) -> bool:
|
|
470
|
+
"""
|
|
471
|
+
Check if label is not empty (only whitespace) or None.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
label (str): The label to check.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
(bool): True if label is not None or empty (only whitespace), False otherwise.
|
|
478
|
+
"""
|
|
479
|
+
return label and label.strip()
|
|
480
|
+
|
|
481
|
+
@staticmethod
|
|
482
|
+
def get_items_for_action(items: BreadcrumbItemsType, action: str, detail: bool) -> list[BaseBreadcrumbItem]:
|
|
483
|
+
"""
|
|
484
|
+
Get the breadcrumb items for a specific action, with fallback to 'detail' if not found
|
|
485
|
+
and to asterisk (*) if present.
|
|
486
|
+
|
|
487
|
+
Args:
|
|
488
|
+
items (BreadcrumbItemsType): Dictionary mapping action names to breadcrumb item lists.
|
|
489
|
+
action (str): Current action name (e.g., "list", "detail").
|
|
490
|
+
detail (bool): Whether this is a detail view (for fallback).
|
|
491
|
+
|
|
492
|
+
Returns:
|
|
493
|
+
list[BaseBreadcrumbItem]: List of breadcrumb items for the action.
|
|
494
|
+
"""
|
|
495
|
+
breadcrumbs_list = items.get(action, [])
|
|
496
|
+
if breadcrumbs_list:
|
|
497
|
+
return breadcrumbs_list
|
|
498
|
+
|
|
499
|
+
if detail:
|
|
500
|
+
return items.get("detail", [])
|
|
501
|
+
|
|
502
|
+
return items.get("*", [])
|
|
503
|
+
|
|
504
|
+
def render(self, context):
|
|
505
|
+
"""
|
|
506
|
+
Render the breadcrumbs HTML.
|
|
507
|
+
|
|
508
|
+
This method updates the context with the generated breadcrumb items and any additional context from `get_extra_context`.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
context (Context): The current rendering context.
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
(str): Rendered HTML for the breadcrumb component.
|
|
515
|
+
"""
|
|
516
|
+
with context.update(
|
|
517
|
+
{
|
|
518
|
+
**self.get_extra_context(context),
|
|
519
|
+
}
|
|
520
|
+
):
|
|
521
|
+
breadcrumbs_items = self.get_breadcrumbs_items(context)
|
|
522
|
+
filtered_items = self.filter_breadcrumbs_items(breadcrumbs_items, context)
|
|
523
|
+
return render_component_template(self.template, context, breadcrumbs_items=filtered_items)
|
|
524
|
+
|
|
525
|
+
def get_extra_context(self, context: Context):
|
|
526
|
+
"""
|
|
527
|
+
Provide additional data to include in the rendering context, based on the configuration of this component.
|
|
528
|
+
|
|
529
|
+
Context updated here will be applied to resolving url and labels.
|
|
530
|
+
Please ote that you can't override `breadcrumb_items` here because items are generated after this method call.
|
|
531
|
+
|
|
532
|
+
Args:
|
|
533
|
+
context (Context): The current context passed to `render()`.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
(dict): A dictionary of extra context variables.
|
|
537
|
+
"""
|
|
538
|
+
return {}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from nautobot.core.choices import ButtonActionColorChoices
|
|
2
|
+
from nautobot.core.ui import object_detail
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BaseBulkButton(object_detail.FormButton):
|
|
6
|
+
"""Base class for bulk action buttons."""
|
|
7
|
+
|
|
8
|
+
action = None
|
|
9
|
+
color = None
|
|
10
|
+
icon = None
|
|
11
|
+
label = None
|
|
12
|
+
weight = None
|
|
13
|
+
|
|
14
|
+
def __init__(self, *, form_id: str, model, **kwargs):
|
|
15
|
+
model_name = model.__name__.lower()
|
|
16
|
+
app_label = model._meta.app_label
|
|
17
|
+
link_name = f"{app_label}:{model_name}_bulk_{self.action}"
|
|
18
|
+
|
|
19
|
+
super().__init__(
|
|
20
|
+
link_name=link_name,
|
|
21
|
+
link_includes_pk=False,
|
|
22
|
+
label=self.label,
|
|
23
|
+
color=self.color,
|
|
24
|
+
icon=self.icon,
|
|
25
|
+
size="xs",
|
|
26
|
+
form_id=form_id,
|
|
27
|
+
weight=self.weight,
|
|
28
|
+
**kwargs,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BulkRenameButton(BaseBulkButton):
|
|
33
|
+
action = "rename"
|
|
34
|
+
color = ButtonActionColorChoices.RENAME
|
|
35
|
+
icon = "mdi-pencil"
|
|
36
|
+
label = "Rename"
|
|
37
|
+
weight = 200
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BulkEditButton(BaseBulkButton):
|
|
41
|
+
action = "edit"
|
|
42
|
+
color = ButtonActionColorChoices.EDIT
|
|
43
|
+
icon = "mdi-pencil"
|
|
44
|
+
label = "Edit"
|
|
45
|
+
weight = 300
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BulkDeleteButton(BaseBulkButton):
|
|
49
|
+
action = "delete"
|
|
50
|
+
color = ButtonActionColorChoices.DELETE
|
|
51
|
+
icon = "mdi-trash-can-outline"
|
|
52
|
+
label = "Delete"
|
|
53
|
+
weight = 400
|
|
@@ -182,6 +182,7 @@ class Button(Component):
|
|
|
182
182
|
attributes=None,
|
|
183
183
|
size=None,
|
|
184
184
|
link_includes_pk=True,
|
|
185
|
+
context_object_key=None,
|
|
185
186
|
**kwargs,
|
|
186
187
|
):
|
|
187
188
|
"""
|
|
@@ -194,6 +195,7 @@ class Button(Component):
|
|
|
194
195
|
This link will be reversed and will automatically include the current object's PK as a parameter to the
|
|
195
196
|
`reverse()` call when the button is rendered. For more complex link construction, you can subclass this
|
|
196
197
|
and override the `get_link()` method.
|
|
198
|
+
context_object_key (str, optional): The key in the render context that will contain the linked object.
|
|
197
199
|
icon (str, optional): Material Design Icons icon, to include on the button, for example `"mdi-plus-bold"`.
|
|
198
200
|
template_path (str): Template to render for this button.
|
|
199
201
|
required_permissions (list, optional): Permissions such as `["dcim.add_consoleport"]`.
|
|
@@ -213,6 +215,7 @@ class Button(Component):
|
|
|
213
215
|
self.attributes = attributes
|
|
214
216
|
self.size = size
|
|
215
217
|
self.link_includes_pk = link_includes_pk
|
|
218
|
+
self.context_object_key = context_object_key
|
|
216
219
|
super().__init__(**kwargs)
|
|
217
220
|
|
|
218
221
|
def should_render(self, context: Context):
|
|
@@ -227,7 +230,7 @@ class Button(Component):
|
|
|
227
230
|
more advanced link construction.
|
|
228
231
|
"""
|
|
229
232
|
if self.link_name and self.link_includes_pk:
|
|
230
|
-
obj = get_obj_from_context(context)
|
|
233
|
+
obj = get_obj_from_context(context, self.context_object_key)
|
|
231
234
|
return reverse(self.link_name, kwargs={"pk": obj.pk})
|
|
232
235
|
elif self.link_name:
|
|
233
236
|
return reverse(self.link_name)
|
|
@@ -685,6 +688,8 @@ class ObjectsTablePanel(Panel):
|
|
|
685
688
|
order_by_fields=None,
|
|
686
689
|
table_title=None,
|
|
687
690
|
max_display_count=None,
|
|
691
|
+
paginate=True,
|
|
692
|
+
show_table_config_button=True,
|
|
688
693
|
include_columns=None,
|
|
689
694
|
exclude_columns=None,
|
|
690
695
|
add_button_route="default",
|
|
@@ -725,6 +730,10 @@ class ObjectsTablePanel(Panel):
|
|
|
725
730
|
order_by_fields (list, optional): list of fields to order the table queryset by.
|
|
726
731
|
max_display_count (int, optional): Maximum number of items to display in the table.
|
|
727
732
|
If None, defaults to the `get_paginate_count()` (which is user's preference or a global setting).
|
|
733
|
+
paginate (bool, optional): If False, do not attach a paginator to the table and render all rows
|
|
734
|
+
(or up to `max_display_count` if provided). Defaults to True.
|
|
735
|
+
show_table_config_button (bool, optional): If False, hide the small "Configure" button rendered in the
|
|
736
|
+
panel header for this table. Defaults to True.
|
|
728
737
|
table_title (str, optional): The title to display in the panel heading for the table.
|
|
729
738
|
If None, defaults to the plural verbose name of the table model.
|
|
730
739
|
include_columns (list, optional): A list of field names to include in the table display.
|
|
@@ -775,6 +784,8 @@ class ObjectsTablePanel(Panel):
|
|
|
775
784
|
self.order_by_fields = order_by_fields
|
|
776
785
|
self.table_title = table_title
|
|
777
786
|
self.max_display_count = max_display_count
|
|
787
|
+
self.paginate = paginate
|
|
788
|
+
self.show_table_config_button = show_table_config_button
|
|
778
789
|
self.include_columns = include_columns
|
|
779
790
|
self.exclude_columns = exclude_columns
|
|
780
791
|
self.add_button_route = add_button_route
|
|
@@ -893,13 +904,24 @@ class ObjectsTablePanel(Panel):
|
|
|
893
904
|
):
|
|
894
905
|
body_content_table.columns.show("pk")
|
|
895
906
|
|
|
896
|
-
|
|
897
|
-
paginate
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
907
|
+
more_queryset_count = 0
|
|
908
|
+
if self.paginate:
|
|
909
|
+
per_page = self.max_display_count if self.max_display_count is not None else get_paginate_count(request)
|
|
910
|
+
paginate = {"paginator_class": EnhancedPaginator, "per_page": per_page}
|
|
911
|
+
RequestConfig(request, paginate).configure(body_content_table)
|
|
912
|
+
try:
|
|
913
|
+
more_queryset_count = max(body_content_table.data.data.count() - per_page, 0)
|
|
914
|
+
except TypeError:
|
|
915
|
+
more_queryset_count = max(len(body_content_table.data.data) - per_page, 0)
|
|
916
|
+
elif self.max_display_count is not None:
|
|
917
|
+
# If not paginating but a cap is desired, slice the table's data source.
|
|
918
|
+
try:
|
|
919
|
+
more_queryset_count = max(body_content_table.data.data.count() - self.max_display_count, 0)
|
|
920
|
+
body_content_table.data.data = body_content_table.data.data[: self.max_display_count]
|
|
921
|
+
except TypeError:
|
|
922
|
+
# Non-queryset iterable; fall back to list slicing
|
|
923
|
+
more_queryset_count = max(len(body_content_table.data.data) - self.max_display_count, 0)
|
|
924
|
+
body_content_table.data.data = list(body_content_table.data.data)[: self.max_display_count]
|
|
903
925
|
|
|
904
926
|
obj = get_obj_from_context(context)
|
|
905
927
|
body_content_table_model = body_content_table.Meta.model
|
|
@@ -931,6 +953,7 @@ class ObjectsTablePanel(Panel):
|
|
|
931
953
|
"footer_buttons": self.footer_buttons,
|
|
932
954
|
"form_id": self.form_id,
|
|
933
955
|
"more_queryset_count": more_queryset_count,
|
|
956
|
+
"show_table_config_button": self.show_table_config_button,
|
|
934
957
|
}
|
|
935
958
|
|
|
936
959
|
|