nautobot 2.3.6__py3-none-any.whl → 2.3.7__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/__init__.py +4 -2
- nautobot/circuits/tests/test_views.py +4 -5
- nautobot/core/api/views.py +15 -3
- nautobot/core/templates/inc/javascript.html +2 -0
- nautobot/core/templates/inc/nav_menu.html +0 -251
- nautobot/core/testing/mixins.py +59 -2
- nautobot/core/testing/views.py +45 -61
- nautobot/core/tests/runner.py +6 -3
- nautobot/core/tests/test_paginator.py +4 -3
- nautobot/core/tests/test_views.py +39 -56
- nautobot/core/views/__init__.py +27 -11
- nautobot/dcim/tests/test_views.py +26 -67
- nautobot/extras/datasources/git.py +6 -1
- nautobot/extras/migrations/0112_dynamic_group_group_type_data_migration.py +3 -0
- nautobot/extras/migrations/0116_fix_dynamic_group_group_type_data_migration.py +16 -0
- nautobot/extras/tests/test_customfields.py +9 -16
- nautobot/extras/tests/test_dynamicgroups.py +116 -0
- nautobot/extras/tests/test_plugins.py +4 -6
- nautobot/extras/tests/test_utils.py +5 -0
- nautobot/extras/tests/test_views.py +61 -159
- nautobot/extras/utils.py +50 -11
- nautobot/ipam/tests/test_api.py +18 -12
- nautobot/ipam/tests/test_views.py +6 -15
- nautobot/project-static/docs/404.html +3 -3
- nautobot/project-static/docs/apps/index.html +3 -3
- nautobot/project-static/docs/apps/nautobot-apps.html +3 -3
- nautobot/project-static/docs/assets/javascripts/bundle.525ec568.min.js +16 -0
- nautobot/project-static/docs/assets/javascripts/{bundle.56dfad97.min.js.map → bundle.525ec568.min.js.map} +4 -4
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css +1 -0
- nautobot/project-static/docs/assets/stylesheets/main.8c3ca2c6.min.css.map +1 -0
- nautobot/project-static/docs/code-reference/nautobot/apps/__init__.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/admin.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/api.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/change_logging.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/choices.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/config.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/constants.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/datasources.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/exceptions.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/factory.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/filters.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/forms.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/graphql.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/jobs.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/models.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/querysets.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/secrets.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/tables.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/testing.html +124 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/ui.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/urls.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/utils.html +3 -3
- nautobot/project-static/docs/code-reference/nautobot/apps/views.html +3 -3
- nautobot/project-static/docs/development/apps/api/configuration-view.html +3 -3
- nautobot/project-static/docs/development/apps/api/database-backend-config.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/django-admin.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/global-search.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/graphql.html +3 -3
- nautobot/project-static/docs/development/apps/api/models/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/nautobot-app-config.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/custom-validators.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/filter-extensions.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/git-repository-content.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jinja2-filters.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/jobs.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/populating-extensibility-features.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/secrets-providers.html +3 -3
- nautobot/project-static/docs/development/apps/api/platform-features/uniquely-identify-objects.html +3 -3
- nautobot/project-static/docs/development/apps/api/prometheus.html +3 -3
- nautobot/project-static/docs/development/apps/api/setup.html +3 -3
- nautobot/project-static/docs/development/apps/api/testing.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/banners.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/home-page.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/navigation.html +3 -3
- nautobot/project-static/docs/development/apps/api/ui-extensions/object-views.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/base-template.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/core-view-overrides.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/django-generic-views.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/help-documentation.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/index.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/nautobot-generic-views.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewset.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/nautobotuiviewsetrouter.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/notes.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/rest-api.html +3 -3
- nautobot/project-static/docs/development/apps/api/views/urls.html +3 -3
- nautobot/project-static/docs/development/apps/index.html +3 -3
- nautobot/project-static/docs/development/apps/migration/code-updates.html +3 -3
- nautobot/project-static/docs/development/apps/migration/dependency-updates.html +3 -3
- nautobot/project-static/docs/development/apps/migration/from-v1.html +3 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/dcim.html +3 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/extras.html +3 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/global.html +3 -3
- nautobot/project-static/docs/development/apps/migration/model-updates/ipam.html +3 -3
- nautobot/project-static/docs/development/apps/porting-from-netbox.html +3 -3
- nautobot/project-static/docs/development/core/application-registry.html +3 -3
- nautobot/project-static/docs/development/core/best-practices.html +3 -3
- nautobot/project-static/docs/development/core/bootstrap-ui.html +3 -3
- nautobot/project-static/docs/development/core/caching.html +3 -3
- nautobot/project-static/docs/development/core/controllers.html +3 -3
- nautobot/project-static/docs/development/core/docker-compose-advanced-use-cases.html +3 -3
- nautobot/project-static/docs/development/core/generic-views.html +3 -3
- nautobot/project-static/docs/development/core/getting-started.html +3 -3
- nautobot/project-static/docs/development/core/homepage.html +3 -3
- nautobot/project-static/docs/development/core/index.html +3 -3
- nautobot/project-static/docs/development/core/model-checklist.html +3 -3
- nautobot/project-static/docs/development/core/model-features.html +3 -3
- nautobot/project-static/docs/development/core/natural-keys.html +3 -3
- nautobot/project-static/docs/development/core/navigation-menu.html +3 -3
- nautobot/project-static/docs/development/core/release-checklist.html +3 -3
- nautobot/project-static/docs/development/core/role-internals.html +3 -3
- nautobot/project-static/docs/development/core/settings.html +3 -3
- nautobot/project-static/docs/development/core/style-guide.html +3 -3
- nautobot/project-static/docs/development/core/templates.html +3 -3
- nautobot/project-static/docs/development/core/testing.html +3 -3
- nautobot/project-static/docs/development/core/user-preferences.html +3 -3
- nautobot/project-static/docs/development/index.html +3 -3
- nautobot/project-static/docs/development/jobs/index.html +3 -3
- nautobot/project-static/docs/development/jobs/migration/from-v1.html +3 -3
- nautobot/project-static/docs/index.html +3 -3
- nautobot/project-static/docs/objects.inv +0 -0
- nautobot/project-static/docs/overview/application_stack.html +3 -3
- nautobot/project-static/docs/overview/design_philosophy.html +3 -3
- nautobot/project-static/docs/release-notes/index.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.0.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.1.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.2.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.3.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.4.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.5.html +3 -3
- nautobot/project-static/docs/release-notes/version-1.6.html +3 -3
- nautobot/project-static/docs/release-notes/version-2.0.html +3 -3
- nautobot/project-static/docs/release-notes/version-2.1.html +3 -3
- nautobot/project-static/docs/release-notes/version-2.2.html +3 -3
- nautobot/project-static/docs/release-notes/version-2.3.html +247 -96
- nautobot/project-static/docs/requirements.txt +1 -1
- nautobot/project-static/docs/search/search_index.json +1 -1
- nautobot/project-static/docs/sitemap.xml +269 -269
- nautobot/project-static/docs/sitemap.xml.gz +0 -0
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/ldap.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/remote.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/authentication/sso.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/index.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/redis.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/settings.html +3 -3
- nautobot/project-static/docs/user-guide/administration/configuration/time-zones.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/celery-queues.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/docker.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/health-checks.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/permissions.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/prometheus-metrics.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/replicating-nautobot.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/request-profiling.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/s3-django-storage.html +3 -3
- nautobot/project-static/docs/user-guide/administration/guides/selinux-troubleshooting.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/app-install.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/external-authentication.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/http-server.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/index.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/install_system.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/nautobot.html +3 -3
- nautobot/project-static/docs/user-guide/administration/installation/services.html +3 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-netbox.html +3 -3
- nautobot/project-static/docs/user-guide/administration/migration/migrating-from-postgresql.html +3 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-server.html +3 -3
- nautobot/project-static/docs/user-guide/administration/tools/nautobot-shell.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/database-backup.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/after-you-upgrade.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/before-you-upgrade.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/for-developers.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/index.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/ipam/whats-changed.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/region-and-site-data-migration-guide.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/from-v1/upgrading-from-nautobot-v1.html +3 -3
- nautobot/project-static/docs/user-guide/administration/upgrading/upgrading.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuit.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittermination.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/circuittype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/provider.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/circuits/providernetwork.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloud.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudaccount.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetwork.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudnetworkprefixassignment.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudresourcetype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservice.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/cloud/cloudservicenetworkassignment.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/cable.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/consoleserverporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controller.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/controllermanageddevicegroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/device.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebay.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicebaytemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicefamily.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/deviceredundancygroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/devicetype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/frontporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interface.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfaceredundancygroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/interfacetemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/inventoryitem.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/location.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/locationtype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/manufacturer.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/module.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebay.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/modulebaytemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/moduletype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/platform.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerfeed.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlet.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/poweroutlettemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerpanel.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/powerporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rack.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackgroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rackreservation.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearport.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/rearporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareimagefile.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/softwareversion.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/dcim/virtualchassis.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontext.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/configcontextschema.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/contact.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/extras/team.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/ipaddress.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/namespace.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/prefix.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/rir.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/routetarget.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/service.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlan.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vlangroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/ipam/vrf.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/overview/introduction.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenant.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/tenancy/tenantgroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/cluster.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustergroup.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/clustertype.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/virtualmachine.html +3 -3
- nautobot/project-static/docs/user-guide/core-data-model/virtualization/vminterface.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/contacts-and-teams.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/custom-fields.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-devices.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/creating-location-types-and-locations.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/index.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/interfaces.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/ipam.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/platforms.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/search-bar.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/tenants.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/getting-started/vlans-and-vlan-groups.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/git-data-source.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/graphql.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/ip-address-merge-tool.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/relationships.html +3 -3
- nautobot/project-static/docs/user-guide/feature-guides/software-image-files-and-versions.html +3 -3
- nautobot/project-static/docs/user-guide/index.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/change-logging.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/computedfield.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customfield.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/customlink.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/dynamicgroup.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/exporttemplate.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/externalintegration.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/gitrepository.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphql.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/graphqlquery.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/imageattachment.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/index.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/job-scheduling-and-approvals.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobbutton.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/jobhook.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/jobs/models.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/napalm.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/note.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/objectmetadata.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/relationship.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/authentication.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/filtering.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/overview.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/rest-api/ui-related-endpoints.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/role.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/savedview.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/secret.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/staticgroupassociation.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/status.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/tag.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/template-filters.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/objectpermission.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/users/token.html +3 -3
- nautobot/project-static/docs/user-guide/platform-functionality/webhook.html +3 -3
- nautobot/project-static/js/nav_menu.js +249 -0
- nautobot/tenancy/templates/tenancy/tenant.html +1 -1
- nautobot/users/tests/test_views.py +9 -11
- nautobot/virtualization/tests/test_views.py +3 -5
- {nautobot-2.3.6.dist-info → nautobot-2.3.7.dist-info}/METADATA +2 -1
- {nautobot-2.3.6.dist-info → nautobot-2.3.7.dist-info}/RECORD +312 -310
- {nautobot-2.3.6.dist-info → nautobot-2.3.7.dist-info}/WHEEL +1 -1
- nautobot/project-static/docs/assets/javascripts/bundle.56dfad97.min.js +0 -16
- nautobot/project-static/docs/assets/stylesheets/main.35f28582.min.css +0 -1
- nautobot/project-static/docs/assets/stylesheets/main.35f28582.min.css.map +0 -1
- {nautobot-2.3.6.dist-info → nautobot-2.3.7.dist-info}/LICENSE.txt +0 -0
- {nautobot-2.3.6.dist-info → nautobot-2.3.7.dist-info}/NOTICE +0 -0
- {nautobot-2.3.6.dist-info → nautobot-2.3.7.dist-info}/entry_points.txt +0 -0
nautobot/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from importlib import metadata
|
|
2
2
|
import logging
|
|
3
3
|
import os
|
|
4
|
+
import sys
|
|
4
5
|
|
|
5
6
|
import django
|
|
6
7
|
|
|
@@ -28,8 +29,9 @@ def setup(config_path=None):
|
|
|
28
29
|
# Point Django to our 'nautobot_config' pseudo-module that we'll load from the provided config path
|
|
29
30
|
os.environ["DJANGO_SETTINGS_MODULE"] = "nautobot_config"
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
if "nautobot_config" not in sys.modules:
|
|
33
|
+
load_settings(config_path)
|
|
32
34
|
django.setup()
|
|
33
35
|
|
|
34
|
-
logger.info("Nautobot initialized!")
|
|
36
|
+
logger.info("Nautobot %s initialized!", __version__)
|
|
35
37
|
__initialized = True
|
|
@@ -10,7 +10,7 @@ from nautobot.circuits.models import (
|
|
|
10
10
|
Provider,
|
|
11
11
|
ProviderNetwork,
|
|
12
12
|
)
|
|
13
|
-
from nautobot.core.testing import post_data, TestCase as NautobotTestCase, ViewTestCases
|
|
13
|
+
from nautobot.core.testing import post_data, TestCase as NautobotTestCase, utils, ViewTestCases
|
|
14
14
|
from nautobot.extras.models import Status, Tag
|
|
15
15
|
|
|
16
16
|
|
|
@@ -176,13 +176,12 @@ class CircuitTerminationTestCase(
|
|
|
176
176
|
|
|
177
177
|
# Visit the termination detail page and assert responses:
|
|
178
178
|
response = self.client.get(reverse("circuits:circuittermination", kwargs={"pk": termination.pk}))
|
|
179
|
-
self.
|
|
180
|
-
self.
|
|
181
|
-
self.assertNotIn("</span> Connect", str(response.content))
|
|
179
|
+
self.assertBodyContains(response, "Test Provider Network")
|
|
180
|
+
self.assertNotIn("</span> Connect", utils.extract_page_body(response.content.decode(response.charset)))
|
|
182
181
|
|
|
183
182
|
# Visit the circuit object detail page and check there is no connect button present:
|
|
184
183
|
response = self.client.get(reverse("circuits:circuit", kwargs={"pk": circuit.pk}))
|
|
185
|
-
self.assertNotIn("</span> Connect",
|
|
184
|
+
self.assertNotIn("</span> Connect", utils.extract_page_body(response.content.decode(response.charset)))
|
|
186
185
|
|
|
187
186
|
|
|
188
187
|
class CircuitSwapTerminationsTestCase(NautobotTestCase):
|
nautobot/core/api/views.py
CHANGED
|
@@ -24,6 +24,7 @@ from graphql import get_default_backend
|
|
|
24
24
|
from graphql.execution import ExecutionResult
|
|
25
25
|
from graphql.execution.middleware import MiddlewareManager
|
|
26
26
|
from graphql.type.schema import GraphQLSchema
|
|
27
|
+
import redis.exceptions
|
|
27
28
|
from rest_framework import routers, status
|
|
28
29
|
from rest_framework.exceptions import ParseError, PermissionDenied
|
|
29
30
|
from rest_framework.permissions import IsAuthenticated
|
|
@@ -58,6 +59,10 @@ HTTP_ACTIONS = {
|
|
|
58
59
|
"DELETE": "delete",
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
|
|
63
|
+
logger = logging.getLogger(__name__)
|
|
64
|
+
|
|
65
|
+
|
|
61
66
|
#
|
|
62
67
|
# Mixins
|
|
63
68
|
#
|
|
@@ -467,7 +472,16 @@ class StatusView(NautobotAPIVersionMixin, APIView):
|
|
|
467
472
|
nautobot_apps = dict(sorted(nautobot_apps.items()))
|
|
468
473
|
|
|
469
474
|
# Gather Celery workers
|
|
470
|
-
|
|
475
|
+
try:
|
|
476
|
+
workers = celery_app.control.inspect().active() # list or None
|
|
477
|
+
except redis.exceptions.ConnectionError:
|
|
478
|
+
# Celery seems to be not smart enough to auto-retry on intermittent failures, so let's do it ourselves:
|
|
479
|
+
try:
|
|
480
|
+
workers = celery_app.control.inspect().active() # list or None
|
|
481
|
+
except redis.exceptions.ConnectionError as err:
|
|
482
|
+
logger.error("Repeated ConnectionError from Celery/Redis: %s", err)
|
|
483
|
+
workers = None
|
|
484
|
+
|
|
471
485
|
worker_count = len(workers) if workers is not None else 0
|
|
472
486
|
|
|
473
487
|
return Response(
|
|
@@ -872,7 +886,6 @@ class GetObjectCountsView(NautobotAPIVersionMixin, APIView):
|
|
|
872
886
|
try:
|
|
873
887
|
data["url"] = django_reverse(get_route_for_model(model, "list"))
|
|
874
888
|
except NoReverseMatch:
|
|
875
|
-
logger = logging.getLogger(__name__)
|
|
876
889
|
route = get_route_for_model(model, "list")
|
|
877
890
|
logger.warning(f"Handled expected exception when generating filter field: {route}")
|
|
878
891
|
manager = model.objects
|
|
@@ -975,7 +988,6 @@ class GetFilterSetFieldDOMElementAPIView(NautobotAPIVersionMixin, APIView):
|
|
|
975
988
|
# Cant determine the exceptions to handle because any exception could be raised,
|
|
976
989
|
# e.g InterfaceForm would raise a ObjectDoesNotExist Error since no device was provided
|
|
977
990
|
# While other forms might raise other errors, also if model_form is None a TypeError would be raised.
|
|
978
|
-
logger = logging.getLogger(__name__)
|
|
979
991
|
logger.debug(f"Handled expected exception when generating filter field: {err}")
|
|
980
992
|
|
|
981
993
|
# Create a temporary form and get a BoundField for the specified field
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
onerror="window.location='{% url 'media_failure' %}?filename=highlight.js-11.9.0/highlight.min.js'"></script>
|
|
19
19
|
<script src="{% versioned_static 'js/forms.js' %}"
|
|
20
20
|
onerror="window.location='{% url 'media_failure' %}?filename=js/forms.js'"></script>
|
|
21
|
+
<script src="{% versioned_static 'js/nav_menu.js' %}"
|
|
22
|
+
onerror="window.location='{% url 'media_failure' %}?filename=js/nav_menu.js'"></script>
|
|
21
23
|
<script src="{% versioned_static 'js/theme.js' %}"
|
|
22
24
|
onerror="window.location='{% url 'media_failure' %}?filename=js/theme.js'"></script>
|
|
23
25
|
<script src="{% versioned_static 'js/table_sorting_indicator.js' %}"
|
|
@@ -117,254 +117,3 @@
|
|
|
117
117
|
<button type="button" class="btn btn-xs btn-warning navbar-toggler" aria-label="Collapse navbar">
|
|
118
118
|
<span class="mdi mdi-chevron-up mdi-rotate-270 navbar-toggler-arrow"></span>
|
|
119
119
|
</button>
|
|
120
|
-
|
|
121
|
-
<script>
|
|
122
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
123
|
-
const navbar = document.querySelector('.navbar-fixed-left');
|
|
124
|
-
const navbarHeader = document.querySelector('.navbar-header');
|
|
125
|
-
const mainContent = document.querySelector('#main-content');
|
|
126
|
-
const footer = document.querySelector('#footer');
|
|
127
|
-
const dropdownToggles = document.querySelectorAll('.navbar-fixed-left .navbar-nav > .dropdown > a[data-toggle="collapse"]');
|
|
128
|
-
const dropdowns = document.querySelectorAll('.navbar-fixed-left .navbar-nav .collapse');
|
|
129
|
-
const toggler = document.querySelector('.navbar-toggler');
|
|
130
|
-
const togglerIcon = toggler.querySelector('.navbar-toggler-arrow');
|
|
131
|
-
let lastDropdownId = sessionStorage.getItem('lastOpenedDropdown');
|
|
132
|
-
let savedScrollPosition = sessionStorage.getItem('navbarScrollPosition');
|
|
133
|
-
let activeLink = sessionStorage.getItem('activeLink');
|
|
134
|
-
let expandedByHover = false;
|
|
135
|
-
let manuallyToggled = sessionStorage.getItem('manuallyToggled') === 'true';
|
|
136
|
-
|
|
137
|
-
// Function to reset stored dropdown state information
|
|
138
|
-
function resetNavbarState() {
|
|
139
|
-
sessionStorage.removeItem('lastOpenedDropdown');
|
|
140
|
-
sessionStorage.removeItem('savedScrollPosition');
|
|
141
|
-
sessionStorage.removeItem('activeLink');
|
|
142
|
-
sessionStorage.removeItem('navbarCollapsed');
|
|
143
|
-
expandedByHover = false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
toggler.addEventListener('click', function() {
|
|
147
|
-
let isNowCollapsed;
|
|
148
|
-
if (expandedByHover) {
|
|
149
|
-
expandedByHover = false;
|
|
150
|
-
isNowCollapsed = false;
|
|
151
|
-
} else {
|
|
152
|
-
isNowCollapsed = navbar.classList.toggle('collapsed');
|
|
153
|
-
}
|
|
154
|
-
sessionStorage.setItem('navbarCollapsed', isNowCollapsed ? 'true' : 'false');
|
|
155
|
-
// Set 'navbarManuallyToggled' to track any manual toggle
|
|
156
|
-
sessionStorage.setItem('navbarManuallyToggled', 'true');
|
|
157
|
-
// Track if the action was an expansion or a collapse
|
|
158
|
-
sessionStorage.setItem('navbarExpanded', !isNowCollapsed ? 'true' : 'false');
|
|
159
|
-
if (isNowCollapsed) {
|
|
160
|
-
togglerIcon.classList.add("mdi-rotate-90");
|
|
161
|
-
togglerIcon.classList.remove("mdi-rotate-270");
|
|
162
|
-
} else {
|
|
163
|
-
togglerIcon.classList.remove("mdi-rotate-90");
|
|
164
|
-
togglerIcon.classList.add("mdi-rotate-270");
|
|
165
|
-
}
|
|
166
|
-
adjustElementsForNavbarState(isNowCollapsed);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// Retrieve the navbar collapsed state from session storage on page load
|
|
170
|
-
const navbarCollapsed = sessionStorage.getItem('navbarCollapsed') === 'true';
|
|
171
|
-
if (navbarCollapsed) {
|
|
172
|
-
navbar.classList.add('collapsed');
|
|
173
|
-
togglerIcon.classList.remove("mdi-rotate-270");
|
|
174
|
-
togglerIcon.classList.add("mdi-rotate-90");
|
|
175
|
-
adjustElementsForNavbarState(true);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function adjustElementsForNavbarState(isCollapsed) {
|
|
179
|
-
const marginLeftValue = isCollapsed ? '-240px' : '0px';
|
|
180
|
-
mainContent.style.marginLeft = marginLeftValue;
|
|
181
|
-
if(footer) footer.style.marginLeft = marginLeftValue;
|
|
182
|
-
toggler.style.left = isCollapsed ? '-5px' : '225px';
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Expand navbar when hovering near the left edge of the screen
|
|
186
|
-
document.addEventListener('mousemove', function(e) {
|
|
187
|
-
if (
|
|
188
|
-
e.clientX < 20 // 20px from the left edge
|
|
189
|
-
&& (e.clientY < 20 || e.clientY > 50) // not near the toggle button
|
|
190
|
-
&& navbar.classList.contains('collapsed')
|
|
191
|
-
) {
|
|
192
|
-
navbar.classList.remove('collapsed');
|
|
193
|
-
toggler.style.left = '225px';
|
|
194
|
-
expandedByHover = true; // Set flag when expanded by hover
|
|
195
|
-
} else if (expandedByHover && e.clientX > 240) {
|
|
196
|
-
navbar.classList.add('collapsed');
|
|
197
|
-
toggler.style.left = '-5px';
|
|
198
|
-
expandedByHover = false; // Reset flag after auto-collapse
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
function collapseNavbarIfNeeded() {
|
|
203
|
-
const windowWidth = window.innerWidth;
|
|
204
|
-
const navbarManuallyToggled = sessionStorage.getItem('navbarManuallyToggled') === 'true';
|
|
205
|
-
const navbarExpanded = sessionStorage.getItem('navbarExpanded') === 'true';
|
|
206
|
-
const isCollapsed = navbar.classList.contains('collapsed');
|
|
207
|
-
|
|
208
|
-
if (windowWidth < 1007) {
|
|
209
|
-
if (!isCollapsed) {
|
|
210
|
-
navbar.classList.add('collapsed');
|
|
211
|
-
togglerIcon.classList.remove("mdi-rotate-270");
|
|
212
|
-
togglerIcon.classList.add("mdi-rotate-90");
|
|
213
|
-
adjustElementsForNavbarState(true);
|
|
214
|
-
sessionStorage.setItem('navbarCollapsed', 'true');
|
|
215
|
-
}
|
|
216
|
-
} else if (windowWidth >= 1007) {
|
|
217
|
-
// Only expand automatically if it was not manually collapsed
|
|
218
|
-
if (isCollapsed && (navbarManuallyToggled && navbarExpanded)) {
|
|
219
|
-
navbar.classList.remove('collapsed');
|
|
220
|
-
togglerIcon.classList.add("mdi-rotate-270");
|
|
221
|
-
togglerIcon.classList.remove("mdi-rotate-90");
|
|
222
|
-
adjustElementsForNavbarState(false);
|
|
223
|
-
sessionStorage.setItem('navbarCollapsed', 'false');
|
|
224
|
-
}
|
|
225
|
-
// Do not automatically change the state if it was manually collapsed
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Update the window resize listener
|
|
230
|
-
function toggleNavbarOnResize() {
|
|
231
|
-
collapseNavbarIfNeeded(); // Use the new function to decide whether to collapse
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
let debouncedToggleNavbarOnResize = debounce(toggleNavbarOnResize, 50);
|
|
235
|
-
window.addEventListener('resize', debouncedToggleNavbarOnResize);
|
|
236
|
-
|
|
237
|
-
// Select the navbar dropdown elements
|
|
238
|
-
let navbarItems = document.querySelectorAll('.navbar-fixed-left .navbar-nav > .dropdown > .dropdown-toggle > #dropdown_title');
|
|
239
|
-
|
|
240
|
-
// Add a title attribute and tooltip, only if necessary
|
|
241
|
-
navbarItems.forEach(function(item) {
|
|
242
|
-
// Check if the text overflows
|
|
243
|
-
if (item.scrollWidth > item.clientWidth) {
|
|
244
|
-
// Set the title attribute
|
|
245
|
-
item.setAttribute('title', item.innerText);
|
|
246
|
-
|
|
247
|
-
// Reinitialize Bootstrap tooltip
|
|
248
|
-
$(item).tooltip();
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Add an event listener for the home link click
|
|
253
|
-
const homeLink = document.querySelector('.navbar-fixed-left .navbar-brand');
|
|
254
|
-
if (homeLink) {
|
|
255
|
-
homeLink.addEventListener('click', function() {
|
|
256
|
-
resetNavbarState();
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Close all dropdowns except the one specified
|
|
261
|
-
function closeAllDropdownsExcept(exceptId) {
|
|
262
|
-
dropdowns.forEach(function(collapse) {
|
|
263
|
-
if (collapse.id !== exceptId && collapse.classList.contains('in')) {
|
|
264
|
-
$(collapse).collapse('hide');
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Add click event listener to the dropdown links and save the clicked one
|
|
270
|
-
function addLinkClickListeners() {
|
|
271
|
-
const dropdownLinks = document.querySelectorAll('.navbar-fixed-left .navbar-nav > .dropdown > .nav-dropdown-menu > li > a');
|
|
272
|
-
|
|
273
|
-
dropdownLinks.forEach(function(link) {
|
|
274
|
-
link.addEventListener('click', function() {
|
|
275
|
-
sessionStorage.setItem('activeLink', link.getAttribute('href'));
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
collapseNavbarIfNeeded();
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Close all dropdowns except the last opened one
|
|
282
|
-
dropdownToggles.forEach(function(toggle) {
|
|
283
|
-
toggle.addEventListener('click', function(event) {
|
|
284
|
-
event.preventDefault();
|
|
285
|
-
const collapseElement = document.getElementById(this.getAttribute('href').substring(1));
|
|
286
|
-
|
|
287
|
-
if (!collapseElement.classList.contains('in')) {
|
|
288
|
-
closeAllDropdownsExcept(collapseElement.id);
|
|
289
|
-
$(collapseElement).collapse('show');
|
|
290
|
-
sessionStorage.setItem('lastOpenedDropdown', collapseElement.id);
|
|
291
|
-
} else {
|
|
292
|
-
$(collapseElement).collapse('hide');
|
|
293
|
-
sessionStorage.removeItem('lastOpenedDropdown');
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
// Open the last opened dropdown
|
|
299
|
-
if (lastDropdownId) {
|
|
300
|
-
let lastDropdownMenu = document.getElementById(lastDropdownId);
|
|
301
|
-
if (lastDropdownMenu && !lastDropdownMenu.classList.contains('in')) {
|
|
302
|
-
$(lastDropdownMenu).collapse('show');
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Restore the last saved scroll position
|
|
307
|
-
if (savedScrollPosition) {
|
|
308
|
-
navbar.scrollTop = savedScrollPosition;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Function to adjust navbar header visibility based on scroll position and navbar collapsed state
|
|
312
|
-
function adjustNavbarHeaderVisibility() {
|
|
313
|
-
// Check if the navbar is collapsed and mainContent is defined
|
|
314
|
-
if (navbar.classList.contains('collapsed') && mainContent) {
|
|
315
|
-
const mainContentTop = mainContent.getBoundingClientRect().top;
|
|
316
|
-
// Show or hide the navbar header based on mainContent's position
|
|
317
|
-
if (mainContentTop < 0) {
|
|
318
|
-
// Main content top is out of view, hide navbar header
|
|
319
|
-
navbarHeader.style.top = '-60px'; // height of navbar header
|
|
320
|
-
} else {
|
|
321
|
-
// Main content top is in view, show navbar header
|
|
322
|
-
navbarHeader.style.top = '0';
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Add scroll event listener to adjust navbar header visibility
|
|
328
|
-
window.addEventListener('scroll', adjustNavbarHeaderVisibility);
|
|
329
|
-
|
|
330
|
-
// Call the function initially to set the correct state when the page loads
|
|
331
|
-
adjustNavbarHeaderVisibility();
|
|
332
|
-
|
|
333
|
-
// Debounce function to limit the rate at which the handleScroll function is executed
|
|
334
|
-
function debounce(func, wait, immediate) {
|
|
335
|
-
let timeout;
|
|
336
|
-
return function() {
|
|
337
|
-
const context = this, args = arguments;
|
|
338
|
-
const later = function() {
|
|
339
|
-
timeout = null;
|
|
340
|
-
if (!immediate) func.apply(context, args);
|
|
341
|
-
};
|
|
342
|
-
const callNow = immediate && !timeout;
|
|
343
|
-
clearTimeout(timeout);
|
|
344
|
-
timeout = setTimeout(later, wait);
|
|
345
|
-
if (callNow) func.apply(context, args);
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Save the scroll position when the navbar is scrolled
|
|
350
|
-
navbar.addEventListener('scroll', debounce(function() {
|
|
351
|
-
sessionStorage.setItem('navbarScrollPosition', navbar.scrollTop);
|
|
352
|
-
}, 250));
|
|
353
|
-
|
|
354
|
-
// Add click event listeners to dropdown links
|
|
355
|
-
addLinkClickListeners();
|
|
356
|
-
|
|
357
|
-
// Apply the 'active' class to the previously clicked link
|
|
358
|
-
if (activeLink) {
|
|
359
|
-
let previouslyClickedLink = document.querySelector('.navbar-fixed-left .navbar-nav > .dropdown > .nav-dropdown-menu > li > a[href="' + activeLink + '"]');
|
|
360
|
-
let currentLocation = window.location.pathname + window.location.search;
|
|
361
|
-
|
|
362
|
-
if (previouslyClickedLink && currentLocation.includes(previouslyClickedLink.getAttribute('href'))) {
|
|
363
|
-
previouslyClickedLink.parentElement.classList.add('active');
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
sessionStorage.removeItem('activeLink');
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
</script>
|
nautobot/core/testing/mixins.py
CHANGED
|
@@ -9,6 +9,7 @@ from django.core.exceptions import FieldDoesNotExist
|
|
|
9
9
|
from django.db import connections, DEFAULT_DB_ALIAS
|
|
10
10
|
from django.db.models import JSONField, ManyToManyField, ManyToManyRel
|
|
11
11
|
from django.forms.models import model_to_dict
|
|
12
|
+
from django.test.testcases import assert_and_parse_html
|
|
12
13
|
from django.test.utils import CaptureQueriesContext
|
|
13
14
|
from netaddr import IPNetwork
|
|
14
15
|
from rest_framework.test import APIClient, APIRequestFactory
|
|
@@ -172,8 +173,12 @@ class NautobotTestCaseMixin:
|
|
|
172
173
|
# REST API response; pass the response data through directly
|
|
173
174
|
err_message += f"\n{response.data}"
|
|
174
175
|
# Attempt to extract form validation errors from the response HTML
|
|
175
|
-
form_errors
|
|
176
|
-
|
|
176
|
+
elif form_errors := utils.extract_form_failures(response.content.decode(response.charset)):
|
|
177
|
+
err_message += f"\n{form_errors}"
|
|
178
|
+
elif body_content := utils.extract_page_body(response.content.decode(response.charset)):
|
|
179
|
+
err_message += f"\n{body_content}"
|
|
180
|
+
else:
|
|
181
|
+
err_message += "No data"
|
|
177
182
|
if msg:
|
|
178
183
|
err_message = f"{msg}\n{err_message}"
|
|
179
184
|
self.assertIn(response.status_code, expected_status, err_message)
|
|
@@ -277,6 +282,58 @@ class NautobotTestCaseMixin:
|
|
|
277
282
|
|
|
278
283
|
return None
|
|
279
284
|
|
|
285
|
+
def assertBodyContains(self, response, text, count=None, status_code=200, msg_prefix="", html=False):
|
|
286
|
+
"""
|
|
287
|
+
Like Django's `assertContains`, but uses `extract_page_body` utility function to scope the check more narrowly.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
response (HttpResponse): The response to inspect
|
|
291
|
+
text (str): Plaintext or HTML to check for in the response body
|
|
292
|
+
count (int, optional): Number of times the `text` should occur, or None if we don't care as long as
|
|
293
|
+
it's present at all.
|
|
294
|
+
status_code (int): HTTP status code expected
|
|
295
|
+
html (bool): If True, handle `text` as HTML, ignoring whitespace etc, as in Django's `assertHTMLEqual()`.
|
|
296
|
+
"""
|
|
297
|
+
# The below is copied from SimpleTestCase._assert_contains and SimpleTestCase.assertContains
|
|
298
|
+
# If the response supports deferred rendering and hasn't been rendered
|
|
299
|
+
# yet, then ensure that it does get rendered before proceeding further.
|
|
300
|
+
if hasattr(response, "render") and callable(response.render) and not response.is_rendered:
|
|
301
|
+
response.render()
|
|
302
|
+
|
|
303
|
+
if msg_prefix:
|
|
304
|
+
msg_prefix += ": "
|
|
305
|
+
|
|
306
|
+
self.assertHttpStatus( # Nautobot-specific, original uses simple assertEqual()
|
|
307
|
+
response, status_code, msg_prefix
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
if response.streaming:
|
|
311
|
+
content = b"".join(response.streaming_content)
|
|
312
|
+
else:
|
|
313
|
+
content = response.content
|
|
314
|
+
|
|
315
|
+
if not isinstance(text, bytes) or html:
|
|
316
|
+
text = str(text)
|
|
317
|
+
content = content.decode(response.charset)
|
|
318
|
+
content = utils.extract_page_body(content) # Nautobot-specific
|
|
319
|
+
text_repr = f"'{text}'"
|
|
320
|
+
else:
|
|
321
|
+
text_repr = repr(text)
|
|
322
|
+
|
|
323
|
+
if html:
|
|
324
|
+
content = assert_and_parse_html(self, content, None, "Response's content is not valid HTML:")
|
|
325
|
+
text = assert_and_parse_html(self, text, None, "Second argument is not valid HTML:")
|
|
326
|
+
real_count = content.count(text)
|
|
327
|
+
|
|
328
|
+
if count is not None:
|
|
329
|
+
self.assertEqual(
|
|
330
|
+
real_count,
|
|
331
|
+
count,
|
|
332
|
+
msg_prefix + f"Found {real_count} instances of {text_repr} in response (expected {count}):\n{content}",
|
|
333
|
+
)
|
|
334
|
+
else:
|
|
335
|
+
self.assertTrue(real_count != 0, msg_prefix + f"Couldn't find {text_repr} in response:\n{content}")
|
|
336
|
+
|
|
280
337
|
#
|
|
281
338
|
# Convenience methods
|
|
282
339
|
#
|