nautobot 2.4.16__py3-none-any.whl → 2.4.18__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.
Potentially problematic release.
This version of nautobot might be problematic. Click here for more details.
- nautobot/apps/utils.py +2 -0
- nautobot/apps/views.py +2 -0
- nautobot/circuits/templates/circuits/circuittermination_retrieve.html +1 -8
- nautobot/circuits/templates/circuits/inc/circuit_termination_speed_fragment.html +9 -0
- nautobot/circuits/tests/integration/test_circuit.py +2 -2
- nautobot/circuits/views.py +32 -15
- nautobot/cloud/templates/cloud/cloudresourcetype_retrieve.html +3 -3
- nautobot/cloud/views.py +7 -0
- nautobot/core/apps/__init__.py +1 -0
- nautobot/core/celery/__init__.py +2 -1
- nautobot/core/filters.py +2 -2
- nautobot/core/settings.py +1 -0
- nautobot/core/settings.yaml +9 -0
- nautobot/core/tables.py +21 -23
- nautobot/core/templates/components/breadcrumbs.html +19 -0
- nautobot/core/templates/components/panel/panel.html +1 -1
- nautobot/core/templates/generic/object_changelog.html +0 -2
- nautobot/core/templates/generic/object_list.html +15 -12
- nautobot/core/templates/generic/object_notes.html +0 -2
- nautobot/core/templates/generic/object_retrieve.html +16 -9
- nautobot/core/templates/inc/paginator.html +3 -3
- nautobot/core/templates/inc/table.html +2 -2
- nautobot/core/templatetags/helpers.py +104 -6
- nautobot/core/templatetags/ui_framework.py +40 -5
- nautobot/core/testing/filters.py +37 -21
- nautobot/core/testing/mixins.py +1 -1
- nautobot/core/testing/views.py +27 -4
- nautobot/core/tests/test_tables.py +43 -6
- nautobot/core/tests/test_templatetags_ui_framework.py +146 -0
- nautobot/core/tests/test_titles.py +2 -2
- nautobot/core/tests/test_ui.py +14 -1
- nautobot/core/tests/test_views.py +45 -0
- nautobot/core/ui/breadcrumbs.py +13 -8
- nautobot/core/ui/bulk_buttons.py +53 -53
- nautobot/core/ui/object_detail.py +52 -9
- nautobot/core/ui/titles.py +9 -5
- nautobot/core/utils/data.py +13 -0
- nautobot/core/utils/deprecation.py +2 -0
- nautobot/core/views/__init__.py +24 -3
- nautobot/core/views/generic.py +42 -17
- nautobot/core/views/mixins.py +146 -12
- nautobot/core/views/utils.py +117 -0
- nautobot/dcim/migrations/0073_alter_powerport_power_factor_and_more.py +41 -0
- nautobot/dcim/models/device_component_templates.py +4 -2
- nautobot/dcim/models/device_components.py +3 -2
- nautobot/dcim/models/devices.py +4 -0
- nautobot/dcim/tables/__init__.py +2 -0
- nautobot/dcim/tables/devices.py +24 -0
- nautobot/dcim/tables/power.py +2 -2
- nautobot/dcim/templates/dcim/device/base.html +1 -11
- nautobot/dcim/templates/dcim/device_component.html +0 -19
- nautobot/dcim/templates/dcim/modulebay_retrieve.html +0 -16
- nautobot/dcim/templates/dcim/rack_elevation_list.html +4 -4
- nautobot/dcim/templates/dcim/virtualchassis_retrieve.html +1 -50
- nautobot/dcim/tests/test_views.py +41 -0
- nautobot/dcim/views.py +169 -39
- nautobot/extras/filters/mixins.py +1 -1
- nautobot/extras/forms/forms.py +15 -0
- nautobot/extras/models/customfields.py +45 -9
- nautobot/extras/models/groups.py +10 -1
- nautobot/extras/models/jobs.py +2 -2
- nautobot/extras/plugins/views.py +18 -5
- nautobot/extras/tables.py +4 -2
- nautobot/extras/templates/extras/configcontext_retrieve.html +1 -1
- nautobot/extras/templates/extras/configcontext_update.html +49 -49
- nautobot/extras/templates/extras/configcontextschema_retrieve.html +47 -47
- nautobot/extras/templates/extras/configcontextschema_update.html +18 -18
- nautobot/extras/templates/extras/customfield_retrieve.html +1 -128
- nautobot/extras/templates/extras/dynamicgroup.html +2 -99
- nautobot/extras/templates/extras/dynamicgroup_edit.html +2 -199
- nautobot/extras/templates/extras/dynamicgroup_retrieve.html +99 -0
- nautobot/extras/templates/extras/dynamicgroup_update.html +199 -0
- nautobot/extras/templates/extras/gitrepository.html +2 -82
- nautobot/extras/templates/extras/gitrepository_object_edit.html +2 -13
- nautobot/extras/templates/extras/gitrepository_retrieve.html +82 -0
- nautobot/extras/templates/extras/gitrepository_update.html +13 -0
- nautobot/extras/templates/extras/inc/job_table.html +1 -1
- nautobot/extras/templates/extras/inc/object_contact_header.html +2 -2
- nautobot/extras/templates/extras/note_retrieve.html +1 -53
- nautobot/extras/templates/extras/plugin_detail.html +3 -7
- nautobot/extras/templates/extras/plugins_list.html +0 -2
- nautobot/extras/templates/extras/tag_retrieve.html +1 -1
- nautobot/extras/templates/extras/tag_update.html +14 -14
- nautobot/extras/templates/extras/team_retrieve.html +1 -1
- nautobot/extras/tests/test_dynamicgroups.py +73 -18
- nautobot/extras/tests/test_models.py +216 -0
- nautobot/extras/tests/test_views.py +7 -2
- nautobot/extras/urls.py +2 -94
- nautobot/extras/views.py +425 -430
- nautobot/ipam/apps.py +1 -0
- nautobot/ipam/jobs/__init__.py +10 -0
- nautobot/ipam/jobs/cleanup.py +296 -0
- nautobot/ipam/models.py +301 -178
- nautobot/ipam/querysets.py +3 -3
- nautobot/ipam/signals.py +6 -1
- nautobot/ipam/templates/ipam/inc/ipadress_edit_header.html +3 -3
- nautobot/ipam/templates/ipam/inc/toggle_available.html +2 -2
- nautobot/ipam/templates/ipam/ipaddress_assign.html +1 -1
- nautobot/ipam/templates/ipam/prefix.html +0 -8
- nautobot/ipam/templates/ipam/prefix_list.html +1 -1
- nautobot/ipam/templates/ipam/vlan_retrieve.html +1 -77
- nautobot/ipam/tests/test_api.py +5 -0
- nautobot/ipam/tests/test_jobs.py +454 -0
- nautobot/ipam/tests/test_models.py +677 -122
- nautobot/ipam/tests/test_querysets.py +46 -0
- nautobot/ipam/tests/test_views.py +40 -164
- nautobot/ipam/urls.py +0 -11
- nautobot/ipam/utils/migrations.py +1 -1
- nautobot/ipam/utils/testing.py +9 -4
- nautobot/ipam/views.py +175 -235
- nautobot/project-static/docs/404.html +9 -6
- nautobot/project-static/docs/apps/index.html +9 -6
- nautobot/project-static/docs/apps/nautobot-apps.html +9 -6
- nautobot/project-static/docs/assets/javascripts/bundle.92b07e13.min.js +16 -0
- nautobot/project-static/docs/assets/javascripts/{bundle.50899def.min.js.map → bundle.92b07e13.min.js.map} +2 -2
- nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js → search.973d3a69.min.js} +4 -4
- nautobot/project-static/docs/assets/javascripts/workers/{search.d50fe291.min.js.map → search.973d3a69.min.js.map} +1 -1
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +10 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/events.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +11 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +11 -8
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +81 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +73 -18
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +9 -6
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +69 -7
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +402 -21
- nautobot/project-static/docs/development/apps/api/configuration-view.html +13 -10
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +11 -8
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +13 -10
- nautobot/project-static/docs/development/apps/api/models/global-search.html +10 -7
- nautobot/project-static/docs/development/apps/api/models/graphql.html +18 -15
- nautobot/project-static/docs/development/apps/api/models/index.html +14 -11
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +12 -9
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +9 -6
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +11 -8
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +16 -13
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +12 -10305
- nautobot/project-static/docs/development/apps/api/platform-features/prepopulating-data.html +10722 -0
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +15 -12
- nautobot/project-static/docs/development/apps/api/platform-features/table-extensions.html +14 -11
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +9 -6
- nautobot/project-static/docs/development/apps/api/prometheus.html +15 -12
- nautobot/project-static/docs/development/apps/api/setup.html +9 -6
- nautobot/project-static/docs/development/apps/api/testing.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +12 -9
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +9 -6
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +20 -17
- nautobot/project-static/docs/development/apps/api/views/base-template.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +15 -12
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +14 -11
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/index.html +9 -6
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +10 -7
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +24 -21
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +12 -9
- nautobot/project-static/docs/development/apps/api/views/notes.html +10 -7
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +19 -16
- nautobot/project-static/docs/development/apps/api/views/urls.html +11 -8
- nautobot/project-static/docs/development/apps/index.html +9 -6
- nautobot/project-static/docs/development/apps/migration/code-updates.html +19 -16
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +9 -6
- nautobot/project-static/docs/development/apps/migration/from-v1.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +22 -19
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +9 -6
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +9 -6
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/best-practices.html +9 -6
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/breadcrumbs-titles.html +14 -11
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/custom-content.html +27 -24
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/index.html +20 -17
- nautobot/project-static/docs/development/apps/migration/ui-component-framework/migration-steps.html +20 -17
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +9 -6
- nautobot/project-static/docs/development/core/application-registry.html +23 -20
- nautobot/project-static/docs/development/core/best-practices.html +23 -20
- nautobot/project-static/docs/development/core/bootstrap-ui.html +9 -6
- nautobot/project-static/docs/development/core/caching.html +9 -6
- nautobot/project-static/docs/development/core/controllers.html +9 -6
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +10 -7
- nautobot/project-static/docs/development/core/generic-views.html +9 -6
- nautobot/project-static/docs/development/core/getting-started.html +9 -21
- nautobot/project-static/docs/development/core/homepage.html +12 -9
- nautobot/project-static/docs/development/core/index.html +9 -6
- nautobot/project-static/docs/development/core/minikube-dev-environment-for-k8s-jobs.html +9 -6
- nautobot/project-static/docs/development/core/model-checklist.html +9 -6
- nautobot/project-static/docs/development/core/model-features.html +11 -8
- nautobot/project-static/docs/development/core/natural-keys.html +21 -18
- nautobot/project-static/docs/development/core/navigation-menu.html +10 -7
- nautobot/project-static/docs/development/core/release-checklist.html +9 -6
- nautobot/project-static/docs/development/core/role-internals.html +9 -6
- nautobot/project-static/docs/development/core/settings.html +9 -6
- nautobot/project-static/docs/development/core/style-guide.html +32 -29
- nautobot/project-static/docs/development/core/templates.html +9 -6
- nautobot/project-static/docs/development/core/testing.html +10 -7
- nautobot/project-static/docs/development/core/ui-component-framework.html +42 -44
- nautobot/project-static/docs/development/core/user-preferences.html +9 -6
- nautobot/project-static/docs/development/index.html +9 -6
- nautobot/project-static/docs/development/jobs/getting-started.html +13 -10
- nautobot/project-static/docs/development/jobs/index.html +9 -6
- nautobot/project-static/docs/development/jobs/installation.html +23 -20
- nautobot/project-static/docs/development/jobs/job-extensions.html +25 -22
- nautobot/project-static/docs/development/jobs/job-logging.html +12 -9
- nautobot/project-static/docs/development/jobs/job-patterns.html +45 -42
- nautobot/project-static/docs/development/jobs/job-structure.html +53 -50
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +23 -20
- nautobot/project-static/docs/development/jobs/testing.html +14 -11
- nautobot/project-static/docs/index.html +9 -6
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +9 -6
- nautobot/project-static/docs/overview/design_philosophy.html +9 -6
- nautobot/project-static/docs/release-notes/index.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.0.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.1.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.2.html +10 -7
- nautobot/project-static/docs/release-notes/version-1.3.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.4.html +9 -6
- nautobot/project-static/docs/release-notes/version-1.5.html +13 -10
- nautobot/project-static/docs/release-notes/version-1.6.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.0.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.1.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.2.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.3.html +9 -6
- nautobot/project-static/docs/release-notes/version-2.4.html +489 -6
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +301 -301
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +15 -12
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +16 -13
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +9 -6
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +38 -8
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +9 -6
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +16 -13
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +9 -6
- nautobot/project-static/docs/user-guide/administration/installation/services.html +12 -9
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +13 -10
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +10 -7
- nautobot/project-static/docs/user-guide/administration/security/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/security/notices.html +9 -6
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +9 -6
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +10 -7
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +9 -6
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +15 -12
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +13 -10
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulefamily.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualdevicecontext.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +11 -8
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +11 -8
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +41 -41
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +197 -54
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/index.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/radioprofile.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/supporteddatarate.html +9 -6
- nautobot/project-static/docs/user-guide/core-data-model/wireless/wirelessnetwork.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +13 -10
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +9 -6
- nautobot/project-static/docs/user-guide/feature-guides/wireless-networks-and-controllers.html +9 -6
- nautobot/project-static/docs/user-guide/index.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +10 -7
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/events.html +11 -8
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobqueue.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/kubernetes-job-support.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/managing-jobs.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +12 -9
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rendering-jinja-templates.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +11 -8
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +9 -6
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +9 -6
- nautobot/project-static/fonts/UFL.txt +96 -96
- nautobot/project-static/img/nautobot_icon.svg +32 -34
- nautobot/project-static/js/forms.js +35 -2
- nautobot/project-static/js/table_sorting_indicator.js +0 -2
- nautobot/virtualization/filters.py +7 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/METADATA +8 -8
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/RECORD +431 -421
- nautobot/core/templates/inc/breadcrumbs.html +0 -14
- nautobot/project-static/docs/assets/javascripts/bundle.50899def.min.js +0 -16
- nautobot/project-static/docs/requirements.txt +0 -14
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/NOTICE +0 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/WHEEL +0 -0
- {nautobot-2.4.16.dist-info → nautobot-2.4.18.dist-info}/entry_points.txt +0 -0
|
@@ -8,6 +8,7 @@ import urllib.parse
|
|
|
8
8
|
from django.apps import apps
|
|
9
9
|
from django.conf import settings
|
|
10
10
|
from django.contrib.contenttypes.models import ContentType
|
|
11
|
+
from django.core.cache import cache
|
|
11
12
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
12
13
|
from django.test import override_settings, RequestFactory
|
|
13
14
|
from django.test.utils import override_script_prefix
|
|
@@ -23,6 +24,7 @@ from nautobot.core.testing.utils import extract_page_body
|
|
|
23
24
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
24
25
|
from nautobot.core.views import NautobotMetricsView
|
|
25
26
|
from nautobot.core.views.mixins import GetReturnURLMixin
|
|
27
|
+
from nautobot.core.views.utils import METRICS_CACHE_KEY
|
|
26
28
|
from nautobot.dcim.models.locations import Location
|
|
27
29
|
from nautobot.extras.choices import CustomFieldTypeChoices
|
|
28
30
|
from nautobot.extras.models import FileProxy, Status
|
|
@@ -566,6 +568,49 @@ class MetricsViewTestCase(TestCase):
|
|
|
566
568
|
metric_names_with_app.remove(test_metric_name)
|
|
567
569
|
self.assertSetEqual(metric_names_with_app, metric_names_without_app)
|
|
568
570
|
|
|
571
|
+
def test_enabled_metrics_cache_disabled(self):
|
|
572
|
+
"""Assert that when cache is disabled the cache enabled function doesn't get called."""
|
|
573
|
+
with mock.patch("nautobot.core.views.utils.generate_latest_with_cache") as mock_generate_latest_with_cache:
|
|
574
|
+
self.query_and_parse_metrics()
|
|
575
|
+
self.assertTrue(mock_generate_latest_with_cache.call_count == 0)
|
|
576
|
+
|
|
577
|
+
@override_settings(METRICS_EXPERIMENTAL_CACHING_DURATION=30)
|
|
578
|
+
def test_enabled_metrics_cache_enabled(self):
|
|
579
|
+
"""Assert that multiple calls to metrics with caching returns expected response."""
|
|
580
|
+
test_metric_name = "nautobot_example_metric_count"
|
|
581
|
+
metrics_with_app = self.query_and_parse_metrics()
|
|
582
|
+
metrics_with_app_cached = self.query_and_parse_metrics()
|
|
583
|
+
metric_names_with_app = {metric.name for metric in metrics_with_app}
|
|
584
|
+
metric_names_with_app_cached = {metric.name for metric in metrics_with_app_cached}
|
|
585
|
+
self.assertIn(test_metric_name, metric_names_with_app)
|
|
586
|
+
self.assertIn(test_metric_name, metric_names_with_app_cached)
|
|
587
|
+
|
|
588
|
+
# In some circumstances (e.g. if this test is run first) metrics_with_app may not have
|
|
589
|
+
# metrics from Django like total view counts. Since metrics_with_app_cached is called
|
|
590
|
+
# second, it should have everything that metrics_with_app has (plus potentially more).
|
|
591
|
+
# We at least want to ensure the cached version has everything the non-cached version has.
|
|
592
|
+
self.assertTrue(
|
|
593
|
+
metric_names_with_app.issubset(metric_names_with_app_cached),
|
|
594
|
+
msg="Cached metrics should be a superset of non-cached metrics.",
|
|
595
|
+
)
|
|
596
|
+
with mock.patch("nautobot.core.views.generate_latest_with_cache") as mock_generate_latest_with_cache:
|
|
597
|
+
self.query_and_parse_metrics()
|
|
598
|
+
self.query_and_parse_metrics()
|
|
599
|
+
self.assertEqual(mock_generate_latest_with_cache.call_count, 2)
|
|
600
|
+
|
|
601
|
+
from example_app.metrics import metric_example
|
|
602
|
+
|
|
603
|
+
cache.delete(METRICS_CACHE_KEY) # Ensure we start with a clean cache for the next part of the test
|
|
604
|
+
# Assert that the metric function only gets called once even though we scrape metrics twice
|
|
605
|
+
|
|
606
|
+
mock_metric_function = mock.Mock(name="mock_metric_function", side_effect=metric_example)
|
|
607
|
+
with mock.patch.dict("nautobot.core.views.registry", app_metrics=[mock_metric_function]):
|
|
608
|
+
self.query_and_parse_metrics()
|
|
609
|
+
first_call_count = mock_metric_function.call_count
|
|
610
|
+
self.query_and_parse_metrics()
|
|
611
|
+
second_call_count = mock_metric_function.call_count
|
|
612
|
+
self.assertEqual(first_call_count, second_call_count)
|
|
613
|
+
|
|
569
614
|
|
|
570
615
|
class AuthenticateMetricsTestCase(APITestCase):
|
|
571
616
|
def test_metrics_authentication(self):
|
nautobot/core/ui/breadcrumbs.py
CHANGED
|
@@ -192,9 +192,14 @@ class ViewNameBreadcrumbItem(BaseBreadcrumbItem):
|
|
|
192
192
|
|
|
193
193
|
def get_label(self, context: Context) -> str:
|
|
194
194
|
if self.label_from_view_name:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
195
|
+
try:
|
|
196
|
+
model = get_model_for_view_name(self.get_view_name(context))
|
|
197
|
+
if model is not None:
|
|
198
|
+
return model._meta.verbose_name_plural
|
|
199
|
+
except ValueError:
|
|
200
|
+
# `get_model_for_view_name` is not working properly with some proper paths like "home"
|
|
201
|
+
# and because by default we're trying to resolve label by using `list_url` this error may occur in some apps
|
|
202
|
+
pass
|
|
198
203
|
return super().get_label(context)
|
|
199
204
|
|
|
200
205
|
def get_view_name(self, context: Context) -> Optional[str]:
|
|
@@ -401,6 +406,7 @@ class Breadcrumbs:
|
|
|
401
406
|
# Default breadcrumb if view defines `list_url` in the Context
|
|
402
407
|
ViewNameBreadcrumbItem(
|
|
403
408
|
view_name_key="list_url",
|
|
409
|
+
label_key="title",
|
|
404
410
|
label_from_view_name=True,
|
|
405
411
|
should_render=lambda context: context.get("list_url") is not None,
|
|
406
412
|
),
|
|
@@ -411,7 +417,7 @@ class Breadcrumbs:
|
|
|
411
417
|
def __init__(
|
|
412
418
|
self,
|
|
413
419
|
items: BreadcrumbItemsType = None,
|
|
414
|
-
template: str = "
|
|
420
|
+
template: str = "components/breadcrumbs.html",
|
|
415
421
|
):
|
|
416
422
|
"""
|
|
417
423
|
Initialize the Breadcrumbs configuration.
|
|
@@ -447,7 +453,7 @@ class Breadcrumbs:
|
|
|
447
453
|
Returns:
|
|
448
454
|
(list[tuple[str, str]]): A list of (url, label) tuples representing breadcrumb entries.
|
|
449
455
|
"""
|
|
450
|
-
action = context.get("view_action", "
|
|
456
|
+
action = context.get("view_action", "")
|
|
451
457
|
detail = context.get("detail", False)
|
|
452
458
|
items = self.get_items_for_action(self.items, action, detail)
|
|
453
459
|
return [item.as_pair(context) for item in items if item.should_render(context)]
|
|
@@ -457,7 +463,7 @@ class Breadcrumbs:
|
|
|
457
463
|
Filters out all items that both label and url are None or empty str.
|
|
458
464
|
|
|
459
465
|
Args:
|
|
460
|
-
items (list[tuple[str, str]]): breadcrumb items
|
|
466
|
+
items (list[tuple[str, str]]): breadcrumb items pairs.
|
|
461
467
|
context (Context): The view or template context.
|
|
462
468
|
|
|
463
469
|
Returns:
|
|
@@ -481,8 +487,7 @@ class Breadcrumbs:
|
|
|
481
487
|
@staticmethod
|
|
482
488
|
def get_items_for_action(items: BreadcrumbItemsType, action: str, detail: bool) -> list[BaseBreadcrumbItem]:
|
|
483
489
|
"""
|
|
484
|
-
Get the breadcrumb items for a specific action,
|
|
485
|
-
and to asterisk (*) if present.
|
|
490
|
+
Get the breadcrumb items for a specific action, 'detail' or to asterisk (*) if present.
|
|
486
491
|
|
|
487
492
|
Args:
|
|
488
493
|
items (BreadcrumbItemsType): Dictionary mapping action names to breadcrumb item lists.
|
nautobot/core/ui/bulk_buttons.py
CHANGED
|
@@ -1,53 +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
|
|
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
|
|
@@ -9,6 +9,7 @@ from django.contrib.contenttypes.models import ContentType
|
|
|
9
9
|
from django.core.exceptions import FieldDoesNotExist, ObjectDoesNotExist
|
|
10
10
|
from django.db import models
|
|
11
11
|
from django.db.models import CharField, JSONField, Q, URLField
|
|
12
|
+
from django.db.models.constants import LOOKUP_SEP
|
|
12
13
|
from django.db.models.fields.related import ManyToManyField
|
|
13
14
|
from django.template import Context
|
|
14
15
|
from django.template.defaultfilters import truncatechars
|
|
@@ -459,7 +460,7 @@ class DistinctViewTab(Tab):
|
|
|
459
460
|
return format_html(
|
|
460
461
|
"{} {}",
|
|
461
462
|
self.label,
|
|
462
|
-
render_to_string("utilities/templatetags/badge.html", badge(related_obj_count)),
|
|
463
|
+
render_to_string("utilities/templatetags/badge.html", badge(related_obj_count, True)),
|
|
463
464
|
)
|
|
464
465
|
except AttributeError:
|
|
465
466
|
logger.warning(
|
|
@@ -704,6 +705,7 @@ class ObjectsTablePanel(Panel):
|
|
|
704
705
|
footer_content_template_path="components/panel/footer_content_table.html",
|
|
705
706
|
footer_buttons=None,
|
|
706
707
|
form_id=None,
|
|
708
|
+
include_paginator=False,
|
|
707
709
|
**kwargs,
|
|
708
710
|
):
|
|
709
711
|
"""Instantiate an ObjectsTable panel.
|
|
@@ -752,6 +754,7 @@ class ObjectsTablePanel(Panel):
|
|
|
752
754
|
footer_buttons (list, optional): A list of Button or FormButton components to render in the panel footer.
|
|
753
755
|
These buttons typically perform actions like bulk delete, edit, or custom form submission.
|
|
754
756
|
form_id (str, optional): A unique ID for this table's form; used to set the `data-form-id` attribute on each `FormButton`.
|
|
757
|
+
include_paginator (bool, optional): If True, renders a paginator in the panel footer.
|
|
755
758
|
"""
|
|
756
759
|
if context_table_key and any(
|
|
757
760
|
[
|
|
@@ -796,6 +799,7 @@ class ObjectsTablePanel(Panel):
|
|
|
796
799
|
self.tab_id = tab_id
|
|
797
800
|
self.footer_buttons = footer_buttons
|
|
798
801
|
self.form_id = form_id
|
|
802
|
+
self.include_paginator = include_paginator
|
|
799
803
|
|
|
800
804
|
super().__init__(
|
|
801
805
|
body_wrapper_template_path=body_wrapper_template_path,
|
|
@@ -953,6 +957,7 @@ class ObjectsTablePanel(Panel):
|
|
|
953
957
|
"footer_buttons": self.footer_buttons,
|
|
954
958
|
"form_id": self.form_id,
|
|
955
959
|
"more_queryset_count": more_queryset_count,
|
|
960
|
+
"include_paginator": self.include_paginator,
|
|
956
961
|
"show_table_config_button": self.show_table_config_button,
|
|
957
962
|
}
|
|
958
963
|
|
|
@@ -967,6 +972,7 @@ class KeyValueTablePanel(Panel):
|
|
|
967
972
|
context_data_key=None,
|
|
968
973
|
hide_if_unset=(),
|
|
969
974
|
value_transforms=None,
|
|
975
|
+
key_transforms=None,
|
|
970
976
|
body_wrapper_template_path="components/panel/body_wrapper_key_value_table.html",
|
|
971
977
|
**kwargs,
|
|
972
978
|
):
|
|
@@ -985,6 +991,8 @@ class KeyValueTablePanel(Panel):
|
|
|
985
991
|
|
|
986
992
|
- `[render_markdown, placeholder]` - render the given text as Markdown, or render a placeholder if blank
|
|
987
993
|
- `[humanize_speed, placeholder]` - convert the given kbps value to Mbps or Gbps for display
|
|
994
|
+
key_transforms (dict, optional): A mapping of original field names to custom display names to be used when rendering keys
|
|
995
|
+
For example: {'content_types': 'Content Type'}.
|
|
988
996
|
"""
|
|
989
997
|
if data and context_data_key:
|
|
990
998
|
raise ValueError("The data and context_data_key parameters are mutually exclusive")
|
|
@@ -992,6 +1000,7 @@ class KeyValueTablePanel(Panel):
|
|
|
992
1000
|
self.context_data_key = context_data_key or "data"
|
|
993
1001
|
self.hide_if_unset = hide_if_unset
|
|
994
1002
|
self.value_transforms = value_transforms or {}
|
|
1003
|
+
self.key_transforms = key_transforms or {}
|
|
995
1004
|
super().__init__(body_wrapper_template_path=body_wrapper_template_path, **kwargs)
|
|
996
1005
|
|
|
997
1006
|
def should_render(self, context: Context):
|
|
@@ -1172,6 +1181,7 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1172
1181
|
self,
|
|
1173
1182
|
*,
|
|
1174
1183
|
fields="__all__",
|
|
1184
|
+
additional_fields=(),
|
|
1175
1185
|
exclude_fields=(),
|
|
1176
1186
|
context_object_key=None,
|
|
1177
1187
|
ignore_nonexistent_fields=False,
|
|
@@ -1185,6 +1195,11 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1185
1195
|
fields (str, list): The ordered list of fields to display, or `"__all__"` to display fields automatically.
|
|
1186
1196
|
Note that ManyToMany fields and reverse relations are **not** included in `"__all__"` at this time, nor
|
|
1187
1197
|
are any hidden fields, nor the specially handled `id`, `created`, `last_updated` fields on most models.
|
|
1198
|
+
When a list is specified, it may include model `@property` attributes and nested lookups (if desired) in
|
|
1199
|
+
addition to concrete model fields; when using `fields="__all__"`, such additional attributes and lookups
|
|
1200
|
+
may be specified with the `additional_fields` parameter.
|
|
1201
|
+
additional_fields (list): Only relevant if `fields == "__all__"`, in which case it can specify additional
|
|
1202
|
+
non-default fields to include such as reverse relations, `@property` attributes, nested lookups, etc.
|
|
1188
1203
|
exclude_fields (list): Only relevant if `fields == "__all__"`, in which case it excludes the given fields.
|
|
1189
1204
|
context_object_key (str): The key in the render context that will contain the object to derive fields from.
|
|
1190
1205
|
ignore_nonexistent_fields (bool): If True, `fields` is permitted to include field names that don't actually
|
|
@@ -1193,6 +1208,11 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1193
1208
|
(see `render_label()`).
|
|
1194
1209
|
"""
|
|
1195
1210
|
self.fields = fields
|
|
1211
|
+
if additional_fields and fields != "__all__":
|
|
1212
|
+
raise ValueError("additional_fields may only be used in combination with fields='__all__'")
|
|
1213
|
+
self.additional_fields = additional_fields
|
|
1214
|
+
if exclude_fields and fields != "__all__":
|
|
1215
|
+
raise ValueError("exclude_fields may only be used in combination with fields='__all__'")
|
|
1196
1216
|
self.exclude_fields = exclude_fields
|
|
1197
1217
|
self.context_object_key = context_object_key
|
|
1198
1218
|
self.ignore_nonexistent_fields = ignore_nonexistent_fields
|
|
@@ -1206,6 +1226,7 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1206
1226
|
|
|
1207
1227
|
def render_value(self, key, value, context: Context):
|
|
1208
1228
|
obj = get_obj_from_context(context, self.context_object_key)
|
|
1229
|
+
# TODO: handle nested keys, e.g. device_type__device_family
|
|
1209
1230
|
try:
|
|
1210
1231
|
field_instance = obj._meta.get_field(key)
|
|
1211
1232
|
except FieldDoesNotExist:
|
|
@@ -1226,8 +1247,18 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1226
1247
|
if isinstance(field_instance, JSONField):
|
|
1227
1248
|
return format_html("<pre>{}</pre>", render_json(value))
|
|
1228
1249
|
|
|
1229
|
-
if isinstance(field_instance, ManyToManyField)
|
|
1230
|
-
|
|
1250
|
+
if isinstance(field_instance, ManyToManyField):
|
|
1251
|
+
if field_instance.related_model == ContentType:
|
|
1252
|
+
return render_content_types(value)
|
|
1253
|
+
# TODO: this would be nice but it's probably too error-prone in general:
|
|
1254
|
+
# return render_m2m(
|
|
1255
|
+
# value.all(),
|
|
1256
|
+
# (
|
|
1257
|
+
# reverse(get_route_for_model(field_instance.related_model, "list")) + "?" +
|
|
1258
|
+
# obj._meta.verbose_name_plural.lower().replace(" ", "_") + "=" + str(obj.pk)
|
|
1259
|
+
# ),
|
|
1260
|
+
# key,
|
|
1261
|
+
# )
|
|
1231
1262
|
|
|
1232
1263
|
if isinstance(field_instance, CharField) and hasattr(obj, f"get_{key}_display"):
|
|
1233
1264
|
# For example, Secret.provider -> Secret.get_provider_display()
|
|
@@ -1269,6 +1300,8 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1269
1300
|
# TODO: apply a default ordering "smarter" than declaration order? Alphabetical? By field type?
|
|
1270
1301
|
# TODO: allow model to specify an alternative field ordering?
|
|
1271
1302
|
|
|
1303
|
+
fields += self.additional_fields
|
|
1304
|
+
|
|
1272
1305
|
data = {}
|
|
1273
1306
|
|
|
1274
1307
|
if isinstance(instance, TreeModel) and (self.fields == "__all__" or "_hierarchy" in self.fields):
|
|
@@ -1279,9 +1312,14 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1279
1312
|
if field_name in self.exclude_fields:
|
|
1280
1313
|
continue
|
|
1281
1314
|
try:
|
|
1282
|
-
field_value =
|
|
1283
|
-
|
|
1284
|
-
|
|
1315
|
+
field_value = instance
|
|
1316
|
+
# Handle nested lookups, e.g. device_type__device_family
|
|
1317
|
+
for token in field_name.split(LOOKUP_SEP):
|
|
1318
|
+
try:
|
|
1319
|
+
field_value = getattr(field_value, token)
|
|
1320
|
+
except ObjectDoesNotExist:
|
|
1321
|
+
field_value = None
|
|
1322
|
+
break
|
|
1285
1323
|
except AttributeError:
|
|
1286
1324
|
if self.ignore_nonexistent_fields:
|
|
1287
1325
|
continue
|
|
@@ -1297,6 +1335,10 @@ class ObjectFieldsPanel(KeyValueTablePanel):
|
|
|
1297
1335
|
|
|
1298
1336
|
def render_key(self, key, value, context: Context):
|
|
1299
1337
|
"""Render the `verbose_name` of the model field whose name corresponds to the given key, if applicable."""
|
|
1338
|
+
|
|
1339
|
+
if key in self.key_transforms:
|
|
1340
|
+
return self.key_transforms[key]
|
|
1341
|
+
|
|
1300
1342
|
instance = get_obj_from_context(context, self.context_object_key)
|
|
1301
1343
|
|
|
1302
1344
|
if instance is not None:
|
|
@@ -1912,7 +1954,8 @@ class _ObjectDetailContactsTab(Tab):
|
|
|
1912
1954
|
"{} {}",
|
|
1913
1955
|
self.label,
|
|
1914
1956
|
render_to_string(
|
|
1915
|
-
"utilities/templatetags/badge.html",
|
|
1957
|
+
"utilities/templatetags/badge.html",
|
|
1958
|
+
badge(get_obj_from_context(context).associated_contacts.count(), True),
|
|
1916
1959
|
),
|
|
1917
1960
|
)
|
|
1918
1961
|
|
|
@@ -1956,7 +1999,7 @@ class _ObjectDetailGroupsTab(Tab):
|
|
|
1956
1999
|
"{} {}",
|
|
1957
2000
|
self.label,
|
|
1958
2001
|
render_to_string(
|
|
1959
|
-
"utilities/templatetags/badge.html", badge(get_obj_from_context(context).dynamic_groups.count())
|
|
2002
|
+
"utilities/templatetags/badge.html", badge(get_obj_from_context(context).dynamic_groups.count(), True)
|
|
1960
2003
|
),
|
|
1961
2004
|
)
|
|
1962
2005
|
|
|
@@ -2003,6 +2046,6 @@ class _ObjectDetailMetadataTab(Tab):
|
|
|
2003
2046
|
self.label,
|
|
2004
2047
|
render_to_string(
|
|
2005
2048
|
"utilities/templatetags/badge.html",
|
|
2006
|
-
badge(get_obj_from_context(context).associated_object_metadata.count()),
|
|
2049
|
+
badge(get_obj_from_context(context).associated_object_metadata.count(), True),
|
|
2007
2050
|
),
|
|
2008
2051
|
)
|
nautobot/core/ui/titles.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from typing import Literal, Optional
|
|
1
|
+
from typing import Literal, Optional, Union
|
|
2
2
|
|
|
3
3
|
from django.template import Context, Template
|
|
4
4
|
from django.utils.html import strip_tags
|
|
5
5
|
|
|
6
6
|
DEFAULT_TITLES: dict[str, str] = {
|
|
7
|
+
"*": "{{ verbose_name_plural|bettertitle }}",
|
|
7
8
|
"list": "{{ verbose_name_plural|bettertitle }}",
|
|
8
9
|
"detail": "{{ object.display|default:object }}",
|
|
9
10
|
"retrieve": "{{ object.display|default:object }}",
|
|
@@ -59,7 +60,7 @@ class Titles:
|
|
|
59
60
|
if template_plugins:
|
|
60
61
|
self.template_plugins.extend(template_plugins)
|
|
61
62
|
|
|
62
|
-
def render(self, context: Context, mode: ModeType = "html") -> str:
|
|
63
|
+
def render(self, context: Union[dict, Context], mode: ModeType = "html") -> str:
|
|
63
64
|
"""
|
|
64
65
|
Renders the title based on given context and current action.
|
|
65
66
|
|
|
@@ -68,12 +69,15 @@ class Titles:
|
|
|
68
69
|
Make sure that needed context variables are in context and needed plugins are loaded.
|
|
69
70
|
|
|
70
71
|
Args:
|
|
71
|
-
context (Context): Render context.
|
|
72
|
+
context (Union[dict, Context]): Render context.
|
|
72
73
|
mode (ModeType): Rendering mode: "html" or "plain".
|
|
73
74
|
|
|
74
75
|
Returns:
|
|
75
76
|
(str): HTML fragment or plain text, depending on `mode`.
|
|
76
77
|
"""
|
|
78
|
+
if isinstance(context, dict):
|
|
79
|
+
context = Context(context)
|
|
80
|
+
|
|
77
81
|
with context.update(self.get_extra_context(context)):
|
|
78
82
|
template_str = self.get_template_str(context)
|
|
79
83
|
template = Template(self.template_plugins_str + template_str)
|
|
@@ -92,7 +96,7 @@ class Titles:
|
|
|
92
96
|
Returns:
|
|
93
97
|
str: The template string for the current action, or an empty string if not found.
|
|
94
98
|
"""
|
|
95
|
-
action = context.get("view_action", "
|
|
99
|
+
action = context.get("view_action", "")
|
|
96
100
|
|
|
97
101
|
template_str = self.titles.get(action)
|
|
98
102
|
if template_str:
|
|
@@ -102,7 +106,7 @@ class Titles:
|
|
|
102
106
|
if detail:
|
|
103
107
|
return self.titles.get("detail", "")
|
|
104
108
|
|
|
105
|
-
return ""
|
|
109
|
+
return self.titles.get("*", "")
|
|
106
110
|
|
|
107
111
|
@property
|
|
108
112
|
def template_plugins_str(self) -> str:
|
nautobot/core/utils/data.py
CHANGED
|
@@ -92,6 +92,19 @@ def merge_dicts_without_collision(d1, d2):
|
|
|
92
92
|
return {**d1, **d2}
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
def validate_jinja2(template_code):
|
|
96
|
+
"""
|
|
97
|
+
Parse a Jinja2 template to validate its syntax. Returns True if the template is valid.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
jinja2.TemplateSyntaxError: If the template is syntactically invalid.
|
|
101
|
+
"""
|
|
102
|
+
rendering_engine = engines["jinja"]
|
|
103
|
+
rendering_engine.env.parse(template_code)
|
|
104
|
+
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
|
|
95
108
|
def render_jinja2(template_code, context):
|
|
96
109
|
"""
|
|
97
110
|
Render a Jinja2 template with the provided context. Return the rendered content.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Utilities for handling deprecation of code and features."""
|
|
2
2
|
|
|
3
|
+
from functools import wraps
|
|
3
4
|
import logging
|
|
4
5
|
import traceback
|
|
5
6
|
import warnings
|
|
@@ -51,6 +52,7 @@ def method_deprecated(message):
|
|
|
51
52
|
"""Decorator to mark a method as deprecated with a custom message about what to call instead."""
|
|
52
53
|
|
|
53
54
|
def decorate(method):
|
|
55
|
+
@wraps(method)
|
|
54
56
|
def decorated_method(*args, **kwargs):
|
|
55
57
|
warnings.warn(
|
|
56
58
|
f"Method `{method.__name__}` is deprecated, and will be removed in a future Nautobot release. "
|
nautobot/core/views/__init__.py
CHANGED
|
@@ -15,6 +15,7 @@ from django.contrib import messages
|
|
|
15
15
|
from django.contrib.auth.decorators import permission_required
|
|
16
16
|
from django.contrib.auth.mixins import AccessMixin, LoginRequiredMixin, UserPassesTestMixin
|
|
17
17
|
from django.contrib.contenttypes.models import ContentType
|
|
18
|
+
from django.core.cache import cache
|
|
18
19
|
from django.http import HttpResponseForbidden, HttpResponseServerError, JsonResponse
|
|
19
20
|
from django.shortcuts import get_object_or_404, render
|
|
20
21
|
from django.template import loader, RequestContext, Template
|
|
@@ -51,6 +52,11 @@ from nautobot.core.releases import get_latest_release
|
|
|
51
52
|
from nautobot.core.utils.config import get_settings_or_config
|
|
52
53
|
from nautobot.core.utils.lookup import get_route_for_model
|
|
53
54
|
from nautobot.core.utils.permissions import get_permission_for_model
|
|
55
|
+
from nautobot.core.views.utils import (
|
|
56
|
+
generate_latest_with_cache,
|
|
57
|
+
is_metrics_experimental_caching_enabled,
|
|
58
|
+
METRICS_CACHE_KEY,
|
|
59
|
+
)
|
|
54
60
|
from nautobot.extras.forms import GraphQLQueryForm
|
|
55
61
|
from nautobot.extras.models import FileProxy, GraphQLQuery, Status
|
|
56
62
|
from nautobot.extras.registry import registry
|
|
@@ -435,8 +441,17 @@ class NautobotAppMetricsCollector(Collector):
|
|
|
435
441
|
def collect(self):
|
|
436
442
|
"""Collect metrics from plugins."""
|
|
437
443
|
start = time.time()
|
|
438
|
-
|
|
439
|
-
|
|
444
|
+
cached_lines = cache.get(METRICS_CACHE_KEY)
|
|
445
|
+
if not is_metrics_experimental_caching_enabled() or not cached_lines:
|
|
446
|
+
# If caching is disabled or no cache is found, generate metrics
|
|
447
|
+
for metric_generator in registry["app_metrics"]:
|
|
448
|
+
yield from metric_generator()
|
|
449
|
+
else:
|
|
450
|
+
# We stash the cached lines on the instance of the collector so that we can
|
|
451
|
+
# avoid a potential race condition where the cache expires between
|
|
452
|
+
# the time we check for it and the time we go to use it
|
|
453
|
+
# in generate_latest_with_cache()
|
|
454
|
+
self.local_cache = cached_lines
|
|
440
455
|
gauge = GaugeMetricFamily("nautobot_app_metrics_processing_ms", "Time in ms to generate the app metrics")
|
|
441
456
|
duration = time.time() - start
|
|
442
457
|
gauge.add_metric([], format(duration * 1000, ".5f"))
|
|
@@ -485,7 +500,13 @@ class NautobotMetricsView(APIView):
|
|
|
485
500
|
except ValueError:
|
|
486
501
|
# Collector already registered, we are running without multiprocessing
|
|
487
502
|
pass
|
|
488
|
-
|
|
503
|
+
|
|
504
|
+
if is_metrics_experimental_caching_enabled():
|
|
505
|
+
# Use the vendored version of generate_latest with Caching support
|
|
506
|
+
metrics_page = generate_latest_with_cache(prometheus_registry)
|
|
507
|
+
else:
|
|
508
|
+
# Use the original version of generate_latest to generate the metrics
|
|
509
|
+
metrics_page = generate_latest(prometheus_registry)
|
|
489
510
|
return Response(metrics_page, content_type=CONTENT_TYPE_LATEST)
|
|
490
511
|
|
|
491
512
|
|